#include "chrome/browser/background/background_contents_service.h"
#include <utility>
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/observer_list.h"
#include "base/one_shot_event.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/apps/platform_apps/app_load_service.h"
#include "chrome/browser/background/background_contents_service_factory.h"
#include "chrome/browser/background/background_contents_service_observer.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/notifications/notification_common.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/notifications/notification_display_service_factory.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/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/icons/extension_icon_set.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "extensions/grit/extensions_browser_resources.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 BUILDFLAG(IS_CHROMEOS)
#include "ash/constants/notifier_catalogs.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;
NotificationDisplayServiceFactory::GetForProfile(profile)->Close(
NotificationHandler::Type::TRANSIENT,
kCrashedNotificationPrefix + extension_id);
}
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()) {}
CrashNotificationDelegate(const CrashNotificationDelegate&) = delete;
CrashNotificationDelegate& operator=(const CrashNotificationDelegate&) =
delete;
void Click(const std::optional<int>& button_index,
const std::optional<std::u16string>& reply) override {
HandleClick(is_hosted_app_, is_platform_app_, extension_id_, profile_);
}
private:
~CrashNotificationDelegate() override = default;
static void HandleClick(bool is_hosted_app,
bool is_platform_app,
std::string extension_id,
Profile* profile) {
if (is_hosted_app) {
BackgroundContentsService* service =
BackgroundContentsServiceFactory::GetForProfile(profile);
if (!service->GetAppBackgroundContents(extension_id))
service->LoadBackgroundContentsForExtension(extension_id);
} else if (is_platform_app) {
apps::AppLoadService::Get(profile)->RestartApplication(extension_id);
} else {
extensions::ExtensionRegistrar::Get(profile)->ReloadExtension(
extension_id);
}
CloseBalloon(extension_id, profile);
}
raw_ptr<Profile, FlakyDanglingUntriaged> profile_;
bool is_hosted_app_;
bool is_platform_app_;
extensions::ExtensionId extension_id_;
};
void ReloadExtension(const std::string& extension_id, Profile* profile) {
if (g_browser_process->IsShuttingDown() ||
!g_browser_process->profile_manager()->IsValidProfile(profile)) {
return;
}
auto* extension_registrar = extensions::ExtensionRegistrar::Get(profile);
auto* extension_registry = extensions::ExtensionRegistry::Get(profile);
if (!extension_registrar || !extension_registry) {
return;
}
if (!extension_registry->terminated_extensions().GetByID(extension_id)) {
return;
}
extension_registrar->ReloadExtension(extension_id);
}
}
const char kUrlKey[] = "url";
const char kFrameNameKey[] = "name";
const net::BackoffEntry::Policy kExtensionReloadBackoffPolicy = {
0,
3000,
2,
0.1,
-1,
-1,
false,
};
int BackgroundContentsService::restart_delay_in_ms_ = 3000;
BackgroundContentsService::BackgroundContentsService(Profile* profile)
: profile_(profile) {
if (!profile->IsOffTheRecord())
prefs_ = profile->GetPrefs();
StartObserving();
}
BackgroundContentsService::~BackgroundContentsService() {
for (auto& observer : observers_)
observer.OnBackgroundContentsServiceDestroying();
}
void BackgroundContentsService::
SetRestartDelayForForceInstalledAppsAndExtensionsForTesting(
int restart_delay_in_ms) {
restart_delay_in_ms_ = restart_delay_in_ms;
}
std::string
BackgroundContentsService::GetNotificationDelegateIdForExtensionForTesting(
const std::string& extension_id) {
return kCrashedNotificationPrefix + extension_id;
}
void BackgroundContentsService::DisableCloseBalloonForTesting(
bool disable_close_balloon_for_testing) {
g_disable_close_balloon_for_testing = disable_close_balloon_for_testing;
}
void BackgroundContentsService::ShowBalloonForTesting(
const extensions::Extension* extension) {
ShowBalloon(extension);
}
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.get());
return contents;
}
void BackgroundContentsService::StartObserving() {
extensions::ExtensionSystem::Get(profile_)->ready().Post(
FROM_HERE,
base::BindOnce(&BackgroundContentsService::OnExtensionSystemReady,
weak_ptr_factory_.GetWeakPtr()));
extension_host_registry_observation_.Observe(
extensions::ExtensionHostRegistry::Get(profile_));
extension_registry_observation_.Observe(
extensions::ExtensionRegistry::Get(profile_));
}
void BackgroundContentsService::OnExtensionSystemReady() {
LoadBackgroundContentsFromManifests();
LoadBackgroundContentsFromPrefs();
SendChangeNotification();
}
void BackgroundContentsService::OnExtensionHostRenderProcessGone(
content::BrowserContext* browser_context,
extensions::ExtensionHost* extension_host) {
if (browser_context != profile_)
return;
TRACE_EVENT0("browser,startup",
"BackgroundContentsService::OnExtensionHostRenderProcessGone");
HandleExtensionCrashed(extension_host->extension());
}
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)) {
ShutdownAssociatedBackgroundContents(extension->id());
extensions::ExtensionSystem* extension_system =
extensions::ExtensionSystem::Get(profile);
if (extension_system->is_ready()) {
LoadBackgroundContents(BackgroundInfo::GetBackgroundURL(extension),
"background", extension->id());
}
}
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::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&BackgroundContentsService::MaybeClearBackoffEntry,
weak_ptr_factory_.GetWeakPtr(), extension->id(),
entry->failure_count()),
base::Seconds(60));
}
}
CloseBalloon(extension->id(), profile);
SendChangeNotification();
}
void BackgroundContentsService::OnExtensionUnloaded(
content::BrowserContext* browser_context,
const extensions::Extension* extension,
extensions::UnloadedExtensionReason reason) {
switch (reason) {
case UnloadedExtensionReason::DISABLE:
case UnloadedExtensionReason::TERMINATE:
case UnloadedExtensionReason::UNINSTALL:
case UnloadedExtensionReason::BLOCKLIST:
case UnloadedExtensionReason::LOCK_ALL:
case UnloadedExtensionReason::MIGRATED_TO_COMPONENT:
case UnloadedExtensionReason::PROFILE_SHUTDOWN:
ShutdownAssociatedBackgroundContents(extension->id());
SendChangeNotification();
return;
case UnloadedExtensionReason::UPDATE: {
if (BackgroundInfo::HasBackgroundPage(extension))
ShutdownAssociatedBackgroundContents(extension->id());
return;
case UnloadedExtensionReason::UNDEFINED:
break;
}
}
NOTREACHED() << "Undefined UnloadedExtensionReason.";
}
void BackgroundContentsService::OnExtensionUninstalled(
content::BrowserContext* browser_context,
const extensions::Extension* extension,
extensions::UninstallReason reason) {
Profile* profile = Profile::FromBrowserContext(browser_context);
CloseBalloon(extension->id(), profile);
}
void BackgroundContentsService::RestartForceInstalledExtensionOnCrash(
const Extension* extension) {
int restart_delay = restart_delay_in_ms_;
if (extensions::Manifest::IsComponentLocation(extension->location())) {
ComponentExtensionBackoffEntryMap::const_iterator it =
component_backoff_map_.find(extension->id());
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();
}
DCHECK_GT(restart_delay, 0);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, base::BindOnce(&ReloadExtension, extension->id(), profile_),
base::Milliseconds(restart_delay));
}
void BackgroundContentsService::LoadBackgroundContentsFromPrefs() {
if (!prefs_)
return;
const base::Value::Dict& contents =
prefs_->GetDict(prefs::kRegisteredBackgroundContents);
extensions::ExtensionRegistry* extension_registry =
extensions::ExtensionRegistry::Get(profile_);
DCHECK(extension_registry);
for (const auto [extension_id, _] : contents) {
const Extension* extension =
extension_registry->enabled_extensions().GetByID(extension_id);
if (!extension) {
LOG(ERROR) << "No extension found for BackgroundContents - id = "
<< extension_id;
continue;
}
LoadBackgroundContentsFromDictionary(extension_id, contents);
}
}
void BackgroundContentsService::SendChangeNotification() {
for (auto& observer : observers_)
observer.OnBackgroundContentsServiceChanged();
}
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();
if (entry->failure_count() == expected_failure_count)
component_backoff_map_.erase(it);
}
void BackgroundContentsService::LoadBackgroundContentsForExtension(
const std::string& extension_id) {
const Extension* extension = extensions::ExtensionRegistry::Get(profile_)
->enabled_extensions()
.GetByID(extension_id);
DCHECK(!extension || extension->is_hosted_app());
if (extension && BackgroundInfo::HasBackgroundPage(extension)) {
LoadBackgroundContents(BackgroundInfo::GetBackgroundURL(extension),
"background", extension->id());
return;
}
if (!prefs_)
return;
const base::Value::Dict& contents =
prefs_->GetDict(prefs::kRegisteredBackgroundContents);
LoadBackgroundContentsFromDictionary(extension_id, contents);
}
void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
const std::string& extension_id,
const base::Value::Dict& contents) {
extensions::ExtensionService* extensions_service =
extensions::ExtensionSystem::Get(profile_)->extension_service();
DCHECK(extensions_service);
const base::Value::Dict* dict = contents.FindDict(extension_id);
if (!dict)
return;
const std::string* maybe_frame_name = dict->FindString(kUrlKey);
const std::string* maybe_url = dict->FindString(kFrameNameKey);
std::string frame_name = maybe_frame_name ? *maybe_frame_name : std::string();
std::string url = maybe_url ? *maybe_url : std::string();
LoadBackgroundContents(GURL(url), frame_name, extension_id);
}
void BackgroundContentsService::LoadBackgroundContentsFromManifests() {
for (const scoped_refptr<const extensions::Extension>& extension :
extensions::ExtensionRegistry::Get(profile_)->enabled_extensions()) {
if (extension->is_hosted_app() &&
BackgroundInfo::HasBackgroundPage(extension.get())) {
LoadBackgroundContents(BackgroundInfo::GetBackgroundURL(extension.get()),
"background", extension->id());
}
}
}
void BackgroundContentsService::LoadBackgroundContents(
const GURL& url,
const std::string& frame_name,
const std::string& 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, true, frame_name,
application_id, content::StoragePartitionConfig::CreateDefault(profile_),
nullptr);
contents->CreateRendererSoon(url);
}
BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
scoped_refptr<SiteInstance> site,
content::RenderFrameHost* opener,
bool is_new_browsing_instance,
const std::string& frame_name,
const std::string& application_id,
const content::StoragePartitionConfig& partition_config,
content::SessionStorageNamespace* session_storage_namespace) {
auto contents = std::make_unique<BackgroundContents>(
std::move(site), opener, is_new_browsing_instance, this, partition_config,
session_storage_namespace);
BackgroundContents* contents_ptr = contents.get();
AddBackgroundContents(std::move(contents), application_id, frame_name);
BackgroundContentsOpenedDetails details = {contents_ptr, raw_ref(frame_name),
raw_ref(application_id)};
for (auto& observer : observers_)
observer.OnBackgroundContentsOpened(details);
SendChangeNotification();
return contents_ptr;
}
void BackgroundContentsService::DeleteBackgroundContents(
BackgroundContents* contents) {
contents_map_.erase(GetParentApplicationId(contents));
SendChangeNotification();
}
void BackgroundContentsService::RegisterBackgroundContents(
BackgroundContents* background_contents) {
DCHECK(IsTracked(background_contents));
if (!prefs_)
return;
ScopedDictPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
base::Value::Dict& pref = update.Get();
const std::string& appid = GetParentApplicationId(background_contents);
if (pref.FindDict(appid)) {
return;
}
base::Value::Dict dict;
dict.Set(kUrlKey, background_contents->GetURL().spec());
dict.Set(kFrameNameKey, contents_map_[appid].frame_name);
pref.Set(appid, std::move(dict));
}
bool BackgroundContentsService::HasRegisteredBackgroundContents(
const std::string& app_id) {
if (!prefs_)
return false;
const base::Value::Dict& contents =
prefs_->GetDict(prefs::kRegisteredBackgroundContents);
return contents.Find(app_id);
}
void BackgroundContentsService::UnregisterBackgroundContents(
BackgroundContents* background_contents) {
if (!prefs_)
return;
DCHECK(IsTracked(background_contents));
const std::string& appid = GetParentApplicationId(background_contents);
ScopedDictPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
update->Remove(appid);
}
void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
const std::string& appid) {
BackgroundContents* contents = GetAppBackgroundContents(appid);
if (contents) {
UnregisterBackgroundContents(contents);
DeleteBackgroundContents(contents);
}
}
void BackgroundContentsService::AddBackgroundContents(
std::unique_ptr<BackgroundContents> contents,
const std::string& application_id,
const std::string& frame_name) {
DCHECK(!application_id.empty());
BackgroundContentsInfo& info = contents_map_[application_id];
info.contents = std::move(contents);
info.frame_name = frame_name;
CloseBalloon(application_id, profile_);
}
bool BackgroundContentsService::IsTracked(
BackgroundContents* background_contents) const {
return !GetParentApplicationId(background_contents).empty();
}
void BackgroundContentsService::AddObserver(
BackgroundContentsServiceObserver* observer) {
observers_.AddObserver(observer);
}
void BackgroundContentsService::RemoveObserver(
BackgroundContentsServiceObserver* observer) {
observers_.RemoveObserver(observer);
}
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.get() : 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.get())
return it->first;
}
return base::EmptyString();
}
void BackgroundContentsService::AddWebContents(
std::unique_ptr<WebContents> new_contents,
const GURL& target_url,
WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& window_features,
bool* was_blocked) {
Browser* browser = chrome::FindLastActiveWithProfile(
Profile::FromBrowserContext(new_contents->GetBrowserContext()));
if (browser) {
chrome::AddWebContents(browser, nullptr, std::move(new_contents),
target_url, disposition, window_features);
}
}
void BackgroundContentsService::OnBackgroundContentsNavigated(
BackgroundContents* contents) {
DCHECK(IsTracked(contents));
const std::string& appid = GetParentApplicationId(contents);
extensions::ExtensionRegistry* extension_registry =
extensions::ExtensionRegistry::Get(profile_);
const Extension* extension =
extension_registry->enabled_extensions().GetByID(appid);
if (extension && BackgroundInfo::HasBackgroundPage(extension))
return;
RegisterBackgroundContents(contents);
}
void BackgroundContentsService::OnBackgroundContentsTerminated(
BackgroundContents* contents) {
HandleExtensionCrashed(extensions::ExtensionRegistry::Get(profile_)
->enabled_extensions()
.GetByID(GetParentApplicationId(contents)));
DeleteBackgroundContents(contents);
}
void BackgroundContentsService::OnBackgroundContentsClosed(
BackgroundContents* contents) {
DCHECK(IsTracked(contents));
UnregisterBackgroundContents(contents);
DeleteBackgroundContents(contents);
for (auto& observer : observers_)
observer.OnBackgroundContentsClosed();
}
void BackgroundContentsService::Shutdown() {
contents_map_.clear();
}
void BackgroundContentsService::HandleExtensionCrashed(
const extensions::Extension* extension) {
if (!extension)
return;
const bool force_installed =
extensions::Manifest::IsComponentLocation(extension->location()) ||
extensions::Manifest::IsPolicyLocation(extension->location());
if (!force_installed) {
ShowBalloon(extension);
} else {
RestartForceInstalledExtensionOnCrash(extension);
}
}
void BackgroundContentsService::NotificationImageReady(
const std::string extension_name,
const std::string extension_id,
const std::u16string message,
scoped_refptr<message_center::NotificationDelegate> delegate,
const gfx::Image& icon) {
NotificationDisplayService* notification_service =
NotificationDisplayServiceFactory::GetForProfile(profile_);
CHECK(notification_service);
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);
}
std::string id = kCrashedNotificationPrefix + extension_id;
#if BUILDFLAG(ARKWEB_NOTIFICATION) && BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
const std::u16string title =
l10n_util::GetStringUTF16(IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_TITLE);
message_center::Notification notification(
message_center::NOTIFICATION_TYPE_SIMPLE, id, title, message,
#else
message_center::Notification notification(
message_center::NOTIFICATION_TYPE_SIMPLE, id, std::u16string(), message,
#endif
ui::ImageModel::FromImage(notification_icon), std::u16string(),
GURL("chrome://extension-crash"),
#if BUILDFLAG(IS_CHROMEOS)
message_center::NotifierId(
message_center::NotifierType::SYSTEM_COMPONENT, kNotifierId,
ash::NotificationCatalogName::kBackgroundCrash),
#else
message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
kNotifierId),
#endif
{}, delegate);
notification_service->Display(NotificationHandler::Type::TRANSIENT,
notification,
nullptr);
}
void BackgroundContentsService::ShowBalloon(const Extension* extension) {
const std::u16string 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::kSmaller);
extensions::ImageLoader::Get(profile_)->LoadImageAsync(
extension, resource, gfx::Size(size, size),
base::BindOnce(&BackgroundContentsService::NotificationImageReady,
weak_ptr_factory_.GetWeakPtr(), extension->name(),
extension->id(), message,
base::MakeRefCounted<CrashNotificationDelegate>(
profile_, extension)));
}
BackgroundContentsService::BackgroundContentsInfo::BackgroundContentsInfo() =
default;
BackgroundContentsService::BackgroundContentsInfo::~BackgroundContentsInfo() =
default;