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