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/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_host.h"
23 #include "chrome/browser/extensions/extension_service.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/extension_constants.h"
37 #include "chrome/common/extensions/extension_icon_set.h"
38 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
39 #include "chrome/common/pref_names.h"
40 #include "content/public/browser/notification_service.h"
41 #include "content/public/browser/site_instance.h"
42 #include "content/public/browser/web_contents.h"
43 #include "extensions/browser/extension_system.h"
44 #include "extensions/common/extension.h"
45 #include "extensions/common/extension_set.h"
46 #include "extensions/common/manifest_handlers/background_info.h"
47 #include "grit/generated_resources.h"
48 #include "grit/theme_resources.h"
49 #include "ipc/ipc_message.h"
50 #include "ui/base/l10n/l10n_util.h"
51 #include "ui/base/resource/resource_bundle.h"
52 #include "ui/gfx/image/image.h"
54 #if defined(ENABLE_NOTIFICATIONS)
55 #include "ui/message_center/message_center.h"
56 #include "ui/message_center/message_center_util.h"
59 using content::SiteInstance;
60 using content::WebContents;
61 using extensions::BackgroundInfo;
62 using extensions::Extension;
63 using extensions::UnloadedExtensionInfo;
67 const char kNotificationPrefix[] = "app.background.crashed.";
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 && message_center::IsRichNotificationEnabled()) {
75 // TODO(dewittj): Add this functionality to the notification UI manager's
77 g_browser_process->message_center()->SetVisibility(
78 message_center::VISIBILITY_TRANSIENT);
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
87 base::MessageLoop::current()->PostTask(
88 FROM_HERE, base::Bind(&CloseBalloon, kNotificationPrefix + extension_id));
91 // Delegate for the app/extension crash notification balloon. Restarts the
92 // app/extension when the balloon is clicked.
93 class CrashNotificationDelegate : public NotificationDelegate {
95 CrashNotificationDelegate(Profile* profile,
96 const Extension* extension)
98 is_hosted_app_(extension->is_hosted_app()),
99 is_platform_app_(extension->is_platform_app()),
100 extension_id_(extension->id()) {
103 virtual void Display() OVERRIDE {}
105 virtual void Error() OVERRIDE {}
107 virtual void Close(bool by_user) OVERRIDE {}
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);
127 } else if (is_platform_app_) {
128 apps::AppLoadService::Get(profile_)->
129 RestartApplication(copied_extension_id);
131 extensions::ExtensionSystem::Get(profile_)->extension_service()->
132 ReloadExtension(copied_extension_id);
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);
140 virtual bool HasClickedListener() OVERRIDE { return true; }
142 virtual std::string id() const OVERRIDE {
143 return kNotificationPrefix + extension_id_;
146 virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE {
151 virtual ~CrashNotificationDelegate() {}
155 bool is_platform_app_;
156 std::string extension_id_;
158 DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
161 #if defined(ENABLE_NOTIFICATIONS)
162 void NotificationImageReady(
163 const std::string extension_name,
164 const base::string16 message,
165 scoped_refptr<CrashNotificationDelegate> delegate,
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);
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.
177 // TODO(mukai, dewittj): remove this and switch to message center
179 DesktopNotificationService::AddIconNotification(
180 GURL() /* empty origin */,
190 // Show a popup notification balloon with a crash message for a given app/
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(
209 gfx::Size(size, size),
211 &NotificationImageReady,
214 make_scoped_refptr(new CrashNotificationDelegate(profile, extension)),
219 void ReloadExtension(const std::string& extension_id, Profile* profile) {
220 if (g_browser_process->IsShuttingDown() ||
221 !g_browser_process->profile_manager()->IsValidProfile(profile)) {
224 extensions::ExtensionSystem* extension_system =
225 extensions::ExtensionSystem::Get(profile);
226 if (!extension_system || !extension_system->extension_service())
228 if (!extension_system->extension_service()->
229 GetTerminatedExtension(extension_id)) {
230 // Either the app/extension was uninstalled by policy or it has since
231 // been restarted successfully by someone else (the user).
234 extension_system->extension_service()->ReloadExtension(extension_id);
239 // Keys for the information we store about individual BackgroundContents in
240 // prefs. There is one top-level DictionaryValue (stored at
241 // prefs::kRegisteredBackgroundContents). Information about each
242 // BackgroundContents is stored under that top-level DictionaryValue, keyed
243 // by the parent application ID for easy lookup.
245 // kRegisteredBackgroundContents:
247 // <appid_1>: { "url": <url1>, "name": <frame_name> },
248 // <appid_2>: { "url": <url2>, "name": <frame_name> },
251 const char kUrlKey[] = "url";
252 const char kFrameNameKey[] = "name";
254 int BackgroundContentsService::restart_delay_in_ms_ = 3000; // 3 seconds.
256 BackgroundContentsService::BackgroundContentsService(
257 Profile* profile, const CommandLine* command_line)
259 // Don't load/store preferences if the proper switch is not enabled, or if
260 // the parent profile is incognito.
261 if (!profile->IsOffTheRecord() &&
262 !command_line->HasSwitch(switches::kDisableRestoreBackgroundContents))
263 prefs_ = profile->GetPrefs();
265 // Listen for events to tell us when to load/unload persisted background
267 StartObserving(profile);
270 BackgroundContentsService::~BackgroundContentsService() {
271 // BackgroundContents should be shutdown before we go away, as otherwise
272 // our browser process refcount will be off.
273 DCHECK(contents_map_.empty());
277 void BackgroundContentsService::
278 SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(
279 int restart_delay_in_ms) {
280 restart_delay_in_ms_ = restart_delay_in_ms;
284 std::string BackgroundContentsService::GetNotificationIdForExtensionForTesting(
285 const std::string& extension_id) {
286 return kNotificationPrefix + extension_id;
290 void BackgroundContentsService::ShowBalloonForTesting(
291 const extensions::Extension* extension,
293 ShowBalloon(extension, profile);
296 std::vector<BackgroundContents*>
297 BackgroundContentsService::GetBackgroundContents() const
299 std::vector<BackgroundContents*> contents;
300 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
301 it != contents_map_.end(); ++it)
302 contents.push_back(it->second.contents);
306 void BackgroundContentsService::StartObserving(Profile* profile) {
307 // On startup, load our background pages after extension-apps have loaded.
308 registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
309 content::Source<Profile>(profile));
311 // Track the lifecycle of all BackgroundContents in the system to allow us
312 // to store an up-to-date list of the urls. Start tracking contents when they
313 // have been opened via CreateBackgroundContents(), and stop tracking them
314 // when they are closed by script.
315 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED,
316 content::Source<Profile>(profile));
318 // Stop tracking BackgroundContents when they have been deleted (happens
319 // during shutdown or if the render process dies).
320 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
321 content::Source<Profile>(profile));
323 // Track when the BackgroundContents navigates to a new URL so we can update
324 // our persisted information as appropriate.
325 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
326 content::Source<Profile>(profile));
328 // Listen for new extension installs so that we can load any associated
330 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
331 content::Source<Profile>(profile));
333 // Track when the extensions crash so that the user can be notified
334 // about it, and the crashed contents can be restarted.
335 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
336 content::Source<Profile>(profile));
337 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED,
338 content::Source<Profile>(profile));
340 // Listen for extensions to be unloaded so we can shutdown associated
341 // BackgroundContents.
342 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
343 content::Source<Profile>(profile));
345 // Make sure the extension-crash balloons are removed when the extension is
346 // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed
347 // extension is unloaded immediately after the crash, not when user reloads or
348 // uninstalls the extension.
349 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
350 content::Source<Profile>(profile));
353 void BackgroundContentsService::Observe(
355 const content::NotificationSource& source,
356 const content::NotificationDetails& details) {
358 case chrome::NOTIFICATION_EXTENSIONS_READY: {
359 Profile* profile = content::Source<Profile>(source).ptr();
360 LoadBackgroundContentsFromManifests(profile);
361 LoadBackgroundContentsFromPrefs(profile);
362 SendChangeNotification(profile);
365 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED:
366 BackgroundContentsShutdown(
367 content::Details<BackgroundContents>(details).ptr());
368 SendChangeNotification(content::Source<Profile>(source).ptr());
370 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED:
371 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
372 UnregisterBackgroundContents(
373 content::Details<BackgroundContents>(details).ptr());
374 // CLOSED is always followed by a DELETED notification so we'll send our
375 // change notification there.
377 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: {
378 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
380 // Do not register in the pref if the extension has a manifest-specified
382 BackgroundContents* bgcontents =
383 content::Details<BackgroundContents>(details).ptr();
384 Profile* profile = content::Source<Profile>(source).ptr();
385 const base::string16& appid = GetParentApplicationId(bgcontents);
386 ExtensionService* extension_service =
387 extensions::ExtensionSystem::Get(profile)->extension_service();
388 // extension_service can be NULL when running tests.
389 if (extension_service) {
390 const Extension* extension = extension_service->GetExtensionById(
391 base::UTF16ToUTF8(appid), false);
392 if (extension && BackgroundInfo::HasBackgroundPage(extension))
395 RegisterBackgroundContents(bgcontents);
398 case chrome::NOTIFICATION_EXTENSION_LOADED: {
399 const Extension* extension =
400 content::Details<const Extension>(details).ptr();
401 Profile* profile = content::Source<Profile>(source).ptr();
402 if (extension->is_hosted_app() &&
403 BackgroundInfo::HasBackgroundPage(extension)) {
404 // If there is a background page specified in the manifest for a hosted
405 // app, then blow away registered urls in the pref.
406 ShutdownAssociatedBackgroundContents(
407 base::ASCIIToUTF16(extension->id()));
409 ExtensionService* service =
410 extensions::ExtensionSystem::Get(profile)->extension_service();
411 if (service && service->is_ready()) {
412 // Now load the manifest-specified background page. If service isn't
413 // ready, then the background page will be loaded from the
414 // EXTENSIONS_READY callback.
415 LoadBackgroundContents(profile,
416 BackgroundInfo::GetBackgroundURL(extension),
417 base::ASCIIToUTF16("background"),
418 base::UTF8ToUTF16(extension->id()));
422 // Close the crash notification balloon for the app/extension, if any.
423 ScheduleCloseBalloon(extension->id());
424 SendChangeNotification(profile);
427 case chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED:
428 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED: {
429 Profile* profile = content::Source<Profile>(source).ptr();
430 const Extension* extension = NULL;
431 if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED) {
432 BackgroundContents* bg =
433 content::Details<BackgroundContents>(details).ptr();
434 std::string extension_id = UTF16ToASCII(
435 BackgroundContentsServiceFactory::GetForProfile(profile)->
436 GetParentApplicationId(bg));
438 extensions::ExtensionSystem::Get(profile)->extension_service()->
439 GetExtensionById(extension_id, false);
441 extensions::ExtensionHost* extension_host =
442 content::Details<extensions::ExtensionHost>(details).ptr();
443 extension = extension_host->extension();
448 const bool force_installed =
449 extensions::Manifest::IsComponentLocation(extension->location()) ||
450 extensions::Manifest::IsPolicyLocation(extension->location());
451 if (!force_installed) {
452 ShowBalloon(extension, profile);
454 // Restart the extension.
455 RestartForceInstalledExtensionOnCrash(extension, profile);
459 case chrome::NOTIFICATION_EXTENSION_UNLOADED:
460 switch (content::Details<UnloadedExtensionInfo>(details)->reason) {
461 case UnloadedExtensionInfo::REASON_DISABLE: // Fall through.
462 case UnloadedExtensionInfo::REASON_TERMINATE: // Fall through.
463 case UnloadedExtensionInfo::REASON_UNINSTALL: // Fall through.
464 case UnloadedExtensionInfo::REASON_BLACKLIST:
465 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(
466 content::Details<UnloadedExtensionInfo>(details)->
468 SendChangeNotification(content::Source<Profile>(source).ptr());
470 case UnloadedExtensionInfo::REASON_UPDATE: {
471 // If there is a manifest specified background page, then shut it down
472 // here, since if the updated extension still has the background page,
473 // then it will be loaded from LOADED callback. Otherwise, leave
474 // BackgroundContents in place.
475 // We don't call SendChangeNotification here - it will be generated
476 // from the LOADED callback.
477 const Extension* extension =
478 content::Details<UnloadedExtensionInfo>(details)->extension;
479 if (BackgroundInfo::HasBackgroundPage(extension)) {
480 ShutdownAssociatedBackgroundContents(
481 base::ASCIIToUTF16(extension->id()));
487 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(
488 content::Details<UnloadedExtensionInfo>(details)->
494 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
495 // Close the crash notification balloon for the app/extension, if any.
496 ScheduleCloseBalloon(
497 content::Details<const Extension>(details).ptr()->id());
507 void BackgroundContentsService::RestartForceInstalledExtensionOnCrash(
508 const Extension* extension,
510 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
511 base::Bind(&ReloadExtension, extension->id(), profile),
512 base::TimeDelta::FromMilliseconds(restart_delay_in_ms_));
515 // Loads all background contents whose urls have been stored in prefs.
516 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
520 const base::DictionaryValue* contents =
521 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
524 ExtensionService* extensions_service =
525 extensions::ExtensionSystem::Get(profile)->extension_service();
526 DCHECK(extensions_service);
527 for (base::DictionaryValue::Iterator it(*contents);
528 !it.IsAtEnd(); it.Advance()) {
529 // Check to make sure that the parent extension is still enabled.
530 const Extension* extension = extensions_service->
531 GetExtensionById(it.key(), false);
533 // We should never reach here - it should not be possible for an app
534 // to become uninstalled without the associated BackgroundContents being
535 // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a
536 // crash before we could save our prefs, or if the user deletes the
537 // extension files manually rather than uninstalling it.
538 NOTREACHED() << "No extension found for BackgroundContents - id = "
540 // Don't cancel out of our loop, just ignore this BackgroundContents and
541 // load the next one.
544 LoadBackgroundContentsFromDictionary(profile, it.key(), contents);
548 void BackgroundContentsService::SendChangeNotification(Profile* profile) {
549 content::NotificationService::current()->Notify(
550 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
551 content::Source<Profile>(profile),
552 content::Details<BackgroundContentsService>(this));
555 void BackgroundContentsService::LoadBackgroundContentsForExtension(
557 const std::string& extension_id) {
558 // First look if the manifest specifies a background page.
559 const Extension* extension =
560 extensions::ExtensionSystem::Get(profile)->extension_service()->
561 GetExtensionById(extension_id, false);
562 DCHECK(!extension || extension->is_hosted_app());
563 if (extension && BackgroundInfo::HasBackgroundPage(extension)) {
564 LoadBackgroundContents(profile,
565 BackgroundInfo::GetBackgroundURL(extension),
566 base::ASCIIToUTF16("background"),
567 base::UTF8ToUTF16(extension->id()));
571 // Now look in the prefs.
574 const base::DictionaryValue* contents =
575 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
578 LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
581 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
583 const std::string& extension_id,
584 const base::DictionaryValue* contents) {
585 ExtensionService* extensions_service =
586 extensions::ExtensionSystem::Get(profile)->extension_service();
587 DCHECK(extensions_service);
589 const base::DictionaryValue* dict;
590 if (!contents->GetDictionaryWithoutPathExpansion(extension_id, &dict) ||
594 base::string16 frame_name;
596 dict->GetString(kUrlKey, &url);
597 dict->GetString(kFrameNameKey, &frame_name);
598 LoadBackgroundContents(profile,
601 base::UTF8ToUTF16(extension_id));
604 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
606 const extensions::ExtensionSet* extensions =
607 extensions::ExtensionSystem::Get(profile)->
608 extension_service()->extensions();
609 for (extensions::ExtensionSet::const_iterator iter = extensions->begin();
610 iter != extensions->end(); ++iter) {
611 const Extension* extension = iter->get();
612 if (extension->is_hosted_app() &&
613 BackgroundInfo::HasBackgroundPage(extension)) {
614 LoadBackgroundContents(profile,
615 BackgroundInfo::GetBackgroundURL(extension),
616 base::ASCIIToUTF16("background"),
617 base::UTF8ToUTF16(extension->id()));
622 void BackgroundContentsService::LoadBackgroundContents(
625 const base::string16& frame_name,
626 const base::string16& application_id) {
627 // We are depending on the fact that we will initialize before any user
628 // actions or session restore can take place, so no BackgroundContents should
629 // be running yet for the passed application_id.
630 DCHECK(!GetAppBackgroundContents(application_id));
631 DCHECK(!application_id.empty());
632 DCHECK(url.is_valid());
633 DVLOG(1) << "Loading background content url: " << url;
635 BackgroundContents* contents = CreateBackgroundContents(
636 SiteInstance::CreateForURL(profile, url),
644 // TODO(atwilson): Create RenderViews asynchronously to avoid increasing
645 // startup latency (http://crbug.com/47236).
646 contents->web_contents()->GetController().LoadURL(
647 url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string());
650 BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
654 const base::string16& frame_name,
655 const base::string16& application_id,
656 const std::string& partition_id,
657 content::SessionStorageNamespace* session_storage_namespace) {
658 BackgroundContents* contents = new BackgroundContents(
659 site, routing_id, this, partition_id, session_storage_namespace);
661 // Register the BackgroundContents internally, then send out a notification
662 // to external listeners.
663 BackgroundContentsOpenedDetails details = {contents,
666 BackgroundContentsOpened(&details);
667 content::NotificationService::current()->Notify(
668 chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
669 content::Source<Profile>(profile),
670 content::Details<BackgroundContentsOpenedDetails>(&details));
672 // A new background contents has been created - notify our listeners.
673 SendChangeNotification(profile);
677 void BackgroundContentsService::RegisterBackgroundContents(
678 BackgroundContents* background_contents) {
679 DCHECK(IsTracked(background_contents));
683 // We store the first URL we receive for a given application. If there's
684 // already an entry for this application, no need to do anything.
685 // TODO(atwilson): Verify that this is the desired behavior based on developer
686 // feedback (http://crbug.com/47118).
687 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
688 base::DictionaryValue* pref = update.Get();
689 const base::string16& appid = GetParentApplicationId(background_contents);
690 base::DictionaryValue* current;
691 if (pref->GetDictionaryWithoutPathExpansion(base::UTF16ToUTF8(appid),
696 // No entry for this application yet, so add one.
697 base::DictionaryValue* dict = new base::DictionaryValue();
698 dict->SetString(kUrlKey, background_contents->GetURL().spec());
699 dict->SetString(kFrameNameKey, contents_map_[appid].frame_name);
700 pref->SetWithoutPathExpansion(base::UTF16ToUTF8(appid), dict);
703 bool BackgroundContentsService::HasRegisteredBackgroundContents(
704 const base::string16& app_id) {
707 std::string app = base::UTF16ToUTF8(app_id);
708 const base::DictionaryValue* contents =
709 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
710 return contents->HasKey(app);
713 void BackgroundContentsService::UnregisterBackgroundContents(
714 BackgroundContents* background_contents) {
717 DCHECK(IsTracked(background_contents));
718 const base::string16 appid = GetParentApplicationId(background_contents);
719 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
720 update.Get()->RemoveWithoutPathExpansion(base::UTF16ToUTF8(appid), NULL);
723 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
724 const base::string16& appid) {
725 BackgroundContents* contents = GetAppBackgroundContents(appid);
727 UnregisterBackgroundContents(contents);
728 // Background contents destructor shuts down the renderer.
733 void BackgroundContentsService::BackgroundContentsOpened(
734 BackgroundContentsOpenedDetails* details) {
735 // Add the passed object to our list. Should not already be tracked.
736 DCHECK(!IsTracked(details->contents));
737 DCHECK(!details->application_id.empty());
738 contents_map_[details->application_id].contents = details->contents;
739 contents_map_[details->application_id].frame_name = details->frame_name;
741 ScheduleCloseBalloon(UTF16ToASCII(details->application_id));
744 // Used by test code and debug checks to verify whether a given
745 // BackgroundContents is being tracked by this instance.
746 bool BackgroundContentsService::IsTracked(
747 BackgroundContents* background_contents) const {
748 return !GetParentApplicationId(background_contents).empty();
751 void BackgroundContentsService::BackgroundContentsShutdown(
752 BackgroundContents* background_contents) {
753 // Remove the passed object from our list.
754 DCHECK(IsTracked(background_contents));
755 base::string16 appid = GetParentApplicationId(background_contents);
756 contents_map_.erase(appid);
759 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
760 const base::string16& application_id) {
761 BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
762 return (it != contents_map_.end()) ? it->second.contents : NULL;
765 const base::string16& BackgroundContentsService::GetParentApplicationId(
766 BackgroundContents* contents) const {
767 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
768 it != contents_map_.end(); ++it) {
769 if (contents == it->second.contents)
772 return base::EmptyString16();
775 void BackgroundContentsService::AddWebContents(
776 WebContents* new_contents,
777 WindowOpenDisposition disposition,
778 const gfx::Rect& initial_pos,
781 Browser* browser = chrome::FindLastActiveWithProfile(
782 Profile::FromBrowserContext(new_contents->GetBrowserContext()),
783 chrome::GetActiveDesktop());
785 chrome::AddWebContents(browser, NULL, new_contents, disposition,
786 initial_pos, user_gesture, was_blocked);