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