910e62b5创建于 1月15日历史提交
// Copyright 2025 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/extensions/app_tab_helper.h"

#include <memory>

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper_factory.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/common/buildflags.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "chrome/common/url_constants.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/browser/invalidate_type.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extension_web_contents_observer.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_resource.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/icons/extension_icon_set.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "ui/gfx/image/image.h"
#include "url/url_constants.h"

#if BUILDFLAG(ENABLE_SESSION_SERVICE)
#include "chrome/browser/sessions/session_service.h"
#include "chrome/browser/sessions/session_service_factory.h"
#endif

using content::WebContents;

namespace extensions {

AppTabHelper::~AppTabHelper() = default;

AppTabHelper::AppTabHelper(content::WebContents* web_contents)
    : content::WebContentsObserver(web_contents),
      content::WebContentsUserData<AppTabHelper>(*web_contents),
      profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())) {
  // Ensure we have a SessionTabHelper.
  CreateSessionServiceTabHelper(web_contents);

  registry_observation_.Observe(ExtensionRegistry::Get(profile_));
}

void AppTabHelper::SetExtensionApp(const Extension* extension) {
  DCHECK(!extension || AppLaunchInfo::GetFullLaunchURL(extension).is_valid());
  if (extension_app_ == extension) {
    return;
  }

  DCHECK(!extension || extension->is_app());

  extension_app_ = extension;

  UpdateExtensionAppIcon(extension_app_);

#if BUILDFLAG(ENABLE_SESSION_SERVICE)
  if (extension_app_) {
    SessionService* session_service = SessionServiceFactory::GetForProfile(
        Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
    if (session_service) {
      sessions::SessionTabHelper* session_tab_helper =
          sessions::SessionTabHelper::FromWebContents(web_contents());
      CHECK(session_tab_helper);
      session_service->SetTabExtensionAppID(session_tab_helper->window_id(),
                                            session_tab_helper->session_id(),
                                            GetExtensionAppId());
    }
  }
#endif
}

void AppTabHelper::SetExtensionAppById(const ExtensionId& extension_app_id) {
  const Extension* extension = GetExtension(extension_app_id);
  if (extension) {
    SetExtensionApp(extension);
  }
}

ExtensionId AppTabHelper::GetExtensionAppId() const {
  return extension_app_ ? extension_app_->id() : ExtensionId();
}

SkBitmap* AppTabHelper::GetExtensionAppIcon() {
  if (extension_app_icon_.empty()) {
    return nullptr;
  }

  return &extension_app_icon_;
}

void AppTabHelper::DidFinishNavigation(
    content::NavigationHandle* navigation_handle) {
  if (!navigation_handle->HasCommitted() ||
      !navigation_handle->IsInPrimaryMainFrame()) {
    return;
  }

  content::BrowserContext* context = web_contents()->GetBrowserContext();
  ExtensionRegistry* registry = ExtensionRegistry::Get(context);
  const ExtensionSet& enabled_extensions = registry->enabled_extensions();

  Browser* browser = chrome::FindBrowserWithTab(web_contents());
  if (browser && (browser->is_type_app() || browser->is_type_app_popup())) {
    const Extension* extension = registry->GetInstalledExtension(
        web_app::GetAppIdFromApplicationName(browser->app_name()));
    if (extension && AppLaunchInfo::GetFullLaunchURL(extension).is_valid()) {
      DCHECK(extension->is_app());
      SetExtensionApp(extension);
    }
  } else {
    UpdateExtensionAppIcon(
        enabled_extensions.GetExtensionOrAppByURL(navigation_handle->GetURL()));
  }
}

void AppTabHelper::DidCloneToNewWebContents(WebContents* old_web_contents,
                                            WebContents* new_web_contents) {
  // When the WebContents that this is attached to is cloned, give the new clone
  // a AppTabHelper and copy state over.
  CreateForWebContents(new_web_contents);
  AppTabHelper* new_helper = FromWebContents(new_web_contents);

  new_helper->SetExtensionApp(extension_app_);
  new_helper->extension_app_icon_ = extension_app_icon_;
}

const Extension* AppTabHelper::GetExtension(
    const ExtensionId& extension_app_id) {
  if (extension_app_id.empty()) {
    return nullptr;
  }

  content::BrowserContext* context = web_contents()->GetBrowserContext();
  return ExtensionRegistry::Get(context)->enabled_extensions().GetByID(
      extension_app_id);
}

void AppTabHelper::UpdateExtensionAppIcon(const Extension* extension) {
  extension_app_icon_.reset();
  // Ensure previously enqueued callbacks are ignored.
  image_loader_ptr_factory_.InvalidateWeakPtrs();

  // Enqueue OnImageLoaded callback.
  if (extension) {
    ImageLoader* loader = ImageLoader::Get(profile_);
    loader->LoadImageAsync(
        extension,
        IconsInfo::GetIconResource(extension,
                                   extension_misc::EXTENSION_ICON_SMALL,
                                   ExtensionIconSet::Match::kBigger),
        gfx::Size(extension_misc::EXTENSION_ICON_SMALL,
                  extension_misc::EXTENSION_ICON_SMALL),
        base::BindOnce(&AppTabHelper::OnImageLoaded,
                       image_loader_ptr_factory_.GetWeakPtr()));
  }
}

void AppTabHelper::OnImageLoaded(const gfx::Image& image) {
  if (!image.IsEmpty()) {
    extension_app_icon_ = *image.ToSkBitmap();
    web_contents()->NotifyNavigationStateChanged(content::INVALIDATE_TYPE_TAB);
  }
}

void AppTabHelper::OnExtensionUnloaded(content::BrowserContext* browser_context,
                                       const Extension* extension,
                                       UnloadedExtensionReason reason) {
  if (!extension_app_) {
    return;
  }

  if (extension == extension_app_) {
    SetExtensionApp(nullptr);
  }
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(AppTabHelper);

}  // namespace extensions