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