| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/background/background_contents_service.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "chrome/browser/apps/platform_apps/app_load_service.h" |
| #include "chrome/browser/background/background_contents_service_factory.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/notifications/notification_common.h" |
| #include "chrome/browser/notifications/notification_display_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/site_instance.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/browser/extension_host.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/browser/image_loader.h" |
| #include "extensions/browser/notification_types.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_icon_set.h" |
| #include "extensions/common/extension_set.h" |
| #include "extensions/common/manifest_handlers/background_info.h" |
| #include "extensions/common/manifest_handlers/icons_handler.h" |
| #include "extensions/common/one_shot_event.h" |
| #include "extensions/grit/extensions_browser_resources.h" |
| #include "ipc/ipc_message.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/message_center/public/cpp/notification.h" |
| #include "ui/message_center/public/cpp/notification_delegate.h" |
| #include "ui/message_center/public/cpp/notification_types.h" |
| #include "ui/message_center/public/cpp/notifier_id.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "chrome/browser/chromeos/login/user_flow.h" |
| #include "chrome/browser/chromeos/login/users/chrome_user_manager.h" |
| #endif |
| |
| using content::SiteInstance; |
| using content::WebContents; |
| using extensions::BackgroundInfo; |
| using extensions::Extension; |
| using extensions::UnloadedExtensionReason; |
| |
| namespace { |
| |
| const char kCrashedNotificationPrefix[] = "app.background.crashed."; |
| const char kNotifierId[] = "app.background.crashed"; |
| bool g_disable_close_balloon_for_testing = false; |
| |
| void CloseBalloon(const std::string& extension_id, Profile* profile) { |
| if (g_disable_close_balloon_for_testing) |
| return; |
| |
| NotificationDisplayService::GetForProfile(profile)->Close( |
| NotificationHandler::Type::TRANSIENT, |
| kCrashedNotificationPrefix + extension_id); |
| } |
| |
| // Delegate for the app/extension crash notification balloon. Restarts the |
| // app/extension when the balloon is clicked. |
| class CrashNotificationDelegate : public message_center::NotificationDelegate { |
| public: |
| CrashNotificationDelegate(Profile* profile, const Extension* extension) |
| : profile_(profile), |
| is_hosted_app_(extension->is_hosted_app()), |
| is_platform_app_(extension->is_platform_app()), |
| extension_id_(extension->id()) {} |
| |
| void Click(const base::Optional<int>& button_index, |
| const base::Optional<base::string16>& reply) override { |
| // http://crbug.com/247790 involves a crash notification balloon being |
| // clicked while the extension isn't in the TERMINATED state. In that case, |
| // any of the "reload" methods called below can unload the extension, which |
| // indirectly destroys *this, invalidating all the member variables, so we |
| // copy the extension ID before using it. |
| std::string copied_extension_id = extension_id_; |
| if (is_hosted_app_) { |
| // There can be a race here: user clicks the balloon, and simultaneously |
| // reloads the sad tab for the app. So we check here to be safe before |
| // loading the background page. |
| BackgroundContentsService* service = |
| BackgroundContentsServiceFactory::GetForProfile(profile_); |
| if (!service->GetAppBackgroundContents(copied_extension_id)) { |
| service->LoadBackgroundContentsForExtension(profile_, |
| copied_extension_id); |
| } |
| } else if (is_platform_app_) { |
| apps::AppLoadService::Get(profile_)->RestartApplication( |
| copied_extension_id); |
| } else { |
| extensions::ExtensionSystem::Get(profile_) |
| ->extension_service() |
| ->ReloadExtension(copied_extension_id); |
| } |
| |
| CloseBalloon(copied_extension_id, profile_); |
| } |
| |
| private: |
| ~CrashNotificationDelegate() override {} |
| |
| Profile* profile_; |
| bool is_hosted_app_; |
| bool is_platform_app_; |
| std::string extension_id_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate); |
| }; |
| |
| void NotificationImageReady(const std::string extension_name, |
| const std::string extension_id, |
| const base::string16 message, |
| scoped_refptr<CrashNotificationDelegate> delegate, |
| Profile* profile, |
| const gfx::Image& icon) { |
| if (g_browser_process->IsShuttingDown()) |
| return; |
| |
| gfx::Image notification_icon(icon); |
| if (notification_icon.IsEmpty()) { |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| notification_icon = rb.GetImageNamed(IDR_EXTENSION_DEFAULT_ICON); |
| } |
| |
| // Origin URL must be different from the crashed extension to avoid the |
| // conflict. NotificationSystemObserver will cancel all notifications from |
| // the same origin when OnExtensionUnloaded() is called. |
| std::string id = kCrashedNotificationPrefix + extension_id; |
| message_center::Notification notification( |
| message_center::NOTIFICATION_TYPE_SIMPLE, id, base::string16(), message, |
| notification_icon, base::string16(), GURL("chrome://extension-crash"), |
| message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT, |
| kNotifierId), |
| {}, delegate); |
| |
| NotificationDisplayService::GetForProfile(profile)->Display( |
| NotificationHandler::Type::TRANSIENT, notification); |
| } |
| |
| // Show a popup notification balloon with a crash message for a given app/ |
| // extension. |
| void ShowBalloon(const Extension* extension, Profile* profile) { |
| const base::string16 message = l10n_util::GetStringFUTF16( |
| extension->is_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE |
| : IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE, |
| base::UTF8ToUTF16(extension->name())); |
| extension_misc::ExtensionIcons size(extension_misc::EXTENSION_ICON_LARGE); |
| extensions::ExtensionResource resource = |
| extensions::IconsInfo::GetIconResource(extension, size, |
| ExtensionIconSet::MATCH_SMALLER); |
| // We can't just load the image in the Observe method below because, despite |
| // what this method is called, it may call the callback synchronously. |
| // However, it's possible that the extension went away during the interim, |
| // so we'll bind all the pertinent data here. |
| extensions::ImageLoader::Get(profile)->LoadImageAsync( |
| extension, resource, gfx::Size(size, size), |
| base::BindOnce( |
| &NotificationImageReady, extension->name(), extension->id(), message, |
| base::MakeRefCounted<CrashNotificationDelegate>(profile, extension), |
| profile)); |
| } |
| |
| void ReloadExtension(const std::string& extension_id, Profile* profile) { |
| if (g_browser_process->IsShuttingDown() || |
| !g_browser_process->profile_manager()->IsValidProfile(profile)) { |
| return; |
| } |
| |
| extensions::ExtensionSystem* extension_system = |
| extensions::ExtensionSystem::Get(profile); |
| extensions::ExtensionRegistry* extension_registry = |
| extensions::ExtensionRegistry::Get(profile); |
| if (!extension_system || !extension_system->extension_service() || |
| !extension_registry) { |
| return; |
| } |
| |
| if (!extension_registry->GetExtensionById( |
| extension_id, extensions::ExtensionRegistry::TERMINATED)) { |
| // Either the app/extension was uninstalled by policy or it has since |
| // been restarted successfully by someone else (the user). |
| return; |
| } |
| extension_system->extension_service()->ReloadExtension(extension_id); |
| } |
| |
| } // namespace |
| |
| // Keys for the information we store about individual BackgroundContents in |
| // prefs. There is one top-level DictionaryValue (stored at |
| // prefs::kRegisteredBackgroundContents). Information about each |
| // BackgroundContents is stored under that top-level DictionaryValue, keyed |
| // by the parent application ID for easy lookup. |
| // |
| // kRegisteredBackgroundContents: |
| // DictionaryValue { |
| // <appid_1>: { "url": <url1>, "name": <frame_name> }, |
| // <appid_2>: { "url": <url2>, "name": <frame_name> }, |
| // ... etc ... |
| // } |
| const char kUrlKey[] = "url"; |
| const char kFrameNameKey[] = "name"; |
| |
| // Defines the backoff policy used for attempting to reload extensions. |
| const net::BackoffEntry::Policy kExtensionReloadBackoffPolicy = { |
| 0, // Initial errors to ignore before applying backoff. |
| 3000, // Initial delay: 3 seconds. |
| 2, // Multiply factor. |
| 0.1, // Fuzzing percentage. |
| -1, // Maximum backoff time: -1 for no maximum. |
| -1, // Entry lifetime: -1 to never discard. |
| false, // Whether to always use initial delay. No-op as there are |
| // no initial errors to ignore. |
| }; |
| |
| int BackgroundContentsService::restart_delay_in_ms_ = 3000; // 3 seconds. |
| |
| BackgroundContentsService::BackgroundContentsService( |
| Profile* profile, |
| const base::CommandLine* command_line) |
| : prefs_(nullptr), |
| extension_registry_observer_(this), |
| weak_ptr_factory_(this) { |
| // Don't load/store preferences if the parent profile is incognito. |
| if (!profile->IsOffTheRecord()) |
| prefs_ = profile->GetPrefs(); |
| |
| // Listen for events to tell us when to load/unload persisted background |
| // contents. |
| StartObserving(profile); |
| } |
| |
| BackgroundContentsService::~BackgroundContentsService() { |
| // BackgroundContents should be shutdown before we go away, as otherwise |
| // our browser process refcount will be off. |
| DCHECK(contents_map_.empty()); |
| } |
| |
| // static |
| void BackgroundContentsService:: |
| SetRestartDelayForForceInstalledAppsAndExtensionsForTesting( |
| int restart_delay_in_ms) { |
| restart_delay_in_ms_ = restart_delay_in_ms; |
| } |
| |
| // static |
| std::string |
| BackgroundContentsService::GetNotificationDelegateIdForExtensionForTesting( |
| const std::string& extension_id) { |
| return kCrashedNotificationPrefix + extension_id; |
| } |
| |
| // static |
| void BackgroundContentsService::ShowBalloonForTesting( |
| const extensions::Extension* extension, |
| Profile* profile) { |
| ShowBalloon(extension, profile); |
| } |
| |
| // static |
| void BackgroundContentsService::DisableCloseBalloonForTesting( |
| bool disable_close_balloon_for_testing) { |
| g_disable_close_balloon_for_testing = disable_close_balloon_for_testing; |
| } |
| |
| std::vector<BackgroundContents*> |
| BackgroundContentsService::GetBackgroundContents() const { |
| std::vector<BackgroundContents*> contents; |
| for (auto it = contents_map_.begin(); it != contents_map_.end(); ++it) |
| contents.push_back(it->second.contents); |
| return contents; |
| } |
| |
| void BackgroundContentsService::StartObserving(Profile* profile) { |
| // On startup, load our background pages after extension-apps have loaded. |
| extensions::ExtensionSystem::Get(profile)->ready().Post( |
| FROM_HERE, base::Bind(&BackgroundContentsService::OnExtensionSystemReady, |
| weak_ptr_factory_.GetWeakPtr(), profile)); |
| |
| // Track the lifecycle of all BackgroundContents in the system to allow us |
| // to store an up-to-date list of the urls. Start tracking contents when they |
| // have been opened via CreateBackgroundContents(), and stop tracking them |
| // when they are closed by script. |
| registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED, |
| content::Source<Profile>(profile)); |
| |
| // Stop tracking BackgroundContents when they have been deleted (happens |
| // during shutdown or if the render process dies). |
| registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED, |
| content::Source<Profile>(profile)); |
| |
| // Track when the BackgroundContents navigates to a new URL so we can update |
| // our persisted information as appropriate. |
| registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED, |
| content::Source<Profile>(profile)); |
| |
| // Track when the extensions crash so that the user can be notified |
| // about it, and the crashed contents can be restarted. |
| registrar_.Add(this, extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED, |
| content::Source<Profile>(profile)); |
| registrar_.Add(this, chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED, |
| content::Source<Profile>(profile)); |
| |
| // Listen for extension uninstall, load, unloaded notification. |
| extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile)); |
| } |
| |
| void BackgroundContentsService::OnExtensionSystemReady(Profile* profile) { |
| LoadBackgroundContentsFromManifests(profile); |
| LoadBackgroundContentsFromPrefs(profile); |
| SendChangeNotification(profile); |
| } |
| |
| void BackgroundContentsService::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| TRACE_EVENT0("browser,startup", "BackgroundContentsService::Observe"); |
| switch (type) { |
| case chrome::NOTIFICATION_BACKGROUND_CONTENTS_DELETED: |
| BackgroundContentsShutdown( |
| content::Details<BackgroundContents>(details).ptr()); |
| SendChangeNotification(content::Source<Profile>(source).ptr()); |
| break; |
| case chrome::NOTIFICATION_BACKGROUND_CONTENTS_CLOSED: |
| DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr())); |
| UnregisterBackgroundContents( |
| content::Details<BackgroundContents>(details).ptr()); |
| // CLOSED is always followed by a DELETED notification so we'll send our |
| // change notification there. |
| break; |
| case chrome::NOTIFICATION_BACKGROUND_CONTENTS_NAVIGATED: { |
| DCHECK(IsTracked(content::Details<BackgroundContents>(details).ptr())); |
| |
| // Do not register in the pref if the extension has a manifest-specified |
| // background page. |
| BackgroundContents* bgcontents = |
| content::Details<BackgroundContents>(details).ptr(); |
| Profile* profile = content::Source<Profile>(source).ptr(); |
| const std::string& appid = GetParentApplicationId(bgcontents); |
| extensions::ExtensionService* extension_service = |
| extensions::ExtensionSystem::Get(profile)->extension_service(); |
| // extension_service can be nullptr when running tests. |
| if (extension_service) { |
| const Extension* extension = |
| extension_service->GetExtensionById(appid, false); |
| if (extension && BackgroundInfo::HasBackgroundPage(extension)) |
| break; |
| } |
| RegisterBackgroundContents(bgcontents); |
| break; |
| } |
| case extensions::NOTIFICATION_EXTENSION_PROCESS_TERMINATED: |
| case chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED: { |
| Profile* profile = content::Source<Profile>(source).ptr(); |
| const Extension* extension = nullptr; |
| if (type == chrome::NOTIFICATION_BACKGROUND_CONTENTS_TERMINATED) { |
| BackgroundContents* bg = |
| content::Details<BackgroundContents>(details).ptr(); |
| std::string extension_id = |
| BackgroundContentsServiceFactory::GetForProfile(profile) |
| ->GetParentApplicationId(bg); |
| extension = extensions::ExtensionSystem::Get(profile) |
| ->extension_service() |
| ->GetExtensionById(extension_id, false); |
| } else { |
| extensions::ExtensionHost* extension_host = |
| content::Details<extensions::ExtensionHost>(details).ptr(); |
| extension = extension_host->extension(); |
| } |
| if (!extension) |
| break; |
| |
| const bool force_installed = |
| extensions::Manifest::IsComponentLocation(extension->location()) || |
| extensions::Manifest::IsPolicyLocation(extension->location()); |
| if (!force_installed) { |
| #if defined(OS_CHROMEOS) |
| chromeos::UserFlow* user_flow = |
| chromeos::ChromeUserManager::Get()->GetCurrentUserFlow(); |
| if (!user_flow->AllowsNotificationBalloons()) |
| break; |
| #endif |
| ShowBalloon(extension, profile); |
| } else { |
| // Restart the extension. |
| RestartForceInstalledExtensionOnCrash(extension, profile); |
| } |
| break; |
| } |
| |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void BackgroundContentsService::OnExtensionLoaded( |
| content::BrowserContext* browser_context, |
| const extensions::Extension* extension) { |
| Profile* profile = Profile::FromBrowserContext(browser_context); |
| if (extension->is_hosted_app() && |
| BackgroundInfo::HasBackgroundPage(extension)) { |
| // If there is a background page specified in the manifest for a hosted |
| // app, then blow away registered urls in the pref. |
| ShutdownAssociatedBackgroundContents(extension->id()); |
| |
| extensions::ExtensionService* service = |
| extensions::ExtensionSystem::Get(browser_context)->extension_service(); |
| if (service && service->is_ready()) { |
| // Now load the manifest-specified background page. If service isn't |
| // ready, then the background page will be loaded from the |
| // EXTENSIONS_READY callback. |
| LoadBackgroundContents(profile, |
| BackgroundInfo::GetBackgroundURL(extension), |
| "background", extension->id()); |
| } |
| } |
| |
| // If there is an existing BackoffEntry for the extension, clear it if |
| // the component extension stays loaded for 60 seconds. This avoids the |
| // situation of effectively disabling an extension for the entire browser |
| // session if there was a periodic crash (sometimes caused by another source). |
| if (extensions::Manifest::IsComponentLocation(extension->location())) { |
| ComponentExtensionBackoffEntryMap::const_iterator it = |
| component_backoff_map_.find(extension->id()); |
| if (it != component_backoff_map_.end()) { |
| net::BackoffEntry* entry = component_backoff_map_[extension->id()].get(); |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&BackgroundContentsService::MaybeClearBackoffEntry, |
| weak_ptr_factory_.GetWeakPtr(), extension->id(), |
| entry->failure_count()), |
| base::TimeDelta::FromSeconds(60)); |
| } |
| } |
| |
| // Close the crash notification balloon for the app/extension, if any. |
| CloseBalloon(extension->id(), profile); |
| SendChangeNotification(profile); |
| } |
| |
| void BackgroundContentsService::OnExtensionUnloaded( |
| content::BrowserContext* browser_context, |
| const extensions::Extension* extension, |
| extensions::UnloadedExtensionReason reason) { |
| switch (reason) { |
| case UnloadedExtensionReason::DISABLE: // Fall through. |
| case UnloadedExtensionReason::TERMINATE: // Fall through. |
| case UnloadedExtensionReason::UNINSTALL: // Fall through. |
| case UnloadedExtensionReason::BLACKLIST: // Fall through. |
| case UnloadedExtensionReason::LOCK_ALL: // Fall through. |
| case UnloadedExtensionReason::MIGRATED_TO_COMPONENT: // Fall through. |
| case UnloadedExtensionReason::PROFILE_SHUTDOWN: |
| ShutdownAssociatedBackgroundContents(extension->id()); |
| SendChangeNotification(Profile::FromBrowserContext(browser_context)); |
| return; |
| case UnloadedExtensionReason::UPDATE: { |
| // If there is a manifest specified background page, then shut it down |
| // here, since if the updated extension still has the background page, |
| // then it will be loaded from LOADED callback. Otherwise, leave |
| // BackgroundContents in place. |
| // We don't call SendChangeNotification here - it will be generated |
| // from the LOADED callback. |
| if (BackgroundInfo::HasBackgroundPage(extension)) |
| ShutdownAssociatedBackgroundContents(extension->id()); |
| return; |
| case UnloadedExtensionReason::UNDEFINED: |
| // Fall through to undefined case. |
| break; |
| } |
| } |
| NOTREACHED() << "Undefined UnloadedExtensionReason."; |
| return ShutdownAssociatedBackgroundContents(extension->id()); |
| } |
| |
| void BackgroundContentsService::OnExtensionUninstalled( |
| content::BrowserContext* browser_context, |
| const extensions::Extension* extension, |
| extensions::UninstallReason reason) { |
| Profile* profile = Profile::FromBrowserContext(browser_context); |
| // Make sure the extension-crash balloons are removed when the extension is |
| // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed |
| // extension is unloaded immediately after the crash, not when user reloads or |
| // uninstalls the extension. |
| CloseBalloon(extension->id(), profile); |
| } |
| |
| void BackgroundContentsService::RestartForceInstalledExtensionOnCrash( |
| const Extension* extension, |
| Profile* profile) { |
| int restart_delay = restart_delay_in_ms_; |
| |
| // If the extension was a component extension, use exponential backoff when |
| // attempting to reload. |
| if (extensions::Manifest::IsComponentLocation(extension->location())) { |
| ComponentExtensionBackoffEntryMap::const_iterator it = |
| component_backoff_map_.find(extension->id()); |
| |
| // Create a BackoffEntry if this is the first time we try to reload this |
| // particular extension. |
| if (it == component_backoff_map_.end()) { |
| std::unique_ptr<net::BackoffEntry> backoff_entry( |
| new net::BackoffEntry(&kExtensionReloadBackoffPolicy)); |
| component_backoff_map_.insert( |
| std::pair<extensions::ExtensionId, |
| std::unique_ptr<net::BackoffEntry>>( |
| extension->id(), std::move(backoff_entry))); |
| } |
| |
| net::BackoffEntry* entry = component_backoff_map_[extension->id()].get(); |
| entry->InformOfRequest(false); |
| restart_delay = entry->GetTimeUntilRelease().InMilliseconds(); |
| } |
| |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::BindOnce(&ReloadExtension, extension->id(), profile), |
| base::TimeDelta::FromMilliseconds(restart_delay)); |
| } |
| |
| // Loads all background contents whose urls have been stored in prefs. |
| void BackgroundContentsService::LoadBackgroundContentsFromPrefs( |
| Profile* profile) { |
| if (!prefs_) |
| return; |
| const base::DictionaryValue* contents = |
| prefs_->GetDictionary(prefs::kRegisteredBackgroundContents); |
| if (!contents) |
| return; |
| extensions::ExtensionService* extensions_service = |
| extensions::ExtensionSystem::Get(profile)->extension_service(); |
| DCHECK(extensions_service); |
| for (base::DictionaryValue::Iterator it(*contents); !it.IsAtEnd(); |
| it.Advance()) { |
| // Check to make sure that the parent extension is still enabled. |
| const Extension* extension = |
| extensions_service->GetExtensionById(it.key(), false); |
| if (!extension) { |
| // We should never reach here - it should not be possible for an app |
| // to become uninstalled without the associated BackgroundContents being |
| // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a |
| // crash before we could save our prefs, or if the user deletes the |
| // extension files manually rather than uninstalling it. |
| NOTREACHED() << "No extension found for BackgroundContents - id = " |
| << it.key(); |
| // Don't cancel out of our loop, just ignore this BackgroundContents and |
| // load the next one. |
| continue; |
| } |
| LoadBackgroundContentsFromDictionary(profile, it.key(), contents); |
| } |
| } |
| |
| void BackgroundContentsService::SendChangeNotification(Profile* profile) { |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_BACKGROUND_CONTENTS_SERVICE_CHANGED, |
| content::Source<Profile>(profile), |
| content::Details<BackgroundContentsService>(this)); |
| } |
| |
| void BackgroundContentsService::MaybeClearBackoffEntry( |
| const std::string extension_id, |
| int expected_failure_count) { |
| ComponentExtensionBackoffEntryMap::const_iterator it = |
| component_backoff_map_.find(extension_id); |
| if (it == component_backoff_map_.end()) |
| return; |
| |
| net::BackoffEntry* entry = component_backoff_map_[extension_id].get(); |
| |
| // Only remove the BackoffEntry if there has has been no failure for |
| // |extension_id| since loading. |
| if (entry->failure_count() == expected_failure_count) |
| component_backoff_map_.erase(it); |
| } |
| |
| void BackgroundContentsService::LoadBackgroundContentsForExtension( |
| Profile* profile, |
| const std::string& extension_id) { |
| // First look if the manifest specifies a background page. |
| const Extension* extension = extensions::ExtensionSystem::Get(profile) |
| ->extension_service() |
| ->GetExtensionById(extension_id, false); |
| DCHECK(!extension || extension->is_hosted_app()); |
| if (extension && BackgroundInfo::HasBackgroundPage(extension)) { |
| LoadBackgroundContents(profile, BackgroundInfo::GetBackgroundURL(extension), |
| "background", extension->id()); |
| return; |
| } |
| |
| // Now look in the prefs. |
| if (!prefs_) |
| return; |
| const base::DictionaryValue* contents = |
| prefs_->GetDictionary(prefs::kRegisteredBackgroundContents); |
| if (!contents) |
| return; |
| LoadBackgroundContentsFromDictionary(profile, extension_id, contents); |
| } |
| |
| void BackgroundContentsService::LoadBackgroundContentsFromDictionary( |
| Profile* profile, |
| const std::string& extension_id, |
| const base::DictionaryValue* contents) { |
| extensions::ExtensionService* extensions_service = |
| extensions::ExtensionSystem::Get(profile)->extension_service(); |
| DCHECK(extensions_service); |
| |
| const base::DictionaryValue* dict; |
| if (!contents->GetDictionaryWithoutPathExpansion(extension_id, &dict) || |
| dict == nullptr) |
| return; |
| |
| std::string frame_name; |
| std::string url; |
| dict->GetString(kUrlKey, &url); |
| dict->GetString(kFrameNameKey, &frame_name); |
| LoadBackgroundContents(profile, GURL(url), frame_name, extension_id); |
| } |
| |
| void BackgroundContentsService::LoadBackgroundContentsFromManifests( |
| Profile* profile) { |
| for (const scoped_refptr<const extensions::Extension>& extension : |
| extensions::ExtensionRegistry::Get(profile)->enabled_extensions()) { |
| if (extension->is_hosted_app() && |
| BackgroundInfo::HasBackgroundPage(extension.get())) { |
| LoadBackgroundContents(profile, |
| BackgroundInfo::GetBackgroundURL(extension.get()), |
| "background", extension->id()); |
| } |
| } |
| } |
| |
| void BackgroundContentsService::LoadBackgroundContents( |
| Profile* profile, |
| const GURL& url, |
| const std::string& frame_name, |
| const std::string& application_id) { |
| // We are depending on the fact that we will initialize before any user |
| // actions or session restore can take place, so no BackgroundContents should |
| // be running yet for the passed application_id. |
| DCHECK(!GetAppBackgroundContents(application_id)); |
| DCHECK(!application_id.empty()); |
| DCHECK(url.is_valid()); |
| DVLOG(1) << "Loading background content url: " << url; |
| |
| BackgroundContents* contents = CreateBackgroundContents( |
| SiteInstance::CreateForURL(profile, url), nullptr, MSG_ROUTING_NONE, |
| MSG_ROUTING_NONE, MSG_ROUTING_NONE, profile, frame_name, application_id, |
| std::string(), nullptr); |
| |
| contents->CreateRenderViewSoon(url); |
| } |
| |
| BackgroundContents* BackgroundContentsService::CreateBackgroundContents( |
| scoped_refptr<SiteInstance> site, |
| content::RenderFrameHost* opener, |
| int32_t routing_id, |
| int32_t main_frame_route_id, |
| int32_t main_frame_widget_route_id, |
| Profile* profile, |
| const std::string& frame_name, |
| const std::string& application_id, |
| const std::string& partition_id, |
| content::SessionStorageNamespace* session_storage_namespace) { |
| BackgroundContents* contents = |
| new BackgroundContents(std::move(site), opener, routing_id, |
| main_frame_route_id, main_frame_widget_route_id, |
| this, partition_id, session_storage_namespace); |
| |
| // Register the BackgroundContents internally, then send out a notification |
| // to external listeners. |
| BackgroundContentsOpenedDetails details = {contents, frame_name, |
| application_id}; |
| BackgroundContentsOpened(&details, profile); |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_BACKGROUND_CONTENTS_OPENED, |
| content::Source<Profile>(profile), |
| content::Details<BackgroundContentsOpenedDetails>(&details)); |
| |
| // A new background contents has been created - notify our listeners. |
| SendChangeNotification(profile); |
| return contents; |
| } |
| |
| void BackgroundContentsService::RegisterBackgroundContents( |
| BackgroundContents* background_contents) { |
| DCHECK(IsTracked(background_contents)); |
| if (!prefs_) |
| return; |
| |
| // We store the first URL we receive for a given application. If there's |
| // already an entry for this application, no need to do anything. |
| // TODO(atwilson): Verify that this is the desired behavior based on developer |
| // feedback (http://crbug.com/47118). |
| DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents); |
| base::DictionaryValue* pref = update.Get(); |
| const std::string& appid = GetParentApplicationId(background_contents); |
| base::DictionaryValue* current; |
| if (pref->GetDictionaryWithoutPathExpansion(appid, ¤t)) |
| return; |
| |
| // No entry for this application yet, so add one. |
| auto dict = std::make_unique<base::DictionaryValue>(); |
| dict->SetString(kUrlKey, background_contents->GetURL().spec()); |
| dict->SetString(kFrameNameKey, contents_map_[appid].frame_name); |
| pref->SetWithoutPathExpansion(appid, std::move(dict)); |
| } |
| |
| bool BackgroundContentsService::HasRegisteredBackgroundContents( |
| const std::string& app_id) { |
| if (!prefs_) |
| return false; |
| const base::DictionaryValue* contents = |
| prefs_->GetDictionary(prefs::kRegisteredBackgroundContents); |
| return contents->HasKey(app_id); |
| } |
| |
| void BackgroundContentsService::UnregisterBackgroundContents( |
| BackgroundContents* background_contents) { |
| if (!prefs_) |
| return; |
| DCHECK(IsTracked(background_contents)); |
| const std::string& appid = GetParentApplicationId(background_contents); |
| DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents); |
| update.Get()->RemoveWithoutPathExpansion(appid, nullptr); |
| } |
| |
| void BackgroundContentsService::ShutdownAssociatedBackgroundContents( |
| const std::string& appid) { |
| BackgroundContents* contents = GetAppBackgroundContents(appid); |
| if (contents) { |
| UnregisterBackgroundContents(contents); |
| // Background contents destructor shuts down the renderer. |
| delete contents; |
| } |
| } |
| |
| void BackgroundContentsService::BackgroundContentsOpened( |
| BackgroundContentsOpenedDetails* details, |
| Profile* profile) { |
| // Add the passed object to our list. Should not already be tracked. |
| DCHECK(!IsTracked(details->contents)); |
| DCHECK(!details->application_id.empty()); |
| contents_map_[details->application_id].contents = details->contents; |
| contents_map_[details->application_id].frame_name = details->frame_name; |
| |
| CloseBalloon(details->application_id, profile); |
| } |
| |
| // Used by test code and debug checks to verify whether a given |
| // BackgroundContents is being tracked by this instance. |
| bool BackgroundContentsService::IsTracked( |
| BackgroundContents* background_contents) const { |
| return !GetParentApplicationId(background_contents).empty(); |
| } |
| |
| void BackgroundContentsService::BackgroundContentsShutdown( |
| BackgroundContents* background_contents) { |
| // Remove the passed object from our list. |
| DCHECK(IsTracked(background_contents)); |
| contents_map_.erase(GetParentApplicationId(background_contents)); |
| } |
| |
| BackgroundContents* BackgroundContentsService::GetAppBackgroundContents( |
| const std::string& application_id) { |
| BackgroundContentsMap::const_iterator it = contents_map_.find(application_id); |
| return (it != contents_map_.end()) ? it->second.contents : nullptr; |
| } |
| |
| const std::string& BackgroundContentsService::GetParentApplicationId( |
| BackgroundContents* contents) const { |
| for (auto it = contents_map_.begin(); it != contents_map_.end(); ++it) { |
| if (contents == it->second.contents) |
| return it->first; |
| } |
| return base::EmptyString(); |
| } |
| |
| void BackgroundContentsService::AddWebContents( |
| std::unique_ptr<WebContents> new_contents, |
| WindowOpenDisposition disposition, |
| const gfx::Rect& initial_rect, |
| bool* was_blocked) { |
| Browser* browser = chrome::FindLastActiveWithProfile( |
| Profile::FromBrowserContext(new_contents->GetBrowserContext())); |
| if (browser) { |
| chrome::AddWebContents(browser, nullptr, std::move(new_contents), |
| disposition, initial_rect); |
| } |
| } |