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.
5 #include "chrome/browser/background/background_contents_service.h"
7 #include "apps/app_load_service.h"
8 #include "base/basictypes.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/prefs/scoped_user_pref_update.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/time/time.h"
17 #include "base/values.h"
18 #include "chrome/browser/background/background_contents_service_factory.h"
19 #include "chrome/browser/browser_process.h"
20 #include "chrome/browser/chrome_notification_types.h"
21 #include "chrome/browser/extensions/extension_host.h"
22 #include "chrome/browser/extensions/extension_service.h"
23 #include "chrome/browser/extensions/extension_system.h"
24 #include "chrome/browser/extensions/image_loader.h"
25 #include "chrome/browser/notifications/desktop_notification_service.h"
26 #include "chrome/browser/notifications/notification.h"
27 #include "chrome/browser/notifications/notification_delegate.h"
28 #include "chrome/browser/notifications/notification_ui_manager.h"
29 #include "chrome/browser/profiles/profile.h"
30 #include "chrome/browser/profiles/profile_manager.h"
31 #include "chrome/browser/ui/browser.h"
32 #include "chrome/browser/ui/browser_finder.h"
33 #include "chrome/browser/ui/browser_tabstrip.h"
34 #include "chrome/browser/ui/host_desktop.h"
35 #include "chrome/common/chrome_switches.h"
36 #include "chrome/common/extensions/background_info.h"
37 #include "chrome/common/extensions/extension.h"
38 #include "chrome/common/extensions/extension_constants.h"
39 #include "chrome/common/extensions/extension_icon_set.h"
40 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
41 #include "chrome/common/pref_names.h"
42 #include "content/public/browser/notification_service.h"
43 #include "content/public/browser/site_instance.h"
44 #include "content/public/browser/web_contents.h"
45 #include "grit/generated_resources.h"
46 #include "grit/theme_resources.h"
47 #include "ipc/ipc_message.h"
48 #include "ui/base/l10n/l10n_util.h"
49 #include "ui/base/resource/resource_bundle.h"
50 #include "ui/gfx/image/image.h"
52 using content::SiteInstance;
53 using content::WebContents;
54 using extensions::BackgroundInfo;
55 using extensions::Extension;
56 using extensions::UnloadedExtensionInfo;
60 const char kNotificationPrefix[] = "app.background.crashed.";
62 void CloseBalloon(const std::string& balloon_id) {
63 g_browser_process->notification_ui_manager()->
64 CancelById(balloon_id);
67 // Closes the crash notification balloon for the app/extension with this id.
68 void ScheduleCloseBalloon(const std::string& extension_id) {
69 if (!base::MessageLoop::current()) // For unit_tests
71 base::MessageLoop::current()->PostTask(
72 FROM_HERE, base::Bind(&CloseBalloon, kNotificationPrefix + extension_id));
75 // Delegate for the app/extension crash notification balloon. Restarts the
76 // app/extension when the balloon is clicked.
77 class CrashNotificationDelegate : public NotificationDelegate {
79 CrashNotificationDelegate(Profile* profile,
80 const Extension* extension)
82 is_hosted_app_(extension->is_hosted_app()),
83 is_platform_app_(extension->is_platform_app()),
84 extension_id_(extension->id()) {
87 virtual void Display() OVERRIDE {}
89 virtual void Error() OVERRIDE {}
91 virtual void Close(bool by_user) OVERRIDE {}
93 virtual void Click() OVERRIDE {
94 // http://crbug.com/247790 involves a crash notification balloon being
95 // clicked while the extension isn't in the TERMINATED state. In that case,
96 // any of the "reload" methods called below can unload the extension, which
97 // indirectly destroys *this, invalidating all the member variables, so we
98 // copy the extension ID before using it.
99 std::string copied_extension_id = extension_id_;
100 if (is_hosted_app_) {
101 // There can be a race here: user clicks the balloon, and simultaneously
102 // reloads the sad tab for the app. So we check here to be safe before
103 // loading the background page.
104 BackgroundContentsService* service =
105 BackgroundContentsServiceFactory::GetForProfile(profile_);
106 if (!service->GetAppBackgroundContents(ASCIIToUTF16(copied_extension_id)))
107 service->LoadBackgroundContentsForExtension(profile_,
108 copied_extension_id);
109 } else if (is_platform_app_) {
110 apps::AppLoadService::Get(profile_)->
111 RestartApplication(copied_extension_id);
113 extensions::ExtensionSystem::Get(profile_)->extension_service()->
114 ReloadExtension(copied_extension_id);
117 // Closing the crash notification balloon for the app/extension here should
118 // be OK, but it causes a crash on Mac, see: http://crbug.com/78167
119 ScheduleCloseBalloon(copied_extension_id);
122 virtual bool HasClickedListener() OVERRIDE { return true; }
124 virtual std::string id() const OVERRIDE {
125 return kNotificationPrefix + extension_id_;
128 virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE {
133 virtual ~CrashNotificationDelegate() {}
137 bool is_platform_app_;
138 std::string extension_id_;
140 DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
143 #if defined(ENABLE_NOTIFICATIONS)
144 void NotificationImageReady(
145 const std::string extension_name,
146 const string16 message,
147 const GURL extension_url,
148 scoped_refptr<CrashNotificationDelegate> delegate,
150 const gfx::Image& icon) {
151 gfx::Image notification_icon(icon);
152 if (icon.IsEmpty()) {
153 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
154 notification_icon = rb.GetImageNamed(IDR_EXTENSION_DEFAULT_ICON);
156 string16 title; // no notification title
157 DesktopNotificationService::AddIconNotification(extension_url,
167 // Show a popup notification balloon with a crash message for a given app/
169 void ShowBalloon(const Extension* extension, Profile* profile) {
170 #if defined(ENABLE_NOTIFICATIONS)
171 const string16 message = l10n_util::GetStringFUTF16(
172 extension->is_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE :
173 IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE,
174 UTF8ToUTF16(extension->name()));
175 extension_misc::ExtensionIcons size(extension_misc::EXTENSION_ICON_MEDIUM);
176 extensions::ExtensionResource resource =
177 extensions::IconsInfo::GetIconResource(
178 extension, size, ExtensionIconSet::MATCH_SMALLER);
179 // We can't just load the image in the Observe method below because, despite
180 // what this method is called, it may call the callback synchronously.
181 // However, it's possible that the extension went away during the interim,
182 // so we'll bind all the pertinent data here.
183 extensions::ImageLoader::Get(profile)->LoadImageAsync(
186 gfx::Size(size, size),
188 &NotificationImageReady,
192 make_scoped_refptr(new CrashNotificationDelegate(profile, extension)),
197 void ReloadExtension(const std::string& extension_id, Profile* profile) {
198 if (g_browser_process->IsShuttingDown() ||
199 !g_browser_process->profile_manager()->IsValidProfile(profile)) {
202 extensions::ExtensionSystem* extension_system =
203 extensions::ExtensionSystem::Get(profile);
204 if (!extension_system || !extension_system->extension_service())
206 if (!extension_system->extension_service()->
207 GetTerminatedExtension(extension_id)) {
208 // Either the app/extension was uninstalled by policy or it has since
209 // been restarted successfully by someone else (the user).
212 extension_system->extension_service()->ReloadExtension(extension_id);
217 // Keys for the information we store about individual BackgroundContents in
218 // prefs. There is one top-level DictionaryValue (stored at
219 // prefs::kRegisteredBackgroundContents). Information about each
220 // BackgroundContents is stored under that top-level DictionaryValue, keyed
221 // by the parent application ID for easy lookup.
223 // kRegisteredBackgroundContents:
225 // <appid_1>: { "url": <url1>, "name": <frame_name> },
226 // <appid_2>: { "url": <url2>, "name": <frame_name> },
229 const char kUrlKey[] = "url";
230 const char kFrameNameKey[] = "name";
232 int BackgroundContentsService::restart_delay_in_ms_ = 3000; // 3 seconds.
234 BackgroundContentsService::BackgroundContentsService(
235 Profile* profile, const CommandLine* command_line)
237 // Don't load/store preferences if the proper switch is not enabled, or if
238 // the parent profile is incognito.
239 if (!profile->IsOffTheRecord() &&
240 !command_line->HasSwitch(switches::kDisableRestoreBackgroundContents))
241 prefs_ = profile->GetPrefs();
243 // Listen for events to tell us when to load/unload persisted background
245 StartObserving(profile);
248 BackgroundContentsService::~BackgroundContentsService() {
249 // BackgroundContents should be shutdown before we go away, as otherwise
250 // our browser process refcount will be off.
251 DCHECK(contents_map_.empty());
255 void BackgroundContentsService::
256 SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(
257 int restart_delay_in_ms) {
258 restart_delay_in_ms_ = restart_delay_in_ms;
261 std::vector<BackgroundContents*>
262 BackgroundContentsService::GetBackgroundContents() const
264 std::vector<BackgroundContents*> contents;
265 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
266 it != contents_map_.end(); ++it)
267 contents.push_back(it->second.contents);
271 void BackgroundContentsService::StartObserving(Profile* profile) {
272 // On startup, load our background pages after extension-apps have loaded.
273 registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
274 content::Source<Profile>(profile));
276 // Track the lifecycle of all BackgroundContents in the system to allow us
277 // to store an up-to-date list of the urls. Start tracking contents when they
278 // have been opened via CreateBackgroundContents(), and stop tracking them
279 // when they are closed by script.
280 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED,
281 content::Source<Profile>(profile));
283 // Stop tracking BackgroundContents when they have been deleted (happens
284 // during shutdown or if the render process dies).
285 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
286 content::Source<Profile>(profile));
288 // Track when the BackgroundContents navigates to a new URL so we can update
289 // our persisted information as appropriate.
290 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
291 content::Source<Profile>(profile));
293 // Listen for new extension installs so that we can load any associated
295 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
296 content::Source<Profile>(profile));
298 // Track when the extensions crash so that the user can be notified
299 // about it, and the crashed contents can be restarted.
300 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
301 content::Source<Profile>(profile));
302 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED,
303 content::Source<Profile>(profile));
305 // Listen for extensions to be unloaded so we can shutdown associated
306 // BackgroundContents.
307 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
308 content::Source<Profile>(profile));
310 // Make sure the extension-crash balloons are removed when the extension is
311 // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed
312 // extension is unloaded immediately after the crash, not when user reloads or
313 // uninstalls the extension.
314 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
315 content::Source<Profile>(profile));
318 void BackgroundContentsService::Observe(
320 const content::NotificationSource& source,
321 const content::NotificationDetails& details) {
323 case chrome::NOTIFICATION_EXTENSIONS_READY: {
324 Profile* profile = content::Source<Profile>(source).ptr();
325 LoadBackgroundContentsFromManifests(profile);
326 LoadBackgroundContentsFromPrefs(profile);
327 SendChangeNotification(profile);
330 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED:
331 BackgroundContentsShutdown(
332 content::Details<BackgroundContents>(details).ptr());
333 SendChangeNotification(content::Source<Profile>(source).ptr());
335 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED:
336 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
337 UnregisterBackgroundContents(
338 content::Details<BackgroundContents>(details).ptr());
339 // CLOSED is always followed by a DELETED notification so we'll send our
340 // change notification there.
342 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: {
343 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
345 // Do not register in the pref if the extension has a manifest-specified
347 BackgroundContents* bgcontents =
348 content::Details<BackgroundContents>(details).ptr();
349 Profile* profile = content::Source<Profile>(source).ptr();
350 const string16& appid = GetParentApplicationId(bgcontents);
351 ExtensionService* extension_service =
352 extensions::ExtensionSystem::Get(profile)->extension_service();
353 // extension_service can be NULL when running tests.
354 if (extension_service) {
355 const Extension* extension =
356 extension_service->GetExtensionById(UTF16ToUTF8(appid), false);
357 if (extension && BackgroundInfo::HasBackgroundPage(extension))
360 RegisterBackgroundContents(bgcontents);
363 case chrome::NOTIFICATION_EXTENSION_LOADED: {
364 const Extension* extension =
365 content::Details<const Extension>(details).ptr();
366 Profile* profile = content::Source<Profile>(source).ptr();
367 if (extension->is_hosted_app() &&
368 BackgroundInfo::HasBackgroundPage(extension)) {
369 // If there is a background page specified in the manifest for a hosted
370 // app, then blow away registered urls in the pref.
371 ShutdownAssociatedBackgroundContents(ASCIIToUTF16(extension->id()));
373 ExtensionService* service =
374 extensions::ExtensionSystem::Get(profile)->extension_service();
375 if (service && service->is_ready()) {
376 // Now load the manifest-specified background page. If service isn't
377 // ready, then the background page will be loaded from the
378 // EXTENSIONS_READY callback.
379 LoadBackgroundContents(profile,
380 BackgroundInfo::GetBackgroundURL(extension),
381 ASCIIToUTF16("background"),
382 UTF8ToUTF16(extension->id()));
386 // Close the crash notification balloon for the app/extension, if any.
387 ScheduleCloseBalloon(extension->id());
388 SendChangeNotification(profile);
391 case chrome::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 = UTF16ToASCII(
399 BackgroundContentsServiceFactory::GetForProfile(profile)->
400 GetParentApplicationId(bg));
402 extensions::ExtensionSystem::Get(profile)->extension_service()->
403 GetExtensionById(extension_id, false);
405 extensions::ExtensionHost* extension_host =
406 content::Details<extensions::ExtensionHost>(details).ptr();
407 extension = extension_host->extension();
412 const bool force_installed =
413 extensions::Manifest::IsPolicyLocation(extension->location());
414 if (!force_installed) {
415 // When an extension crashes, EXTENSION_PROCESS_TERMINATED is followed
416 // by an EXTENSION_UNLOADED notification. This UNLOADED signal causes
417 // all the notifications for this extension to be cancelled by
418 // DesktopNotificationService. For this reason, we post the crash
419 // handling code as a task here so that it is not executed before this
421 base::MessageLoop::current()->PostTask(
422 FROM_HERE, base::Bind(&ShowBalloon, extension, profile));
424 // Restart the extension.
425 RestartForceInstalledExtensionOnCrash(extension, profile);
429 case chrome::NOTIFICATION_EXTENSION_UNLOADED:
430 switch (content::Details<UnloadedExtensionInfo>(details)->reason) {
431 case UnloadedExtensionInfo::REASON_DISABLE: // Fall through.
432 case UnloadedExtensionInfo::REASON_TERMINATE: // Fall through.
433 case UnloadedExtensionInfo::REASON_UNINSTALL: // Fall through.
434 case UnloadedExtensionInfo::REASON_BLACKLIST:
435 ShutdownAssociatedBackgroundContents(
436 ASCIIToUTF16(content::Details<UnloadedExtensionInfo>(details)->
438 SendChangeNotification(content::Source<Profile>(source).ptr());
440 case UnloadedExtensionInfo::REASON_UPDATE: {
441 // If there is a manifest specified background page, then shut it down
442 // here, since if the updated extension still has the background page,
443 // then it will be loaded from LOADED callback. Otherwise, leave
444 // BackgroundContents in place.
445 // We don't call SendChangeNotification here - it will be generated
446 // from the LOADED callback.
447 const Extension* extension =
448 content::Details<UnloadedExtensionInfo>(details)->extension;
449 if (BackgroundInfo::HasBackgroundPage(extension))
450 ShutdownAssociatedBackgroundContents(ASCIIToUTF16(extension->id()));
455 ShutdownAssociatedBackgroundContents(
456 ASCIIToUTF16(content::Details<UnloadedExtensionInfo>(details)->
462 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
463 // Close the crash notification balloon for the app/extension, if any.
464 ScheduleCloseBalloon(
465 content::Details<const Extension>(details).ptr()->id());
475 void BackgroundContentsService::RestartForceInstalledExtensionOnCrash(
476 const Extension* extension,
478 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
479 base::Bind(&ReloadExtension, extension->id(), profile),
480 base::TimeDelta::FromMilliseconds(restart_delay_in_ms_));
483 // Loads all background contents whose urls have been stored in prefs.
484 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
488 const DictionaryValue* contents =
489 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
492 ExtensionService* extensions_service =
493 extensions::ExtensionSystem::Get(profile)->extension_service();
494 DCHECK(extensions_service);
495 for (DictionaryValue::Iterator it(*contents); !it.IsAtEnd(); it.Advance()) {
496 // Check to make sure that the parent extension is still enabled.
497 const Extension* extension = extensions_service->
498 GetExtensionById(it.key(), false);
500 // We should never reach here - it should not be possible for an app
501 // to become uninstalled without the associated BackgroundContents being
502 // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a
503 // crash before we could save our prefs, or if the user deletes the
504 // extension files manually rather than uninstalling it.
505 NOTREACHED() << "No extension found for BackgroundContents - id = "
507 // Don't cancel out of our loop, just ignore this BackgroundContents and
508 // load the next one.
511 LoadBackgroundContentsFromDictionary(profile, it.key(), contents);
515 void BackgroundContentsService::SendChangeNotification(Profile* profile) {
516 content::NotificationService::current()->Notify(
517 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
518 content::Source<Profile>(profile),
519 content::Details<BackgroundContentsService>(this));
522 void BackgroundContentsService::LoadBackgroundContentsForExtension(
524 const std::string& extension_id) {
525 // First look if the manifest specifies a background page.
526 const Extension* extension =
527 extensions::ExtensionSystem::Get(profile)->extension_service()->
528 GetExtensionById(extension_id, false);
529 DCHECK(!extension || extension->is_hosted_app());
530 if (extension && BackgroundInfo::HasBackgroundPage(extension)) {
531 LoadBackgroundContents(profile,
532 BackgroundInfo::GetBackgroundURL(extension),
533 ASCIIToUTF16("background"),
534 UTF8ToUTF16(extension->id()));
538 // Now look in the prefs.
541 const DictionaryValue* contents =
542 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
545 LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
548 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
550 const std::string& extension_id,
551 const DictionaryValue* contents) {
552 ExtensionService* extensions_service =
553 extensions::ExtensionSystem::Get(profile)->extension_service();
554 DCHECK(extensions_service);
556 const DictionaryValue* dict;
557 if (!contents->GetDictionaryWithoutPathExpansion(extension_id, &dict) ||
563 dict->GetString(kUrlKey, &url);
564 dict->GetString(kFrameNameKey, &frame_name);
565 LoadBackgroundContents(profile,
568 UTF8ToUTF16(extension_id));
571 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
573 const ExtensionSet* extensions = extensions::ExtensionSystem::Get(profile)->
574 extension_service()->extensions();
575 ExtensionSet::const_iterator iter = extensions->begin();
576 for (; iter != extensions->end(); ++iter) {
577 const Extension* extension = iter->get();
578 if (extension->is_hosted_app() &&
579 BackgroundInfo::HasBackgroundPage(extension)) {
580 LoadBackgroundContents(profile,
581 BackgroundInfo::GetBackgroundURL(extension),
582 ASCIIToUTF16("background"),
583 UTF8ToUTF16(extension->id()));
588 void BackgroundContentsService::LoadBackgroundContents(
591 const string16& frame_name,
592 const string16& application_id) {
593 // We are depending on the fact that we will initialize before any user
594 // actions or session restore can take place, so no BackgroundContents should
595 // be running yet for the passed application_id.
596 DCHECK(!GetAppBackgroundContents(application_id));
597 DCHECK(!application_id.empty());
598 DCHECK(url.is_valid());
599 DVLOG(1) << "Loading background content url: " << url;
601 BackgroundContents* contents = CreateBackgroundContents(
602 SiteInstance::CreateForURL(profile, url),
610 // TODO(atwilson): Create RenderViews asynchronously to avoid increasing
611 // startup latency (http://crbug.com/47236).
612 contents->web_contents()->GetController().LoadURL(
613 url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string());
616 BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
620 const string16& frame_name,
621 const string16& application_id,
622 const std::string& partition_id,
623 content::SessionStorageNamespace* session_storage_namespace) {
624 BackgroundContents* contents = new BackgroundContents(
625 site, routing_id, this, partition_id, session_storage_namespace);
627 // Register the BackgroundContents internally, then send out a notification
628 // to external listeners.
629 BackgroundContentsOpenedDetails details = {contents,
632 BackgroundContentsOpened(&details);
633 content::NotificationService::current()->Notify(
634 chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
635 content::Source<Profile>(profile),
636 content::Details<BackgroundContentsOpenedDetails>(&details));
638 // A new background contents has been created - notify our listeners.
639 SendChangeNotification(profile);
643 void BackgroundContentsService::RegisterBackgroundContents(
644 BackgroundContents* background_contents) {
645 DCHECK(IsTracked(background_contents));
649 // We store the first URL we receive for a given application. If there's
650 // already an entry for this application, no need to do anything.
651 // TODO(atwilson): Verify that this is the desired behavior based on developer
652 // feedback (http://crbug.com/47118).
653 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
654 DictionaryValue* pref = update.Get();
655 const string16& appid = GetParentApplicationId(background_contents);
656 DictionaryValue* current;
657 if (pref->GetDictionaryWithoutPathExpansion(UTF16ToUTF8(appid), ¤t))
660 // No entry for this application yet, so add one.
661 DictionaryValue* dict = new DictionaryValue();
662 dict->SetString(kUrlKey, background_contents->GetURL().spec());
663 dict->SetString(kFrameNameKey, contents_map_[appid].frame_name);
664 pref->SetWithoutPathExpansion(UTF16ToUTF8(appid), dict);
667 bool BackgroundContentsService::HasRegisteredBackgroundContents(
668 const string16& app_id) {
671 std::string app = UTF16ToUTF8(app_id);
672 const DictionaryValue* contents =
673 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
674 return contents->HasKey(app);
677 void BackgroundContentsService::UnregisterBackgroundContents(
678 BackgroundContents* background_contents) {
681 DCHECK(IsTracked(background_contents));
682 const string16 appid = GetParentApplicationId(background_contents);
683 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
684 update.Get()->RemoveWithoutPathExpansion(UTF16ToUTF8(appid), NULL);
687 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
688 const string16& appid) {
689 BackgroundContents* contents = GetAppBackgroundContents(appid);
691 UnregisterBackgroundContents(contents);
692 // Background contents destructor shuts down the renderer.
697 void BackgroundContentsService::BackgroundContentsOpened(
698 BackgroundContentsOpenedDetails* details) {
699 // Add the passed object to our list. Should not already be tracked.
700 DCHECK(!IsTracked(details->contents));
701 DCHECK(!details->application_id.empty());
702 contents_map_[details->application_id].contents = details->contents;
703 contents_map_[details->application_id].frame_name = details->frame_name;
705 ScheduleCloseBalloon(UTF16ToASCII(details->application_id));
708 // Used by test code and debug checks to verify whether a given
709 // BackgroundContents is being tracked by this instance.
710 bool BackgroundContentsService::IsTracked(
711 BackgroundContents* background_contents) const {
712 return !GetParentApplicationId(background_contents).empty();
715 void BackgroundContentsService::BackgroundContentsShutdown(
716 BackgroundContents* background_contents) {
717 // Remove the passed object from our list.
718 DCHECK(IsTracked(background_contents));
719 string16 appid = GetParentApplicationId(background_contents);
720 contents_map_.erase(appid);
723 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
724 const string16& application_id) {
725 BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
726 return (it != contents_map_.end()) ? it->second.contents : NULL;
729 const string16& BackgroundContentsService::GetParentApplicationId(
730 BackgroundContents* contents) const {
731 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
732 it != contents_map_.end(); ++it) {
733 if (contents == it->second.contents)
736 return EmptyString16();
739 void BackgroundContentsService::AddWebContents(
740 WebContents* new_contents,
741 WindowOpenDisposition disposition,
742 const gfx::Rect& initial_pos,
745 Browser* browser = chrome::FindLastActiveWithProfile(
746 Profile::FromBrowserContext(new_contents->GetBrowserContext()),
747 chrome::GetActiveDesktop());
749 chrome::AddWebContents(browser, NULL, new_contents, disposition,
750 initial_pos, user_gesture, was_blocked);