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/strings/string_number_conversions.h" 11 #include "chrome/browser/chrome_notification_types.h" 12 #include "chrome/browser/history/history_notifications.h" 13 #include "chrome/browser/history/most_visited_tiles_experiment.h" 14 #include "chrome/browser/history/top_sites.h" 15 #include "chrome/browser/profiles/profile.h" 16 #include "chrome/browser/search/instant_io_context.h" 17 #include "chrome/browser/search/instant_service_factory.h" 18 #include "chrome/browser/search/instant_service_observer.h" 19 #include "chrome/browser/search/local_ntp_source.h" 20 #include "chrome/browser/search/most_visited_iframe_source.h" 21 #include "chrome/browser/search/search.h" 22 #include "chrome/browser/themes/theme_properties.h" 23 #include "chrome/browser/themes/theme_service.h" 24 #include "chrome/browser/themes/theme_service_factory.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 "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 "net/url_request/url_request.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 #include "url/gurl.h" 39 40 using content::BrowserThread; 41 42 namespace { 43 44 const int kSectionBorderAlphaTransparency = 80; 45 46 // Converts SkColor to RGBAColor 47 RGBAColor SkColorToRGBAColor(const SkColor& sKColor) { 48 RGBAColor color; 49 color.r = SkColorGetR(sKColor); 50 color.g = SkColorGetG(sKColor); 51 color.b = SkColorGetB(sKColor); 52 color.a = SkColorGetA(sKColor); 53 return color; 54 } 55 56 } // namespace 57 58 InstantService::InstantService(Profile* profile) 59 : profile_(profile), 60 ntp_prerenderer_(profile, profile->GetPrefs()), 61 browser_instant_controller_object_count_(0), 62 weak_ptr_factory_(this) { 63 // Stub for unit tests. 64 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) 65 return; 66 67 registrar_.Add(this, 68 content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, 69 content::NotificationService::AllSources()); 70 71 history::TopSites* top_sites = profile_->GetTopSites(); 72 if (top_sites) { 73 registrar_.Add(this, 74 chrome::NOTIFICATION_TOP_SITES_CHANGED, 75 content::Source<history::TopSites>(top_sites)); 76 } 77 instant_io_context_ = new InstantIOContext(); 78 79 if (profile_ && profile_->GetResourceContext()) { 80 BrowserThread::PostTask( 81 BrowserThread::IO, FROM_HERE, 82 base::Bind(&InstantIOContext::SetUserDataOnIO, 83 profile->GetResourceContext(), instant_io_context_)); 84 } 85 86 // Set up the data sources that Instant uses on the NTP. 87 #if defined(ENABLE_THEMES) 88 // Listen for theme installation. 89 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED, 90 content::Source<ThemeService>( 91 ThemeServiceFactory::GetForProfile(profile_))); 92 93 content::URLDataSource::Add(profile, new ThemeSource(profile)); 94 #endif // defined(ENABLE_THEMES) 95 96 content::URLDataSource::Add(profile, new ThumbnailSource(profile)); 97 content::URLDataSource::Add(profile, new FaviconSource( 98 profile, FaviconSource::FAVICON)); 99 content::URLDataSource::Add(profile, new LocalNtpSource(profile)); 100 content::URLDataSource::Add(profile, new MostVisitedIframeSource()); 101 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, 102 content::Source<Profile>(profile_)); 103 } 104 105 InstantService::~InstantService() { 106 } 107 108 void InstantService::AddInstantProcess(int process_id) { 109 process_ids_.insert(process_id); 110 111 if (instant_io_context_.get()) { 112 BrowserThread::PostTask(BrowserThread::IO, 113 FROM_HERE, 114 base::Bind(&InstantIOContext::AddInstantProcessOnIO, 115 instant_io_context_, 116 process_id)); 117 } 118 } 119 120 bool InstantService::IsInstantProcess(int process_id) const { 121 return process_ids_.find(process_id) != process_ids_.end(); 122 } 123 124 void InstantService::AddObserver(InstantServiceObserver* observer) { 125 observers_.AddObserver(observer); 126 } 127 128 void InstantService::RemoveObserver(InstantServiceObserver* observer) { 129 observers_.RemoveObserver(observer); 130 } 131 132 void InstantService::DeleteMostVisitedItem(const GURL& url) { 133 history::TopSites* top_sites = profile_->GetTopSites(); 134 if (!top_sites) 135 return; 136 137 top_sites->AddBlacklistedURL(url); 138 } 139 140 void InstantService::UndoMostVisitedDeletion(const GURL& url) { 141 history::TopSites* top_sites = profile_->GetTopSites(); 142 if (!top_sites) 143 return; 144 145 top_sites->RemoveBlacklistedURL(url); 146 } 147 148 void InstantService::UndoAllMostVisitedDeletions() { 149 history::TopSites* top_sites = profile_->GetTopSites(); 150 if (!top_sites) 151 return; 152 153 top_sites->ClearBlacklistedURLs(); 154 } 155 156 void InstantService::UpdateThemeInfo() { 157 // Update theme background info. 158 // Initialize |theme_info| if necessary. 159 if (!theme_info_) 160 OnThemeChanged(ThemeServiceFactory::GetForProfile(profile_)); 161 else 162 OnThemeChanged(NULL); 163 } 164 165 void InstantService::UpdateMostVisitedItemsInfo() { 166 NotifyAboutMostVisitedItems(); 167 } 168 169 void InstantService::Shutdown() { 170 process_ids_.clear(); 171 172 if (instant_io_context_.get()) { 173 BrowserThread::PostTask( 174 BrowserThread::IO, 175 FROM_HERE, 176 base::Bind(&InstantIOContext::ClearInstantProcessesOnIO, 177 instant_io_context_)); 178 } 179 instant_io_context_ = NULL; 180 } 181 182 scoped_ptr<content::WebContents> InstantService::ReleaseNTPContents() { 183 return ntp_prerenderer_.ReleaseNTPContents(); 184 } 185 186 content::WebContents* InstantService::GetNTPContents() const { 187 return ntp_prerenderer_.GetNTPContents(); 188 } 189 190 void InstantService::OnBrowserInstantControllerCreated() { 191 if (profile_->IsOffTheRecord()) 192 return; 193 194 ++browser_instant_controller_object_count_; 195 196 if (browser_instant_controller_object_count_ == 1) 197 ntp_prerenderer_.PreloadInstantNTP(); 198 } 199 200 void InstantService::OnBrowserInstantControllerDestroyed() { 201 if (profile_->IsOffTheRecord()) 202 return; 203 204 DCHECK_GT(browser_instant_controller_object_count_, 0U); 205 --browser_instant_controller_object_count_; 206 207 // All browser windows have closed, so release the InstantNTP resources to 208 // work around http://crbug.com/180810. 209 if (browser_instant_controller_object_count_ == 0) 210 ntp_prerenderer_.DeleteNTPContents(); 211 } 212 213 void InstantService::Observe(int type, 214 const content::NotificationSource& source, 215 const content::NotificationDetails& details) { 216 switch (type) { 217 case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: { 218 int process_id = 219 content::Source<content::RenderProcessHost>(source)->GetID(); 220 process_ids_.erase(process_id); 221 222 if (instant_io_context_.get()) { 223 BrowserThread::PostTask( 224 BrowserThread::IO, 225 FROM_HERE, 226 base::Bind(&InstantIOContext::RemoveInstantProcessOnIO, 227 instant_io_context_, 228 process_id)); 229 } 230 break; 231 } 232 case chrome::NOTIFICATION_TOP_SITES_CHANGED: { 233 history::TopSites* top_sites = profile_->GetTopSites(); 234 if (top_sites) { 235 top_sites->GetMostVisitedURLs( 236 base::Bind(&InstantService::OnMostVisitedItemsReceived, 237 weak_ptr_factory_.GetWeakPtr())); 238 } 239 break; 240 } 241 #if defined(ENABLE_THEMES) 242 case chrome::NOTIFICATION_BROWSER_THEME_CHANGED: { 243 OnThemeChanged(content::Source<ThemeService>(source).ptr()); 244 break; 245 } 246 #endif // defined(ENABLE_THEMES) 247 case chrome::NOTIFICATION_PROFILE_DESTROYED: { 248 // Last chance to delete InstantNTP contents. We generally delete 249 // preloaded InstantNTP when the last BrowserInstantController object is 250 // destroyed. When the browser shutdown happens without closing browsers, 251 // there is a race condition between BrowserInstantController destruction 252 // and Profile destruction. 253 if (GetNTPContents()) 254 ntp_prerenderer_.DeleteNTPContents(); 255 break; 256 } 257 default: 258 NOTREACHED() << "Unexpected notification type in InstantService."; 259 } 260 } 261 262 void InstantService::OnMostVisitedItemsReceived( 263 const history::MostVisitedURLList& data) { 264 history::MostVisitedURLList reordered_data(data); 265 history::MostVisitedTilesExperiment::MaybeShuffle(&reordered_data); 266 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 // Set logo for the theme. By default, use alternate logo. 336 theme_info_->logo_alternate = true; 337 int logo_alternate = 0; 338 if (theme_service->GetDisplayProperty( 339 ThemeProperties::NTP_LOGO_ALTERNATE, &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 = 0; 348 theme_service->GetDisplayProperty( 349 ThemeProperties::NTP_BACKGROUND_ALIGNMENT, &alignment); 350 if (alignment & ThemeProperties::ALIGN_LEFT) 351 theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_LEFT; 352 else if (alignment & ThemeProperties::ALIGN_RIGHT) 353 theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_RIGHT; 354 else 355 theme_info_->image_horizontal_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER; 356 357 // Set theme background image vertical alignment. 358 if (alignment & ThemeProperties::ALIGN_TOP) 359 theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_TOP; 360 else if (alignment & ThemeProperties::ALIGN_BOTTOM) 361 theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_BOTTOM; 362 else 363 theme_info_->image_vertical_alignment = THEME_BKGRND_IMAGE_ALIGN_CENTER; 364 365 // Set theme backgorund image tiling. 366 int tiling = 0; 367 theme_service->GetDisplayProperty(ThemeProperties::NTP_BACKGROUND_TILING, 368 &tiling); 369 switch (tiling) { 370 case ThemeProperties::NO_REPEAT: 371 theme_info_->image_tiling = THEME_BKGRND_IMAGE_NO_REPEAT; 372 break; 373 case ThemeProperties::REPEAT_X: 374 theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_X; 375 break; 376 case ThemeProperties::REPEAT_Y: 377 theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT_Y; 378 break; 379 case ThemeProperties::REPEAT: 380 theme_info_->image_tiling = THEME_BKGRND_IMAGE_REPEAT; 381 break; 382 } 383 384 // Set theme background image height. 385 gfx::ImageSkia* image = theme_service->GetImageSkiaNamed( 386 IDR_THEME_NTP_BACKGROUND); 387 DCHECK(image); 388 theme_info_->image_height = image->height(); 389 390 theme_info_->has_attribution = 391 theme_service->HasCustomImage(IDR_THEME_NTP_ATTRIBUTION); 392 } 393 394 FOR_EACH_OBSERVER(InstantServiceObserver, observers_, 395 ThemeInfoChanged(*theme_info_)); 396 } 397 398 InstantNTPPrerenderer* InstantService::ntp_prerenderer() { 399 return &ntp_prerenderer_; 400 } 401