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