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 <vector>
      8 
      9 #include "base/logging.h"
     10 #include "base/prefs/pref_service.h"
     11 #include "base/strings/string_number_conversions.h"
     12 #include "chrome/browser/chrome_notification_types.h"
     13 #include "chrome/browser/history/history_notifications.h"
     14 #include "chrome/browser/history/most_visited_tiles_experiment.h"
     15 #include "chrome/browser/history/top_sites.h"
     16 #include "chrome/browser/profiles/profile.h"
     17 #include "chrome/browser/search/instant_io_context.h"
     18 #include "chrome/browser/search/instant_service_factory.h"
     19 #include "chrome/browser/search/instant_service_observer.h"
     20 #include "chrome/browser/search/local_ntp_source.h"
     21 #include "chrome/browser/search/most_visited_iframe_source.h"
     22 #include "chrome/browser/search/search.h"
     23 #include "chrome/browser/search_engines/template_url.h"
     24 #include "chrome/browser/search_engines/template_url_service.h"
     25 #include "chrome/browser/search_engines/template_url_service_factory.h"
     26 #include "chrome/browser/themes/theme_properties.h"
     27 #include "chrome/browser/themes/theme_service.h"
     28 #include "chrome/browser/themes/theme_service_factory.h"
     29 #include "chrome/browser/ui/webui/favicon_source.h"
     30 #include "chrome/browser/ui/webui/ntp/thumbnail_list_source.h"
     31 #include "chrome/browser/ui/webui/ntp/thumbnail_source.h"
     32 #include "chrome/browser/ui/webui/theme_source.h"
     33 #include "chrome/common/pref_names.h"
     34 #include "chrome/common/render_messages.h"
     35 #include "content/public/browser/browser_thread.h"
     36 #include "content/public/browser/notification_details.h"
     37 #include "content/public/browser/notification_service.h"
     38 #include "content/public/browser/notification_source.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 "net/base/net_util.h"
     44 #include "net/url_request/url_request.h"
     45 #include "ui/gfx/color_utils.h"
     46 #include "ui/gfx/image/image_skia.h"
     47 #include "ui/gfx/sys_color_change_listener.h"
     48 #include "url/gurl.h"
     49 
     50 using content::BrowserThread;
     51 
     52 namespace {
     53 
     54 const int kSectionBorderAlphaTransparency = 80;
     55 
     56 // Converts SkColor to RGBAColor
     57 RGBAColor SkColorToRGBAColor(const SkColor& sKColor) {
     58   RGBAColor color;
     59   color.r = SkColorGetR(sKColor);
     60   color.g = SkColorGetG(sKColor);
     61   color.b = SkColorGetB(sKColor);
     62   color.a = SkColorGetA(sKColor);
     63   return color;
     64 }
     65 
     66 }  // namespace
     67 
     68 InstantService::InstantService(Profile* profile)
     69     : profile_(profile),
     70       ntp_prerenderer_(profile, this, profile->GetPrefs()),
     71       browser_instant_controller_object_count_(0),
     72       weak_ptr_factory_(this) {
     73   // Stub for unit tests.
     74   if (!BrowserThread::CurrentlyOn(BrowserThread::UI))
     75     return;
     76 
     77   ResetInstantSearchPrerenderer();
     78 
     79   registrar_.Add(this,
     80                  content::NOTIFICATION_RENDERER_PROCESS_CREATED,
     81                  content::NotificationService::AllSources());
     82   registrar_.Add(this,
     83                  content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
     84                  content::NotificationService::AllSources());
     85 
     86   history::TopSites* top_sites = profile_->GetTopSites();
     87   if (top_sites) {
     88     registrar_.Add(this,
     89                    chrome::NOTIFICATION_TOP_SITES_CHANGED,
     90                    content::Source<history::TopSites>(top_sites));
     91   }
     92   instant_io_context_ = new InstantIOContext();
     93 
     94   if (profile_ && profile_->GetResourceContext()) {
     95     BrowserThread::PostTask(
     96         BrowserThread::IO, FROM_HERE,
     97         base::Bind(&InstantIOContext::SetUserDataOnIO,
     98                    profile->GetResourceContext(), instant_io_context_));
     99   }
    100 
    101   // Set up the data sources that Instant uses on the NTP.
    102 #if defined(ENABLE_THEMES)
    103   // Listen for theme installation.
    104   registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
    105                  content::Source<ThemeService>(
    106                      ThemeServiceFactory::GetForProfile(profile_)));
    107 
    108   content::URLDataSource::Add(profile_, new ThemeSource(profile_));
    109 #endif  // defined(ENABLE_THEMES)
    110 
    111   content::URLDataSource::Add(profile_, new ThumbnailSource(profile_, false));
    112   content::URLDataSource::Add(profile_, new ThumbnailSource(profile_, true));
    113   content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_));
    114   content::URLDataSource::Add(
    115       profile_, new FaviconSource(profile_, FaviconSource::FAVICON));
    116   content::URLDataSource::Add(profile_, new LocalNtpSource(profile_));
    117   content::URLDataSource::Add(profile_, new MostVisitedIframeSource());
    118 
    119   profile_pref_registrar_.Init(profile_->GetPrefs());
    120   profile_pref_registrar_.Add(
    121       prefs::kDefaultSearchProviderID,
    122       base::Bind(&InstantService::OnDefaultSearchProviderChanged,
    123                  base::Unretained(this)));
    124 
    125   registrar_.Add(this, chrome::NOTIFICATION_GOOGLE_URL_UPDATED,
    126                  content::Source<Profile>(profile_->GetOriginalProfile()));
    127   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
    128                  content::Source<Profile>(profile_));
    129 }
    130 
    131 InstantService::~InstantService() {
    132 }
    133 
    134 void InstantService::AddInstantProcess(int process_id) {
    135   process_ids_.insert(process_id);
    136 
    137   if (instant_io_context_.get()) {
    138     BrowserThread::PostTask(BrowserThread::IO,
    139                             FROM_HERE,
    140                             base::Bind(&InstantIOContext::AddInstantProcessOnIO,
    141                                        instant_io_context_,
    142                                        process_id));
    143   }
    144 }
    145 
    146 bool InstantService::IsInstantProcess(int process_id) const {
    147   return process_ids_.find(process_id) != process_ids_.end();
    148 }
    149 
    150 void InstantService::AddObserver(InstantServiceObserver* observer) {
    151   observers_.AddObserver(observer);
    152 }
    153 
    154 void InstantService::RemoveObserver(InstantServiceObserver* observer) {
    155   observers_.RemoveObserver(observer);
    156 }
    157 
    158 void InstantService::DeleteMostVisitedItem(const GURL& url) {
    159   history::TopSites* top_sites = profile_->GetTopSites();
    160   if (!top_sites)
    161     return;
    162 
    163   top_sites->AddBlacklistedURL(url);
    164 }
    165 
    166 void InstantService::UndoMostVisitedDeletion(const GURL& url) {
    167   history::TopSites* top_sites = profile_->GetTopSites();
    168   if (!top_sites)
    169     return;
    170 
    171   top_sites->RemoveBlacklistedURL(url);
    172 }
    173 
    174 void InstantService::UndoAllMostVisitedDeletions() {
    175   history::TopSites* top_sites = profile_->GetTopSites();
    176   if (!top_sites)
    177     return;
    178 
    179   top_sites->ClearBlacklistedURLs();
    180 }
    181 
    182 void InstantService::UpdateThemeInfo() {
    183   // Update theme background info.
    184   // Initialize |theme_info| if necessary.
    185   if (!theme_info_)
    186     OnThemeChanged(ThemeServiceFactory::GetForProfile(profile_));
    187   else
    188     OnThemeChanged(NULL);
    189 }
    190 
    191 void InstantService::UpdateMostVisitedItemsInfo() {
    192   NotifyAboutMostVisitedItems();
    193 }
    194 
    195 void InstantService::Shutdown() {
    196   process_ids_.clear();
    197 
    198   if (instant_io_context_.get()) {
    199     BrowserThread::PostTask(
    200         BrowserThread::IO,
    201         FROM_HERE,
    202         base::Bind(&InstantIOContext::ClearInstantProcessesOnIO,
    203                    instant_io_context_));
    204   }
    205   instant_io_context_ = NULL;
    206 }
    207 
    208 scoped_ptr<content::WebContents> InstantService::ReleaseNTPContents() {
    209   return ntp_prerenderer_.ReleaseNTPContents();
    210 }
    211 
    212 content::WebContents* InstantService::GetNTPContents() const {
    213   return ntp_prerenderer_.GetNTPContents();
    214 }
    215 
    216 void InstantService::OnBrowserInstantControllerCreated() {
    217   if (profile_->IsOffTheRecord())
    218     return;
    219 
    220   ++browser_instant_controller_object_count_;
    221 
    222   if (browser_instant_controller_object_count_ == 1)
    223     ntp_prerenderer_.ReloadInstantNTP();
    224 }
    225 
    226 void InstantService::OnBrowserInstantControllerDestroyed() {
    227   if (profile_->IsOffTheRecord())
    228     return;
    229 
    230   DCHECK_GT(browser_instant_controller_object_count_, 0U);
    231   --browser_instant_controller_object_count_;
    232 
    233   // All browser windows have closed, so release the InstantNTP resources to
    234   // work around http://crbug.com/180810.
    235   if (browser_instant_controller_object_count_ == 0)
    236     ntp_prerenderer_.DeleteNTPContents();
    237 }
    238 
    239 void InstantService::Observe(int type,
    240                              const content::NotificationSource& source,
    241                              const content::NotificationDetails& details) {
    242   switch (type) {
    243     case content::NOTIFICATION_RENDERER_PROCESS_CREATED:
    244       SendSearchURLsToRenderer(
    245           content::Source<content::RenderProcessHost>(source).ptr());
    246       break;
    247     case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED:
    248       OnRendererProcessTerminated(
    249           content::Source<content::RenderProcessHost>(source)->GetID());
    250       break;
    251     case chrome::NOTIFICATION_TOP_SITES_CHANGED: {
    252       history::TopSites* top_sites = profile_->GetTopSites();
    253       if (top_sites) {
    254         top_sites->GetMostVisitedURLs(
    255             base::Bind(&InstantService::OnMostVisitedItemsReceived,
    256                        weak_ptr_factory_.GetWeakPtr()), false);
    257       }
    258       break;
    259     }
    260 #if defined(ENABLE_THEMES)
    261     case chrome::NOTIFICATION_BROWSER_THEME_CHANGED: {
    262       OnThemeChanged(content::Source<ThemeService>(source).ptr());
    263       break;
    264     }
    265 #endif  // defined(ENABLE_THEMES)
    266     case chrome::NOTIFICATION_PROFILE_DESTROYED: {
    267       // Last chance to delete InstantNTP contents. We generally delete
    268       // preloaded InstantNTP when the last BrowserInstantController object is
    269       // destroyed. When the browser shutdown happens without closing browsers,
    270       // there is a race condition between BrowserInstantController destruction
    271       // and Profile destruction.
    272       if (GetNTPContents())
    273         ntp_prerenderer_.DeleteNTPContents();
    274       break;
    275     }
    276     case chrome::NOTIFICATION_GOOGLE_URL_UPDATED: {
    277       OnGoogleURLUpdated(
    278           content::Source<Profile>(source).ptr(),
    279           content::Details<GoogleURLTracker::UpdatedDetails>(details).ptr());
    280       break;
    281     }
    282     default:
    283       NOTREACHED() << "Unexpected notification type in InstantService.";
    284   }
    285 }
    286 
    287 void InstantService::SendSearchURLsToRenderer(content::RenderProcessHost* rph) {
    288   rph->Send(new ChromeViewMsg_SetSearchURLs(
    289       chrome::GetSearchURLs(profile_), chrome::GetNewTabPageURL(profile_)));
    290 }
    291 
    292 void InstantService::OnRendererProcessTerminated(int process_id) {
    293   process_ids_.erase(process_id);
    294 
    295   if (instant_io_context_.get()) {
    296     BrowserThread::PostTask(
    297         BrowserThread::IO,
    298         FROM_HERE,
    299         base::Bind(&InstantIOContext::RemoveInstantProcessOnIO,
    300                    instant_io_context_,
    301                    process_id));
    302   }
    303 }
    304 
    305 void InstantService::OnMostVisitedItemsReceived(
    306     const history::MostVisitedURLList& data) {
    307   history::MostVisitedURLList reordered_data(data);
    308   history::MostVisitedTilesExperiment::MaybeShuffle(&reordered_data);
    309 
    310   std::vector<InstantMostVisitedItem> new_most_visited_items;
    311   for (size_t i = 0; i < reordered_data.size(); i++) {
    312     const history::MostVisitedURL& url = reordered_data[i];
    313     InstantMostVisitedItem item;
    314     item.url = url.url;
    315     item.title = url.title;
    316     new_most_visited_items.push_back(item);
    317   }
    318 
    319   most_visited_items_ = new_most_visited_items;
    320   NotifyAboutMostVisitedItems();
    321 }
    322 
    323 void InstantService::NotifyAboutMostVisitedItems() {
    324   FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
    325                     MostVisitedItemsChanged(most_visited_items_));
    326 }
    327 
    328 void InstantService::OnThemeChanged(ThemeService* theme_service) {
    329   if (!theme_service) {
    330     DCHECK(theme_info_.get());
    331     FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
    332                       ThemeInfoChanged(*theme_info_));
    333     return;
    334   }
    335 
    336   // Get theme information from theme service.
    337   theme_info_.reset(new ThemeBackgroundInfo());
    338 
    339   // Get if the current theme is the default theme.
    340   theme_info_->using_default_theme = theme_service->UsingDefaultTheme();
    341 
    342   // Get theme colors.
    343   SkColor background_color =
    344       theme_service->GetColor(ThemeProperties::COLOR_NTP_BACKGROUND);
    345   SkColor text_color =
    346       theme_service->GetColor(ThemeProperties::COLOR_NTP_TEXT);
    347   SkColor link_color =
    348       theme_service->GetColor(ThemeProperties::COLOR_NTP_LINK);
    349   SkColor text_color_light =
    350       theme_service->GetColor(ThemeProperties::COLOR_NTP_TEXT_LIGHT);
    351   SkColor header_color =
    352       theme_service->GetColor(ThemeProperties::COLOR_NTP_HEADER);
    353   // Generate section border color from the header color.
    354   SkColor section_border_color =
    355       SkColorSetARGB(kSectionBorderAlphaTransparency,
    356                      SkColorGetR(header_color),
    357                      SkColorGetG(header_color),
    358                      SkColorGetB(header_color));
    359 
    360   // Invert colors if needed.
    361   if (gfx::IsInvertedColorScheme()) {
    362     background_color = color_utils::InvertColor(background_color);
    363     text_color = color_utils::InvertColor(text_color);
    364     link_color = color_utils::InvertColor(link_color);
    365     text_color_light = color_utils::InvertColor(text_color_light);
    366     header_color = color_utils::InvertColor(header_color);
    367     section_border_color = color_utils::InvertColor(section_border_color);
    368   }
    369 
    370   // Set colors.
    371   theme_info_->background_color = SkColorToRGBAColor(background_color);
    372   theme_info_->text_color = SkColorToRGBAColor(text_color);
    373   theme_info_->link_color = SkColorToRGBAColor(link_color);
    374   theme_info_->text_color_light = SkColorToRGBAColor(text_color_light);
    375   theme_info_->header_color = SkColorToRGBAColor(header_color);
    376   theme_info_->section_border_color = SkColorToRGBAColor(section_border_color);
    377 
    378   int logo_alternate = theme_service->GetDisplayProperty(
    379       ThemeProperties::NTP_LOGO_ALTERNATE);
    380   theme_info_->logo_alternate = logo_alternate == 1;
    381 
    382   if (theme_service->HasCustomImage(IDR_THEME_NTP_BACKGROUND)) {
    383     // Set theme id for theme background image url.
    384     theme_info_->theme_id = theme_service->GetThemeID();
    385 
    386     // Set theme background image horizontal alignment.
    387     int alignment = theme_service->GetDisplayProperty(
    388         ThemeProperties::NTP_BACKGROUND_ALIGNMENT);
    389     if (alignment & ThemeProperties::ALIGN_LEFT)
    390       theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_LEFT;
    391     else if (alignment & ThemeProperties::ALIGN_RIGHT)
    392       theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_RIGHT;
    393     else
    394       theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER;
    395 
    396     // Set theme background image vertical alignment.
    397     if (alignment & ThemeProperties::ALIGN_TOP)
    398       theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_TOP;
    399     else if (alignment & ThemeProperties::ALIGN_BOTTOM)
    400       theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_BOTTOM;
    401     else
    402       theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER;
    403 
    404     // Set theme backgorund image tiling.
    405     int tiling = theme_service->GetDisplayProperty(
    406         ThemeProperties::NTP_BACKGROUND_TILING);
    407     switch (tiling) {
    408       case ThemeProperties::NO_REPEAT:
    409         theme_info_->image_tiling = THEME_BKGRND_IMAGE_NO_REPEAT;
    410         break;
    411       case ThemeProperties::REPEAT_X:
    412         theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_X;
    413         break;
    414       case ThemeProperties::REPEAT_Y:
    415         theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_Y;
    416         break;
    417       case ThemeProperties::REPEAT:
    418         theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT;
    419         break;
    420     }
    421 
    422     // Set theme background image height.
    423     gfx::ImageSkia* image = theme_service->GetImageSkiaNamed(
    424         IDR_THEME_NTP_BACKGROUND);
    425     DCHECK(image);
    426     theme_info_->image_height = image->height();
    427 
    428     theme_info_->has_attribution =
    429        theme_service->HasCustomImage(IDR_THEME_NTP_ATTRIBUTION);
    430   }
    431 
    432   FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
    433                     ThemeInfoChanged(*theme_info_));
    434 }
    435 
    436 void InstantService::OnGoogleURLUpdated(
    437     Profile* profile,
    438     GoogleURLTracker::UpdatedDetails* details) {
    439   GURL last_prompted_url(
    440       profile->GetPrefs()->GetString(prefs::kLastPromptedGoogleURL));
    441 
    442   // See GoogleURLTracker::OnURLFetchComplete().
    443   // last_prompted_url.is_empty() indicates very first run of Chrome. So there
    444   // is no need to notify, as there won't be any old state.
    445   if (last_prompted_url.is_empty())
    446     return;
    447 
    448   ResetInstantSearchPrerenderer();
    449 
    450   // Only the scheme changed. Ignore it since we do not prompt the user in this
    451   // case.
    452   if (net::StripWWWFromHost(details->first) ==
    453       net::StripWWWFromHost(details->second))
    454     return;
    455 
    456   FOR_EACH_OBSERVER(InstantServiceObserver, observers_, GoogleURLUpdated());
    457 }
    458 
    459 void InstantService::OnDefaultSearchProviderChanged(
    460     const std::string& pref_name) {
    461   DCHECK_EQ(pref_name, std::string(prefs::kDefaultSearchProviderID));
    462   const TemplateURL* template_url = TemplateURLServiceFactory::GetForProfile(
    463       profile_)->GetDefaultSearchProvider();
    464   if (!template_url) {
    465     // A NULL |template_url| could mean either this notification is sent during
    466     // the browser start up operation or the user now has no default search
    467     // provider. There is no way for the user to reach this state using the
    468     // Chrome settings. Only explicitly poking at the DB or bugs in the Sync
    469     // could cause that, neither of which we support.
    470     return;
    471   }
    472 
    473   ResetInstantSearchPrerenderer();
    474 
    475   FOR_EACH_OBSERVER(
    476       InstantServiceObserver, observers_, DefaultSearchProviderChanged());
    477 }
    478 
    479 InstantNTPPrerenderer* InstantService::ntp_prerenderer() {
    480   return &ntp_prerenderer_;
    481 }
    482 
    483 void InstantService::ResetInstantSearchPrerenderer() {
    484   if (!chrome::ShouldPrefetchSearchResults())
    485     return;
    486 
    487   GURL url(chrome::GetSearchResultPrefetchBaseURL(profile_));
    488   if (url.is_valid())
    489     instant_prerenderer_.reset(new InstantSearchPrerenderer(profile_, url));
    490   else
    491     instant_prerenderer_.reset();
    492 }
    493