1 // Copyright (c) 2011 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/background_contents_service.h" 6 7 #include "base/basictypes.h" 8 #include "base/command_line.h" 9 #include "base/string_util.h" 10 #include "base/utf_string_conversions.h" 11 #include "base/values.h" 12 #include "chrome/browser/background_contents_service_factory.h" 13 #include "chrome/browser/browser_process.h" 14 #include "chrome/browser/extensions/extension_host.h" 15 #include "chrome/browser/extensions/extension_service.h" 16 #include "chrome/browser/notifications/desktop_notification_service.h" 17 #include "chrome/browser/notifications/notification.h" 18 #include "chrome/browser/notifications/notification_ui_manager.h" 19 #include "chrome/browser/prefs/pref_service.h" 20 #include "chrome/browser/prefs/scoped_user_pref_update.h" 21 #include "chrome/browser/profiles/profile.h" 22 #include "chrome/browser/ui/browser.h" 23 #include "chrome/browser/ui/browser_list.h" 24 #include "chrome/common/chrome_switches.h" 25 #include "chrome/common/extensions/extension.h" 26 #include "chrome/common/pref_names.h" 27 #include "content/browser/renderer_host/render_view_host.h" 28 #include "content/browser/site_instance.h" 29 #include "content/browser/tab_contents/tab_contents.h" 30 #include "content/common/notification_service.h" 31 #include "content/common/notification_type.h" 32 #include "grit/generated_resources.h" 33 #include "ui/base/l10n/l10n_util.h" 34 35 namespace { 36 37 const char kNotificationPrefix[] = "app.background.crashed."; 38 39 void CloseBalloon(const std::string id) { 40 g_browser_process->notification_ui_manager()->CancelById(id); 41 } 42 43 void ScheduleCloseBalloon(const std::string& extension_id) { 44 MessageLoop::current()->PostTask(FROM_HERE, 45 NewRunnableFunction(&CloseBalloon, 46 kNotificationPrefix + extension_id)); 47 } 48 49 class CrashNotificationDelegate : public NotificationDelegate { 50 public: 51 CrashNotificationDelegate(Profile* profile, const Extension* extension) 52 : profile_(profile), 53 is_hosted_app_(extension->is_hosted_app()), 54 extension_id_(extension->id()) { 55 } 56 57 ~CrashNotificationDelegate() { 58 } 59 60 void Display() {} 61 62 void Error() {} 63 64 void Close(bool by_user) {} 65 66 void Click() { 67 if (is_hosted_app_) { 68 BackgroundContentsServiceFactory::GetForProfile(profile_)-> 69 LoadBackgroundContentsForExtension(profile_, extension_id_); 70 } else { 71 profile_->GetExtensionService()->ReloadExtension(extension_id_); 72 } 73 74 // Closing the balloon here should be OK, but it causes a crash on Mac 75 // http://crbug.com/78167 76 ScheduleCloseBalloon(extension_id_); 77 } 78 79 std::string id() const { 80 return kNotificationPrefix + extension_id_; 81 } 82 83 private: 84 Profile* profile_; 85 bool is_hosted_app_; 86 std::string extension_id_; 87 88 DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate); 89 }; 90 91 void ShowBalloon(const Extension* extension, Profile* profile) { 92 string16 message = l10n_util::GetStringFUTF16( 93 extension->is_hosted_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE : 94 IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE, 95 UTF8ToUTF16(extension->name())); 96 string16 content_url = DesktopNotificationService::CreateDataUrl( 97 extension->GetIconURL(Extension::EXTENSION_ICON_SMALLISH, 98 ExtensionIconSet::MATCH_BIGGER), 99 string16(), message, WebKit::WebTextDirectionDefault); 100 Notification notification( 101 extension->url(), GURL(content_url), string16(), string16(), 102 new CrashNotificationDelegate(profile, extension)); 103 g_browser_process->notification_ui_manager()->Add(notification, profile); 104 } 105 106 } 107 108 // Keys for the information we store about individual BackgroundContents in 109 // prefs. There is one top-level DictionaryValue (stored at 110 // prefs::kRegisteredBackgroundContents). Information about each 111 // BackgroundContents is stored under that top-level DictionaryValue, keyed 112 // by the parent application ID for easy lookup. 113 // 114 // kRegisteredBackgroundContents: 115 // DictionaryValue { 116 // <appid_1>: { "url": <url1>, "name": <frame_name> }, 117 // <appid_2>: { "url": <url2>, "name": <frame_name> }, 118 // ... etc ... 119 // } 120 const char kUrlKey[] = "url"; 121 const char kFrameNameKey[] = "name"; 122 123 BackgroundContentsService::BackgroundContentsService( 124 Profile* profile, const CommandLine* command_line) 125 : prefs_(NULL) { 126 // Don't load/store preferences if the proper switch is not enabled, or if 127 // the parent profile is incognito. 128 if (!profile->IsOffTheRecord() && 129 !command_line->HasSwitch(switches::kDisableRestoreBackgroundContents)) 130 prefs_ = profile->GetPrefs(); 131 132 // Listen for events to tell us when to load/unload persisted background 133 // contents. 134 StartObserving(profile); 135 } 136 137 BackgroundContentsService::~BackgroundContentsService() { 138 // BackgroundContents should be shutdown before we go away, as otherwise 139 // our browser process refcount will be off. 140 DCHECK(contents_map_.empty()); 141 } 142 143 std::vector<BackgroundContents*> 144 BackgroundContentsService::GetBackgroundContents() const 145 { 146 std::vector<BackgroundContents*> contents; 147 for (BackgroundContentsMap::const_iterator it = contents_map_.begin(); 148 it != contents_map_.end(); ++it) 149 contents.push_back(it->second.contents); 150 return contents; 151 } 152 153 void BackgroundContentsService::StartObserving(Profile* profile) { 154 // On startup, load our background pages after extension-apps have loaded. 155 registrar_.Add(this, NotificationType::EXTENSIONS_READY, 156 Source<Profile>(profile)); 157 158 // Track the lifecycle of all BackgroundContents in the system to allow us 159 // to store an up-to-date list of the urls. Start tracking contents when they 160 // have been opened via CreateBackgroundContents(), and stop tracking them 161 // when they are closed by script. 162 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_CLOSED, 163 Source<Profile>(profile)); 164 165 // Stop tracking BackgroundContents when they have been deleted (happens 166 // during shutdown or if the render process dies). 167 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_DELETED, 168 Source<Profile>(profile)); 169 170 // Track when the BackgroundContents navigates to a new URL so we can update 171 // our persisted information as appropriate. 172 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_NAVIGATED, 173 Source<Profile>(profile)); 174 175 // Listen for new extension installs so that we can load any associated 176 // background page. 177 registrar_.Add(this, NotificationType::EXTENSION_LOADED, 178 Source<Profile>(profile)); 179 180 // Track when the extensions crash so that the user can be notified 181 // about it, and the crashed contents can be restarted. 182 registrar_.Add(this, NotificationType::EXTENSION_PROCESS_TERMINATED, 183 Source<Profile>(profile)); 184 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_TERMINATED, 185 Source<Profile>(profile)); 186 187 // Listen for extensions to be unloaded so we can shutdown associated 188 // BackgroundContents. 189 registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, 190 Source<Profile>(profile)); 191 192 // Make sure the extension-crash balloons are removed when the extension is 193 // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed 194 // extension is unloaded immediately after the crash, not when user reloads or 195 // uninstalls the extension. 196 registrar_.Add(this, NotificationType::EXTENSION_UNINSTALLED, 197 Source<Profile>(profile)); 198 } 199 200 void BackgroundContentsService::Observe(NotificationType type, 201 const NotificationSource& source, 202 const NotificationDetails& details) { 203 switch (type.value) { 204 case NotificationType::EXTENSIONS_READY: 205 LoadBackgroundContentsFromManifests(Source<Profile>(source).ptr()); 206 LoadBackgroundContentsFromPrefs(Source<Profile>(source).ptr()); 207 break; 208 case NotificationType::BACKGROUND_CONTENTS_DELETED: 209 BackgroundContentsShutdown(Details<BackgroundContents>(details).ptr()); 210 break; 211 case NotificationType::BACKGROUND_CONTENTS_CLOSED: 212 DCHECK(IsTracked(Details<BackgroundContents>(details).ptr())); 213 UnregisterBackgroundContents(Details<BackgroundContents>(details).ptr()); 214 break; 215 case NotificationType::BACKGROUND_CONTENTS_NAVIGATED: { 216 DCHECK(IsTracked(Details<BackgroundContents>(details).ptr())); 217 218 // Do not register in the pref if the extension has a manifest-specified 219 // background page. 220 BackgroundContents* bgcontents = 221 Details<BackgroundContents>(details).ptr(); 222 Profile* profile = Source<Profile>(source).ptr(); 223 const string16& appid = GetParentApplicationId(bgcontents); 224 ExtensionService* extension_service = profile->GetExtensionService(); 225 // extension_service can be NULL when running tests. 226 if (extension_service) { 227 const Extension* extension = 228 extension_service->GetExtensionById(UTF16ToUTF8(appid), false); 229 if (extension && extension->background_url().is_valid()) 230 break; 231 } 232 RegisterBackgroundContents(Details<BackgroundContents>(details).ptr()); 233 break; 234 } 235 case NotificationType::EXTENSION_LOADED: { 236 const Extension* extension = Details<const Extension>(details).ptr(); 237 Profile* profile = Source<Profile>(source).ptr(); 238 if (extension->is_hosted_app() && 239 extension->background_url().is_valid()) { 240 // If there is a background page specified in the manifest for a hosted 241 // app, then blow away registered urls in the pref. 242 ShutdownAssociatedBackgroundContents(ASCIIToUTF16(extension->id())); 243 244 ExtensionService* service = profile->GetExtensionService(); 245 if (service && service->is_ready()) { 246 // Now load the manifest-specified background page. If service isn't 247 // ready, then the background page will be loaded from the 248 // EXTENSIONS_READY callback. 249 LoadBackgroundContents(profile, extension->background_url(), 250 ASCIIToUTF16("background"), UTF8ToUTF16(extension->id())); 251 } 252 } 253 254 // Remove any "This extension has crashed" balloons. 255 ScheduleCloseBalloon(extension->id()); 256 break; 257 } 258 case NotificationType::EXTENSION_PROCESS_TERMINATED: 259 case NotificationType::BACKGROUND_CONTENTS_TERMINATED: { 260 Profile* profile = Source<Profile>(source).ptr(); 261 const Extension* extension = NULL; 262 if (type.value == NotificationType::BACKGROUND_CONTENTS_TERMINATED) { 263 BackgroundContents* bg = 264 Details<BackgroundContents>(details).ptr(); 265 std::string extension_id = UTF16ToASCII( 266 BackgroundContentsServiceFactory::GetForProfile(profile)-> 267 GetParentApplicationId(bg)); 268 extension = 269 profile->GetExtensionService()->GetExtensionById(extension_id, false); 270 } else { 271 ExtensionHost* extension_host = Details<ExtensionHost>(details).ptr(); 272 extension = extension_host->extension(); 273 } 274 if (!extension) 275 break; 276 277 // When an extension crashes, EXTENSION_PROCESS_TERMINATED is followed by 278 // an EXTENSION_UNLOADED notification. This UNLOADED signal causes all the 279 // notifications for this extension to be cancelled by 280 // DesktopNotificationService. For this reason, instead of showing the 281 // balloon right now, we schedule it to show a little later. 282 MessageLoop::current()->PostTask(FROM_HERE, 283 NewRunnableFunction(&ShowBalloon, extension, profile)); 284 break; 285 } 286 case NotificationType::EXTENSION_UNLOADED: 287 switch (Details<UnloadedExtensionInfo>(details)->reason) { 288 case UnloadedExtensionInfo::DISABLE: // Intentionally fall through. 289 case UnloadedExtensionInfo::UNINSTALL: 290 ShutdownAssociatedBackgroundContents( 291 ASCIIToUTF16( 292 Details<UnloadedExtensionInfo>(details)->extension->id())); 293 break; 294 case UnloadedExtensionInfo::UPDATE: { 295 // If there is a manifest specified background page, then shut it down 296 // here, since if the updated extension still has the background page, 297 // then it will be loaded from LOADED callback. Otherwise, leave 298 // BackgroundContents in place. 299 const Extension* extension = 300 Details<UnloadedExtensionInfo>(details)->extension; 301 if (extension->background_url().is_valid()) 302 ShutdownAssociatedBackgroundContents(ASCIIToUTF16(extension->id())); 303 break; 304 } 305 default: 306 NOTREACHED(); 307 ShutdownAssociatedBackgroundContents( 308 ASCIIToUTF16( 309 Details<UnloadedExtensionInfo>(details)->extension->id())); 310 break; 311 } 312 break; 313 314 case NotificationType::EXTENSION_UNINSTALLED: { 315 // Remove any "This extension has crashed" balloons. 316 const UninstalledExtensionInfo* uninstalled_extension = 317 Details<const UninstalledExtensionInfo>(details).ptr(); 318 ScheduleCloseBalloon(uninstalled_extension->extension_id); 319 break; 320 } 321 322 default: 323 NOTREACHED(); 324 break; 325 } 326 } 327 328 // Loads all background contents whose urls have been stored in prefs. 329 void BackgroundContentsService::LoadBackgroundContentsFromPrefs( 330 Profile* profile) { 331 if (!prefs_) 332 return; 333 const DictionaryValue* contents = 334 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents); 335 if (!contents) 336 return; 337 ExtensionService* extensions_service = profile->GetExtensionService(); 338 DCHECK(extensions_service); 339 for (DictionaryValue::key_iterator it = contents->begin_keys(); 340 it != contents->end_keys(); ++it) { 341 // Check to make sure that the parent extension is still enabled. 342 const Extension* extension = extensions_service->GetExtensionById( 343 *it, false); 344 if (!extension) { 345 // We should never reach here - it should not be possible for an app 346 // to become uninstalled without the associated BackgroundContents being 347 // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a 348 // crash before we could save our prefs. 349 NOTREACHED() << "No extension found for BackgroundContents - id = " 350 << *it; 351 return; 352 } 353 LoadBackgroundContentsFromDictionary(profile, *it, contents); 354 } 355 } 356 357 void BackgroundContentsService::LoadBackgroundContentsForExtension( 358 Profile* profile, 359 const std::string& extension_id) { 360 // First look if the manifest specifies a background page. 361 const Extension* extension = 362 profile->GetExtensionService()->GetExtensionById(extension_id, false); 363 DCHECK(!extension || extension->is_hosted_app()); 364 if (extension && extension->background_url().is_valid()) { 365 LoadBackgroundContents(profile, 366 extension->background_url(), 367 ASCIIToUTF16("background"), 368 UTF8ToUTF16(extension->id())); 369 return; 370 } 371 372 // Now look in the prefs. 373 if (!prefs_) 374 return; 375 const DictionaryValue* contents = 376 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents); 377 if (!contents) 378 return; 379 LoadBackgroundContentsFromDictionary(profile, extension_id, contents); 380 } 381 382 void BackgroundContentsService::LoadBackgroundContentsFromDictionary( 383 Profile* profile, 384 const std::string& extension_id, 385 const DictionaryValue* contents) { 386 ExtensionService* extensions_service = profile->GetExtensionService(); 387 DCHECK(extensions_service); 388 389 DictionaryValue* dict; 390 contents->GetDictionaryWithoutPathExpansion(extension_id, &dict); 391 if (dict == NULL) 392 return; 393 string16 frame_name; 394 std::string url; 395 dict->GetString(kUrlKey, &url); 396 dict->GetString(kFrameNameKey, &frame_name); 397 LoadBackgroundContents(profile, 398 GURL(url), 399 frame_name, 400 UTF8ToUTF16(extension_id)); 401 } 402 403 void BackgroundContentsService::LoadBackgroundContentsFromManifests( 404 Profile* profile) { 405 const ExtensionList* extensions = 406 profile->GetExtensionService()->extensions(); 407 ExtensionList::const_iterator iter = extensions->begin(); 408 for (; iter != extensions->end(); ++iter) { 409 const Extension* extension = *iter; 410 if (extension->is_hosted_app() && 411 extension->background_url().is_valid()) { 412 LoadBackgroundContents(profile, 413 extension->background_url(), 414 ASCIIToUTF16("background"), 415 UTF8ToUTF16(extension->id())); 416 } 417 } 418 } 419 420 void BackgroundContentsService::LoadBackgroundContents( 421 Profile* profile, 422 const GURL& url, 423 const string16& frame_name, 424 const string16& application_id) { 425 // We are depending on the fact that we will initialize before any user 426 // actions or session restore can take place, so no BackgroundContents should 427 // be running yet for the passed application_id. 428 DCHECK(!GetAppBackgroundContents(application_id)); 429 DCHECK(!application_id.empty()); 430 DCHECK(url.is_valid()); 431 DVLOG(1) << "Loading background content url: " << url; 432 433 BackgroundContents* contents = CreateBackgroundContents( 434 SiteInstance::CreateSiteInstanceForURL(profile, url), 435 MSG_ROUTING_NONE, 436 profile, 437 frame_name, 438 application_id); 439 440 RenderViewHost* render_view_host = contents->render_view_host(); 441 // TODO(atwilson): Create RenderViews asynchronously to avoid increasing 442 // startup latency (http://crbug.com/47236). 443 render_view_host->CreateRenderView(frame_name); 444 render_view_host->NavigateToURL(url); 445 } 446 447 BackgroundContents* BackgroundContentsService::CreateBackgroundContents( 448 SiteInstance* site, 449 int routing_id, 450 Profile* profile, 451 const string16& frame_name, 452 const string16& application_id) { 453 BackgroundContents* contents = new BackgroundContents(site, routing_id, this); 454 455 // Register the BackgroundContents internally, then send out a notification 456 // to external listeners. 457 BackgroundContentsOpenedDetails details = {contents, 458 frame_name, 459 application_id}; 460 BackgroundContentsOpened(&details); 461 NotificationService::current()->Notify( 462 NotificationType::BACKGROUND_CONTENTS_OPENED, 463 Source<Profile>(profile), 464 Details<BackgroundContentsOpenedDetails>(&details)); 465 return contents; 466 } 467 468 void BackgroundContentsService::RegisterBackgroundContents( 469 BackgroundContents* background_contents) { 470 DCHECK(IsTracked(background_contents)); 471 if (!prefs_) 472 return; 473 474 // We store the first URL we receive for a given application. If there's 475 // already an entry for this application, no need to do anything. 476 // TODO(atwilson): Verify that this is the desired behavior based on developer 477 // feedback (http://crbug.com/47118). 478 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents); 479 DictionaryValue* pref = update.Get(); 480 const string16& appid = GetParentApplicationId(background_contents); 481 DictionaryValue* current; 482 if (pref->GetDictionaryWithoutPathExpansion(UTF16ToUTF8(appid), ¤t)) 483 return; 484 485 // No entry for this application yet, so add one. 486 DictionaryValue* dict = new DictionaryValue(); 487 dict->SetString(kUrlKey, background_contents->GetURL().spec()); 488 dict->SetString(kFrameNameKey, contents_map_[appid].frame_name); 489 pref->SetWithoutPathExpansion(UTF16ToUTF8(appid), dict); 490 prefs_->ScheduleSavePersistentPrefs(); 491 } 492 493 void BackgroundContentsService::UnregisterBackgroundContents( 494 BackgroundContents* background_contents) { 495 if (!prefs_) 496 return; 497 DCHECK(IsTracked(background_contents)); 498 const string16 appid = GetParentApplicationId(background_contents); 499 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents); 500 update.Get()->RemoveWithoutPathExpansion(UTF16ToUTF8(appid), NULL); 501 prefs_->ScheduleSavePersistentPrefs(); 502 } 503 504 void BackgroundContentsService::ShutdownAssociatedBackgroundContents( 505 const string16& appid) { 506 BackgroundContents* contents = GetAppBackgroundContents(appid); 507 if (contents) { 508 UnregisterBackgroundContents(contents); 509 // Background contents destructor shuts down the renderer. 510 delete contents; 511 } 512 } 513 514 void BackgroundContentsService::BackgroundContentsOpened( 515 BackgroundContentsOpenedDetails* details) { 516 // Add the passed object to our list. Should not already be tracked. 517 DCHECK(!IsTracked(details->contents)); 518 DCHECK(!details->application_id.empty()); 519 contents_map_[details->application_id].contents = details->contents; 520 contents_map_[details->application_id].frame_name = details->frame_name; 521 } 522 523 // Used by test code and debug checks to verify whether a given 524 // BackgroundContents is being tracked by this instance. 525 bool BackgroundContentsService::IsTracked( 526 BackgroundContents* background_contents) const { 527 return !GetParentApplicationId(background_contents).empty(); 528 } 529 530 void BackgroundContentsService::BackgroundContentsShutdown( 531 BackgroundContents* background_contents) { 532 // Remove the passed object from our list. 533 DCHECK(IsTracked(background_contents)); 534 string16 appid = GetParentApplicationId(background_contents); 535 contents_map_.erase(appid); 536 } 537 538 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents( 539 const string16& application_id) { 540 BackgroundContentsMap::const_iterator it = contents_map_.find(application_id); 541 return (it != contents_map_.end()) ? it->second.contents : NULL; 542 } 543 544 const string16& BackgroundContentsService::GetParentApplicationId( 545 BackgroundContents* contents) const { 546 for (BackgroundContentsMap::const_iterator it = contents_map_.begin(); 547 it != contents_map_.end(); ++it) { 548 if (contents == it->second.contents) 549 return it->first; 550 } 551 return EmptyString16(); 552 } 553 554 // static 555 void BackgroundContentsService::RegisterUserPrefs(PrefService* prefs) { 556 prefs->RegisterDictionaryPref(prefs::kRegisteredBackgroundContents); 557 } 558 559 void BackgroundContentsService::AddTabContents( 560 TabContents* new_contents, 561 WindowOpenDisposition disposition, 562 const gfx::Rect& initial_pos, 563 bool user_gesture) { 564 Browser* browser = BrowserList::GetLastActiveWithProfile( 565 new_contents->profile()); 566 if (!browser) 567 return; 568 browser->AddTabContents(new_contents, disposition, initial_pos, user_gesture); 569 } 570