// Copyright 2012 The Chromium Authors
// 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/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  // BUILDFLAG(IS_CHROMEOS)

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);
}

// 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()) {}

  CrashNotificationDelegate(const CrashNotificationDelegate&) = delete;
  CrashNotificationDelegate& operator=(const CrashNotificationDelegate&) =
      delete;

  void Click(const std::optional<int>& button_index,
             const std::optional<std::u16string>& reply) override {
    // Pass arguments by value as HandleClick() might destroy *this.
    HandleClick(is_hosted_app_, is_platform_app_, extension_id_, profile_);
    // *this might be destroyed now, do not access any members anymore!
  }

 private:
  ~CrashNotificationDelegate() override = default;

  // Static to prevent accidental use of members as *this might get destroyed.
  static void HandleClick(bool is_hosted_app,
                          bool is_platform_app,
                          std::string extension_id,
                          Profile* profile) {
    // 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 the CrashNotificationDelegate, invalidating all its
    // member variables. Make sure to pass arguments by value when adding new
    // ones to this method.
    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(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);
  }

  // This dangling raw_ptr occurred in:
  // unit_tests:
  // BackgroundContentsServiceNotificationTest.TestShowBalloonShutdown
  // https://ci.chromium.org/ui/p/chromium/builders/try/linux-rel/1427454/test-results?q=ExactID%3Aninja%3A%2F%2Fchrome%2Ftest%3Aunit_tests%2FBackgroundContentsServiceNotificationTest.TestShowBalloonShutdown+VHash%3A728d3f3a440b40c1
  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)) {
    // Either the app/extension was uninstalled by policy or it has since
    // been restarted successfully by someone else (the user).
    return;
  }
  extension_registrar->ReloadExtension(extension_id);
}

}  // namespace

// Keys for the information we store about individual BackgroundContents in
// prefs. There is one top-level base::Value::Dict (stored at
// prefs::kRegisteredBackgroundContents). Information about each
// BackgroundContents is stored under that top-level base::Value::Dict, keyed
// by the parent application ID for easy lookup.
//
// kRegisteredBackgroundContents:
//    base::Value::Dict {
//       <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)
    : profile_(profile) {
  // 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();
}

BackgroundContentsService::~BackgroundContentsService() {
  for (auto& observer : observers_)
    observer.OnBackgroundContentsServiceDestroying();
}

// 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::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() {
  // On startup, load our background pages after extension-apps have loaded.
  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_));

  // Listen for extension uninstall, load, unloaded notification.
  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) {
  // The notification may be for the on/off-the-record pair to this object's
  // `profile_`; since the BackgroundContentsService has its own instance in
  // incognito, only consider notifications from our exact context.
  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)) {
    // 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::ExtensionSystem* extension_system =
        extensions::ExtensionSystem::Get(profile);

    if (extension_system->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(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::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&BackgroundContentsService::MaybeClearBackoffEntry,
                         weak_ptr_factory_.GetWeakPtr(), extension->id(),
                         entry->failure_count()),
          base::Seconds(60));
    }
  }

  // Close the crash notification balloon for the app/extension, if any.
  CloseBalloon(extension->id(), profile);
  SendChangeNotification();
}

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::BLOCKLIST:              // Fall through.
    case UnloadedExtensionReason::LOCK_ALL:               // Fall through.
    case UnloadedExtensionReason::MIGRATED_TO_COMPONENT:  // Fall through.
    case UnloadedExtensionReason::PROFILE_SHUTDOWN:
      ShutdownAssociatedBackgroundContents(extension->id());
      SendChangeNotification();
      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.";
}

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) {
  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();
  }

  // Ugly implementation detail: ExtensionService listens to the same
  // notification that can lead us here, and asynchronously marks the extension
  // as terminated (with an effective delay of 0) to allow other systems to
  // receive the notification. However, that means we need to ensure this task
  // gets ran *after* that, so that the extension is in the terminated set by
  // the time we try to reload it (even if this service gets the notification
  // first).
  //
  // TODO(devlin): This would be unnecessary if we listened to the
  // OnExtensionUnloaded() notification and checked the unload reason.
  DCHECK_GT(restart_delay, 0);
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, base::BindOnce(&ReloadExtension, extension->id(), profile_),
      base::Milliseconds(restart_delay));
}

// Loads all background contents whose urls have been stored in prefs.
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) {
    // Check to make sure that the parent extension is still enabled.
    const Extension* extension =
        extension_registry->enabled_extensions().GetByID(extension_id);
    if (!extension) {
      // Normally, we shouldn't reach here - it shouldn't be possible for an app
      // to become uninstalled without the associated BackgroundContents being
      // unregistered via the EXTENSIONS_UNLOADED notification. However, this
      // is possible if there's e.g. a crash before we could save our prefs or
      // the user deletes the extension files manually rather than uninstalling
      // it.
      LOG(ERROR) << "No extension found for BackgroundContents - id = "
                 << extension_id;
      // Don't cancel out of our loop, just ignore this BackgroundContents and
      // load the next one.
      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();

  // 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(
    const std::string& extension_id) {
  // First look if the manifest specifies a background page.
  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;
  }

  // Now look in the prefs.
  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) {
  // 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, 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);

  // Register the BackgroundContents internally, then send out a notification
  // to external listeners.
  BackgroundContentsOpenedDetails details = {contents_ptr, raw_ref(frame_name),
                                             raw_ref(application_id)};
  for (auto& observer : observers_)
    observer.OnBackgroundContentsOpened(details);

  // A new background contents has been created - notify our listeners.
  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;

  // 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.
  ScopedDictPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
  base::Value::Dict& pref = update.Get();
  const std::string& appid = GetParentApplicationId(background_contents);
  if (pref.FindDict(appid)) {
    return;
  }

  // No entry for this application yet, so add one.
  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);
    // Background contents destructor shuts down the renderer.
    DeleteBackgroundContents(contents);
  }
}

void BackgroundContentsService::AddBackgroundContents(
    std::unique_ptr<BackgroundContents> contents,
    const std::string& application_id,
    const std::string& frame_name) {
  // Add the passed object to our list.
  DCHECK(!application_id.empty());
  BackgroundContentsInfo& info = contents_map_[application_id];
  info.contents = std::move(contents);
  info.frame_name = frame_name;

  CloseBalloon(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::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));
  // Do not register in the pref if the extension has a manifest-specified
  // background page.
  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) {
  // When the extensions crash, notify the user about it and restart the crashed
  // contents.
  if (!extension)
    return;

  const bool force_installed =
      extensions::Manifest::IsComponentLocation(extension->location()) ||
      extensions::Manifest::IsPolicyLocation(extension->location());
  if (!force_installed) {
    ShowBalloon(extension);
  } else {
    // Restart the extension.
    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);
  }

  // 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;
#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  // BUILDFLAG(ARKWEB_NOTIFICATION) && BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
      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  // BUILDFLAG(IS_CHROMEOS)
      {}, delegate);
  notification_service->Display(NotificationHandler::Type::TRANSIENT,
                                notification,
                                /*metadata=*/nullptr);
}

// Show a popup notification balloon with a crash message for a given app/
// extension.
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);
  // 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(&BackgroundContentsService::NotificationImageReady,
                     weak_ptr_factory_.GetWeakPtr(), extension->name(),
                     extension->id(), message,
                     base::MakeRefCounted<CrashNotificationDelegate>(
                         profile_, extension)));
}

BackgroundContentsService::BackgroundContentsInfo::BackgroundContentsInfo() =
    default;
BackgroundContentsService::BackgroundContentsInfo::~BackgroundContentsInfo() =
    default;