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