Upstream version 7.36.149.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/extensions/image_loader.h"
24 #include "chrome/browser/notifications/desktop_notification_service.h"
25 #include "chrome/browser/notifications/notification.h"
26 #include "chrome/browser/notifications/notification_delegate.h"
27 #include "chrome/browser/notifications/notification_ui_manager.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/browser/profiles/profile_manager.h"
30 #include "chrome/browser/ui/browser.h"
31 #include "chrome/browser/ui/browser_finder.h"
32 #include "chrome/browser/ui/browser_tabstrip.h"
33 #include "chrome/browser/ui/host_desktop.h"
34 #include "chrome/common/chrome_switches.h"
35 #include "chrome/common/extensions/extension_constants.h"
36 #include "chrome/common/pref_names.h"
37 #include "content/public/browser/notification_service.h"
38 #include "content/public/browser/site_instance.h"
39 #include "content/public/browser/web_contents.h"
40 #include "extensions/browser/extension_host.h"
41 #include "extensions/browser/extension_registry.h"
42 #include "extensions/browser/extension_system.h"
43 #include "extensions/common/extension.h"
44 #include "extensions/common/extension_icon_set.h"
45 #include "extensions/common/extension_set.h"
46 #include "extensions/common/manifest_handlers/background_info.h"
47 #include "extensions/common/manifest_handlers/icons_handler.h"
48 #include "grit/generated_resources.h"
49 #include "grit/theme_resources.h"
50 #include "ipc/ipc_message.h"
51 #include "ui/base/l10n/l10n_util.h"
52 #include "ui/base/resource/resource_bundle.h"
53 #include "ui/gfx/image/image.h"
54
55 #if defined(ENABLE_NOTIFICATIONS)
56 #include "ui/message_center/message_center.h"
57 #endif
58
59 using content::SiteInstance;
60 using content::WebContents;
61 using extensions::BackgroundInfo;
62 using extensions::Extension;
63 using extensions::UnloadedExtensionInfo;
64
65 namespace {
66
67 const char kNotificationPrefix[] = "app.background.crashed.";
68
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) {
75     // TODO(dewittj): Add this functionality to the notification UI manager's
76     // API.
77     g_browser_process->message_center()->SetVisibility(
78         message_center::VISIBILITY_TRANSIENT);
79   }
80 #endif
81 }
82
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
86     return;
87   base::MessageLoop::current()->PostTask(
88       FROM_HERE, base::Bind(&CloseBalloon, kNotificationPrefix + extension_id));
89 }
90
91 // Delegate for the app/extension crash notification balloon. Restarts the
92 // app/extension when the balloon is clicked.
93 class CrashNotificationDelegate : public NotificationDelegate {
94  public:
95   CrashNotificationDelegate(Profile* profile,
96                             const Extension* extension)
97       : profile_(profile),
98         is_hosted_app_(extension->is_hosted_app()),
99         is_platform_app_(extension->is_platform_app()),
100         extension_id_(extension->id()) {
101   }
102
103   virtual void Display() OVERRIDE {}
104
105   virtual void Error() OVERRIDE {}
106
107   virtual void Close(bool by_user) OVERRIDE {}
108
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);
126       }
127     } else if (is_platform_app_) {
128       apps::AppLoadService::Get(profile_)->
129           RestartApplication(copied_extension_id);
130     } else {
131       extensions::ExtensionSystem::Get(profile_)->extension_service()->
132           ReloadExtension(copied_extension_id);
133     }
134
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);
138   }
139
140   virtual bool HasClickedListener() OVERRIDE { return true; }
141
142   virtual std::string id() const OVERRIDE {
143     return kNotificationPrefix + extension_id_;
144   }
145
146   virtual content::WebContents* GetWebContents() const OVERRIDE {
147     return NULL;
148   }
149
150  private:
151   virtual ~CrashNotificationDelegate() {}
152
153   Profile* profile_;
154   bool is_hosted_app_;
155   bool is_platform_app_;
156   std::string extension_id_;
157
158   DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
159 };
160
161 #if defined(ENABLE_NOTIFICATIONS)
162 void NotificationImageReady(
163     const std::string extension_name,
164     const base::string16 message,
165     scoped_refptr<CrashNotificationDelegate> delegate,
166     Profile* profile,
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);
172   }
173
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_DEPRECATED.
177   // TODO(mukai, dewittj): remove this and switch to message center
178   // notifications.
179   DesktopNotificationService::AddIconNotification(
180       GURL() /* empty origin */,
181       base::string16(),
182       message,
183       notification_icon,
184       base::string16(),
185       delegate.get(),
186       profile);
187 }
188 #endif
189
190 // Show a popup notification balloon with a crash message for a given app/
191 // extension.
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(
207       extension,
208       resource,
209       gfx::Size(size, size),
210       base::Bind(
211           &NotificationImageReady,
212           extension->name(),
213           message,
214           make_scoped_refptr(new CrashNotificationDelegate(profile, extension)),
215           profile));
216 #endif
217 }
218
219 void ReloadExtension(const std::string& extension_id, Profile* profile) {
220   if (g_browser_process->IsShuttingDown() ||
221       !g_browser_process->profile_manager()->IsValidProfile(profile)) {
222       return;
223   }
224
225   extensions::ExtensionSystem* extension_system =
226       extensions::ExtensionSystem::Get(profile);
227   extensions::ExtensionRegistry* extension_registry =
228       extensions::ExtensionRegistry::Get(profile);
229   if (!extension_system || !extension_system->extension_service() ||
230       !extension_registry) {
231     return;
232   }
233
234   if (!extension_registry->GetExtensionById(
235           extension_id, extensions::ExtensionRegistry::TERMINATED)) {
236     // Either the app/extension was uninstalled by policy or it has since
237     // been restarted successfully by someone else (the user).
238     return;
239   }
240   extension_system->extension_service()->ReloadExtension(extension_id);
241 }
242
243 }  // namespace
244
245 // Keys for the information we store about individual BackgroundContents in
246 // prefs. There is one top-level DictionaryValue (stored at
247 // prefs::kRegisteredBackgroundContents). Information about each
248 // BackgroundContents is stored under that top-level DictionaryValue, keyed
249 // by the parent application ID for easy lookup.
250 //
251 // kRegisteredBackgroundContents:
252 //    DictionaryValue {
253 //       <appid_1>: { "url": <url1>, "name": <frame_name> },
254 //       <appid_2>: { "url": <url2>, "name": <frame_name> },
255 //         ... etc ...
256 //    }
257 const char kUrlKey[] = "url";
258 const char kFrameNameKey[] = "name";
259
260 int BackgroundContentsService::restart_delay_in_ms_ = 3000;  // 3 seconds.
261
262 BackgroundContentsService::BackgroundContentsService(
263     Profile* profile, const CommandLine* command_line)
264     : prefs_(NULL) {
265   // Don't load/store preferences if the proper switch is not enabled, or if
266   // the parent profile is incognito.
267   if (!profile->IsOffTheRecord() &&
268       !command_line->HasSwitch(switches::kDisableRestoreBackgroundContents))
269     prefs_ = profile->GetPrefs();
270
271   // Listen for events to tell us when to load/unload persisted background
272   // contents.
273   StartObserving(profile);
274 }
275
276 BackgroundContentsService::~BackgroundContentsService() {
277   // BackgroundContents should be shutdown before we go away, as otherwise
278   // our browser process refcount will be off.
279   DCHECK(contents_map_.empty());
280 }
281
282 // static
283 void BackgroundContentsService::
284     SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(
285         int restart_delay_in_ms) {
286   restart_delay_in_ms_ = restart_delay_in_ms;
287 }
288
289 // static
290 std::string BackgroundContentsService::GetNotificationIdForExtensionForTesting(
291     const std::string& extension_id) {
292   return kNotificationPrefix + extension_id;
293 }
294
295 // static
296 void BackgroundContentsService::ShowBalloonForTesting(
297     const extensions::Extension* extension,
298     Profile* profile) {
299   ShowBalloon(extension, profile);
300 }
301
302 std::vector<BackgroundContents*>
303 BackgroundContentsService::GetBackgroundContents() const
304 {
305   std::vector<BackgroundContents*> contents;
306   for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
307        it != contents_map_.end(); ++it)
308     contents.push_back(it->second.contents);
309   return contents;
310 }
311
312 void BackgroundContentsService::StartObserving(Profile* profile) {
313   // On startup, load our background pages after extension-apps have loaded.
314   registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
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   // Listen for new extension installs so that we can load any associated
335   // background page.
336   registrar_.Add(this,
337                  chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
338                  content::Source<Profile>(profile));
339
340   // Track when the extensions crash so that the user can be notified
341   // about it, and the crashed contents can be restarted.
342   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
343                  content::Source<Profile>(profile));
344   registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED,
345                  content::Source<Profile>(profile));
346
347   // Listen for extensions to be unloaded so we can shutdown associated
348   // BackgroundContents.
349   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
350                  content::Source<Profile>(profile));
351
352   // Make sure the extension-crash balloons are removed when the extension is
353   // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed
354   // extension is unloaded immediately after the crash, not when user reloads or
355   // uninstalls the extension.
356   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
357                  content::Source<Profile>(profile));
358 }
359
360 void BackgroundContentsService::Observe(
361     int type,
362     const content::NotificationSource& source,
363     const content::NotificationDetails& details) {
364   switch (type) {
365     case chrome::NOTIFICATION_EXTENSIONS_READY: {
366       Profile* profile = content::Source<Profile>(source).ptr();
367       LoadBackgroundContentsFromManifests(profile);
368       LoadBackgroundContentsFromPrefs(profile);
369       SendChangeNotification(profile);
370       break;
371     }
372     case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED:
373       BackgroundContentsShutdown(
374           content::Details<BackgroundContents>(details).ptr());
375       SendChangeNotification(content::Source<Profile>(source).ptr());
376       break;
377     case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED:
378       DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
379       UnregisterBackgroundContents(
380           content::Details<BackgroundContents>(details).ptr());
381       // CLOSED is always followed by a DELETED notification so we'll send our
382       // change notification there.
383       break;
384     case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: {
385       DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
386
387       // Do not register in the pref if the extension has a manifest-specified
388       // background page.
389       BackgroundContents* bgcontents =
390           content::Details<BackgroundContents>(details).ptr();
391       Profile* profile = content::Source<Profile>(source).ptr();
392       const base::string16& appid = GetParentApplicationId(bgcontents);
393       ExtensionService* extension_service =
394           extensions::ExtensionSystem::Get(profile)->extension_service();
395       // extension_service can be NULL when running tests.
396       if (extension_service) {
397         const Extension* extension = extension_service->GetExtensionById(
398             base::UTF16ToUTF8(appid), false);
399         if (extension && BackgroundInfo::HasBackgroundPage(extension))
400           break;
401       }
402       RegisterBackgroundContents(bgcontents);
403       break;
404     }
405     case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: {
406       const Extension* extension =
407           content::Details<const Extension>(details).ptr();
408       Profile* profile = content::Source<Profile>(source).ptr();
409       if (extension->is_hosted_app() &&
410           BackgroundInfo::HasBackgroundPage(extension)) {
411         // If there is a background page specified in the manifest for a hosted
412         // app, then blow away registered urls in the pref.
413         ShutdownAssociatedBackgroundContents(
414             base::ASCIIToUTF16(extension->id()));
415
416         ExtensionService* service =
417             extensions::ExtensionSystem::Get(profile)->extension_service();
418         if (service && service->is_ready()) {
419           // Now load the manifest-specified background page. If service isn't
420           // ready, then the background page will be loaded from the
421           // EXTENSIONS_READY callback.
422           LoadBackgroundContents(profile,
423                                  BackgroundInfo::GetBackgroundURL(extension),
424                                  base::ASCIIToUTF16("background"),
425                                  base::UTF8ToUTF16(extension->id()));
426         }
427       }
428
429       // Close the crash notification balloon for the app/extension, if any.
430       ScheduleCloseBalloon(extension->id());
431       SendChangeNotification(profile);
432       break;
433     }
434     case chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED:
435     case chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED: {
436       Profile* profile = content::Source<Profile>(source).ptr();
437       const Extension* extension = NULL;
438       if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED) {
439         BackgroundContents* bg =
440             content::Details<BackgroundContents>(details).ptr();
441         std::string extension_id = base::UTF16ToASCII(
442             BackgroundContentsServiceFactory::GetForProfile(profile)->
443                 GetParentApplicationId(bg));
444         extension =
445           extensions::ExtensionSystem::Get(profile)->extension_service()->
446               GetExtensionById(extension_id, false);
447       } else {
448         extensions::ExtensionHost* extension_host =
449             content::Details<extensions::ExtensionHost>(details).ptr();
450         extension = extension_host->extension();
451       }
452       if (!extension)
453         break;
454
455       const bool force_installed =
456           extensions::Manifest::IsComponentLocation(extension->location()) ||
457           extensions::Manifest::IsPolicyLocation(extension->location());
458       if (!force_installed) {
459         ShowBalloon(extension, profile);
460       } else {
461         // Restart the extension.
462         RestartForceInstalledExtensionOnCrash(extension, profile);
463       }
464       break;
465     }
466     case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED:
467       switch (content::Details<UnloadedExtensionInfo>(details)->reason) {
468         case UnloadedExtensionInfo::REASON_DISABLE:    // Fall through.
469         case UnloadedExtensionInfo::REASON_TERMINATE:  // Fall through.
470         case UnloadedExtensionInfo::REASON_UNINSTALL:  // Fall through.
471         case UnloadedExtensionInfo::REASON_BLACKLIST:
472           ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(
473               content::Details<UnloadedExtensionInfo>(details)->
474                   extension->id()));
475           SendChangeNotification(content::Source<Profile>(source).ptr());
476           break;
477         case UnloadedExtensionInfo::REASON_UPDATE: {
478           // If there is a manifest specified background page, then shut it down
479           // here, since if the updated extension still has the background page,
480           // then it will be loaded from LOADED callback. Otherwise, leave
481           // BackgroundContents in place.
482           // We don't call SendChangeNotification here - it will be generated
483           // from the LOADED callback.
484           const Extension* extension =
485               content::Details<UnloadedExtensionInfo>(details)->extension;
486           if (BackgroundInfo::HasBackgroundPage(extension)) {
487             ShutdownAssociatedBackgroundContents(
488                 base::ASCIIToUTF16(extension->id()));
489           }
490           break;
491         }
492         default:
493           NOTREACHED();
494           ShutdownAssociatedBackgroundContents(base::ASCIIToUTF16(
495               content::Details<UnloadedExtensionInfo>(details)->
496                   extension->id()));
497           break;
498       }
499       break;
500
501     case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
502       // Close the crash notification balloon for the app/extension, if any.
503       ScheduleCloseBalloon(
504           content::Details<const Extension>(details).ptr()->id());
505       break;
506     }
507
508     default:
509       NOTREACHED();
510       break;
511   }
512 }
513
514 void BackgroundContentsService::RestartForceInstalledExtensionOnCrash(
515     const Extension* extension,
516     Profile* profile) {
517   base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
518       base::Bind(&ReloadExtension, extension->id(), profile),
519       base::TimeDelta::FromMilliseconds(restart_delay_in_ms_));
520 }
521
522 // Loads all background contents whose urls have been stored in prefs.
523 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
524     Profile* profile) {
525   if (!prefs_)
526     return;
527   const base::DictionaryValue* contents =
528       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
529   if (!contents)
530     return;
531   ExtensionService* extensions_service =
532           extensions::ExtensionSystem::Get(profile)->extension_service();
533   DCHECK(extensions_service);
534   for (base::DictionaryValue::Iterator it(*contents);
535        !it.IsAtEnd(); it.Advance()) {
536     // Check to make sure that the parent extension is still enabled.
537     const Extension* extension = extensions_service->
538         GetExtensionById(it.key(), false);
539     if (!extension) {
540       // We should never reach here - it should not be possible for an app
541       // to become uninstalled without the associated BackgroundContents being
542       // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a
543       // crash before we could save our prefs, or if the user deletes the
544       // extension files manually rather than uninstalling it.
545       NOTREACHED() << "No extension found for BackgroundContents - id = "
546                    << it.key();
547       // Don't cancel out of our loop, just ignore this BackgroundContents and
548       // load the next one.
549       continue;
550     }
551     LoadBackgroundContentsFromDictionary(profile, it.key(), contents);
552   }
553 }
554
555 void BackgroundContentsService::SendChangeNotification(Profile* profile) {
556   content::NotificationService::current()->Notify(
557       chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
558       content::Source<Profile>(profile),
559       content::Details<BackgroundContentsService>(this));
560 }
561
562 void BackgroundContentsService::LoadBackgroundContentsForExtension(
563     Profile* profile,
564     const std::string& extension_id) {
565   // First look if the manifest specifies a background page.
566   const Extension* extension =
567       extensions::ExtensionSystem::Get(profile)->extension_service()->
568           GetExtensionById(extension_id, false);
569   DCHECK(!extension || extension->is_hosted_app());
570   if (extension && BackgroundInfo::HasBackgroundPage(extension)) {
571     LoadBackgroundContents(profile,
572                            BackgroundInfo::GetBackgroundURL(extension),
573                            base::ASCIIToUTF16("background"),
574                            base::UTF8ToUTF16(extension->id()));
575     return;
576   }
577
578   // Now look in the prefs.
579   if (!prefs_)
580     return;
581   const base::DictionaryValue* contents =
582       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
583   if (!contents)
584     return;
585   LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
586 }
587
588 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
589     Profile* profile,
590     const std::string& extension_id,
591     const base::DictionaryValue* contents) {
592   ExtensionService* extensions_service =
593       extensions::ExtensionSystem::Get(profile)->extension_service();
594   DCHECK(extensions_service);
595
596   const base::DictionaryValue* dict;
597   if (!contents->GetDictionaryWithoutPathExpansion(extension_id, &dict) ||
598       dict == NULL)
599     return;
600
601   base::string16 frame_name;
602   std::string url;
603   dict->GetString(kUrlKey, &url);
604   dict->GetString(kFrameNameKey, &frame_name);
605   LoadBackgroundContents(profile,
606                          GURL(url),
607                          frame_name,
608                          base::UTF8ToUTF16(extension_id));
609 }
610
611 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
612     Profile* profile) {
613   const extensions::ExtensionSet* extensions =
614       extensions::ExtensionSystem::Get(profile)->
615           extension_service()->extensions();
616   for (extensions::ExtensionSet::const_iterator iter = extensions->begin();
617        iter != extensions->end(); ++iter) {
618     const Extension* extension = iter->get();
619     if (extension->is_hosted_app() &&
620         BackgroundInfo::HasBackgroundPage(extension)) {
621       LoadBackgroundContents(profile,
622                              BackgroundInfo::GetBackgroundURL(extension),
623                              base::ASCIIToUTF16("background"),
624                              base::UTF8ToUTF16(extension->id()));
625     }
626   }
627 }
628
629 void BackgroundContentsService::LoadBackgroundContents(
630     Profile* profile,
631     const GURL& url,
632     const base::string16& frame_name,
633     const base::string16& application_id) {
634   // We are depending on the fact that we will initialize before any user
635   // actions or session restore can take place, so no BackgroundContents should
636   // be running yet for the passed application_id.
637   DCHECK(!GetAppBackgroundContents(application_id));
638   DCHECK(!application_id.empty());
639   DCHECK(url.is_valid());
640   DVLOG(1) << "Loading background content url: " << url;
641
642   BackgroundContents* contents = CreateBackgroundContents(
643       SiteInstance::CreateForURL(profile, url),
644       MSG_ROUTING_NONE,
645       profile,
646       frame_name,
647       application_id,
648       std::string(),
649       NULL);
650
651   // TODO(atwilson): Create RenderViews asynchronously to avoid increasing
652   // startup latency (http://crbug.com/47236).
653   contents->web_contents()->GetController().LoadURL(
654       url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string());
655 }
656
657 BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
658     SiteInstance* site,
659     int routing_id,
660     Profile* profile,
661     const base::string16& frame_name,
662     const base::string16& application_id,
663     const std::string& partition_id,
664     content::SessionStorageNamespace* session_storage_namespace) {
665   BackgroundContents* contents = new BackgroundContents(
666       site, routing_id, this, partition_id, session_storage_namespace);
667
668   // Register the BackgroundContents internally, then send out a notification
669   // to external listeners.
670   BackgroundContentsOpenedDetails details = {contents,
671                                              frame_name,
672                                              application_id};
673   BackgroundContentsOpened(&details);
674   content::NotificationService::current()->Notify(
675       chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
676       content::Source<Profile>(profile),
677       content::Details<BackgroundContentsOpenedDetails>(&details));
678
679   // A new background contents has been created - notify our listeners.
680   SendChangeNotification(profile);
681   return contents;
682 }
683
684 void BackgroundContentsService::RegisterBackgroundContents(
685     BackgroundContents* background_contents) {
686   DCHECK(IsTracked(background_contents));
687   if (!prefs_)
688     return;
689
690   // We store the first URL we receive for a given application. If there's
691   // already an entry for this application, no need to do anything.
692   // TODO(atwilson): Verify that this is the desired behavior based on developer
693   // feedback (http://crbug.com/47118).
694   DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
695   base::DictionaryValue* pref = update.Get();
696   const base::string16& appid = GetParentApplicationId(background_contents);
697   base::DictionaryValue* current;
698   if (pref->GetDictionaryWithoutPathExpansion(base::UTF16ToUTF8(appid),
699                                               &current)) {
700     return;
701   }
702
703   // No entry for this application yet, so add one.
704   base::DictionaryValue* dict = new base::DictionaryValue();
705   dict->SetString(kUrlKey, background_contents->GetURL().spec());
706   dict->SetString(kFrameNameKey, contents_map_[appid].frame_name);
707   pref->SetWithoutPathExpansion(base::UTF16ToUTF8(appid), dict);
708 }
709
710 bool BackgroundContentsService::HasRegisteredBackgroundContents(
711     const base::string16& app_id) {
712   if (!prefs_)
713     return false;
714   std::string app = base::UTF16ToUTF8(app_id);
715   const base::DictionaryValue* contents =
716       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
717   return contents->HasKey(app);
718 }
719
720 void BackgroundContentsService::UnregisterBackgroundContents(
721     BackgroundContents* background_contents) {
722   if (!prefs_)
723     return;
724   DCHECK(IsTracked(background_contents));
725   const base::string16 appid = GetParentApplicationId(background_contents);
726   DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
727   update.Get()->RemoveWithoutPathExpansion(base::UTF16ToUTF8(appid), NULL);
728 }
729
730 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
731     const base::string16& appid) {
732   BackgroundContents* contents = GetAppBackgroundContents(appid);
733   if (contents) {
734     UnregisterBackgroundContents(contents);
735     // Background contents destructor shuts down the renderer.
736     delete contents;
737   }
738 }
739
740 void BackgroundContentsService::BackgroundContentsOpened(
741     BackgroundContentsOpenedDetails* details) {
742   // Add the passed object to our list. Should not already be tracked.
743   DCHECK(!IsTracked(details->contents));
744   DCHECK(!details->application_id.empty());
745   contents_map_[details->application_id].contents = details->contents;
746   contents_map_[details->application_id].frame_name = details->frame_name;
747
748   ScheduleCloseBalloon(base::UTF16ToASCII(details->application_id));
749 }
750
751 // Used by test code and debug checks to verify whether a given
752 // BackgroundContents is being tracked by this instance.
753 bool BackgroundContentsService::IsTracked(
754     BackgroundContents* background_contents) const {
755   return !GetParentApplicationId(background_contents).empty();
756 }
757
758 void BackgroundContentsService::BackgroundContentsShutdown(
759     BackgroundContents* background_contents) {
760   // Remove the passed object from our list.
761   DCHECK(IsTracked(background_contents));
762   base::string16 appid = GetParentApplicationId(background_contents);
763   contents_map_.erase(appid);
764 }
765
766 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
767     const base::string16& application_id) {
768   BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
769   return (it != contents_map_.end()) ? it->second.contents : NULL;
770 }
771
772 const base::string16& BackgroundContentsService::GetParentApplicationId(
773     BackgroundContents* contents) const {
774   for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
775        it != contents_map_.end(); ++it) {
776     if (contents == it->second.contents)
777       return it->first;
778   }
779   return base::EmptyString16();
780 }
781
782 void BackgroundContentsService::AddWebContents(
783     WebContents* new_contents,
784     WindowOpenDisposition disposition,
785     const gfx::Rect& initial_pos,
786     bool user_gesture,
787     bool* was_blocked) {
788   Browser* browser = chrome::FindLastActiveWithProfile(
789       Profile::FromBrowserContext(new_contents->GetBrowserContext()),
790       chrome::GetActiveDesktop());
791   if (browser) {
792     chrome::AddWebContents(browser, NULL, new_contents, disposition,
793                            initial_pos, user_gesture, was_blocked);
794   }
795 }