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