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