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