Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / search / instant_service.cc
blobf8ea9c111d7cb800cca5603884aa0b0eb66de352
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.
5 #include "chrome/browser/search/instant_service.h"
7 #include "base/metrics/field_trial.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/favicon/fallback_icon_service_factory.h"
12 #include "chrome/browser/favicon/large_icon_service_factory.h"
13 #include "chrome/browser/history/top_sites_factory.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/search/instant_io_context.h"
16 #include "chrome/browser/search/instant_service_observer.h"
17 #include "chrome/browser/search/most_visited_iframe_source.h"
18 #include "chrome/browser/search/search.h"
19 #include "chrome/browser/search/suggestions/suggestions_service_factory.h"
20 #include "chrome/browser/search/suggestions/suggestions_source.h"
21 #include "chrome/browser/search/thumbnail_source.h"
22 #include "chrome/browser/search_engines/template_url_service_factory.h"
23 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
24 #include "chrome/browser/thumbnails/thumbnail_list_source.h"
25 #include "chrome/browser/ui/search/instant_search_prerenderer.h"
26 #include "chrome/browser/ui/webui/fallback_icon_source.h"
27 #include "chrome/browser/ui/webui/favicon_source.h"
28 #include "chrome/browser/ui/webui/large_icon_source.h"
29 #include "chrome/browser/ui/webui/theme_source.h"
30 #include "chrome/common/render_messages.h"
31 #include "components/favicon/core/fallback_icon_service.h"
32 #include "components/favicon/core/large_icon_service.h"
33 #include "components/history/core/browser/top_sites.h"
34 #include "components/keyed_service/core/service_access_type.h"
35 #include "components/search/search.h"
36 #include "components/search_engines/template_url_service.h"
37 #include "content/public/browser/browser_thread.h"
38 #include "content/public/browser/notification_service.h"
39 #include "content/public/browser/notification_types.h"
40 #include "content/public/browser/render_process_host.h"
41 #include "content/public/browser/url_data_source.h"
42 #include "grit/theme_resources.h"
43 #include "third_party/skia/include/core/SkColor.h"
44 #include "ui/gfx/color_utils.h"
45 #include "ui/gfx/image/image_skia.h"
47 #if !defined(OS_ANDROID)
48 #include "chrome/browser/search/local_ntp_source.h"
49 #endif
51 #if defined(ENABLE_THEMES)
52 #include "chrome/browser/themes/theme_properties.h"
53 #include "chrome/browser/themes/theme_service.h"
54 #include "chrome/browser/themes/theme_service_factory.h"
55 #endif // defined(ENABLE_THEMES)
57 namespace {
59 const char kLocalNTPSuggestionService[] = "LocalNTPSuggestionsService";
60 const char kLocalNTPSuggestionServiceEnabled[] = "Enabled";
62 bool IsLocalNTPSuggestionServiceEnabled() {
63 return base::StartsWith(
64 base::FieldTrialList::FindFullName(kLocalNTPSuggestionService),
65 kLocalNTPSuggestionServiceEnabled, base::CompareCase::INSENSITIVE_ASCII);
68 } // namespace
70 InstantService::InstantService(Profile* profile)
71 : profile_(profile),
72 template_url_service_(TemplateURLServiceFactory::GetForProfile(profile_)),
73 omnibox_start_margin_(search::kDisableStartMargin),
74 suggestions_service_(NULL),
75 weak_ptr_factory_(this) {
76 // The initialization below depends on a typical set of browser threads. Skip
77 // it if we are running in a unit test without the full suite.
78 if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI))
79 return;
81 // This depends on the existence of the typical browser threads. Therefore it
82 // is only instantiated here (after the check for a UI thread above).
83 instant_io_context_ = new InstantIOContext();
85 previous_google_base_url_ =
86 GURL(UIThreadSearchTermsData(profile).GoogleBaseURLValue());
88 // TemplateURLService is NULL by default in tests.
89 if (template_url_service_) {
90 template_url_service_->AddObserver(this);
91 const TemplateURL* default_search_provider =
92 template_url_service_->GetDefaultSearchProvider();
93 if (default_search_provider) {
94 previous_default_search_provider_.reset(
95 new TemplateURLData(default_search_provider->data()));
99 ResetInstantSearchPrerenderer();
101 registrar_.Add(this,
102 content::NOTIFICATION_RENDERER_PROCESS_CREATED,
103 content::NotificationService::AllSources());
104 registrar_.Add(this,
105 content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
106 content::NotificationService::AllSources());
108 scoped_refptr<history::TopSites> top_sites =
109 TopSitesFactory::GetForProfile(profile_);
110 if (top_sites)
111 top_sites->AddObserver(this);
113 if (profile_ && profile_->GetResourceContext()) {
114 content::BrowserThread::PostTask(
115 content::BrowserThread::IO, FROM_HERE,
116 base::Bind(&InstantIOContext::SetUserDataOnIO,
117 profile->GetResourceContext(), instant_io_context_));
120 // Set up the data sources that Instant uses on the NTP.
121 #if defined(ENABLE_THEMES)
122 // Listen for theme installation.
123 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
124 content::Source<ThemeService>(
125 ThemeServiceFactory::GetForProfile(profile_)));
127 content::URLDataSource::Add(profile_, new ThemeSource(profile_));
128 #endif // defined(ENABLE_THEMES)
130 // TODO(aurimas) remove this #if once instant_service.cc is no longer compiled
131 // on Android.
132 #if !defined(OS_ANDROID)
133 content::URLDataSource::Add(profile_, new LocalNtpSource(profile_));
134 content::URLDataSource::Add(profile_, new ThumbnailSource(profile_, false));
135 content::URLDataSource::Add(profile_, new ThumbnailSource(profile_, true));
136 content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_));
137 #endif // !defined(OS_ANDROID)
139 favicon::FallbackIconService* fallback_icon_service =
140 FallbackIconServiceFactory::GetForBrowserContext(profile_);
141 favicon::LargeIconService* large_icon_service =
142 LargeIconServiceFactory::GetForBrowserContext(profile_);
143 content::URLDataSource::Add(
144 profile_, new FallbackIconSource(fallback_icon_service));
145 content::URLDataSource::Add(
146 profile_, new FaviconSource(profile_, FaviconSource::FAVICON));
147 content::URLDataSource::Add(
148 profile_, new LargeIconSource(fallback_icon_service, large_icon_service));
149 content::URLDataSource::Add(profile_, new MostVisitedIframeSource());
150 content::URLDataSource::Add(
151 profile_, new suggestions::SuggestionsSource(profile_));
153 if (IsLocalNTPSuggestionServiceEnabled()) {
154 suggestions_service_ =
155 suggestions::SuggestionsServiceFactory::GetForProfile(profile_);
158 if (suggestions_service_) {
159 suggestions_service_->FetchSuggestionsData(
160 suggestions::INITIALIZED_ENABLED_HISTORY,
161 base::Bind(&InstantService::OnSuggestionsAvailable,
162 weak_ptr_factory_.GetWeakPtr()));
166 InstantService::~InstantService() {
167 if (template_url_service_)
168 template_url_service_->RemoveObserver(this);
171 void InstantService::AddInstantProcess(int process_id) {
172 process_ids_.insert(process_id);
174 if (instant_io_context_.get()) {
175 content::BrowserThread::PostTask(
176 content::BrowserThread::IO, FROM_HERE,
177 base::Bind(&InstantIOContext::AddInstantProcessOnIO,
178 instant_io_context_, process_id));
182 bool InstantService::IsInstantProcess(int process_id) const {
183 return process_ids_.find(process_id) != process_ids_.end();
186 void InstantService::AddObserver(InstantServiceObserver* observer) {
187 observers_.AddObserver(observer);
190 void InstantService::RemoveObserver(InstantServiceObserver* observer) {
191 observers_.RemoveObserver(observer);
194 void InstantService::DeleteMostVisitedItem(const GURL& url) {
195 scoped_refptr<history::TopSites> top_sites =
196 TopSitesFactory::GetForProfile(profile_);
197 if (top_sites)
198 top_sites->AddBlacklistedURL(url);
200 if (suggestions_service_) {
201 suggestions_service_->BlacklistURL(
202 url, base::Bind(&InstantService::OnSuggestionsAvailable,
203 weak_ptr_factory_.GetWeakPtr()),
204 base::Closure());
208 void InstantService::UndoMostVisitedDeletion(const GURL& url) {
209 scoped_refptr<history::TopSites> top_sites =
210 TopSitesFactory::GetForProfile(profile_);
211 if (top_sites)
212 top_sites->RemoveBlacklistedURL(url);
214 if (suggestions_service_) {
215 suggestions_service_->UndoBlacklistURL(
216 url, base::Bind(&InstantService::OnSuggestionsAvailable,
217 weak_ptr_factory_.GetWeakPtr()),
218 base::Closure());
222 void InstantService::UndoAllMostVisitedDeletions() {
223 scoped_refptr<history::TopSites> top_sites =
224 TopSitesFactory::GetForProfile(profile_);
225 if (top_sites)
226 top_sites->ClearBlacklistedURLs();
228 if (suggestions_service_) {
229 suggestions_service_->ClearBlacklist(
230 base::Bind(&InstantService::OnSuggestionsAvailable,
231 weak_ptr_factory_.GetWeakPtr()));
235 void InstantService::UpdateThemeInfo() {
236 #if defined(ENABLE_THEMES)
237 // Update theme background info.
238 // Initialize |theme_info| if necessary.
239 if (!theme_info_)
240 OnThemeChanged(ThemeServiceFactory::GetForProfile(profile_));
241 else
242 OnThemeChanged(NULL);
243 #endif // defined(ENABLE_THEMES)
246 void InstantService::UpdateMostVisitedItemsInfo() {
247 NotifyAboutMostVisitedItems();
250 void InstantService::Shutdown() {
251 process_ids_.clear();
253 if (instant_io_context_.get()) {
254 content::BrowserThread::PostTask(
255 content::BrowserThread::IO, FROM_HERE,
256 base::Bind(&InstantIOContext::ClearInstantProcessesOnIO,
257 instant_io_context_));
260 scoped_refptr<history::TopSites> top_sites =
261 TopSitesFactory::GetForProfile(profile_);
262 if (top_sites)
263 top_sites->RemoveObserver(this);
265 instant_io_context_ = NULL;
268 void InstantService::Observe(int type,
269 const content::NotificationSource& source,
270 const content::NotificationDetails& details) {
271 switch (type) {
272 case content::NOTIFICATION_RENDERER_PROCESS_CREATED:
273 SendSearchURLsToRenderer(
274 content::Source<content::RenderProcessHost>(source).ptr());
275 break;
276 case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED:
277 OnRendererProcessTerminated(
278 content::Source<content::RenderProcessHost>(source)->GetID());
279 break;
280 #if defined(ENABLE_THEMES)
281 case chrome::NOTIFICATION_BROWSER_THEME_CHANGED: {
282 OnThemeChanged(content::Source<ThemeService>(source).ptr());
283 break;
285 #endif // defined(ENABLE_THEMES)
286 default:
287 NOTREACHED() << "Unexpected notification type in InstantService.";
291 void InstantService::SendSearchURLsToRenderer(content::RenderProcessHost* rph) {
292 rph->Send(new ChromeViewMsg_SetSearchURLs(
293 search::GetSearchURLs(profile_), search::GetNewTabPageURL(profile_)));
296 void InstantService::OnOmniboxStartMarginChanged(int start_margin) {
297 omnibox_start_margin_ = start_margin;
298 FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
299 OmniboxStartMarginChanged(omnibox_start_margin_));
302 void InstantService::OnRendererProcessTerminated(int process_id) {
303 process_ids_.erase(process_id);
305 if (instant_io_context_.get()) {
306 content::BrowserThread::PostTask(
307 content::BrowserThread::IO, FROM_HERE,
308 base::Bind(&InstantIOContext::RemoveInstantProcessOnIO,
309 instant_io_context_, process_id));
313 void InstantService::OnSuggestionsAvailable(
314 const suggestions::SuggestionsProfile& profile) {
315 std::vector<InstantMostVisitedItem> new_suggestions_items;
316 for (int i = 0; i < profile.suggestions_size(); ++i) {
317 const suggestions::ChromeSuggestion& suggestion = profile.suggestions(i);
319 InstantMostVisitedItem item;
320 item.url = GURL(suggestion.url());
321 item.title = base::UTF8ToUTF16(suggestion.title());
322 if (suggestion.has_thumbnail()) {
323 item.thumbnail = GURL(suggestion.thumbnail());
325 if (suggestion.has_favicon_url()) {
326 item.favicon = GURL(suggestion.favicon_url());
328 if (suggestion.has_impression_url()) {
329 item.impression_url = GURL(suggestion.impression_url());
331 if (suggestion.has_click_url()) {
332 item.click_url = GURL(suggestion.click_url());
334 new_suggestions_items.push_back(item);
336 suggestions_items_ = new_suggestions_items;
337 NotifyAboutMostVisitedItems();
340 void InstantService::OnMostVisitedItemsReceived(
341 const history::MostVisitedURLList& data) {
342 history::MostVisitedURLList reordered_data(data);
343 std::vector<InstantMostVisitedItem> new_most_visited_items;
344 for (size_t i = 0; i < reordered_data.size(); i++) {
345 const history::MostVisitedURL& url = reordered_data[i];
346 InstantMostVisitedItem item;
347 item.url = url.url;
348 item.title = url.title;
349 new_most_visited_items.push_back(item);
352 most_visited_items_ = new_most_visited_items;
353 NotifyAboutMostVisitedItems();
356 void InstantService::NotifyAboutMostVisitedItems() {
357 if (suggestions_service_ && !suggestions_items_.empty()) {
358 FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
359 MostVisitedItemsChanged(suggestions_items_));
360 } else {
361 FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
362 MostVisitedItemsChanged(most_visited_items_));
366 #if defined(ENABLE_THEMES)
368 namespace {
370 const int kSectionBorderAlphaTransparency = 80;
372 // Converts SkColor to RGBAColor
373 RGBAColor SkColorToRGBAColor(const SkColor& sKColor) {
374 RGBAColor color;
375 color.r = SkColorGetR(sKColor);
376 color.g = SkColorGetG(sKColor);
377 color.b = SkColorGetB(sKColor);
378 color.a = SkColorGetA(sKColor);
379 return color;
382 } // namespace
384 void InstantService::OnThemeChanged(ThemeService* theme_service) {
385 if (!theme_service) {
386 DCHECK(theme_info_.get());
387 FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
388 ThemeInfoChanged(*theme_info_));
389 return;
392 // Get theme information from theme service.
393 theme_info_.reset(new ThemeBackgroundInfo());
395 // Get if the current theme is the default theme.
396 theme_info_->using_default_theme = theme_service->UsingDefaultTheme();
398 // Get theme colors.
399 SkColor background_color =
400 theme_service->GetColor(ThemeProperties::COLOR_NTP_BACKGROUND);
401 SkColor text_color =
402 theme_service->GetColor(ThemeProperties::COLOR_NTP_TEXT);
403 SkColor link_color =
404 theme_service->GetColor(ThemeProperties::COLOR_NTP_LINK);
405 SkColor text_color_light =
406 theme_service->GetColor(ThemeProperties::COLOR_NTP_TEXT_LIGHT);
407 SkColor header_color =
408 theme_service->GetColor(ThemeProperties::COLOR_NTP_HEADER);
409 // Generate section border color from the header color.
410 SkColor section_border_color =
411 SkColorSetARGB(kSectionBorderAlphaTransparency,
412 SkColorGetR(header_color),
413 SkColorGetG(header_color),
414 SkColorGetB(header_color));
416 // Invert colors if needed.
417 if (color_utils::IsInvertedColorScheme()) {
418 background_color = color_utils::InvertColor(background_color);
419 text_color = color_utils::InvertColor(text_color);
420 link_color = color_utils::InvertColor(link_color);
421 text_color_light = color_utils::InvertColor(text_color_light);
422 header_color = color_utils::InvertColor(header_color);
423 section_border_color = color_utils::InvertColor(section_border_color);
426 // Set colors.
427 theme_info_->background_color = SkColorToRGBAColor(background_color);
428 theme_info_->text_color = SkColorToRGBAColor(text_color);
429 theme_info_->link_color = SkColorToRGBAColor(link_color);
430 theme_info_->text_color_light = SkColorToRGBAColor(text_color_light);
431 theme_info_->header_color = SkColorToRGBAColor(header_color);
432 theme_info_->section_border_color = SkColorToRGBAColor(section_border_color);
434 int logo_alternate = theme_service->GetDisplayProperty(
435 ThemeProperties::NTP_LOGO_ALTERNATE);
436 theme_info_->logo_alternate = logo_alternate == 1;
438 if (theme_service->HasCustomImage(IDR_THEME_NTP_BACKGROUND)) {
439 // Set theme id for theme background image url.
440 theme_info_->theme_id = theme_service->GetThemeID();
442 // Set theme background image horizontal alignment.
443 int alignment = theme_service->GetDisplayProperty(
444 ThemeProperties::NTP_BACKGROUND_ALIGNMENT);
445 if (alignment & ThemeProperties::ALIGN_LEFT)
446 theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_LEFT;
447 else if (alignment & ThemeProperties::ALIGN_RIGHT)
448 theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_RIGHT;
449 else
450 theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER;
452 // Set theme background image vertical alignment.
453 if (alignment & ThemeProperties::ALIGN_TOP)
454 theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_TOP;
455 else if (alignment & ThemeProperties::ALIGN_BOTTOM)
456 theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_BOTTOM;
457 else
458 theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER;
460 // Set theme backgorund image tiling.
461 int tiling = theme_service->GetDisplayProperty(
462 ThemeProperties::NTP_BACKGROUND_TILING);
463 switch (tiling) {
464 case ThemeProperties::NO_REPEAT:
465 theme_info_->image_tiling = THEME_BKGRND_IMAGE_NO_REPEAT;
466 break;
467 case ThemeProperties::REPEAT_X:
468 theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_X;
469 break;
470 case ThemeProperties::REPEAT_Y:
471 theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_Y;
472 break;
473 case ThemeProperties::REPEAT:
474 theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT;
475 break;
478 // Set theme background image height.
479 gfx::ImageSkia* image = theme_service->GetImageSkiaNamed(
480 IDR_THEME_NTP_BACKGROUND);
481 DCHECK(image);
482 theme_info_->image_height = image->height();
484 theme_info_->has_attribution =
485 theme_service->HasCustomImage(IDR_THEME_NTP_ATTRIBUTION);
488 FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
489 ThemeInfoChanged(*theme_info_));
491 #endif // defined(ENABLE_THEMES)
493 void InstantService::OnTemplateURLServiceChanged() {
494 // Check whether the default search provider was changed.
495 const TemplateURL* template_url =
496 template_url_service_->GetDefaultSearchProvider();
497 bool default_search_provider_changed = !TemplateURL::MatchesData(
498 template_url, previous_default_search_provider_.get(),
499 UIThreadSearchTermsData(profile_));
500 if (default_search_provider_changed) {
501 previous_default_search_provider_.reset(
502 template_url ? new TemplateURLData(template_url->data()) : NULL);
505 // Note that, even if the TemplateURL for the Default Search Provider has not
506 // changed, the effective URLs might change if they reference the Google base
507 // URL. The TemplateURLService will notify us when the effective URL changes
508 // in this way but it's up to us to do the work to check both.
509 bool google_base_url_domain_changed = false;
510 GURL google_base_url(UIThreadSearchTermsData(profile_).GoogleBaseURLValue());
511 if (google_base_url != previous_google_base_url_) {
512 previous_google_base_url_ = google_base_url;
513 if (template_url && template_url->HasGoogleBaseURLs(
514 UIThreadSearchTermsData(profile_)))
515 google_base_url_domain_changed = true;
518 if (default_search_provider_changed || google_base_url_domain_changed) {
519 ResetInstantSearchPrerenderer();
520 FOR_EACH_OBSERVER(
521 InstantServiceObserver, observers_,
522 DefaultSearchProviderChanged(google_base_url_domain_changed));
526 void InstantService::TopSitesLoaded(history::TopSites* top_sites) {
529 void InstantService::TopSitesChanged(history::TopSites* top_sites,
530 ChangeReason change_reason) {
531 // As forced urls already come from tiles, we can safely ignore those updates.
532 if (change_reason == history::TopSitesObserver::ChangeReason::FORCED_URL)
533 return;
534 top_sites->GetMostVisitedURLs(
535 base::Bind(&InstantService::OnMostVisitedItemsReceived,
536 weak_ptr_factory_.GetWeakPtr()),
537 false);
540 void InstantService::ResetInstantSearchPrerenderer() {
541 if (!search::ShouldPrefetchSearchResults())
542 return;
544 GURL url(search::GetSearchResultPrefetchBaseURL(profile_));
545 instant_prerenderer_.reset(
546 url.is_valid() ? new InstantSearchPrerenderer(profile_, url) : NULL);