1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/search/instant_service.h"
6
7#include "chrome/browser/chrome_notification_types.h"
8#include "chrome/browser/history/top_sites.h"
9#include "chrome/browser/profiles/profile.h"
10#include "chrome/browser/search/instant_io_context.h"
11#include "chrome/browser/search/instant_service_observer.h"
12#include "chrome/browser/search/local_ntp_source.h"
13#include "chrome/browser/search/most_visited_iframe_source.h"
14#include "chrome/browser/search/search.h"
15#include "chrome/browser/search/suggestions/suggestions_source.h"
16#include "chrome/browser/search_engines/template_url_service_factory.h"
17#include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
18#include "chrome/browser/themes/theme_properties.h"
19#include "chrome/browser/themes/theme_service.h"
20#include "chrome/browser/themes/theme_service_factory.h"
21#include "chrome/browser/thumbnails/thumbnail_list_source.h"
22#include "chrome/browser/ui/search/instant_search_prerenderer.h"
23#include "chrome/browser/ui/webui/favicon_source.h"
24#include "chrome/browser/ui/webui/ntp/thumbnail_source.h"
25#include "chrome/browser/ui/webui/theme_source.h"
26#include "chrome/common/render_messages.h"
27#include "components/search_engines/template_url_service.h"
28#include "content/public/browser/browser_thread.h"
29#include "content/public/browser/notification_service.h"
30#include "content/public/browser/notification_types.h"
31#include "content/public/browser/render_process_host.h"
32#include "content/public/browser/url_data_source.h"
33#include "grit/theme_resources.h"
34#include "third_party/skia/include/core/SkColor.h"
35#include "ui/gfx/color_utils.h"
36#include "ui/gfx/image/image_skia.h"
37#include "ui/gfx/sys_color_change_listener.h"
38
39
40namespace {
41
42const int kSectionBorderAlphaTransparency = 80;
43
44// Converts SkColor to RGBAColor
45RGBAColor SkColorToRGBAColor(const SkColor& sKColor) {
46  RGBAColor color;
47  color.r = SkColorGetR(sKColor);
48  color.g = SkColorGetG(sKColor);
49  color.b = SkColorGetB(sKColor);
50  color.a = SkColorGetA(sKColor);
51  return color;
52}
53
54}  // namespace
55
56InstantService::InstantService(Profile* profile)
57    : profile_(profile),
58      template_url_service_(TemplateURLServiceFactory::GetForProfile(profile_)),
59      omnibox_start_margin_(chrome::kDisableStartMargin),
60      weak_ptr_factory_(this) {
61  // The initialization below depends on a typical set of browser threads. Skip
62  // it if we are running in a unit test without the full suite.
63  if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI))
64    return;
65
66  // This depends on the existence of the typical browser threads. Therefore it
67  // is only instantiated here (after the check for a UI thread above).
68  instant_io_context_ = new InstantIOContext();
69
70  previous_google_base_url_ =
71      GURL(UIThreadSearchTermsData(profile).GoogleBaseURLValue());
72
73  // TemplateURLService is NULL by default in tests.
74  if (template_url_service_) {
75    template_url_service_->AddObserver(this);
76    const TemplateURL* default_search_provider =
77        template_url_service_->GetDefaultSearchProvider();
78    if (default_search_provider) {
79      previous_default_search_provider_.reset(
80          new TemplateURLData(default_search_provider->data()));
81    }
82  }
83
84  ResetInstantSearchPrerenderer();
85
86  registrar_.Add(this,
87                 content::NOTIFICATION_RENDERER_PROCESS_CREATED,
88                 content::NotificationService::AllSources());
89  registrar_.Add(this,
90                 content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
91                 content::NotificationService::AllSources());
92
93  history::TopSites* top_sites = profile_->GetTopSites();
94  if (top_sites) {
95    registrar_.Add(this,
96                   chrome::NOTIFICATION_TOP_SITES_CHANGED,
97                   content::Source<history::TopSites>(top_sites));
98  }
99
100  if (profile_ && profile_->GetResourceContext()) {
101    content::BrowserThread::PostTask(
102        content::BrowserThread::IO, FROM_HERE,
103        base::Bind(&InstantIOContext::SetUserDataOnIO,
104                   profile->GetResourceContext(), instant_io_context_));
105  }
106
107  // Set up the data sources that Instant uses on the NTP.
108#if defined(ENABLE_THEMES)
109  // Listen for theme installation.
110  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
111                 content::Source<ThemeService>(
112                     ThemeServiceFactory::GetForProfile(profile_)));
113
114  content::URLDataSource::Add(profile_, new ThemeSource(profile_));
115#endif  // defined(ENABLE_THEMES)
116
117  // TODO(aurimas) remove this #if once instant_service.cc is no longer compiled
118  // on Android.
119#if !defined(OS_ANDROID)
120  content::URLDataSource::Add(profile_, new ThumbnailSource(profile_, false));
121  content::URLDataSource::Add(profile_, new ThumbnailSource(profile_, true));
122  content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_));
123#endif  // !defined(OS_ANDROID)
124
125  content::URLDataSource::Add(
126      profile_, new FaviconSource(profile_, FaviconSource::FAVICON));
127  content::URLDataSource::Add(profile_, new LocalNtpSource(profile_));
128  content::URLDataSource::Add(profile_, new MostVisitedIframeSource());
129  content::URLDataSource::Add(
130      profile_, new suggestions::SuggestionsSource(profile_));
131}
132
133InstantService::~InstantService() {
134  if (template_url_service_)
135    template_url_service_->RemoveObserver(this);
136}
137
138void InstantService::AddInstantProcess(int process_id) {
139  process_ids_.insert(process_id);
140
141  if (instant_io_context_.get()) {
142    content::BrowserThread::PostTask(
143        content::BrowserThread::IO, FROM_HERE,
144        base::Bind(&InstantIOContext::AddInstantProcessOnIO,
145                   instant_io_context_, process_id));
146  }
147}
148
149bool InstantService::IsInstantProcess(int process_id) const {
150  return process_ids_.find(process_id) != process_ids_.end();
151}
152
153void InstantService::AddObserver(InstantServiceObserver* observer) {
154  observers_.AddObserver(observer);
155}
156
157void InstantService::RemoveObserver(InstantServiceObserver* observer) {
158  observers_.RemoveObserver(observer);
159}
160
161void InstantService::DeleteMostVisitedItem(const GURL& url) {
162  history::TopSites* top_sites = profile_->GetTopSites();
163  if (!top_sites)
164    return;
165
166  top_sites->AddBlacklistedURL(url);
167}
168
169void InstantService::UndoMostVisitedDeletion(const GURL& url) {
170  history::TopSites* top_sites = profile_->GetTopSites();
171  if (!top_sites)
172    return;
173
174  top_sites->RemoveBlacklistedURL(url);
175}
176
177void InstantService::UndoAllMostVisitedDeletions() {
178  history::TopSites* top_sites = profile_->GetTopSites();
179  if (!top_sites)
180    return;
181
182  top_sites->ClearBlacklistedURLs();
183}
184
185void InstantService::UpdateThemeInfo() {
186  // Update theme background info.
187  // Initialize |theme_info| if necessary.
188  if (!theme_info_)
189    OnThemeChanged(ThemeServiceFactory::GetForProfile(profile_));
190  else
191    OnThemeChanged(NULL);
192}
193
194void InstantService::UpdateMostVisitedItemsInfo() {
195  NotifyAboutMostVisitedItems();
196}
197
198void InstantService::Shutdown() {
199  process_ids_.clear();
200
201  if (instant_io_context_.get()) {
202    content::BrowserThread::PostTask(
203        content::BrowserThread::IO, FROM_HERE,
204        base::Bind(&InstantIOContext::ClearInstantProcessesOnIO,
205                   instant_io_context_));
206  }
207  instant_io_context_ = NULL;
208}
209
210void InstantService::Observe(int type,
211                             const content::NotificationSource& source,
212                             const content::NotificationDetails& details) {
213  switch (type) {
214    case content::NOTIFICATION_RENDERER_PROCESS_CREATED:
215      SendSearchURLsToRenderer(
216          content::Source<content::RenderProcessHost>(source).ptr());
217      break;
218    case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED:
219      OnRendererProcessTerminated(
220          content::Source<content::RenderProcessHost>(source)->GetID());
221      break;
222    case chrome::NOTIFICATION_TOP_SITES_CHANGED: {
223      history::TopSites* top_sites = profile_->GetTopSites();
224      if (top_sites) {
225        top_sites->GetMostVisitedURLs(
226            base::Bind(&InstantService::OnMostVisitedItemsReceived,
227                       weak_ptr_factory_.GetWeakPtr()), false);
228      }
229      break;
230    }
231#if defined(ENABLE_THEMES)
232    case chrome::NOTIFICATION_BROWSER_THEME_CHANGED: {
233      OnThemeChanged(content::Source<ThemeService>(source).ptr());
234      break;
235    }
236#endif  // defined(ENABLE_THEMES)
237    default:
238      NOTREACHED() << "Unexpected notification type in InstantService.";
239  }
240}
241
242void InstantService::SendSearchURLsToRenderer(content::RenderProcessHost* rph) {
243  rph->Send(new ChromeViewMsg_SetSearchURLs(
244      chrome::GetSearchURLs(profile_), chrome::GetNewTabPageURL(profile_)));
245}
246
247void InstantService::OnOmniboxStartMarginChanged(int start_margin) {
248  omnibox_start_margin_ = start_margin;
249  FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
250                    OmniboxStartMarginChanged(omnibox_start_margin_));
251}
252
253void InstantService::OnRendererProcessTerminated(int process_id) {
254  process_ids_.erase(process_id);
255
256  if (instant_io_context_.get()) {
257    content::BrowserThread::PostTask(
258        content::BrowserThread::IO, FROM_HERE,
259        base::Bind(&InstantIOContext::RemoveInstantProcessOnIO,
260                   instant_io_context_, process_id));
261  }
262}
263
264void InstantService::OnMostVisitedItemsReceived(
265    const history::MostVisitedURLList& data) {
266  history::MostVisitedURLList reordered_data(data);
267  std::vector<InstantMostVisitedItem> new_most_visited_items;
268  for (size_t i = 0; i < reordered_data.size(); i++) {
269    const history::MostVisitedURL& url = reordered_data[i];
270    InstantMostVisitedItem item;
271    item.url = url.url;
272    item.title = url.title;
273    new_most_visited_items.push_back(item);
274  }
275
276  most_visited_items_ = new_most_visited_items;
277  NotifyAboutMostVisitedItems();
278}
279
280void InstantService::NotifyAboutMostVisitedItems() {
281  FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
282                    MostVisitedItemsChanged(most_visited_items_));
283}
284
285void InstantService::OnThemeChanged(ThemeService* theme_service) {
286  if (!theme_service) {
287    DCHECK(theme_info_.get());
288    FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
289                      ThemeInfoChanged(*theme_info_));
290    return;
291  }
292
293  // Get theme information from theme service.
294  theme_info_.reset(new ThemeBackgroundInfo());
295
296  // Get if the current theme is the default theme.
297  theme_info_->using_default_theme = theme_service->UsingDefaultTheme();
298
299  // Get theme colors.
300  SkColor background_color =
301      theme_service->GetColor(ThemeProperties::COLOR_NTP_BACKGROUND);
302  SkColor text_color =
303      theme_service->GetColor(ThemeProperties::COLOR_NTP_TEXT);
304  SkColor link_color =
305      theme_service->GetColor(ThemeProperties::COLOR_NTP_LINK);
306  SkColor text_color_light =
307      theme_service->GetColor(ThemeProperties::COLOR_NTP_TEXT_LIGHT);
308  SkColor header_color =
309      theme_service->GetColor(ThemeProperties::COLOR_NTP_HEADER);
310  // Generate section border color from the header color.
311  SkColor section_border_color =
312      SkColorSetARGB(kSectionBorderAlphaTransparency,
313                     SkColorGetR(header_color),
314                     SkColorGetG(header_color),
315                     SkColorGetB(header_color));
316
317  // Invert colors if needed.
318  if (gfx::IsInvertedColorScheme()) {
319    background_color = color_utils::InvertColor(background_color);
320    text_color = color_utils::InvertColor(text_color);
321    link_color = color_utils::InvertColor(link_color);
322    text_color_light = color_utils::InvertColor(text_color_light);
323    header_color = color_utils::InvertColor(header_color);
324    section_border_color = color_utils::InvertColor(section_border_color);
325  }
326
327  // Set colors.
328  theme_info_->background_color = SkColorToRGBAColor(background_color);
329  theme_info_->text_color = SkColorToRGBAColor(text_color);
330  theme_info_->link_color = SkColorToRGBAColor(link_color);
331  theme_info_->text_color_light = SkColorToRGBAColor(text_color_light);
332  theme_info_->header_color = SkColorToRGBAColor(header_color);
333  theme_info_->section_border_color = SkColorToRGBAColor(section_border_color);
334
335  int logo_alternate = theme_service->GetDisplayProperty(
336      ThemeProperties::NTP_LOGO_ALTERNATE);
337  theme_info_->logo_alternate = logo_alternate == 1;
338
339  if (theme_service->HasCustomImage(IDR_THEME_NTP_BACKGROUND)) {
340    // Set theme id for theme background image url.
341    theme_info_->theme_id = theme_service->GetThemeID();
342
343    // Set theme background image horizontal alignment.
344    int alignment = theme_service->GetDisplayProperty(
345        ThemeProperties::NTP_BACKGROUND_ALIGNMENT);
346    if (alignment & ThemeProperties::ALIGN_LEFT)
347      theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_LEFT;
348    else if (alignment & ThemeProperties::ALIGN_RIGHT)
349      theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_RIGHT;
350    else
351      theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER;
352
353    // Set theme background image vertical alignment.
354    if (alignment & ThemeProperties::ALIGN_TOP)
355      theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_TOP;
356    else if (alignment & ThemeProperties::ALIGN_BOTTOM)
357      theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_BOTTOM;
358    else
359      theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER;
360
361    // Set theme backgorund image tiling.
362    int tiling = theme_service->GetDisplayProperty(
363        ThemeProperties::NTP_BACKGROUND_TILING);
364    switch (tiling) {
365      case ThemeProperties::NO_REPEAT:
366        theme_info_->image_tiling = THEME_BKGRND_IMAGE_NO_REPEAT;
367        break;
368      case ThemeProperties::REPEAT_X:
369        theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_X;
370        break;
371      case ThemeProperties::REPEAT_Y:
372        theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_Y;
373        break;
374      case ThemeProperties::REPEAT:
375        theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT;
376        break;
377    }
378
379    // Set theme background image height.
380    gfx::ImageSkia* image = theme_service->GetImageSkiaNamed(
381        IDR_THEME_NTP_BACKGROUND);
382    DCHECK(image);
383    theme_info_->image_height = image->height();
384
385    theme_info_->has_attribution =
386       theme_service->HasCustomImage(IDR_THEME_NTP_ATTRIBUTION);
387  }
388
389  FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
390                    ThemeInfoChanged(*theme_info_));
391}
392
393void InstantService::OnTemplateURLServiceChanged() {
394  // Check whether the default search provider was changed.
395  const TemplateURL* template_url =
396      template_url_service_->GetDefaultSearchProvider();
397  bool default_search_provider_changed = !TemplateURL::MatchesData(
398      template_url, previous_default_search_provider_.get(),
399      UIThreadSearchTermsData(profile_));
400  if (default_search_provider_changed) {
401    previous_default_search_provider_.reset(
402        template_url ? new TemplateURLData(template_url->data()) : NULL);
403  }
404
405  // Note that, even if the TemplateURL for the Default Search Provider has not
406  // changed, the effective URLs might change if they reference the Google base
407  // URL. The TemplateURLService will notify us when the effective URL changes
408  // in this way but it's up to us to do the work to check both.
409  GURL google_base_url(UIThreadSearchTermsData(profile_).GoogleBaseURLValue());
410  if (google_base_url != previous_google_base_url_) {
411    previous_google_base_url_ = google_base_url;
412    if (template_url && template_url->HasGoogleBaseURLs(
413            UIThreadSearchTermsData(profile_)))
414      default_search_provider_changed = true;
415  }
416
417  if (default_search_provider_changed) {
418    ResetInstantSearchPrerenderer();
419    FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
420                      DefaultSearchProviderChanged());
421  }
422}
423
424void InstantService::ResetInstantSearchPrerenderer() {
425  if (!chrome::ShouldPrefetchSearchResults())
426    return;
427
428  GURL url(chrome::GetSearchResultPrefetchBaseURL(profile_));
429  instant_prerenderer_.reset(
430      url.is_valid() ? new InstantSearchPrerenderer(profile_, url) : NULL);
431}
432