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