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