Home | History | Annotate | Download | only in search
      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 
     40 namespace {
     41 
     42 const int kSectionBorderAlphaTransparency = 80;
     43 
     44 // Converts SkColor to RGBAColor
     45 RGBAColor 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 
     56 InstantService::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 
    133 InstantService::~InstantService() {
    134   if (template_url_service_)
    135     template_url_service_->RemoveObserver(this);
    136 }
    137 
    138 void 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 
    149 bool InstantService::IsInstantProcess(int process_id) const {
    150   return process_ids_.find(process_id) != process_ids_.end();
    151 }
    152 
    153 void InstantService::AddObserver(InstantServiceObserver* observer) {
    154   observers_.AddObserver(observer);
    155 }
    156 
    157 void InstantService::RemoveObserver(InstantServiceObserver* observer) {
    158   observers_.RemoveObserver(observer);
    159 }
    160 
    161 void 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 
    169 void 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 
    177 void InstantService::UndoAllMostVisitedDeletions() {
    178   history::TopSites* top_sites = profile_->GetTopSites();
    179   if (!top_sites)
    180     return;
    181 
    182   top_sites->ClearBlacklistedURLs();
    183 }
    184 
    185 void 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 
    194 void InstantService::UpdateMostVisitedItemsInfo() {
    195   NotifyAboutMostVisitedItems();
    196 }
    197 
    198 void 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 
    210 void 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 
    242 void InstantService::SendSearchURLsToRenderer(content::RenderProcessHost* rph) {
    243   rph->Send(new ChromeViewMsg_SetSearchURLs(
    244       chrome::GetSearchURLs(profile_), chrome::GetNewTabPageURL(profile_)));
    245 }
    246 
    247 void InstantService::OnOmniboxStartMarginChanged(int start_margin) {
    248   omnibox_start_margin_ = start_margin;
    249   FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
    250                     OmniboxStartMarginChanged(omnibox_start_margin_));
    251 }
    252 
    253 void 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 
    264 void 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 
    280 void InstantService::NotifyAboutMostVisitedItems() {
    281   FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
    282                     MostVisitedItemsChanged(most_visited_items_));
    283 }
    284 
    285 void 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 
    393 void 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 
    424 void 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