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_service.h"
23 #include "chrome/browser/notifications/desktop_notification_service.h"
24 #include "chrome/browser/notifications/notification.h"
25 #include "chrome/browser/notifications/notification_delegate.h"
26 #include "chrome/browser/notifications/notification_ui_manager.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/profiles/profile_manager.h"
29 #include "chrome/browser/ui/browser.h"
30 #include "chrome/browser/ui/browser_finder.h"
31 #include "chrome/browser/ui/browser_tabstrip.h"
32 #include "chrome/browser/ui/host_desktop.h"
33 #include "chrome/common/extensions/extension_constants.h"
34 #include "chrome/common/pref_names.h"
35 #include "chrome/grit/generated_resources.h"
36 #include "content/public/browser/notification_service.h"
37 #include "content/public/browser/site_instance.h"
38 #include "content/public/browser/web_contents.h"
39 #include "extensions/browser/extension_host.h"
40 #include "extensions/browser/extension_registry.h"
41 #include "extensions/browser/extension_system.h"
42 #include "extensions/browser/image_loader.h"
43 #include "extensions/browser/notification_types.h"
44 #include "extensions/common/constants.h"
45 #include "extensions/common/extension.h"
46 #include "extensions/common/extension_icon_set.h"
47 #include "extensions/common/extension_set.h"
48 #include "extensions/common/manifest_handlers/background_info.h"
49 #include "extensions/common/manifest_handlers/icons_handler.h"
50 #include "extensions/grit/extensions_browser_resources.h"
51 #include "ipc/ipc_message.h"
52 #include "ui/base/l10n/l10n_util.h"
53 #include "ui/base/resource/resource_bundle.h"
54 #include "ui/gfx/image/image.h"
56 #if defined(ENABLE_NOTIFICATIONS)
57 #include "ui/message_center/message_center.h"
60 using content::SiteInstance;
61 using content::WebContents;
62 using extensions::BackgroundInfo;
63 using extensions::Extension;
64 using extensions::UnloadedExtensionInfo;
68 const char kNotificationPrefix[] = "app.background.crashed.";
70 void CloseBalloon(const std::string& balloon_id) {
71 NotificationUIManager* notification_ui_manager =
72 g_browser_process->notification_ui_manager();
73 bool cancelled ALLOW_UNUSED = notification_ui_manager->CancelById(balloon_id);
74 #if defined(ENABLE_NOTIFICATIONS)
76 // TODO(dewittj): Add this functionality to the notification UI manager's
78 g_browser_process->message_center()->SetVisibility(
79 message_center::VISIBILITY_TRANSIENT);
84 // Closes the crash notification balloon for the app/extension with this id.
85 void ScheduleCloseBalloon(const std::string& extension_id) {
86 if (!base::MessageLoop::current()) // For unit_tests
88 base::MessageLoop::current()->PostTask(
89 FROM_HERE, base::Bind(&CloseBalloon, kNotificationPrefix + extension_id));
92 // Delegate for the app/extension crash notification balloon. Restarts the
93 // app/extension when the balloon is clicked.
94 class CrashNotificationDelegate : public NotificationDelegate {
96 CrashNotificationDelegate(Profile* profile,
97 const Extension* extension)
99 is_hosted_app_(extension->is_hosted_app()),
100 is_platform_app_(extension->is_platform_app()),
101 extension_id_(extension->id()) {
104 virtual void Display() OVERRIDE {}
106 virtual void Error() OVERRIDE {}
108 virtual void Close(bool by_user) OVERRIDE {}
110 virtual void Click() OVERRIDE {
111 // http://crbug.com/247790 involves a crash notification balloon being
112 // clicked while the extension isn't in the TERMINATED state. In that case,
113 // any of the "reload" methods called below can unload the extension, which
114 // indirectly destroys *this, invalidating all the member variables, so we
115 // copy the extension ID before using it.
116 std::string copied_extension_id = extension_id_;
117 if (is_hosted_app_) {
118 // There can be a race here: user clicks the balloon, and simultaneously
119 // reloads the sad tab for the app. So we check here to be safe before
120 // loading the background page.
121 BackgroundContentsService* service =
122 BackgroundContentsServiceFactory::GetForProfile(profile_);
123 if (!service->GetAppBackgroundContents(
124 base::ASCIIToUTF16(copied_extension_id))) {
125 service->LoadBackgroundContentsForExtension(profile_,
126 copied_extension_id);
128 } else if (is_platform_app_) {
129 apps::AppLoadService::Get(profile_)->
130 RestartApplication(copied_extension_id);
132 extensions::ExtensionSystem::Get(profile_)->extension_service()->
133 ReloadExtension(copied_extension_id);
136 // Closing the crash notification balloon for the app/extension here should
137 // be OK, but it causes a crash on Mac, see: http://crbug.com/78167
138 ScheduleCloseBalloon(copied_extension_id);
141 virtual bool HasClickedListener() OVERRIDE { return true; }
143 virtual std::string id() const OVERRIDE {
144 return kNotificationPrefix + extension_id_;
147 virtual content::WebContents* GetWebContents() const OVERRIDE {
152 virtual ~CrashNotificationDelegate() {}
156 bool is_platform_app_;
157 std::string extension_id_;
159 DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
162 #if defined(ENABLE_NOTIFICATIONS)
163 void NotificationImageReady(
164 const std::string extension_name,
165 const base::string16 message,
166 scoped_refptr<CrashNotificationDelegate> delegate,
168 const gfx::Image& icon) {
169 gfx::Image notification_icon(icon);
170 if (notification_icon.IsEmpty()) {
171 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
172 notification_icon = rb.GetImageNamed(IDR_EXTENSION_DEFAULT_ICON);
175 // Origin URL must be different from the crashed extension to avoid the
176 // conflict. NotificationSystemObserver will cancel all notifications from
177 // the same origin when NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED.
178 // TODO(mukai, dewittj): remove this and switch to message center
180 DesktopNotificationService::AddIconNotification(
181 GURL("chrome://extension-crash"), // Origin URL.
182 base::string16(), // Title of notification.
185 base::UTF8ToUTF16(delegate->id()), // Replace ID.
191 // Show a popup notification balloon with a crash message for a given app/
193 void ShowBalloon(const Extension* extension, Profile* profile) {
194 #if defined(ENABLE_NOTIFICATIONS)
195 const base::string16 message = l10n_util::GetStringFUTF16(
196 extension->is_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE :
197 IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE,
198 base::UTF8ToUTF16(extension->name()));
199 extension_misc::ExtensionIcons size(extension_misc::EXTENSION_ICON_MEDIUM);
200 extensions::ExtensionResource resource =
201 extensions::IconsInfo::GetIconResource(
202 extension, size, ExtensionIconSet::MATCH_SMALLER);
203 // We can't just load the image in the Observe method below because, despite
204 // what this method is called, it may call the callback synchronously.
205 // However, it's possible that the extension went away during the interim,
206 // so we'll bind all the pertinent data here.
207 extensions::ImageLoader::Get(profile)->LoadImageAsync(
210 gfx::Size(size, size),
212 &NotificationImageReady,
215 make_scoped_refptr(new CrashNotificationDelegate(profile, extension)),
220 void ReloadExtension(const std::string& extension_id, Profile* profile) {
221 if (g_browser_process->IsShuttingDown() ||
222 !g_browser_process->profile_manager()->IsValidProfile(profile)) {
226 extensions::ExtensionSystem* extension_system =
227 extensions::ExtensionSystem::Get(profile);
228 extensions::ExtensionRegistry* extension_registry =
229 extensions::ExtensionRegistry::Get(profile);
230 if (!extension_system || !extension_system->extension_service() ||
231 !extension_registry) {
235 if (!extension_registry->GetExtensionById(
236 extension_id, extensions::ExtensionRegistry::TERMINATED)) {
237 // Either the app/extension was uninstalled by policy or it has since
238 // been restarted successfully by someone else (the user).
241 extension_system->extension_service()->ReloadExtension(extension_id);
246 // Keys for the information we store about individual BackgroundContents in
247 // prefs. There is one top-level DictionaryValue (stored at
248 // prefs::kRegisteredBackgroundContents). Information about each
249 // BackgroundContents is stored under that top-level DictionaryValue, keyed
250 // by the parent application ID for easy lookup.
252 // kRegisteredBackgroundContents:
254 // <appid_1>: { "url": <url1>, "name": <frame_name> },
255 // <appid_2>: { "url": <url2>, "name": <frame_name> },
258 const char kUrlKey[] = "url";
259 const char kFrameNameKey[] = "name";
261 int BackgroundContentsService::restart_delay_in_ms_ = 3000; // 3 seconds.
263 BackgroundContentsService::BackgroundContentsService(
264 Profile* profile, const CommandLine* command_line)
265 : prefs_(NULL), extension_registry_observer_(this) {
266 // Don't load/store preferences if the parent profile is incognito.
267 if (!profile->IsOffTheRecord())
268 prefs_ = profile->GetPrefs();
270 // Listen for events to tell us when to load/unload persisted background
272 StartObserving(profile);
275 BackgroundContentsService::~BackgroundContentsService() {
276 // BackgroundContents should be shutdown before we go away, as otherwise
277 // our browser process refcount will be off.
278 DCHECK(contents_map_.empty());
282 void BackgroundContentsService::
283 SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(
284 int restart_delay_in_ms) {
285 restart_delay_in_ms_ = restart_delay_in_ms;
289 std::string BackgroundContentsService::GetNotificationIdForExtensionForTesting(
290 const std::string& extension_id) {
291 return kNotificationPrefix + extension_id;
295 void BackgroundContentsService::ShowBalloonForTesting(
296 const extensions::Extension* extension,
298 ShowBalloon(extension, profile);
301 std::vector<BackgroundContents*>
302 BackgroundContentsService::GetBackgroundContents() const
304 std::vector<BackgroundContents*> contents;
305 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
306 it != contents_map_.end(); ++it)
307 contents.push_back(it->second.contents);
311 void BackgroundContentsService::StartObserving(Profile* profile) {
312 // On startup, load our background pages after extension-apps have loaded.
314 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
315 content::Source<Profile>(profile));
317 // Track the lifecycle of all BackgroundContents in the system to allow us
318 // to store an up-to-date list of the urls. Start tracking contents when they
319 // have been opened via CreateBackgroundContents(), and stop tracking them
320 // when they are closed by script.
321 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED,
322 content::Source<Profile>(profile));
324 // Stop tracking BackgroundContents when they have been deleted (happens
325 // during shutdown or if the render process dies).
326 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
327 content::Source<Profile>(profile));
329 // Track when the BackgroundContents navigates to a new URL so we can update
330 // our persisted information as appropriate.
331 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
332 content::Source<Profile>(profile));
334 // Track when the extensions crash so that the user can be notified
335 // about it, and the crashed contents can be restarted.
337 extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
338 content::Source<Profile>(profile));
339 registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED,
340 content::Source<Profile>(profile));
342 // Listen for extension uninstall, load, unloaded notification.
343 extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile));
346 void BackgroundContentsService::Observe(
348 const content::NotificationSource& source,
349 const content::NotificationDetails& details) {
351 case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: {
352 Profile* profile = content::Source<Profile>(source).ptr();
353 LoadBackgroundContentsFromManifests(profile);
354 LoadBackgroundContentsFromPrefs(profile);
355 SendChangeNotification(profile);
358 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED:
359 BackgroundContentsShutdown(
360 content::Details<BackgroundContents>(details).ptr());
361 SendChangeNotification(content::Source<Profile>(source).ptr());
363 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED:
364 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
365 UnregisterBackgroundContents(
366 content::Details<BackgroundContents>(details).ptr());
367 // CLOSED is always followed by a DELETED notification so we'll send our
368 // change notification there.
370 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: {
371 DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
373 // Do not register in the pref if the extension has a manifest-specified
375 BackgroundContents* bgcontents =
376 content::Details<BackgroundContents>(details).ptr();
377 Profile* profile = content::Source<Profile>(source).ptr();
378 const base::string16& appid = GetParentApplicationId(bgcontents);
379 ExtensionService* extension_service =
380 extensions::ExtensionSystem::Get(profile)->extension_service();
381 // extension_service can be NULL when running tests.
382 if (extension_service) {
383 const Extension* extension = extension_service->GetExtensionById(
384 base::UTF16ToUTF8(appid), false);
385 if (extension && BackgroundInfo::HasBackgroundPage(extension))
388 RegisterBackgroundContents(bgcontents);
391 case extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED:
392 case chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED: {
393 Profile* profile = content::Source<Profile>(source).ptr();
394 const Extension* extension = NULL;
395 if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED) {
396 BackgroundContents* bg =
397 content::Details<BackgroundContents>(details).ptr();
398 std::string extension_id = base::UTF16ToASCII(
399 BackgroundContentsServiceFactory::GetForProfile(profile)->
400 GetParentApplicationId(bg));
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::IsComponentLocation(extension->location()) ||
414 extensions::Manifest::IsPolicyLocation(extension->location());
415 if (!force_installed) {
416 ShowBalloon(extension, profile);
418 // Restart the extension.
419 RestartForceInstalledExtensionOnCrash(extension, profile);
430 void BackgroundContentsService::OnExtensionLoaded(
431 content::BrowserContext* browser_context,
432 const extensions::Extension* extension) {
433 Profile* profile = Profile::FromBrowserContext(browser_context);
434 if (extension->is_hosted_app() &&
435 BackgroundInfo::HasBackgroundPage(extension)) {
436 // If there is a background page specified in the manifest for a hosted
437 // app, then blow away registered urls in the pref.
438 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id()));
440 ExtensionService* service =
441 extensions::ExtensionSystem::Get(browser_context)->extension_service();
442 if (service && service->is_ready()) {
443 // Now load the manifest-specified background page. If service isn't
444 // ready, then the background page will be loaded from the
445 // EXTENSIONS_READY callback.
446 LoadBackgroundContents(profile,
447 BackgroundInfo::GetBackgroundURL(extension),
448 base::ASCIIToUTF16("background"),
449 base::UTF8ToUTF16(extension->id()));
453 // Close the crash notification balloon for the app/extension, if any.
454 ScheduleCloseBalloon(extension->id());
455 SendChangeNotification(profile);
458 void BackgroundContentsService::OnExtensionUnloaded(
459 content::BrowserContext* browser_context,
460 const extensions::Extension* extension,
461 extensions::UnloadedExtensionInfo::Reason reason) {
463 case UnloadedExtensionInfo::REASON_DISABLE: // Fall through.
464 case UnloadedExtensionInfo::REASON_TERMINATE: // Fall through.
465 case UnloadedExtensionInfo::REASON_UNINSTALL: // Fall through.
466 case UnloadedExtensionInfo::REASON_BLACKLIST: // Fall through.
467 case UnloadedExtensionInfo::REASON_PROFILE_SHUTDOWN:
468 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id()));
469 SendChangeNotification(Profile::FromBrowserContext(browser_context));
471 case UnloadedExtensionInfo::REASON_UPDATE: {
472 // If there is a manifest specified background page, then shut it down
473 // here, since if the updated extension still has the background page,
474 // then it will be loaded from LOADED callback. Otherwise, leave
475 // BackgroundContents in place.
476 // We don't call SendChangeNotification here - it will be generated
477 // from the LOADED callback.
478 if (BackgroundInfo::HasBackgroundPage(extension)) {
479 ShutdownAssociatedBackgroundContents(
480 base::ASCIIToUTF16(extension->id()));
486 ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id()));
491 void BackgroundContentsService::OnExtensionUninstalled(
492 content::BrowserContext* browser_context,
493 const extensions::Extension* extension,
494 extensions::UninstallReason reason) {
495 // Make sure the extension-crash balloons are removed when the extension is
496 // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed
497 // extension is unloaded immediately after the crash, not when user reloads or
498 // uninstalls the extension.
499 ScheduleCloseBalloon(extension->id());
502 void BackgroundContentsService::RestartForceInstalledExtensionOnCrash(
503 const Extension* extension,
505 base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
506 base::Bind(&ReloadExtension, extension->id(), profile),
507 base::TimeDelta::FromMilliseconds(restart_delay_in_ms_));
510 // Loads all background contents whose urls have been stored in prefs.
511 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
515 const base::DictionaryValue* contents =
516 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
519 ExtensionService* extensions_service =
520 extensions::ExtensionSystem::Get(profile)->extension_service();
521 DCHECK(extensions_service);
522 for (base::DictionaryValue::Iterator it(*contents);
523 !it.IsAtEnd(); it.Advance()) {
524 // Check to make sure that the parent extension is still enabled.
525 const Extension* extension = extensions_service->
526 GetExtensionById(it.key(), false);
528 // We should never reach here - it should not be possible for an app
529 // to become uninstalled without the associated BackgroundContents being
530 // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a
531 // crash before we could save our prefs, or if the user deletes the
532 // extension files manually rather than uninstalling it.
533 NOTREACHED() << "No extension found for BackgroundContents - id = "
535 // Don't cancel out of our loop, just ignore this BackgroundContents and
536 // load the next one.
539 LoadBackgroundContentsFromDictionary(profile, it.key(), contents);
543 void BackgroundContentsService::SendChangeNotification(Profile* profile) {
544 content::NotificationService::current()->Notify(
545 chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
546 content::Source<Profile>(profile),
547 content::Details<BackgroundContentsService>(this));
550 void BackgroundContentsService::LoadBackgroundContentsForExtension(
552 const std::string& extension_id) {
553 // First look if the manifest specifies a background page.
554 const Extension* extension =
555 extensions::ExtensionSystem::Get(profile)->extension_service()->
556 GetExtensionById(extension_id, false);
557 DCHECK(!extension || extension->is_hosted_app());
558 if (extension && BackgroundInfo::HasBackgroundPage(extension)) {
559 LoadBackgroundContents(profile,
560 BackgroundInfo::GetBackgroundURL(extension),
561 base::ASCIIToUTF16("background"),
562 base::UTF8ToUTF16(extension->id()));
566 // Now look in the prefs.
569 const base::DictionaryValue* contents =
570 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
573 LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
576 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
578 const std::string& extension_id,
579 const base::DictionaryValue* contents) {
580 ExtensionService* extensions_service =
581 extensions::ExtensionSystem::Get(profile)->extension_service();
582 DCHECK(extensions_service);
584 const base::DictionaryValue* dict;
585 if (!contents->GetDictionaryWithoutPathExpansion(extension_id, &dict) ||
589 base::string16 frame_name;
591 dict->GetString(kUrlKey, &url);
592 dict->GetString(kFrameNameKey, &frame_name);
593 LoadBackgroundContents(profile,
596 base::UTF8ToUTF16(extension_id));
599 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
601 const extensions::ExtensionSet* extensions =
602 extensions::ExtensionSystem::Get(profile)->
603 extension_service()->extensions();
604 for (extensions::ExtensionSet::const_iterator iter = extensions->begin();
605 iter != extensions->end(); ++iter) {
606 const Extension* extension = iter->get();
607 if (extension->is_hosted_app() &&
608 BackgroundInfo::HasBackgroundPage(extension)) {
609 LoadBackgroundContents(profile,
610 BackgroundInfo::GetBackgroundURL(extension),
611 base::ASCIIToUTF16("background"),
612 base::UTF8ToUTF16(extension->id()));
617 void BackgroundContentsService::LoadBackgroundContents(
620 const base::string16& frame_name,
621 const base::string16& application_id) {
622 // We are depending on the fact that we will initialize before any user
623 // actions or session restore can take place, so no BackgroundContents should
624 // be running yet for the passed application_id.
625 DCHECK(!GetAppBackgroundContents(application_id));
626 DCHECK(!application_id.empty());
627 DCHECK(url.is_valid());
628 DVLOG(1) << "Loading background content url: " << url;
630 BackgroundContents* contents = CreateBackgroundContents(
631 SiteInstance::CreateForURL(profile, url),
639 // TODO(atwilson): Create RenderViews asynchronously to avoid increasing
640 // startup latency (http://crbug.com/47236).
641 contents->web_contents()->GetController().LoadURL(
642 url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
645 BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
649 const base::string16& frame_name,
650 const base::string16& application_id,
651 const std::string& partition_id,
652 content::SessionStorageNamespace* session_storage_namespace) {
653 BackgroundContents* contents = new BackgroundContents(
654 site, routing_id, this, partition_id, session_storage_namespace);
656 // Register the BackgroundContents internally, then send out a notification
657 // to external listeners.
658 BackgroundContentsOpenedDetails details = {contents,
661 BackgroundContentsOpened(&details);
662 content::NotificationService::current()->Notify(
663 chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
664 content::Source<Profile>(profile),
665 content::Details<BackgroundContentsOpenedDetails>(&details));
667 // A new background contents has been created - notify our listeners.
668 SendChangeNotification(profile);
672 void BackgroundContentsService::RegisterBackgroundContents(
673 BackgroundContents* background_contents) {
674 DCHECK(IsTracked(background_contents));
678 // We store the first URL we receive for a given application. If there's
679 // already an entry for this application, no need to do anything.
680 // TODO(atwilson): Verify that this is the desired behavior based on developer
681 // feedback (http://crbug.com/47118).
682 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
683 base::DictionaryValue* pref = update.Get();
684 const base::string16& appid = GetParentApplicationId(background_contents);
685 base::DictionaryValue* current;
686 if (pref->GetDictionaryWithoutPathExpansion(base::UTF16ToUTF8(appid),
691 // No entry for this application yet, so add one.
692 base::DictionaryValue* dict = new base::DictionaryValue();
693 dict->SetString(kUrlKey, background_contents->GetURL().spec());
694 dict->SetString(kFrameNameKey, contents_map_[appid].frame_name);
695 pref->SetWithoutPathExpansion(base::UTF16ToUTF8(appid), dict);
698 bool BackgroundContentsService::HasRegisteredBackgroundContents(
699 const base::string16& app_id) {
702 std::string app = base::UTF16ToUTF8(app_id);
703 const base::DictionaryValue* contents =
704 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
705 return contents->HasKey(app);
708 void BackgroundContentsService::UnregisterBackgroundContents(
709 BackgroundContents* background_contents) {
712 DCHECK(IsTracked(background_contents));
713 const base::string16 appid = GetParentApplicationId(background_contents);
714 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
715 update.Get()->RemoveWithoutPathExpansion(base::UTF16ToUTF8(appid), NULL);
718 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
719 const base::string16& appid) {
720 BackgroundContents* contents = GetAppBackgroundContents(appid);
722 UnregisterBackgroundContents(contents);
723 // Background contents destructor shuts down the renderer.
728 void BackgroundContentsService::BackgroundContentsOpened(
729 BackgroundContentsOpenedDetails* details) {
730 // Add the passed object to our list. Should not already be tracked.
731 DCHECK(!IsTracked(details->contents));
732 DCHECK(!details->application_id.empty());
733 contents_map_[details->application_id].contents = details->contents;
734 contents_map_[details->application_id].frame_name = details->frame_name;
736 ScheduleCloseBalloon(base::UTF16ToASCII(details->application_id));
739 // Used by test code and debug checks to verify whether a given
740 // BackgroundContents is being tracked by this instance.
741 bool BackgroundContentsService::IsTracked(
742 BackgroundContents* background_contents) const {
743 return !GetParentApplicationId(background_contents).empty();
746 void BackgroundContentsService::BackgroundContentsShutdown(
747 BackgroundContents* background_contents) {
748 // Remove the passed object from our list.
749 DCHECK(IsTracked(background_contents));
750 base::string16 appid = GetParentApplicationId(background_contents);
751 contents_map_.erase(appid);
754 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
755 const base::string16& application_id) {
756 BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
757 return (it != contents_map_.end()) ? it->second.contents : NULL;
760 const base::string16& BackgroundContentsService::GetParentApplicationId(
761 BackgroundContents* contents) const {
762 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
763 it != contents_map_.end(); ++it) {
764 if (contents == it->second.contents)
767 return base::EmptyString16();
770 void BackgroundContentsService::AddWebContents(
771 WebContents* new_contents,
772 WindowOpenDisposition disposition,
773 const gfx::Rect& initial_pos,
776 Browser* browser = chrome::FindLastActiveWithProfile(
777 Profile::FromBrowserContext(new_contents->GetBrowserContext()),
778 chrome::GetActiveDesktop());
780 chrome::AddWebContents(browser, NULL, new_contents, disposition,
781 initial_pos, user_gesture, was_blocked);