Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / background / background_contents_service.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/background/background_contents_service.h"
6
7 #include "apps/app_load_service.h"
8 #include "base/basictypes.h"
9 #include "base/bind.h"
10 #include "base/command_line.h"
11 #include "base/compiler_specific.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/prefs/scoped_user_pref_update.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/time/time.h"
18 #include "base/values.h"
19 #include "chrome/browser/background/background_contents_service_factory.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/extensions/extension_service.h"
23 #include "chrome/browser/notifications/desktop_notification_service.h"
24 #include "chrome/browser/notifications/notification.h"
25 #include "chrome/browser/notifications/notification_delegate.h"
26 #include "chrome/browser/notifications/notification_ui_manager.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/profiles/profile_manager.h"
29 #include "chrome/browser/ui/browser.h"
30 #include "chrome/browser/ui/browser_finder.h"
31 #include "chrome/browser/ui/browser_tabstrip.h"
32 #include "chrome/browser/ui/host_desktop.h"
33 #include "chrome/common/extensions/extension_constants.h"
34 #include "chrome/common/pref_names.h"
35 #include "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"
55
56 #if defined(ENABLE_NOTIFICATIONS)
57 #include "ui/message_center/message_center.h"
58 #endif
59
60 using content::SiteInstance;
61 using content::WebContents;
62 using extensions::BackgroundInfo;
63 using extensions::Extension;
64 using extensions::UnloadedExtensionInfo;
65
66 namespace {
67
68 const char kNotificationPrefix[] = "app.background.crashed.";
69
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)
75   if (cancelled) {
76     // TODO(dewittj): Add this functionality to the notification UI manager's
77     // API.
78     g_browser_process->message_center()->SetVisibility(
79         message_center::VISIBILITY_TRANSIENT);
80   }
81 #endif
82 }
83
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
87     return;
88   base::MessageLoop::current()->PostTask(
89       FROM_HERE, base::Bind(&CloseBalloon, kNotificationPrefix + extension_id));
90 }
91
92 // Delegate for the app/extension crash notification balloon. Restarts the
93 // app/extension when the balloon is clicked.
94 class CrashNotificationDelegate : public NotificationDelegate {
95  public:
96   CrashNotificationDelegate(Profile* profile,
97                             const Extension* extension)
98       : profile_(profile),
99         is_hosted_app_(extension->is_hosted_app()),
100         is_platform_app_(extension->is_platform_app()),
101         extension_id_(extension->id()) {
102   }
103
104   virtual void Display() OVERRIDE {}
105
106   virtual void Error() OVERRIDE {}
107
108   virtual void Close(bool by_user) OVERRIDE {}
109
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);
127       }
128     } else if (is_platform_app_) {
129       apps::AppLoadService::Get(profile_)->
130           RestartApplication(copied_extension_id);
131     } else {
132       extensions::ExtensionSystem::Get(profile_)->extension_service()->
133           ReloadExtension(copied_extension_id);
134     }
135
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);
139   }
140
141   virtual bool HasClickedListener() OVERRIDE { return true; }
142
143   virtual std::string id() const OVERRIDE {
144     return kNotificationPrefix + extension_id_;
145   }
146
147   virtual content::WebContents* GetWebContents() const OVERRIDE {
148     return NULL;
149   }
150
151  private:
152   virtual ~CrashNotificationDelegate() {}
153
154   Profile* profile_;
155   bool is_hosted_app_;
156   bool is_platform_app_;
157   std::string extension_id_;
158
159   DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
160 };
161
162 #if defined(ENABLE_NOTIFICATIONS)
163 void NotificationImageReady(
164     const std::string extension_name,
165     const base::string16 message,
166     scoped_refptr<CrashNotificationDelegate> delegate,
167     Profile* profile,
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);
173   }
174
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
179   // notifications.
180   DesktopNotificationService::AddIconNotification(
181       GURL("chrome://extension-crash"),  // Origin URL.
182       base::string16(),                  // Title of notification.
183       message,
184       notification_icon,
185       base::UTF8ToUTF16(delegate->id()),  // Replace ID.
186       delegate.get(),
187       profile);
188 }
189 #endif
190
191 // Show a popup notification balloon with a crash message for a given app/
192 // extension.
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(
208       extension,
209       resource,
210       gfx::Size(size, size),
211       base::Bind(
212           &NotificationImageReady,
213           extension->name(),
214           message,
215           make_scoped_refptr(new CrashNotificationDelegate(profile, extension)),
216           profile));
217 #endif
218 }
219
220 void ReloadExtension(const std::string& extension_id, Profile* profile) {
221   if (g_browser_process->IsShuttingDown() ||
222       !g_browser_process->profile_manager()->IsValidProfile(profile)) {
223       return;
224   }
225
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) {
232     return;
233   }
234
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).
239     return;
240   }
241   extension_system->extension_service()->ReloadExtension(extension_id);
242 }
243
244 }  // namespace
245
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.
251 //
252 // kRegisteredBackgroundContents:
253 //    DictionaryValue {
254 //       <appid_1>: { "url": <url1>, "name": <frame_name> },
255 //       <appid_2>: { "url": <url2>, "name": <frame_name> },
256 //         ... etc ...
257 //    }
258 const char kUrlKey[] = "url";
259 const char kFrameNameKey[] = "name";
260
261 int BackgroundContentsService::restart_delay_in_ms_ = 3000;  // 3 seconds.
262
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();
269
270   // Listen for events to tell us when to load/unload persisted background
271   // contents.
272   StartObserving(profile);
273 }
274
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());
279 }
280
281 // static
282 void BackgroundContentsService::
283     SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(
284         int restart_delay_in_ms) {
285   restart_delay_in_ms_ = restart_delay_in_ms;
286 }
287
288 // static
289 std::string BackgroundContentsService::GetNotificationIdForExtensionForTesting(
290     const std::string& extension_id) {
291   return kNotificationPrefix + extension_id;
292 }
293
294 // static
295 void BackgroundContentsService::ShowBalloonForTesting(
296     const extensions::Extension* extension,
297     Profile* profile) {
298   ShowBalloon(extension, profile);
299 }
300
301 std::vector<BackgroundContents*>
302 BackgroundContentsService::GetBackgroundContents() const
303 {
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);
308   return contents;
309 }
310
311 void BackgroundContentsService::StartObserving(Profile* profile) {
312   // On startup, load our background pages after extension-apps have loaded.
313   registrar_.Add(this,
314                  extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
315                  content::Source<Profile>(profile));
316
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));
323
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));
328
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));
333
334   // Track when the extensions crash so that the user can be notified
335   // about it, and the crashed contents can be restarted.
336   registrar_.Add(this,
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));
341
342   // Listen for extension uninstall, load, unloaded notification.
343   extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile));
344 }
345
346 void BackgroundContentsService::Observe(
347     int type,
348     const content::NotificationSource& source,
349     const content::NotificationDetails& details) {
350   switch (type) {
351     case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: {
352       Profile* profile = content::Source<Profile>(source).ptr();
353       LoadBackgroundContentsFromManifests(profile);
354       LoadBackgroundContentsFromPrefs(profile);
355       SendChangeNotification(profile);
356       break;
357     }
358     case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED:
359       BackgroundContentsShutdown(
360           content::Details<BackgroundContents>(details).ptr());
361       SendChangeNotification(content::Source<Profile>(source).ptr());
362       break;
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.
369       break;
370     case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: {
371       DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
372
373       // Do not register in the pref if the extension has a manifest-specified
374       // background page.
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))
386           break;
387       }
388       RegisterBackgroundContents(bgcontents);
389       break;
390     }
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));
401         extension =
402           extensions::ExtensionSystem::Get(profile)->extension_service()->
403               GetExtensionById(extension_id, false);
404       } else {
405         extensions::ExtensionHost* extension_host =
406             content::Details<extensions::ExtensionHost>(details).ptr();
407         extension = extension_host->extension();
408       }
409       if (!extension)
410         break;
411
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);
417       } else {
418         // Restart the extension.
419         RestartForceInstalledExtensionOnCrash(extension, profile);
420       }
421       break;
422     }
423
424     default:
425       NOTREACHED();
426       break;
427   }
428 }
429
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()));
439
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()));
450     }
451   }
452
453   // Close the crash notification balloon for the app/extension, if any.
454   ScheduleCloseBalloon(extension->id());
455   SendChangeNotification(profile);
456 }
457
458 void BackgroundContentsService::OnExtensionUnloaded(
459     content::BrowserContext* browser_context,
460     const extensions::Extension* extension,
461     extensions::UnloadedExtensionInfo::Reason reason) {
462   switch (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));
470       break;
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()));
481       }
482       break;
483     }
484     default:
485       NOTREACHED();
486       ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(extension->id()));
487       break;
488   }
489 }
490
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());
500 }
501
502 void BackgroundContentsService::RestartForceInstalledExtensionOnCrash(
503     const Extension* extension,
504     Profile* profile) {
505   base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
506       base::Bind(&ReloadExtension, extension->id(), profile),
507       base::TimeDelta::FromMilliseconds(restart_delay_in_ms_));
508 }
509
510 // Loads all background contents whose urls have been stored in prefs.
511 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
512     Profile* profile) {
513   if (!prefs_)
514     return;
515   const base::DictionaryValue* contents =
516       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
517   if (!contents)
518     return;
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);
527     if (!extension) {
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 = "
534                    << it.key();
535       // Don't cancel out of our loop, just ignore this BackgroundContents and
536       // load the next one.
537       continue;
538     }
539     LoadBackgroundContentsFromDictionary(profile, it.key(), contents);
540   }
541 }
542
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));
548 }
549
550 void BackgroundContentsService::LoadBackgroundContentsForExtension(
551     Profile* profile,
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()));
563     return;
564   }
565
566   // Now look in the prefs.
567   if (!prefs_)
568     return;
569   const base::DictionaryValue* contents =
570       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
571   if (!contents)
572     return;
573   LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
574 }
575
576 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
577     Profile* profile,
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);
583
584   const base::DictionaryValue* dict;
585   if (!contents->GetDictionaryWithoutPathExpansion(extension_id, &dict) ||
586       dict == NULL)
587     return;
588
589   base::string16 frame_name;
590   std::string url;
591   dict->GetString(kUrlKey, &url);
592   dict->GetString(kFrameNameKey, &frame_name);
593   LoadBackgroundContents(profile,
594                          GURL(url),
595                          frame_name,
596                          base::UTF8ToUTF16(extension_id));
597 }
598
599 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
600     Profile* profile) {
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()));
613     }
614   }
615 }
616
617 void BackgroundContentsService::LoadBackgroundContents(
618     Profile* profile,
619     const GURL& url,
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;
629
630   BackgroundContents* contents = CreateBackgroundContents(
631       SiteInstance::CreateForURL(profile, url),
632       MSG_ROUTING_NONE,
633       profile,
634       frame_name,
635       application_id,
636       std::string(),
637       NULL);
638
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());
643 }
644
645 BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
646     SiteInstance* site,
647     int routing_id,
648     Profile* profile,
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);
655
656   // Register the BackgroundContents internally, then send out a notification
657   // to external listeners.
658   BackgroundContentsOpenedDetails details = {contents,
659                                              frame_name,
660                                              application_id};
661   BackgroundContentsOpened(&details);
662   content::NotificationService::current()->Notify(
663       chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
664       content::Source<Profile>(profile),
665       content::Details<BackgroundContentsOpenedDetails>(&details));
666
667   // A new background contents has been created - notify our listeners.
668   SendChangeNotification(profile);
669   return contents;
670 }
671
672 void BackgroundContentsService::RegisterBackgroundContents(
673     BackgroundContents* background_contents) {
674   DCHECK(IsTracked(background_contents));
675   if (!prefs_)
676     return;
677
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),
687                                               &current)) {
688     return;
689   }
690
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);
696 }
697
698 bool BackgroundContentsService::HasRegisteredBackgroundContents(
699     const base::string16& app_id) {
700   if (!prefs_)
701     return false;
702   std::string app = base::UTF16ToUTF8(app_id);
703   const base::DictionaryValue* contents =
704       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
705   return contents->HasKey(app);
706 }
707
708 void BackgroundContentsService::UnregisterBackgroundContents(
709     BackgroundContents* background_contents) {
710   if (!prefs_)
711     return;
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);
716 }
717
718 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
719     const base::string16& appid) {
720   BackgroundContents* contents = GetAppBackgroundContents(appid);
721   if (contents) {
722     UnregisterBackgroundContents(contents);
723     // Background contents destructor shuts down the renderer.
724     delete contents;
725   }
726 }
727
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;
735
736   ScheduleCloseBalloon(base::UTF16ToASCII(details->application_id));
737 }
738
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();
744 }
745
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);
752 }
753
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;
758 }
759
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)
765       return it->first;
766   }
767   return base::EmptyString16();
768 }
769
770 void BackgroundContentsService::AddWebContents(
771     WebContents* new_contents,
772     WindowOpenDisposition disposition,
773     const gfx::Rect& initial_pos,
774     bool user_gesture,
775     bool* was_blocked) {
776   Browser* browser = chrome::FindLastActiveWithProfile(
777       Profile::FromBrowserContext(new_contents->GetBrowserContext()),
778       chrome::GetActiveDesktop());
779   if (browser) {
780     chrome::AddWebContents(browser, NULL, new_contents, disposition,
781                            initial_pos, user_gesture, was_blocked);
782   }
783 }