- add sources.
[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/message_loop/message_loop.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/prefs/scoped_user_pref_update.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/time/time.h"
17 #include "base/values.h"
18 #include "chrome/browser/background/background_contents_service_factory.h"
19 #include "chrome/browser/browser_process.h"
20 #include "chrome/browser/chrome_notification_types.h"
21 #include "chrome/browser/extensions/extension_host.h"
22 #include "chrome/browser/extensions/extension_service.h"
23 #include "chrome/browser/extensions/extension_system.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/background_info.h"
37 #include "chrome/common/extensions/extension.h"
38 #include "chrome/common/extensions/extension_constants.h"
39 #include "chrome/common/extensions/extension_icon_set.h"
40 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
41 #include "chrome/common/pref_names.h"
42 #include "content/public/browser/notification_service.h"
43 #include "content/public/browser/site_instance.h"
44 #include "content/public/browser/web_contents.h"
45 #include "grit/generated_resources.h"
46 #include "grit/theme_resources.h"
47 #include "ipc/ipc_message.h"
48 #include "ui/base/l10n/l10n_util.h"
49 #include "ui/base/resource/resource_bundle.h"
50 #include "ui/gfx/image/image.h"
51
52 using content::SiteInstance;
53 using content::WebContents;
54 using extensions::BackgroundInfo;
55 using extensions::Extension;
56 using extensions::UnloadedExtensionInfo;
57
58 namespace {
59
60 const char kNotificationPrefix[] = "app.background.crashed.";
61
62 void CloseBalloon(const std::string& balloon_id) {
63   g_browser_process->notification_ui_manager()->
64       CancelById(balloon_id);
65 }
66
67 // Closes the crash notification balloon for the app/extension with this id.
68 void ScheduleCloseBalloon(const std::string& extension_id) {
69   if (!base::MessageLoop::current())  // For unit_tests
70     return;
71   base::MessageLoop::current()->PostTask(
72       FROM_HERE, base::Bind(&CloseBalloon, kNotificationPrefix + extension_id));
73 }
74
75 // Delegate for the app/extension crash notification balloon. Restarts the
76 // app/extension when the balloon is clicked.
77 class CrashNotificationDelegate : public NotificationDelegate {
78  public:
79   CrashNotificationDelegate(Profile* profile,
80                             const Extension* extension)
81       : profile_(profile),
82         is_hosted_app_(extension->is_hosted_app()),
83         is_platform_app_(extension->is_platform_app()),
84         extension_id_(extension->id()) {
85   }
86
87   virtual void Display() OVERRIDE {}
88
89   virtual void Error() OVERRIDE {}
90
91   virtual void Close(bool by_user) OVERRIDE {}
92
93   virtual void Click() OVERRIDE {
94     // http://crbug.com/247790 involves a crash notification balloon being
95     // clicked while the extension isn't in the TERMINATED state. In that case,
96     // any of the "reload" methods called below can unload the extension, which
97     // indirectly destroys *this, invalidating all the member variables, so we
98     // copy the extension ID before using it.
99     std::string copied_extension_id = extension_id_;
100     if (is_hosted_app_) {
101       // There can be a race here: user clicks the balloon, and simultaneously
102       // reloads the sad tab for the app. So we check here to be safe before
103       // loading the background page.
104       BackgroundContentsService* service =
105           BackgroundContentsServiceFactory::GetForProfile(profile_);
106       if (!service->GetAppBackgroundContents(ASCIIToUTF16(copied_extension_id)))
107         service->LoadBackgroundContentsForExtension(profile_,
108                                                     copied_extension_id);
109     } else if (is_platform_app_) {
110       apps::AppLoadService::Get(profile_)->
111           RestartApplication(copied_extension_id);
112     } else {
113       extensions::ExtensionSystem::Get(profile_)->extension_service()->
114           ReloadExtension(copied_extension_id);
115     }
116
117     // Closing the crash notification balloon for the app/extension here should
118     // be OK, but it causes a crash on Mac, see: http://crbug.com/78167
119     ScheduleCloseBalloon(copied_extension_id);
120   }
121
122   virtual bool HasClickedListener() OVERRIDE { return true; }
123
124   virtual std::string id() const OVERRIDE {
125     return kNotificationPrefix + extension_id_;
126   }
127
128   virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE {
129     return NULL;
130   }
131
132  private:
133   virtual ~CrashNotificationDelegate() {}
134
135   Profile* profile_;
136   bool is_hosted_app_;
137   bool is_platform_app_;
138   std::string extension_id_;
139
140   DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
141 };
142
143 #if defined(ENABLE_NOTIFICATIONS)
144 void NotificationImageReady(
145     const std::string extension_name,
146     const string16 message,
147     const GURL extension_url,
148     scoped_refptr<CrashNotificationDelegate> delegate,
149     Profile* profile,
150     const gfx::Image& icon) {
151   gfx::Image notification_icon(icon);
152   if (icon.IsEmpty()) {
153     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
154     notification_icon = rb.GetImageNamed(IDR_EXTENSION_DEFAULT_ICON);
155   }
156   string16 title;  // no notification title
157   DesktopNotificationService::AddIconNotification(extension_url,
158                                                   title,
159                                                   message,
160                                                   notification_icon,
161                                                   string16(),
162                                                   delegate.get(),
163                                                   profile);
164 }
165 #endif
166
167 // Show a popup notification balloon with a crash message for a given app/
168 // extension.
169 void ShowBalloon(const Extension* extension, Profile* profile) {
170 #if defined(ENABLE_NOTIFICATIONS)
171   const string16 message = l10n_util::GetStringFUTF16(
172       extension->is_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE :
173                             IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE,
174       UTF8ToUTF16(extension->name()));
175   extension_misc::ExtensionIcons size(extension_misc::EXTENSION_ICON_MEDIUM);
176   extensions::ExtensionResource resource =
177       extensions::IconsInfo::GetIconResource(
178           extension, size, ExtensionIconSet::MATCH_SMALLER);
179   // We can't just load the image in the Observe method below because, despite
180   // what this method is called, it may call the callback synchronously.
181   // However, it's possible that the extension went away during the interim,
182   // so we'll bind all the pertinent data here.
183   extensions::ImageLoader::Get(profile)->LoadImageAsync(
184       extension,
185       resource,
186       gfx::Size(size, size),
187       base::Bind(
188           &NotificationImageReady,
189           extension->name(),
190           message,
191           extension->url(),
192           make_scoped_refptr(new CrashNotificationDelegate(profile, extension)),
193           profile));
194 #endif
195 }
196
197 void ReloadExtension(const std::string& extension_id, Profile* profile) {
198   if (g_browser_process->IsShuttingDown() ||
199       !g_browser_process->profile_manager()->IsValidProfile(profile)) {
200       return;
201   }
202   extensions::ExtensionSystem* extension_system =
203       extensions::ExtensionSystem::Get(profile);
204   if (!extension_system || !extension_system->extension_service())
205     return;
206   if (!extension_system->extension_service()->
207           GetTerminatedExtension(extension_id)) {
208     // Either the app/extension was uninstalled by policy or it has since
209     // been restarted successfully by someone else (the user).
210     return;
211   }
212   extension_system->extension_service()->ReloadExtension(extension_id);
213 }
214
215 }  // namespace
216
217 // Keys for the information we store about individual BackgroundContents in
218 // prefs. There is one top-level DictionaryValue (stored at
219 // prefs::kRegisteredBackgroundContents). Information about each
220 // BackgroundContents is stored under that top-level DictionaryValue, keyed
221 // by the parent application ID for easy lookup.
222 //
223 // kRegisteredBackgroundContents:
224 //    DictionaryValue {
225 //       <appid_1>: { "url": <url1>, "name": <frame_name> },
226 //       <appid_2>: { "url": <url2>, "name": <frame_name> },
227 //         ... etc ...
228 //    }
229 const char kUrlKey[] = "url";
230 const char kFrameNameKey[] = "name";
231
232 int BackgroundContentsService::restart_delay_in_ms_ = 3000;  // 3 seconds.
233
234 BackgroundContentsService::BackgroundContentsService(
235     Profile* profile, const CommandLine* command_line)
236     : prefs_(NULL) {
237   // Don't load/store preferences if the proper switch is not enabled, or if
238   // the parent profile is incognito.
239   if (!profile->IsOffTheRecord() &&
240       !command_line->HasSwitch(switches::kDisableRestoreBackgroundContents))
241     prefs_ = profile->GetPrefs();
242
243   // Listen for events to tell us when to load/unload persisted background
244   // contents.
245   StartObserving(profile);
246 }
247
248 BackgroundContentsService::~BackgroundContentsService() {
249   // BackgroundContents should be shutdown before we go away, as otherwise
250   // our browser process refcount will be off.
251   DCHECK(contents_map_.empty());
252 }
253
254 // static
255 void BackgroundContentsService::
256     SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(
257         int restart_delay_in_ms) {
258   restart_delay_in_ms_ = restart_delay_in_ms;
259 }
260
261 std::vector<BackgroundContents*>
262 BackgroundContentsService::GetBackgroundContents() const
263 {
264   std::vector<BackgroundContents*> contents;
265   for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
266        it != contents_map_.end(); ++it)
267     contents.push_back(it->second.contents);
268   return contents;
269 }
270
271 void BackgroundContentsService::StartObserving(Profile* profile) {
272   // On startup, load our background pages after extension-apps have loaded.
273   registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
274                  content::Source<Profile>(profile));
275
276   // Track the lifecycle of all BackgroundContents in the system to allow us
277   // to store an up-to-date list of the urls. Start tracking contents when they
278   // have been opened via CreateBackgroundContents(), and stop tracking them
279   // when they are closed by script.
280   registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED,
281                  content::Source<Profile>(profile));
282
283   // Stop tracking BackgroundContents when they have been deleted (happens
284   // during shutdown or if the render process dies).
285   registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED,
286                  content::Source<Profile>(profile));
287
288   // Track when the BackgroundContents navigates to a new URL so we can update
289   // our persisted information as appropriate.
290   registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED,
291                  content::Source<Profile>(profile));
292
293   // Listen for new extension installs so that we can load any associated
294   // background page.
295   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
296                  content::Source<Profile>(profile));
297
298   // Track when the extensions crash so that the user can be notified
299   // about it, and the crashed contents can be restarted.
300   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
301                  content::Source<Profile>(profile));
302   registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED,
303                  content::Source<Profile>(profile));
304
305   // Listen for extensions to be unloaded so we can shutdown associated
306   // BackgroundContents.
307   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
308                  content::Source<Profile>(profile));
309
310   // Make sure the extension-crash balloons are removed when the extension is
311   // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed
312   // extension is unloaded immediately after the crash, not when user reloads or
313   // uninstalls the extension.
314   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
315                  content::Source<Profile>(profile));
316 }
317
318 void BackgroundContentsService::Observe(
319     int type,
320     const content::NotificationSource& source,
321     const content::NotificationDetails& details) {
322   switch (type) {
323     case chrome::NOTIFICATION_EXTENSIONS_READY: {
324       Profile* profile = content::Source<Profile>(source).ptr();
325       LoadBackgroundContentsFromManifests(profile);
326       LoadBackgroundContentsFromPrefs(profile);
327       SendChangeNotification(profile);
328       break;
329     }
330     case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED:
331       BackgroundContentsShutdown(
332           content::Details<BackgroundContents>(details).ptr());
333       SendChangeNotification(content::Source<Profile>(source).ptr());
334       break;
335     case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED:
336       DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
337       UnregisterBackgroundContents(
338           content::Details<BackgroundContents>(details).ptr());
339       // CLOSED is always followed by a DELETED notification so we'll send our
340       // change notification there.
341       break;
342     case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: {
343       DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr()));
344
345       // Do not register in the pref if the extension has a manifest-specified
346       // background page.
347       BackgroundContents* bgcontents =
348           content::Details<BackgroundContents>(details).ptr();
349       Profile* profile = content::Source<Profile>(source).ptr();
350       const string16& appid = GetParentApplicationId(bgcontents);
351       ExtensionService* extension_service =
352           extensions::ExtensionSystem::Get(profile)->extension_service();
353       // extension_service can be NULL when running tests.
354       if (extension_service) {
355         const Extension* extension =
356             extension_service->GetExtensionById(UTF16ToUTF8(appid), false);
357         if (extension && BackgroundInfo::HasBackgroundPage(extension))
358           break;
359       }
360       RegisterBackgroundContents(bgcontents);
361       break;
362     }
363     case chrome::NOTIFICATION_EXTENSION_LOADED: {
364       const Extension* extension =
365           content::Details<const Extension>(details).ptr();
366       Profile* profile = content::Source<Profile>(source).ptr();
367       if (extension->is_hosted_app() &&
368           BackgroundInfo::HasBackgroundPage(extension)) {
369         // If there is a background page specified in the manifest for a hosted
370         // app, then blow away registered urls in the pref.
371         ShutdownAssociatedBackgroundContents(ASCIIToUTF16(extension->id()));
372
373         ExtensionService* service =
374             extensions::ExtensionSystem::Get(profile)->extension_service();
375         if (service && service->is_ready()) {
376           // Now load the manifest-specified background page. If service isn't
377           // ready, then the background page will be loaded from the
378           // EXTENSIONS_READY callback.
379           LoadBackgroundContents(profile,
380                                  BackgroundInfo::GetBackgroundURL(extension),
381                                  ASCIIToUTF16("background"),
382                                  UTF8ToUTF16(extension->id()));
383         }
384       }
385
386       // Close the crash notification balloon for the app/extension, if any.
387       ScheduleCloseBalloon(extension->id());
388       SendChangeNotification(profile);
389       break;
390     }
391     case chrome::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 = 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::IsPolicyLocation(extension->location());
414       if (!force_installed) {
415         // When an extension crashes, EXTENSION_PROCESS_TERMINATED is followed
416         // by an EXTENSION_UNLOADED notification. This UNLOADED signal causes
417         // all the notifications for this extension to be cancelled by
418         // DesktopNotificationService. For this reason, we post the crash
419         // handling code as a task here so that it is not executed before this
420         // event.
421         base::MessageLoop::current()->PostTask(
422             FROM_HERE, base::Bind(&ShowBalloon, extension, profile));
423       } else {
424         // Restart the extension.
425         RestartForceInstalledExtensionOnCrash(extension, profile);
426       }
427       break;
428     }
429     case chrome::NOTIFICATION_EXTENSION_UNLOADED:
430       switch (content::Details<UnloadedExtensionInfo>(details)->reason) {
431         case UnloadedExtensionInfo::REASON_DISABLE:    // Fall through.
432         case UnloadedExtensionInfo::REASON_TERMINATE:  // Fall through.
433         case UnloadedExtensionInfo::REASON_UNINSTALL:  // Fall through.
434         case UnloadedExtensionInfo::REASON_BLACKLIST:
435           ShutdownAssociatedBackgroundContents(
436               ASCIIToUTF16(content::Details<UnloadedExtensionInfo>(details)->
437                   extension->id()));
438           SendChangeNotification(content::Source<Profile>(source).ptr());
439           break;
440         case UnloadedExtensionInfo::REASON_UPDATE: {
441           // If there is a manifest specified background page, then shut it down
442           // here, since if the updated extension still has the background page,
443           // then it will be loaded from LOADED callback. Otherwise, leave
444           // BackgroundContents in place.
445           // We don't call SendChangeNotification here - it will be generated
446           // from the LOADED callback.
447           const Extension* extension =
448               content::Details<UnloadedExtensionInfo>(details)->extension;
449           if (BackgroundInfo::HasBackgroundPage(extension))
450             ShutdownAssociatedBackgroundContents(ASCIIToUTF16(extension->id()));
451           break;
452         }
453         default:
454           NOTREACHED();
455           ShutdownAssociatedBackgroundContents(
456               ASCIIToUTF16(content::Details<UnloadedExtensionInfo>(details)->
457                   extension->id()));
458           break;
459       }
460       break;
461
462     case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: {
463       // Close the crash notification balloon for the app/extension, if any.
464       ScheduleCloseBalloon(
465           content::Details<const Extension>(details).ptr()->id());
466       break;
467     }
468
469     default:
470       NOTREACHED();
471       break;
472   }
473 }
474
475 void BackgroundContentsService::RestartForceInstalledExtensionOnCrash(
476     const Extension* extension,
477     Profile* profile) {
478   base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
479       base::Bind(&ReloadExtension, extension->id(), profile),
480       base::TimeDelta::FromMilliseconds(restart_delay_in_ms_));
481 }
482
483 // Loads all background contents whose urls have been stored in prefs.
484 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
485     Profile* profile) {
486   if (!prefs_)
487     return;
488   const DictionaryValue* contents =
489       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
490   if (!contents)
491     return;
492   ExtensionService* extensions_service =
493           extensions::ExtensionSystem::Get(profile)->extension_service();
494   DCHECK(extensions_service);
495   for (DictionaryValue::Iterator it(*contents); !it.IsAtEnd(); it.Advance()) {
496     // Check to make sure that the parent extension is still enabled.
497     const Extension* extension = extensions_service->
498         GetExtensionById(it.key(), false);
499     if (!extension) {
500       // We should never reach here - it should not be possible for an app
501       // to become uninstalled without the associated BackgroundContents being
502       // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a
503       // crash before we could save our prefs, or if the user deletes the
504       // extension files manually rather than uninstalling it.
505       NOTREACHED() << "No extension found for BackgroundContents - id = "
506                    << it.key();
507       // Don't cancel out of our loop, just ignore this BackgroundContents and
508       // load the next one.
509       continue;
510     }
511     LoadBackgroundContentsFromDictionary(profile, it.key(), contents);
512   }
513 }
514
515 void BackgroundContentsService::SendChangeNotification(Profile* profile) {
516   content::NotificationService::current()->Notify(
517       chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED,
518       content::Source<Profile>(profile),
519       content::Details<BackgroundContentsService>(this));
520 }
521
522 void BackgroundContentsService::LoadBackgroundContentsForExtension(
523     Profile* profile,
524     const std::string& extension_id) {
525   // First look if the manifest specifies a background page.
526   const Extension* extension =
527       extensions::ExtensionSystem::Get(profile)->extension_service()->
528           GetExtensionById(extension_id, false);
529   DCHECK(!extension || extension->is_hosted_app());
530   if (extension && BackgroundInfo::HasBackgroundPage(extension)) {
531     LoadBackgroundContents(profile,
532                            BackgroundInfo::GetBackgroundURL(extension),
533                            ASCIIToUTF16("background"),
534                            UTF8ToUTF16(extension->id()));
535     return;
536   }
537
538   // Now look in the prefs.
539   if (!prefs_)
540     return;
541   const DictionaryValue* contents =
542       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
543   if (!contents)
544     return;
545   LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
546 }
547
548 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
549     Profile* profile,
550     const std::string& extension_id,
551     const DictionaryValue* contents) {
552   ExtensionService* extensions_service =
553       extensions::ExtensionSystem::Get(profile)->extension_service();
554   DCHECK(extensions_service);
555
556   const DictionaryValue* dict;
557   if (!contents->GetDictionaryWithoutPathExpansion(extension_id, &dict) ||
558       dict == NULL)
559     return;
560
561   string16 frame_name;
562   std::string url;
563   dict->GetString(kUrlKey, &url);
564   dict->GetString(kFrameNameKey, &frame_name);
565   LoadBackgroundContents(profile,
566                          GURL(url),
567                          frame_name,
568                          UTF8ToUTF16(extension_id));
569 }
570
571 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
572     Profile* profile) {
573   const ExtensionSet* extensions = extensions::ExtensionSystem::Get(profile)->
574       extension_service()->extensions();
575   ExtensionSet::const_iterator iter = extensions->begin();
576   for (; iter != extensions->end(); ++iter) {
577     const Extension* extension = iter->get();
578     if (extension->is_hosted_app() &&
579         BackgroundInfo::HasBackgroundPage(extension)) {
580       LoadBackgroundContents(profile,
581                              BackgroundInfo::GetBackgroundURL(extension),
582                              ASCIIToUTF16("background"),
583                              UTF8ToUTF16(extension->id()));
584     }
585   }
586 }
587
588 void BackgroundContentsService::LoadBackgroundContents(
589     Profile* profile,
590     const GURL& url,
591     const string16& frame_name,
592     const string16& application_id) {
593   // We are depending on the fact that we will initialize before any user
594   // actions or session restore can take place, so no BackgroundContents should
595   // be running yet for the passed application_id.
596   DCHECK(!GetAppBackgroundContents(application_id));
597   DCHECK(!application_id.empty());
598   DCHECK(url.is_valid());
599   DVLOG(1) << "Loading background content url: " << url;
600
601   BackgroundContents* contents = CreateBackgroundContents(
602       SiteInstance::CreateForURL(profile, url),
603       MSG_ROUTING_NONE,
604       profile,
605       frame_name,
606       application_id,
607       std::string(),
608       NULL);
609
610   // TODO(atwilson): Create RenderViews asynchronously to avoid increasing
611   // startup latency (http://crbug.com/47236).
612   contents->web_contents()->GetController().LoadURL(
613       url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string());
614 }
615
616 BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
617     SiteInstance* site,
618     int routing_id,
619     Profile* profile,
620     const string16& frame_name,
621     const string16& application_id,
622     const std::string& partition_id,
623     content::SessionStorageNamespace* session_storage_namespace) {
624   BackgroundContents* contents = new BackgroundContents(
625       site, routing_id, this, partition_id, session_storage_namespace);
626
627   // Register the BackgroundContents internally, then send out a notification
628   // to external listeners.
629   BackgroundContentsOpenedDetails details = {contents,
630                                              frame_name,
631                                              application_id};
632   BackgroundContentsOpened(&details);
633   content::NotificationService::current()->Notify(
634       chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED,
635       content::Source<Profile>(profile),
636       content::Details<BackgroundContentsOpenedDetails>(&details));
637
638   // A new background contents has been created - notify our listeners.
639   SendChangeNotification(profile);
640   return contents;
641 }
642
643 void BackgroundContentsService::RegisterBackgroundContents(
644     BackgroundContents* background_contents) {
645   DCHECK(IsTracked(background_contents));
646   if (!prefs_)
647     return;
648
649   // We store the first URL we receive for a given application. If there's
650   // already an entry for this application, no need to do anything.
651   // TODO(atwilson): Verify that this is the desired behavior based on developer
652   // feedback (http://crbug.com/47118).
653   DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
654   DictionaryValue* pref = update.Get();
655   const string16& appid = GetParentApplicationId(background_contents);
656   DictionaryValue* current;
657   if (pref->GetDictionaryWithoutPathExpansion(UTF16ToUTF8(appid), &current))
658     return;
659
660   // No entry for this application yet, so add one.
661   DictionaryValue* dict = new DictionaryValue();
662   dict->SetString(kUrlKey, background_contents->GetURL().spec());
663   dict->SetString(kFrameNameKey, contents_map_[appid].frame_name);
664   pref->SetWithoutPathExpansion(UTF16ToUTF8(appid), dict);
665 }
666
667 bool BackgroundContentsService::HasRegisteredBackgroundContents(
668     const string16& app_id) {
669   if (!prefs_)
670     return false;
671   std::string app = UTF16ToUTF8(app_id);
672   const DictionaryValue* contents =
673       prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
674   return contents->HasKey(app);
675 }
676
677 void BackgroundContentsService::UnregisterBackgroundContents(
678     BackgroundContents* background_contents) {
679   if (!prefs_)
680     return;
681   DCHECK(IsTracked(background_contents));
682   const string16 appid = GetParentApplicationId(background_contents);
683   DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
684   update.Get()->RemoveWithoutPathExpansion(UTF16ToUTF8(appid), NULL);
685 }
686
687 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
688     const string16& appid) {
689   BackgroundContents* contents = GetAppBackgroundContents(appid);
690   if (contents) {
691     UnregisterBackgroundContents(contents);
692     // Background contents destructor shuts down the renderer.
693     delete contents;
694   }
695 }
696
697 void BackgroundContentsService::BackgroundContentsOpened(
698     BackgroundContentsOpenedDetails* details) {
699   // Add the passed object to our list. Should not already be tracked.
700   DCHECK(!IsTracked(details->contents));
701   DCHECK(!details->application_id.empty());
702   contents_map_[details->application_id].contents = details->contents;
703   contents_map_[details->application_id].frame_name = details->frame_name;
704
705   ScheduleCloseBalloon(UTF16ToASCII(details->application_id));
706 }
707
708 // Used by test code and debug checks to verify whether a given
709 // BackgroundContents is being tracked by this instance.
710 bool BackgroundContentsService::IsTracked(
711     BackgroundContents* background_contents) const {
712   return !GetParentApplicationId(background_contents).empty();
713 }
714
715 void BackgroundContentsService::BackgroundContentsShutdown(
716     BackgroundContents* background_contents) {
717   // Remove the passed object from our list.
718   DCHECK(IsTracked(background_contents));
719   string16 appid = GetParentApplicationId(background_contents);
720   contents_map_.erase(appid);
721 }
722
723 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
724     const string16& application_id) {
725   BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
726   return (it != contents_map_.end()) ? it->second.contents : NULL;
727 }
728
729 const string16& BackgroundContentsService::GetParentApplicationId(
730     BackgroundContents* contents) const {
731   for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
732        it != contents_map_.end(); ++it) {
733     if (contents == it->second.contents)
734       return it->first;
735   }
736   return EmptyString16();
737 }
738
739 void BackgroundContentsService::AddWebContents(
740     WebContents* new_contents,
741     WindowOpenDisposition disposition,
742     const gfx::Rect& initial_pos,
743     bool user_gesture,
744     bool* was_blocked) {
745   Browser* browser = chrome::FindLastActiveWithProfile(
746       Profile::FromBrowserContext(new_contents->GetBrowserContext()),
747       chrome::GetActiveDesktop());
748   if (browser) {
749     chrome::AddWebContents(browser, NULL, new_contents, disposition,
750                            initial_pos, user_gesture, was_blocked);
751   }
752 }