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