• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
SkColorToRGBAColor(const SkColor & sKColor)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 
InstantService(Profile * profile)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 
~InstantService()131 InstantService::~InstantService() {
132 }
133 
AddInstantProcess(int process_id)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 
IsInstantProcess(int process_id) const146 bool InstantService::IsInstantProcess(int process_id) const {
147   return process_ids_.find(process_id) != process_ids_.end();
148 }
149 
AddObserver(InstantServiceObserver * observer)150 void InstantService::AddObserver(InstantServiceObserver* observer) {
151   observers_.AddObserver(observer);
152 }
153 
RemoveObserver(InstantServiceObserver * observer)154 void InstantService::RemoveObserver(InstantServiceObserver* observer) {
155   observers_.RemoveObserver(observer);
156 }
157 
DeleteMostVisitedItem(const GURL & url)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 
UndoMostVisitedDeletion(const GURL & url)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 
UndoAllMostVisitedDeletions()174 void InstantService::UndoAllMostVisitedDeletions() {
175   history::TopSites* top_sites = profile_->GetTopSites();
176   if (!top_sites)
177     return;
178 
179   top_sites->ClearBlacklistedURLs();
180 }
181 
UpdateThemeInfo()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 
UpdateMostVisitedItemsInfo()191 void InstantService::UpdateMostVisitedItemsInfo() {
192   NotifyAboutMostVisitedItems();
193 }
194 
Shutdown()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 
ReleaseNTPContents()208 scoped_ptr<content::WebContents> InstantService::ReleaseNTPContents() {
209   return ntp_prerenderer_.ReleaseNTPContents();
210 }
211 
GetNTPContents() const212 content::WebContents* InstantService::GetNTPContents() const {
213   return ntp_prerenderer_.GetNTPContents();
214 }
215 
OnBrowserInstantControllerCreated()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 
OnBrowserInstantControllerDestroyed()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 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)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 
SendSearchURLsToRenderer(content::RenderProcessHost * rph)287 void InstantService::SendSearchURLsToRenderer(content::RenderProcessHost* rph) {
288   rph->Send(new ChromeViewMsg_SetSearchURLs(
289       chrome::GetSearchURLs(profile_), chrome::GetNewTabPageURL(profile_)));
290 }
291 
OnRendererProcessTerminated(int process_id)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 
OnMostVisitedItemsReceived(const history::MostVisitedURLList & data)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 
NotifyAboutMostVisitedItems()323 void InstantService::NotifyAboutMostVisitedItems() {
324   FOR_EACH_OBSERVER(InstantServiceObserver, observers_,
325                     MostVisitedItemsChanged(most_visited_items_));
326 }
327 
OnThemeChanged(ThemeService * theme_service)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 
OnGoogleURLUpdated(Profile * profile,GoogleURLTracker::UpdatedDetails * details)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 
OnDefaultSearchProviderChanged(const std::string & pref_name)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 
ntp_prerenderer()479 InstantNTPPrerenderer* InstantService::ntp_prerenderer() {
480   return &ntp_prerenderer_;
481 }
482 
ResetInstantSearchPrerenderer()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