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