blob: 825805ed30443947b5dbaadb697f904b4ec39ce6 [file] [log] [blame]
// 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/extensions/tab_helper.h"
#include <memory>
#include "base/check_op.h"
#include "base/notreached.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/activity_log/activity_log.h"
#include "chrome/browser/extensions/extension_action_runner.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/permissions/active_tab_permission_granter.h"
#include "chrome/browser/extensions/permissions/site_permissions_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper_factory.h"
#include "chrome/common/buildflags.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "extensions/browser/api/declarative/rules_registry_service.h"
#include "extensions/browser/api/declarative_content/content_rules_registry.h"
#include "extensions/browser/extension_web_contents_observer.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_resource.h"
#include "extensions/common/manifest.h"
#include "extensions/common/permissions/api_permission.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/extensions/api/bookmark_manager_private/bookmark_manager_private_api.h"
#endif
#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 {
namespace {
void DisableBackForwardCacheIfNecessary(
const ExtensionSet& enabled_extensions,
content::BrowserContext* context,
content::NavigationHandle* navigation_handle) {
// User data key for caching if bfcache is disabled.
static const char kIsBFCacheDisabledKey[] =
"extensions.backforward.browsercontext";
if (!content::BackForwardCache::IsBackForwardCacheFeatureEnabled() ||
context->GetUserData(kIsBFCacheDisabledKey)) {
return;
}
}
} // namespace
TabHelper::~TabHelper() = default;
TabHelper::TabHelper(content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
content::WebContentsUserData<TabHelper>(*web_contents),
profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())),
script_executor_(std::make_unique<ScriptExecutor>(web_contents)),
extension_action_runner_(
std::make_unique<ExtensionActionRunner>(web_contents)),
declarative_net_request_helper_(web_contents) {
// The ActiveTabPermissionManager requires a session ID; ensure this
// WebContents has one.
CreateSessionServiceTabHelper(web_contents);
// The [this] is safe because ForEachRenderFrameHost() is synchronous.
web_contents->ForEachRenderFrameHost(
[this](content::RenderFrameHost* host) { SetTabId(host); });
int tab_id = sessions::SessionTabHelper::IdForTab(web_contents).id();
// TODO(crbug.com/393179880): Pull this creation out of TabHelper once
// tab id assignment can be done on desktop Android.
ActiveTabPermissionGranter::CreateForWebContents(web_contents, tab_id,
profile_);
ActivityLog::GetInstance(profile_)->ObserveScripts(script_executor_.get());
InvokeForContentRulesRegistries([this](ContentRulesRegistry* registry) {
registry->MonitorWebContentsForRuleEvaluation(this->web_contents());
});
ExtensionWebContentsObserver::GetForWebContents(web_contents)
->dispatcher()
->set_delegate(this);
registry_observation_.Observe(
ExtensionRegistry::Get(web_contents->GetBrowserContext()));
#if !BUILDFLAG(IS_ANDROID)
// The Android bookmark manager is native UI, not web UI, so this event router
// isn't needed on desktop Android.
BookmarkManagerPrivateDragEventRouter::CreateForWebContents(web_contents);
#endif
}
void TabHelper::SetReloadRequired(
PermissionsManager::UserSiteSetting site_setting) {
switch (site_setting) {
case PermissionsManager::UserSiteSetting::kGrantAllExtensions: {
// Granting access to all extensions is allowed iff feature is
// enabled, and it shouldn't be enabled anywhere where this is called.
NOTREACHED();
}
case PermissionsManager::UserSiteSetting::kBlockAllExtensions: {
// A reload is required if any extension that had site access will lose
// it.
content::WebContents* web_contents = GetVisibleWebContents();
SitePermissionsHelper permissions_helper(profile_);
const ExtensionSet& extensions =
ExtensionRegistry::Get(profile_)->enabled_extensions();
reload_required_ = std::ranges::any_of(
extensions, [&permissions_helper,
web_contents](scoped_refptr<const Extension> extension) {
return permissions_helper.GetSiteInteraction(*extension,
web_contents) ==
SitePermissionsHelper::SiteInteraction::kGranted;
});
break;
}
case PermissionsManager::UserSiteSetting::kCustomizeByExtension:
// When the user selects "customize by extension" it means previously all
// extensions were blocked and each extension's page access is set as
// "denied". Blocked actions in the ExtensionActionRunner are computed by
// checking if a page access is "withheld". Therefore, we always need a
// refresh since we don't know if there are any extensions that would have
// wanted to run if the page had not been restricted by the user.
reload_required_ = true;
break;
}
}
bool TabHelper::IsReloadRequired() {
return reload_required_;
}
void TabHelper::OnWatchedPageChanged(
const std::vector<std::string>& css_selectors) {
InvokeForContentRulesRegistries(
[this, css_selectors](ContentRulesRegistry* registry) {
registry->OnWatchedPageChanged(web_contents(), css_selectors);
});
}
// Encapsulates the logic to decide which ContentRulesRegistries need to be
// invoked, depending on whether this WebContents is associated with an Original
// or OffTheRecord profile. In the latter case, we need to invoke on both the
// Original and OffTheRecord ContentRulesRegistries since the Original registry
// handles spanning-mode incognito extensions.
template <class Func>
void TabHelper::InvokeForContentRulesRegistries(const Func& func) {
RulesRegistryService* rules_registry_service =
RulesRegistryService::Get(profile_);
if (rules_registry_service) {
func(rules_registry_service->content_rules_registry());
if (profile_->IsOffTheRecord()) {
// The original profile's content rules registry handles rules for
// spanning extensions in incognito profiles, so invoke it also.
RulesRegistryService* original_profile_rules_registry_service =
RulesRegistryService::Get(profile_->GetOriginalProfile());
DCHECK_NE(rules_registry_service,
original_profile_rules_registry_service);
if (original_profile_rules_registry_service) {
func(original_profile_rules_registry_service->content_rules_registry());
}
}
}
}
void TabHelper::RenderFrameCreated(content::RenderFrameHost* host) {
SetTabId(host);
}
void TabHelper::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->HasCommitted() ||
!navigation_handle->IsInPrimaryMainFrame()) {
return;
}
InvokeForContentRulesRegistries(
[this, navigation_handle](ContentRulesRegistry* registry) {
registry->DidFinishNavigation(web_contents(), navigation_handle);
});
content::BrowserContext* context = web_contents()->GetBrowserContext();
ExtensionRegistry* registry = ExtensionRegistry::Get(context);
const ExtensionSet& enabled_extensions = registry->enabled_extensions();
DisableBackForwardCacheIfNecessary(enabled_extensions, context,
navigation_handle);
// Reset the `reload_required_` data member, since a page navigation acts as a
// page refresh.
reload_required_ = false;
}
void TabHelper::DidCloneToNewWebContents(WebContents* old_web_contents,
WebContents* new_web_contents) {
// When the WebContents that this is attached to is cloned, give the new clone
// a TabHelper.
// TODO(jamescook): Do we still need to do this if we're not copying any
// state?
CreateForWebContents(new_web_contents);
}
void TabHelper::WebContentsDestroyed() {
InvokeForContentRulesRegistries([this](ContentRulesRegistry* registry) {
registry->WebContentsDestroyed(web_contents());
});
reload_required_ = false;
}
WindowController* TabHelper::GetExtensionWindowController() const {
return ExtensionTabUtil::GetWindowControllerOfTab(web_contents());
}
WebContents* TabHelper::GetAssociatedWebContents() const {
return web_contents();
}
void TabHelper::OnExtensionLoaded(content::BrowserContext* browser_context,
const Extension* extension) {
// Clear the back forward cache for the associated tab to accommodate for any
// side effects of loading/unloading the extension.
web_contents()->GetController().GetBackForwardCache().Flush();
}
void TabHelper::OnExtensionUnloaded(content::BrowserContext* browser_context,
const Extension* extension,
UnloadedExtensionReason reason) {
// Clear the back forward cache for the associated tab to accommodate for any
// side effects of loading/unloading the extension.
web_contents()->GetController().GetBackForwardCache().Flush();
// Technically, the refresh is no longer needed if the unloaded extension was
// the only one causing `refresh_required`. However, we would need to track
// which are the extensions causing the reload, and sometimes it is not
// specific to an extensions. Also, this is a very edge case (site settings
// changed and then extension is installed externally), so it's fine to not
// handle it.
}
void TabHelper::SetTabId(content::RenderFrameHost* render_frame_host) {
// When this is called from the TabHelper constructor during WebContents
// creation, the renderer-side Frame object would not have been created yet.
// We should wait for RenderFrameCreated() to happen, to avoid sending this
// message twice.
if (render_frame_host->IsRenderFrameLive()) {
SessionID id = sessions::SessionTabHelper::IdForTab(web_contents());
CHECK(id.is_valid());
auto* local_frame =
ExtensionWebContentsObserver::GetForWebContents(web_contents())
->GetLocalFrame(render_frame_host);
if (!local_frame) {
return;
}
local_frame->SetTabId(id.id());
}
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(TabHelper);
} // namespace extensions