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/top_sites.h" 9 #include "chrome/browser/profiles/profile.h" 10 #include "chrome/browser/search/instant_io_context.h" 11 #include "chrome/browser/search/instant_service_observer.h" 12 #include "chrome/browser/search/local_ntp_source.h" 13 #include "chrome/browser/search/most_visited_iframe_source.h" 14 #include "chrome/browser/search/search.h" 15 #include "chrome/browser/search/suggestions/suggestions_source.h" 16 #include "chrome/browser/search_engines/template_url_service_factory.h" 17 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h" 18 #include "chrome/browser/themes/theme_properties.h" 19 #include "chrome/browser/themes/theme_service.h" 20 #include "chrome/browser/themes/theme_service_factory.h" 21 #include "chrome/browser/thumbnails/thumbnail_list_source.h" 22 #include "chrome/browser/ui/search/instant_search_prerenderer.h" 23 #include "chrome/browser/ui/webui/favicon_source.h" 24 #include "chrome/browser/ui/webui/ntp/thumbnail_source.h" 25 #include "chrome/browser/ui/webui/theme_source.h" 26 #include "chrome/common/render_messages.h" 27 #include "components/search_engines/template_url_service.h" 28 #include "content/public/browser/browser_thread.h" 29 #include "content/public/browser/notification_service.h" 30 #include "content/public/browser/notification_types.h" 31 #include "content/public/browser/render_process_host.h" 32 #include "content/public/browser/url_data_source.h" 33 #include "grit/theme_resources.h" 34 #include "third_party/skia/include/core/SkColor.h" 35 #include "ui/gfx/color_utils.h" 36 #include "ui/gfx/image/image_skia.h" 37 #include "ui/gfx/sys_color_change_listener.h" 38 39 40 namespace { 41 42 const int kSectionBorderAlphaTransparency = 80; 43 44 // Converts SkColor to RGBAColor 45 RGBAColor SkColorToRGBAColor(const SkColor& sKColor) { 46 RGBAColor color; 47 color.r = SkColorGetR(sKColor); 48 color.g = SkColorGetG(sKColor); 49 color.b = SkColorGetB(sKColor); 50 color.a = SkColorGetA(sKColor); 51 return color; 52 } 53 54 } // namespace 55 56 InstantService::InstantService(Profile* profile) 57 : profile_(profile), 58 template_url_service_(TemplateURLServiceFactory::GetForProfile(profile_)), 59 omnibox_start_margin_(chrome::kDisableStartMargin), 60 weak_ptr_factory_(this) { 61 // The initialization below depends on a typical set of browser threads. Skip 62 // it if we are running in a unit test without the full suite. 63 if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) 64 return; 65 66 // This depends on the existence of the typical browser threads. Therefore it 67 // is only instantiated here (after the check for a UI thread above). 68 instant_io_context_ = new InstantIOContext(); 69 70 previous_google_base_url_ = 71 GURL(UIThreadSearchTermsData(profile).GoogleBaseURLValue()); 72 73 // TemplateURLService is NULL by default in tests. 74 if (template_url_service_) { 75 template_url_service_->AddObserver(this); 76 const TemplateURL* default_search_provider = 77 template_url_service_->GetDefaultSearchProvider(); 78 if (default_search_provider) { 79 previous_default_search_provider_.reset( 80 new TemplateURLData(default_search_provider->data())); 81 } 82 } 83 84 ResetInstantSearchPrerenderer(); 85 86 registrar_.Add(this, 87 content::NOTIFICATION_RENDERER_PROCESS_CREATED, 88 content::NotificationService::AllSources()); 89 registrar_.Add(this, 90 content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, 91 content::NotificationService::AllSources()); 92 93 history::TopSites* top_sites = profile_->GetTopSites(); 94 if (top_sites) { 95 registrar_.Add(this, 96 chrome::NOTIFICATION_TOP_SITES_CHANGED, 97 content::Source<history::TopSites>(top_sites)); 98 } 99 100 if (profile_ && profile_->GetResourceContext()) { 101 content::BrowserThread::PostTask( 102 content::BrowserThread::IO, FROM_HERE, 103 base::Bind(&InstantIOContext::SetUserDataOnIO, 104 profile->GetResourceContext(), instant_io_context_)); 105 } 106 107 // Set up the data sources that Instant uses on the NTP. 108 #if defined(ENABLE_THEMES) 109 // Listen for theme installation. 110 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED, 111 content::Source<ThemeService>( 112 ThemeServiceFactory::GetForProfile(profile_))); 113 114 content::URLDataSource::Add(profile_, new ThemeSource(profile_)); 115 #endif // defined(ENABLE_THEMES) 116 117 // TODO(aurimas) remove this #if once instant_service.cc is no longer compiled 118 // on Android. 119 #if !defined(OS_ANDROID) 120 content::URLDataSource::Add(profile_, new ThumbnailSource(profile_, false)); 121 content::URLDataSource::Add(profile_, new ThumbnailSource(profile_, true)); 122 content::URLDataSource::Add(profile_, new ThumbnailListSource(profile_)); 123 #endif // !defined(OS_ANDROID) 124 125 content::URLDataSource::Add( 126 profile_, new FaviconSource(profile_, FaviconSource::FAVICON)); 127 content::URLDataSource::Add(profile_, new LocalNtpSource(profile_)); 128 content::URLDataSource::Add(profile_, new MostVisitedIframeSource()); 129 content::URLDataSource::Add( 130 profile_, new suggestions::SuggestionsSource(profile_)); 131 } 132 133 InstantService::~InstantService() { 134 if (template_url_service_) 135 template_url_service_->RemoveObserver(this); 136 } 137 138 void InstantService::AddInstantProcess(int process_id) { 139 process_ids_.insert(process_id); 140 141 if (instant_io_context_.get()) { 142 content::BrowserThread::PostTask( 143 content::BrowserThread::IO, FROM_HERE, 144 base::Bind(&InstantIOContext::AddInstantProcessOnIO, 145 instant_io_context_, process_id)); 146 } 147 } 148 149 bool InstantService::IsInstantProcess(int process_id) const { 150 return process_ids_.find(process_id) != process_ids_.end(); 151 } 152 153 void InstantService::AddObserver(InstantServiceObserver* observer) { 154 observers_.AddObserver(observer); 155 } 156 157 void InstantService::RemoveObserver(InstantServiceObserver* observer) { 158 observers_.RemoveObserver(observer); 159 } 160 161 void InstantService::DeleteMostVisitedItem(const GURL& url) { 162 history::TopSites* top_sites = profile_->GetTopSites(); 163 if (!top_sites) 164 return; 165 166 top_sites->AddBlacklistedURL(url); 167 } 168 169 void InstantService::UndoMostVisitedDeletion(const GURL& url) { 170 history::TopSites* top_sites = profile_->GetTopSites(); 171 if (!top_sites) 172 return; 173 174 top_sites->RemoveBlacklistedURL(url); 175 } 176 177 void InstantService::UndoAllMostVisitedDeletions() { 178 history::TopSites* top_sites = profile_->GetTopSites(); 179 if (!top_sites) 180 return; 181 182 top_sites->ClearBlacklistedURLs(); 183 } 184 185 void InstantService::UpdateThemeInfo() { 186 // Update theme background info. 187 // Initialize |theme_info| if necessary. 188 if (!theme_info_) 189 OnThemeChanged(ThemeServiceFactory::GetForProfile(profile_)); 190 else 191 OnThemeChanged(NULL); 192 } 193 194 void InstantService::UpdateMostVisitedItemsInfo() { 195 NotifyAboutMostVisitedItems(); 196 } 197 198 void InstantService::Shutdown() { 199 process_ids_.clear(); 200 201 if (instant_io_context_.get()) { 202 content::BrowserThread::PostTask( 203 content::BrowserThread::IO, FROM_HERE, 204 base::Bind(&InstantIOContext::ClearInstantProcessesOnIO, 205 instant_io_context_)); 206 } 207 instant_io_context_ = NULL; 208 } 209 210 void InstantService::Observe(int type, 211 const content::NotificationSource& source, 212 const content::NotificationDetails& details) { 213 switch (type) { 214 case content::NOTIFICATION_RENDERER_PROCESS_CREATED: 215 SendSearchURLsToRenderer( 216 content::Source<content::RenderProcessHost>(source).ptr()); 217 break; 218 case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: 219 OnRendererProcessTerminated( 220 content::Source<content::RenderProcessHost>(source)->GetID()); 221 break; 222 case chrome::NOTIFICATION_TOP_SITES_CHANGED: { 223 history::TopSites* top_sites = profile_->GetTopSites(); 224 if (top_sites) { 225 top_sites->GetMostVisitedURLs( 226 base::Bind(&InstantService::OnMostVisitedItemsReceived, 227 weak_ptr_factory_.GetWeakPtr()), false); 228 } 229 break; 230 } 231 #if defined(ENABLE_THEMES) 232 case chrome::NOTIFICATION_BROWSER_THEME_CHANGED: { 233 OnThemeChanged(content::Source<ThemeService>(source).ptr()); 234 break; 235 } 236 #endif // defined(ENABLE_THEMES) 237 default: 238 NOTREACHED() << "Unexpected notification type in InstantService."; 239 } 240 } 241 242 void InstantService::SendSearchURLsToRenderer(content::RenderProcessHost* rph) { 243 rph->Send(new ChromeViewMsg_SetSearchURLs( 244 chrome::GetSearchURLs(profile_), chrome::GetNewTabPageURL(profile_))); 245 } 246 247 void InstantService::OnOmniboxStartMarginChanged(int start_margin) { 248 omnibox_start_margin_ = start_margin; 249 FOR_EACH_OBSERVER(InstantServiceObserver, observers_, 250 OmniboxStartMarginChanged(omnibox_start_margin_)); 251 } 252 253 void InstantService::OnRendererProcessTerminated(int process_id) { 254 process_ids_.erase(process_id); 255 256 if (instant_io_context_.get()) { 257 content::BrowserThread::PostTask( 258 content::BrowserThread::IO, FROM_HERE, 259 base::Bind(&InstantIOContext::RemoveInstantProcessOnIO, 260 instant_io_context_, process_id)); 261 } 262 } 263 264 void InstantService::OnMostVisitedItemsReceived( 265 const history::MostVisitedURLList& data) { 266 history::MostVisitedURLList reordered_data(data); 267 std::vector<InstantMostVisitedItem> new_most_visited_items; 268 for (size_t i = 0; i < reordered_data.size(); i++) { 269 const history::MostVisitedURL& url = reordered_data[i]; 270 InstantMostVisitedItem item; 271 item.url = url.url; 272 item.title = url.title; 273 new_most_visited_items.push_back(item); 274 } 275 276 most_visited_items_ = new_most_visited_items; 277 NotifyAboutMostVisitedItems(); 278 } 279 280 void InstantService::NotifyAboutMostVisitedItems() { 281 FOR_EACH_OBSERVER(InstantServiceObserver, observers_, 282 MostVisitedItemsChanged(most_visited_items_)); 283 } 284 285 void InstantService::OnThemeChanged(ThemeService* theme_service) { 286 if (!theme_service) { 287 DCHECK(theme_info_.get()); 288 FOR_EACH_OBSERVER(InstantServiceObserver, observers_, 289 ThemeInfoChanged(*theme_info_)); 290 return; 291 } 292 293 // Get theme information from theme service. 294 theme_info_.reset(new ThemeBackgroundInfo()); 295 296 // Get if the current theme is the default theme. 297 theme_info_->using_default_theme = theme_service->UsingDefaultTheme(); 298 299 // Get theme colors. 300 SkColor background_color = 301 theme_service->GetColor(ThemeProperties::COLOR_NTP_BACKGROUND); 302 SkColor text_color = 303 theme_service->GetColor(ThemeProperties::COLOR_NTP_TEXT); 304 SkColor link_color = 305 theme_service->GetColor(ThemeProperties::COLOR_NTP_LINK); 306 SkColor text_color_light = 307 theme_service->GetColor(ThemeProperties::COLOR_NTP_TEXT_LIGHT); 308 SkColor header_color = 309 theme_service->GetColor(ThemeProperties::COLOR_NTP_HEADER); 310 // Generate section border color from the header color. 311 SkColor section_border_color = 312 SkColorSetARGB(kSectionBorderAlphaTransparency, 313 SkColorGetR(header_color), 314 SkColorGetG(header_color), 315 SkColorGetB(header_color)); 316 317 // Invert colors if needed. 318 if (gfx::IsInvertedColorScheme()) { 319 background_color = color_utils::InvertColor(background_color); 320 text_color = color_utils::InvertColor(text_color); 321 link_color = color_utils::InvertColor(link_color); 322 text_color_light = color_utils::InvertColor(text_color_light); 323 header_color = color_utils::InvertColor(header_color); 324 section_border_color = color_utils::InvertColor(section_border_color); 325 } 326 327 // Set colors. 328 theme_info_->background_color = SkColorToRGBAColor(background_color); 329 theme_info_->text_color = SkColorToRGBAColor(text_color); 330 theme_info_->link_color = SkColorToRGBAColor(link_color); 331 theme_info_->text_color_light = SkColorToRGBAColor(text_color_light); 332 theme_info_->header_color = SkColorToRGBAColor(header_color); 333 theme_info_->section_border_color = SkColorToRGBAColor(section_border_color); 334 335 int logo_alternate = theme_service->GetDisplayProperty( 336 ThemeProperties::NTP_LOGO_ALTERNATE); 337 theme_info_->logo_alternate = logo_alternate == 1; 338 339 if (theme_service->HasCustomImage(IDR_THEME_NTP_BACKGROUND)) { 340 // Set theme id for theme background image url. 341 theme_info_->theme_id = theme_service->GetThemeID(); 342 343 // Set theme background image horizontal alignment. 344 int alignment = theme_service->GetDisplayProperty( 345 ThemeProperties::NTP_BACKGROUND_ALIGNMENT); 346 if (alignment & ThemeProperties::ALIGN_LEFT) 347 theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_LEFT; 348 else if (alignment & ThemeProperties::ALIGN_RIGHT) 349 theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_RIGHT; 350 else 351 theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER; 352 353 // Set theme background image vertical alignment. 354 if (alignment & ThemeProperties::ALIGN_TOP) 355 theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_TOP; 356 else if (alignment & ThemeProperties::ALIGN_BOTTOM) 357 theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_BOTTOM; 358 else 359 theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER; 360 361 // Set theme backgorund image tiling. 362 int tiling = theme_service->GetDisplayProperty( 363 ThemeProperties::NTP_BACKGROUND_TILING); 364 switch (tiling) { 365 case ThemeProperties::NO_REPEAT: 366 theme_info_->image_tiling = THEME_BKGRND_IMAGE_NO_REPEAT; 367 break; 368 case ThemeProperties::REPEAT_X: 369 theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_X; 370 break; 371 case ThemeProperties::REPEAT_Y: 372 theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_Y; 373 break; 374 case ThemeProperties::REPEAT: 375 theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT; 376 break; 377 } 378 379 // Set theme background image height. 380 gfx::ImageSkia* image = theme_service->GetImageSkiaNamed( 381 IDR_THEME_NTP_BACKGROUND); 382 DCHECK(image); 383 theme_info_->image_height = image->height(); 384 385 theme_info_->has_attribution = 386 theme_service->HasCustomImage(IDR_THEME_NTP_ATTRIBUTION); 387 } 388 389 FOR_EACH_OBSERVER(InstantServiceObserver, observers_, 390 ThemeInfoChanged(*theme_info_)); 391 } 392 393 void InstantService::OnTemplateURLServiceChanged() { 394 // Check whether the default search provider was changed. 395 const TemplateURL* template_url = 396 template_url_service_->GetDefaultSearchProvider(); 397 bool default_search_provider_changed = !TemplateURL::MatchesData( 398 template_url, previous_default_search_provider_.get(), 399 UIThreadSearchTermsData(profile_)); 400 if (default_search_provider_changed) { 401 previous_default_search_provider_.reset( 402 template_url ? new TemplateURLData(template_url->data()) : NULL); 403 } 404 405 // Note that, even if the TemplateURL for the Default Search Provider has not 406 // changed, the effective URLs might change if they reference the Google base 407 // URL. The TemplateURLService will notify us when the effective URL changes 408 // in this way but it's up to us to do the work to check both. 409 GURL google_base_url(UIThreadSearchTermsData(profile_).GoogleBaseURLValue()); 410 if (google_base_url != previous_google_base_url_) { 411 previous_google_base_url_ = google_base_url; 412 if (template_url && template_url->HasGoogleBaseURLs( 413 UIThreadSearchTermsData(profile_))) 414 default_search_provider_changed = true; 415 } 416 417 if (default_search_provider_changed) { 418 ResetInstantSearchPrerenderer(); 419 FOR_EACH_OBSERVER(InstantServiceObserver, observers_, 420 DefaultSearchProviderChanged()); 421 } 422 } 423 424 void InstantService::ResetInstantSearchPrerenderer() { 425 if (!chrome::ShouldPrefetchSearchResults()) 426 return; 427 428 GURL url(chrome::GetSearchResultPrefetchBaseURL(profile_)); 429 instant_prerenderer_.reset( 430 url.is_valid() ? new InstantSearchPrerenderer(profile_, url) : NULL); 431 } 432