blob: be306009199d1b7720504dc8ff5bfbffb42d6ff2 [file] [log] [blame]
// 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