1 // Copyright (c) 2012 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/background_contents_service.h" 6 7 #include "apps/app_load_service.h" 8 #include "base/basictypes.h" 9 #include "base/bind.h" 10 #include "base/command_line.h" 11 #include "base/compiler_specific.h" 12 #include "base/message_loop/message_loop.h" 13 #include "base/prefs/pref_service.h" 14 #include "base/prefs/scoped_user_pref_update.h" 15 #include "base/strings/string_util.h" 16 #include "base/strings/utf_string_conversions.h" 17 #include "base/time/time.h" 18 #include "base/values.h" 19 #include "chrome/browser/background/background_contents_service_factory.h" 20 #include "chrome/browser/browser_process.h" 21 #include "chrome/browser/chrome_notification_types.h" 22 #include "chrome/browser/extensions/extension_service.h" 23 #include "chrome/browser/notifications/desktop_notification_service.h" 24 #include "chrome/browser/notifications/notification.h" 25 #include "chrome/browser/notifications/notification_delegate.h" 26 #include "chrome/browser/notifications/notification_ui_manager.h" 27 #include "chrome/browser/profiles/profile.h" 28 #include "chrome/browser/profiles/profile_manager.h" 29 #include "chrome/browser/ui/browser.h" 30 #include "chrome/browser/ui/browser_finder.h" 31 #include "chrome/browser/ui/browser_tabstrip.h" 32 #include "chrome/browser/ui/host_desktop.h" 33 #include "chrome/common/extensions/extension_constants.h" 34 #include "chrome/common/pref_names.h" 35 #include "chrome/grit/generated_resources.h" 36 #include "content/public/browser/notification_service.h" 37 #include "content/public/browser/site_instance.h" 38 #include "content/public/browser/web_contents.h" 39 #include "extensions/browser/extension_host.h" 40 #include "extensions/browser/extension_registry.h" 41 #include "extensions/browser/extension_system.h" 42 #include "extensions/browser/image_loader.h" 43 #include "extensions/browser/notification_types.h" 44 #include "extensions/common/constants.h" 45 #include "extensions/common/extension.h" 46 #include "extensions/common/extension_icon_set.h" 47 #include "extensions/common/extension_set.h" 48 #include "extensions/common/manifest_handlers/background_info.h" 49 #include "extensions/common/manifest_handlers/icons_handler.h" 50 #include "extensions/grit/extensions_browser_resources.h" 51 #include "ipc/ipc_message.h" 52 #include "ui/base/l10n/l10n_util.h" 53 #include "ui/base/resource/resource_bundle.h" 54 #include "ui/gfx/image/image.h" 55 56 #if defined(ENABLE_NOTIFICATIONS) 57 #include "ui/message_center/message_center.h" 58 #endif 59 60 using content::SiteInstance; 61 using content::WebContents; 62 using extensions::BackgroundInfo; 63 using extensions::Extension; 64 using extensions::UnloadedExtensionInfo; 65 66 namespace { 67 68 const char kNotificationPrefix[] = "app.background.crashed."; 69 70 void CloseBalloon(const std::string& balloon_id) { 71 NotificationUIManager* notification_ui_manager = 72 g_browser_process->notification_ui_manager(); 73 bool cancelled ALLOW_UNUSED = notification_ui_manager->CancelById(balloon_id); 74 #if defined(ENABLE_NOTIFICATIONS) 75 if (cancelled) { 76 // TODO(dewittj): Add this functionality to the notification UI manager's 77 // API. 78 g_browser_process->message_center()->SetVisibility( 79 message_center::VISIBILITY_TRANSIENT); 80 } 81 #endif 82 } 83 84 // Closes the crash notification balloon for the app/extension with this id. 85 void ScheduleCloseBalloon(const std::string& extension_id) { 86 if (!base::MessageLoop::current()) // For unit_tests 87 return; 88 base::MessageLoop::current()->PostTask( 89 FROM_HERE, base::Bind(&CloseBalloon, kNotificationPrefix + extension_id)); 90 } 91 92 // Delegate for the app/extension crash notification balloon. Restarts the 93 // app/extension when the balloon is clicked. 94 class CrashNotificationDelegate : public NotificationDelegate { 95 public: 96 CrashNotificationDelegate(Profile* profile, 97 const Extension* extension) 98 : profile_(profile), 99 is_hosted_app_(extension->is_hosted_app()), 100 is_platform_app_(extension->is_platform_app()), 101 extension_id_(extension->id()) { 102 } 103 104 virtual void Display() OVERRIDE {} 105 106 virtual void Error() OVERRIDE {} 107 108 virtual void Close(bool by_user) OVERRIDE {} 109 110 virtual void Click() OVERRIDE { 111 // http://crbug.com/247790 involves a crash notification balloon being 112 // clicked while the extension isn't in the TERMINATED state. In that case, 113 // any of the "reload" methods called below can unload the extension, which 114 // indirectly destroys *this, invalidating all the member variables, so we 115 // copy the extension ID before using it. 116 std::string copied_extension_id = extension_id_; 117 if (is_hosted_app_) { 118 // There can be a race here: user clicks the balloon, and simultaneously 119 // reloads the sad tab for the app. So we check here to be safe before 120 // loading the background page. 121 BackgroundContentsService* service = 122 BackgroundContentsServiceFactory::GetForProfile(profile_); 123 if (!service->GetAppBackgroundContents( 124 base::ASCIIToUTF16(copied_extension_id))) { 125 service->LoadBackgroundContentsForExtension(profile_, 126 copied_extension_id); 127 } 128 } else if (is_platform_app_) { 129 apps::AppLoadService::Get(profile_)-> 130 RestartApplication(copied_extension_id); 131 } else { 132 extensions::ExtensionSystem::Get(profile_)->extension_service()-> 133 ReloadExtension(copied_extension_id); 134 } 135 136 // Closing the crash notification balloon for the app/extension here should 137 // be OK, but it causes a crash on Mac, see: http://crbug.com/78167 138 ScheduleCloseBalloon(copied_extension_id); 139 } 140 141 virtual bool HasClickedListener() OVERRIDE { return true; } 142 143 virtual std::string id() const OVERRIDE { 144 return kNotificationPrefix + extension_id_; 145 } 146 147 virtual content::WebContents* GetWebContents() const OVERRIDE { 148 return NULL; 149 } 150 151 private: 152 virtual ~CrashNotificationDelegate() {} 153 154 Profile* profile_; 155 bool is_hosted_app_; 156 bool is_platform_app_; 157 std::string extension_id_; 158 159 DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate); 160 }; 161 162 #if defined(ENABLE_NOTIFICATIONS) 163 void NotificationImageReady( 164 const std::string extension_name, 165 const base::string16 message, 166 scoped_refptr<CrashNotificationDelegate> delegate, 167 Profile* profile, 168 const gfx::Image& icon) { 169 gfx::Image notification_icon(icon); 170 if (notification_icon.IsEmpty()) { 171 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 172 notification_icon = rb.GetImageNamed(IDR_EXTENSION_DEFAULT_ICON); 173 } 174 175 // Origin URL must be different from the crashed extension to avoid the 176 // conflict. NotificationSystemObserver will cancel all notifications from 177 // the same origin when NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED. 178 // TODO(mukai, dewittj): remove this and switch to message center 179 // notifications. 180 DesktopNotificationService::AddIconNotification( 181 GURL("chrome://extension-crash"), // Origin URL. 182 base::string16(), // Title of notification. 183 message, 184 notification_icon, 185 base::UTF8ToUTF16(delegate->id()), // Replace ID. 186 delegate.get(), 187 profile); 188 } 189 #endif 190 191 // Show a popup notification balloon with a crash message for a given app/ 192 // extension. 193 void ShowBalloon(const Extension* extension, Profile* profile) { 194 #if defined(ENABLE_NOTIFICATIONS) 195 const base::string16 message = l10n_util::GetStringFUTF16( 196 extension->is_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE : 197 IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE, 198 base::UTF8ToUTF16(extension->name())); 199 extension_misc::ExtensionIcons size(extension_misc::EXTENSION_ICON_MEDIUM); 200 extensions::ExtensionResource resource = 201 extensions::IconsInfo::GetIconResource( 202 extension, size, ExtensionIconSet::MATCH_SMALLER); 203 // We can't just load the image in the Observe method below because, despite 204 // what this method is called, it may call the callback synchronously. 205 // However, it's possible that the extension went away during the interim, 206 // so we'll bind all the pertinent data here. 207 extensions::ImageLoader::Get(profile)->LoadImageAsync( 208 extension, 209 resource, 210 gfx::Size(size, size), 211 base::Bind( 212 &NotificationImageReady, 213 extension->name(), 214 message, 215 make_scoped_refptr(new CrashNotificationDelegate(profile, extension)), 216 profile)); 217 #endif 218 } 219 220 void ReloadExtension(const std::string& extension_id, Profile* profile) { 221 if (g_browser_process->IsShuttingDown() || 222 !g_browser_process->profile_manager()->IsValidProfile(profile)) { 223 return; 224 } 225 226 extensions::ExtensionSystem* extension_system = 227 extensions::ExtensionSystem::Get(profile); 228 extensions::ExtensionRegistry* extension_registry = 229 extensions::ExtensionRegistry::Get(profile); 230 if (!extension_system || !extension_system->extension_service() || 231 !extension_registry) { 232 return; 233 } 234 235 if (!extension_registry->GetExtensionById( 236 extension_id, extensions::ExtensionRegistry::TERMINATED)) { 237 // Either the app/extension was uninstalled by policy or it has since 238 // been restarted successfully by someone else (the user). 239 return; 240 } 241 extension_system->extension_service()->ReloadExtension(extension_id); 242 } 243 244 } // namespace 245 246 // Keys for the information we store about individual BackgroundContents in 247 // prefs. There is one top-level DictionaryValue (stored at 248 // prefs::kRegisteredBackgroundContents). Information about each 249 // BackgroundContents is stored under that top-level DictionaryValue, keyed 250 // by the parent application ID for easy lookup. 251 // 252 // kRegisteredBackgroundContents: 253 // DictionaryValue { 254 // <appid_1>: { "url": <url1>, "name": <frame_name> }, 255 // <appid_2>: { "url": <url2>, "name": <frame_name> }, 256 // ... etc ... 257 // } 258 const char kUrlKey[] = "url"; 259 const char kFrameNameKey[] = "name"; 260 261 int BackgroundContentsService::restart_delay_in_ms_ = 3000; // 3 seconds. 262 263 BackgroundContentsService::BackgroundContentsService( 264 Profile* profile, const CommandLine* command_line) 265 : prefs_(NULL), extension_registry_observer_(this) { 266 // Don't load/store preferences if the parent profile is incognito. 267 if (!profile->IsOffTheRecord()) 268 prefs_ = profile->GetPrefs(); 269 270 // Listen for events to tell us when to load/unload persisted background 271 // contents. 272 StartObserving(profile); 273 } 274 275 BackgroundContentsService::~BackgroundContentsService() { 276 // BackgroundContents should be shutdown before we go away, as otherwise 277 // our browser process refcount will be off. 278 DCHECK(contents_map_.empty()); 279 } 280 281 // static 282 void BackgroundContentsService:: 283 SetRestartDelayForForceInstalledAppsAndExtensionsForTesting( 284 int restart_delay_in_ms) { 285 restart_delay_in_ms_ = restart_delay_in_ms; 286 } 287 288 // static 289 std::string BackgroundContentsService::GetNotificationIdForExtensionForTesting( 290 const std::string& extension_id) { 291 return kNotificationPrefix + extension_id; 292 } 293 294 // static 295 void BackgroundContentsService::ShowBalloonForTesting( 296 const extensions::Extension* extension, 297 Profile* profile) { 298 ShowBalloon(extension, profile); 299 } 300 301 std::vector<BackgroundContents*> 302 BackgroundContentsService::GetBackgroundContents() const 303 { 304 std::vector<BackgroundContents*> contents; 305 for (BackgroundContentsMap::const_iterator it = contents_map_.begin(); 306 it != contents_map_.end(); ++it) 307 contents.push_back(it->second.contents); 308 return contents; 309 } 310 311 void BackgroundContentsService::StartObserving(Profile* profile) { 312 // On startup, load our background pages after extension-apps have loaded. 313 registrar_.Add(this, 314 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED, 315 content::Source<Profile>(profile)); 316 317 // Track the lifecycle of all BackgroundContents in the system to allow us 318 // to store an up-to-date list of the urls. Start tracking contents when they 319 // have been opened via CreateBackgroundContents(), and stop tracking them 320 // when they are closed by script. 321 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED, 322 content::Source<Profile>(profile)); 323 324 // Stop tracking BackgroundContents when they have been deleted (happens 325 // during shutdown or if the render process dies). 326 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED, 327 content::Source<Profile>(profile)); 328 329 // Track when the BackgroundContents navigates to a new URL so we can update 330 // our persisted information as appropriate. 331 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED, 332 content::Source<Profile>(profile)); 333 334 // Track when the extensions crash so that the user can be notified 335 // about it, and the crashed contents can be restarted. 336 registrar_.Add(this, 337 extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED, 338 content::Source<Profile>(profile)); 339 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED, 340 content::Source<Profile>(profile)); 341 342 // Listen for extension uninstall, load, unloaded notification. 343 extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile)); 344 } 345 346 void BackgroundContentsService::Observe( 347 int type, 348 const content::NotificationSource& source, 349 const content::NotificationDetails& details) { 350 switch (type) { 351 case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: { 352 Profile* profile = content::Source<Profile>(source).ptr(); 353 LoadBackgroundContentsFromManifests(profile); 354 LoadBackgroundContentsFromPrefs(profile); 355 SendChangeNotification(profile); 356 break; 357 } 358 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED: 359 BackgroundContentsShutdown( 360 content::Details<BackgroundContents>(details).ptr()); 361 SendChangeNotification(content::Source<Profile>(source).ptr()); 362 break; 363 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED: 364 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr())); 365 UnregisterBackgroundContents( 366 content::Details<BackgroundContents>(details).ptr()); 367 // CLOSED is always followed by a DELETED notification so we'll send our 368 // change notification there. 369 break; 370 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: { 371 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr())); 372 373 // Do not register in the pref if the extension has a manifest-specified 374 // background page. 375 BackgroundContents* bgcontents = 376 content::Details<BackgroundContents>(details).ptr(); 377 Profile* profile = content::Source<Profile>(source).ptr(); 378 const base::string16& appid = GetParentApplicationId(bgcontents); 379 ExtensionService* extension_service = 380 extensions::ExtensionSystem::Get(profile)->extension_service(); 381 // extension_service can be NULL when running tests. 382 if (extension_service) { 383 const Extension* extension = extension_service->GetExtensionById( 384 base::UTF16ToUTF8(appid), false); 385 if (extension && BackgroundInfo::HasBackgroundPage(extension)) 386 break; 387 } 388 RegisterBackgroundContents(bgcontents); 389 break; 390 } 391 case extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED: 392 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED: { 393 Profile* profile = content::Source<Profile>(source).ptr(); 394 const Extension* extension = NULL; 395 if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED) { 396 BackgroundContents* bg = 397 content::Details<BackgroundContents>(details).ptr(); 398 std::string extension_id = base::UTF16ToASCII( 399 BackgroundContentsServiceFactory::GetForProfile(profile)-> 400 GetParentApplicationId(bg)); 401 extension = 402 extensions::ExtensionSystem::Get(profile)->extension_service()-> 403 GetExtensionById(extension_id, false); 404 } else { 405 extensions::ExtensionHost* extension_host = 406 content::Details<extensions::ExtensionHost>(details).ptr(); 407 extension = extension_host->extension(); 408 } 409 if (!extension) 410 break; 411 412 const bool force_installed = 413 extensions::Manifest::IsComponentLocation(extension->location()) || 414 extensions::Manifest::IsPolicyLocation(extension->location()); 415 if (!force_installed) { 416 ShowBalloon(extension, profile); 417 } else { 418 // Restart the extension. 419 RestartForceInstalledExtensionOnCrash(extension, profile); 420 } 421 break; 422 } 423 424 default: 425 NOTREACHED(); 426 break; 427 } 428 } 429 430 void BackgroundContentsService::OnExtensionLoaded( 431 content::BrowserContext* browser_context, 432 const extensions::Extension* extension) { 433 Profile* profile = Profile::FromBrowserContext(browser_context); 434 if (extension->is_hosted_app() && 435 BackgroundInfo::HasBackgroundPage(extension)) { 436 // If there is a background page specified in the manifest for a hosted 437 // app, then blow away registered urls in the pref. 438 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id())); 439 440 ExtensionService* service = 441 extensions::ExtensionSystem::Get(browser_context)->extension_service(); 442 if (service && service->is_ready()) { 443 // Now load the manifest-specified background page. If service isn't 444 // ready, then the background page will be loaded from the 445 // EXTENSIONS_READY callback. 446 LoadBackgroundContents(profile, 447 BackgroundInfo::GetBackgroundURL(extension), 448 base::ASCIIToUTF16("background"), 449 base::UTF8ToUTF16(extension->id())); 450 } 451 } 452 453 // Close the crash notification balloon for the app/extension, if any. 454 ScheduleCloseBalloon(extension->id()); 455 SendChangeNotification(profile); 456 } 457 458 void BackgroundContentsService::OnExtensionUnloaded( 459 content::BrowserContext* browser_context, 460 const extensions::Extension* extension, 461 extensions::UnloadedExtensionInfo::Reason reason) { 462 switch (reason) { 463 case UnloadedExtensionInfo::REASON_DISABLE: // Fall through. 464 case UnloadedExtensionInfo::REASON_TERMINATE: // Fall through. 465 case UnloadedExtensionInfo::REASON_UNINSTALL: // Fall through. 466 case UnloadedExtensionInfo::REASON_BLACKLIST: // Fall through. 467 case UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN: 468 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id())); 469 SendChangeNotification(Profile::FromBrowserContext(browser_context)); 470 break; 471 case UnloadedExtensionInfo::REASON_UPDATE: { 472 // If there is a manifest specified background page, then shut it down 473 // here, since if the updated extension still has the background page, 474 // then it will be loaded from LOADED callback. Otherwise, leave 475 // BackgroundContents in place. 476 // We don't call SendChangeNotification here - it will be generated 477 // from the LOADED callback. 478 if (BackgroundInfo::HasBackgroundPage(extension)) { 479 ShutdownAssociatedBackgroundContents( 480 base::ASCIIToUTF16(extension->id())); 481 } 482 break; 483 } 484 default: 485 NOTREACHED(); 486 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id())); 487 break; 488 } 489 } 490 491 void BackgroundContentsService::OnExtensionUninstalled( 492 content::BrowserContext* browser_context, 493 const extensions::Extension* extension, 494 extensions::UninstallReason reason) { 495 // Make sure the extension-crash balloons are removed when the extension is 496 // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed 497 // extension is unloaded immediately after the crash, not when user reloads or 498 // uninstalls the extension. 499 ScheduleCloseBalloon(extension->id()); 500 } 501 502 void BackgroundContentsService::RestartForceInstalledExtensionOnCrash( 503 const Extension* extension, 504 Profile* profile) { 505 base::MessageLoop::current()->PostDelayedTask(FROM_HERE, 506 base::Bind(&ReloadExtension, extension->id(), profile), 507 base::TimeDelta::FromMilliseconds(restart_delay_in_ms_)); 508 } 509 510 // Loads all background contents whose urls have been stored in prefs. 511 void BackgroundContentsService::LoadBackgroundContentsFromPrefs( 512 Profile* profile) { 513 if (!prefs_) 514 return; 515 const base::DictionaryValue* contents = 516 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents); 517 if (!contents) 518 return; 519 ExtensionService* extensions_service = 520 extensions::ExtensionSystem::Get(profile)->extension_service(); 521 DCHECK(extensions_service); 522 for (base::DictionaryValue::Iterator it(*contents); 523 !it.IsAtEnd(); it.Advance()) { 524 // Check to make sure that the parent extension is still enabled. 525 const Extension* extension = extensions_service-> 526 GetExtensionById(it.key(), false); 527 if (!extension) { 528 // We should never reach here - it should not be possible for an app 529 // to become uninstalled without the associated BackgroundContents being 530 // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a 531 // crash before we could save our prefs, or if the user deletes the 532 // extension files manually rather than uninstalling it. 533 NOTREACHED() << "No extension found for BackgroundContents - id = " 534 << it.key(); 535 // Don't cancel out of our loop, just ignore this BackgroundContents and 536 // load the next one. 537 continue; 538 } 539 LoadBackgroundContentsFromDictionary(profile, it.key(), contents); 540 } 541 } 542 543 void BackgroundContentsService::SendChangeNotification(Profile* profile) { 544 content::NotificationService::current()->Notify( 545 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED, 546 content::Source<Profile>(profile), 547 content::Details<BackgroundContentsService>(this)); 548 } 549 550 void BackgroundContentsService::LoadBackgroundContentsForExtension( 551 Profile* profile, 552 const std::string& extension_id) { 553 // First look if the manifest specifies a background page. 554 const Extension* extension = 555 extensions::ExtensionSystem::Get(profile)->extension_service()-> 556 GetExtensionById(extension_id, false); 557 DCHECK(!extension || extension->is_hosted_app()); 558 if (extension && BackgroundInfo::HasBackgroundPage(extension)) { 559 LoadBackgroundContents(profile, 560 BackgroundInfo::GetBackgroundURL(extension), 561 base::ASCIIToUTF16("background"), 562 base::UTF8ToUTF16(extension->id())); 563 return; 564 } 565 566 // Now look in the prefs. 567 if (!prefs_) 568 return; 569 const base::DictionaryValue* contents = 570 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents); 571 if (!contents) 572 return; 573 LoadBackgroundContentsFromDictionary(profile, extension_id, contents); 574 } 575 576 void BackgroundContentsService::LoadBackgroundContentsFromDictionary( 577 Profile* profile, 578 const std::string& extension_id, 579 const base::DictionaryValue* contents) { 580 ExtensionService* extensions_service = 581 extensions::ExtensionSystem::Get(profile)->extension_service(); 582 DCHECK(extensions_service); 583 584 const base::DictionaryValue* dict; 585 if (!contents->GetDictionaryWithoutPathExpansion(extension_id, &dict) || 586 dict == NULL) 587 return; 588 589 base::string16 frame_name; 590 std::string url; 591 dict->GetString(kUrlKey, &url); 592 dict->GetString(kFrameNameKey, &frame_name); 593 LoadBackgroundContents(profile, 594 GURL(url), 595 frame_name, 596 base::UTF8ToUTF16(extension_id)); 597 } 598 599 void BackgroundContentsService::LoadBackgroundContentsFromManifests( 600 Profile* profile) { 601 const extensions::ExtensionSet* extensions = 602 extensions::ExtensionSystem::Get(profile)-> 603 extension_service()->extensions(); 604 for (extensions::ExtensionSet::const_iterator iter = extensions->begin(); 605 iter != extensions->end(); ++iter) { 606 const Extension* extension = iter->get(); 607 if (extension->is_hosted_app() && 608 BackgroundInfo::HasBackgroundPage(extension)) { 609 LoadBackgroundContents(profile, 610 BackgroundInfo::GetBackgroundURL(extension), 611 base::ASCIIToUTF16("background"), 612 base::UTF8ToUTF16(extension->id())); 613 } 614 } 615 } 616 617 void BackgroundContentsService::LoadBackgroundContents( 618 Profile* profile, 619 const GURL& url, 620 const base::string16& frame_name, 621 const base::string16& application_id) { 622 // We are depending on the fact that we will initialize before any user 623 // actions or session restore can take place, so no BackgroundContents should 624 // be running yet for the passed application_id. 625 DCHECK(!GetAppBackgroundContents(application_id)); 626 DCHECK(!application_id.empty()); 627 DCHECK(url.is_valid()); 628 DVLOG(1) << "Loading background content url: " << url; 629 630 BackgroundContents* contents = CreateBackgroundContents( 631 SiteInstance::CreateForURL(profile, url), 632 MSG_ROUTING_NONE, 633 profile, 634 frame_name, 635 application_id, 636 std::string(), 637 NULL); 638 639 // TODO(atwilson): Create RenderViews asynchronously to avoid increasing 640 // startup latency (http://crbug.com/47236). 641 contents->web_contents()->GetController().LoadURL( 642 url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); 643 } 644 645 BackgroundContents* BackgroundContentsService::CreateBackgroundContents( 646 SiteInstance* site, 647 int routing_id, 648 Profile* profile, 649 const base::string16& frame_name, 650 const base::string16& application_id, 651 const std::string& partition_id, 652 content::SessionStorageNamespace* session_storage_namespace) { 653 BackgroundContents* contents = new BackgroundContents( 654 site, routing_id, this, partition_id, session_storage_namespace); 655 656 // Register the BackgroundContents internally, then send out a notification 657 // to external listeners. 658 BackgroundContentsOpenedDetails details = {contents, 659 frame_name, 660 application_id}; 661 BackgroundContentsOpened(&details); 662 content::NotificationService::current()->Notify( 663 chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED, 664 content::Source<Profile>(profile), 665 content::Details<BackgroundContentsOpenedDetails>(&details)); 666 667 // A new background contents has been created - notify our listeners. 668 SendChangeNotification(profile); 669 return contents; 670 } 671 672 void BackgroundContentsService::RegisterBackgroundContents( 673 BackgroundContents* background_contents) { 674 DCHECK(IsTracked(background_contents)); 675 if (!prefs_) 676 return; 677 678 // We store the first URL we receive for a given application. If there's 679 // already an entry for this application, no need to do anything. 680 // TODO(atwilson): Verify that this is the desired behavior based on developer 681 // feedback (http://crbug.com/47118). 682 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents); 683 base::DictionaryValue* pref = update.Get(); 684 const base::string16& appid = GetParentApplicationId(background_contents); 685 base::DictionaryValue* current; 686 if (pref->GetDictionaryWithoutPathExpansion(base::UTF16ToUTF8(appid), 687 ¤t)) { 688 return; 689 } 690 691 // No entry for this application yet, so add one. 692 base::DictionaryValue* dict = new base::DictionaryValue(); 693 dict->SetString(kUrlKey, background_contents->GetURL().spec()); 694 dict->SetString(kFrameNameKey, contents_map_[appid].frame_name); 695 pref->SetWithoutPathExpansion(base::UTF16ToUTF8(appid), dict); 696 } 697 698 bool BackgroundContentsService::HasRegisteredBackgroundContents( 699 const base::string16& app_id) { 700 if (!prefs_) 701 return false; 702 std::string app = base::UTF16ToUTF8(app_id); 703 const base::DictionaryValue* contents = 704 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents); 705 return contents->HasKey(app); 706 } 707 708 void BackgroundContentsService::UnregisterBackgroundContents( 709 BackgroundContents* background_contents) { 710 if (!prefs_) 711 return; 712 DCHECK(IsTracked(background_contents)); 713 const base::string16 appid = GetParentApplicationId(background_contents); 714 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents); 715 update.Get()->RemoveWithoutPathExpansion(base::UTF16ToUTF8(appid), NULL); 716 } 717 718 void BackgroundContentsService::ShutdownAssociatedBackgroundContents( 719 const base::string16& appid) { 720 BackgroundContents* contents = GetAppBackgroundContents(appid); 721 if (contents) { 722 UnregisterBackgroundContents(contents); 723 // Background contents destructor shuts down the renderer. 724 delete contents; 725 } 726 } 727 728 void BackgroundContentsService::BackgroundContentsOpened( 729 BackgroundContentsOpenedDetails* details) { 730 // Add the passed object to our list. Should not already be tracked. 731 DCHECK(!IsTracked(details->contents)); 732 DCHECK(!details->application_id.empty()); 733 contents_map_[details->application_id].contents = details->contents; 734 contents_map_[details->application_id].frame_name = details->frame_name; 735 736 ScheduleCloseBalloon(base::UTF16ToASCII(details->application_id)); 737 } 738 739 // Used by test code and debug checks to verify whether a given 740 // BackgroundContents is being tracked by this instance. 741 bool BackgroundContentsService::IsTracked( 742 BackgroundContents* background_contents) const { 743 return !GetParentApplicationId(background_contents).empty(); 744 } 745 746 void BackgroundContentsService::BackgroundContentsShutdown( 747 BackgroundContents* background_contents) { 748 // Remove the passed object from our list. 749 DCHECK(IsTracked(background_contents)); 750 base::string16 appid = GetParentApplicationId(background_contents); 751 contents_map_.erase(appid); 752 } 753 754 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents( 755 const base::string16& application_id) { 756 BackgroundContentsMap::const_iterator it = contents_map_.find(application_id); 757 return (it != contents_map_.end()) ? it->second.contents : NULL; 758 } 759 760 const base::string16& BackgroundContentsService::GetParentApplicationId( 761 BackgroundContents* contents) const { 762 for (BackgroundContentsMap::const_iterator it = contents_map_.begin(); 763 it != contents_map_.end(); ++it) { 764 if (contents == it->second.contents) 765 return it->first; 766 } 767 return base::EmptyString16(); 768 } 769 770 void BackgroundContentsService::AddWebContents( 771 WebContents* new_contents, 772 WindowOpenDisposition disposition, 773 const gfx::Rect& initial_pos, 774 bool user_gesture, 775 bool* was_blocked) { 776 Browser* browser = chrome::FindLastActiveWithProfile( 777 Profile::FromBrowserContext(new_contents->GetBrowserContext()), 778 chrome::GetActiveDesktop()); 779 if (browser) { 780 chrome::AddWebContents(browser, NULL, new_contents, disposition, 781 initial_pos, user_gesture, was_blocked); 782 } 783 } 784