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