blob: ad11ca7bb844c377e6d9c03d2b2d8bf0f0f558e0 [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/ui/extensions/extensions_menu_view_model.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.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/extensions/tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/tabs/tab_list_interface.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
#include "chrome/browser/ui/views/extensions/extensions_menu_view_platform_delegate_views.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
namespace {
using PermissionsManager = extensions::PermissionsManager;
using SitePermissionsHelper = extensions::SitePermissionsHelper;
// Returns the extension corresponding to `extension_id` on `profile`.
const extensions::Extension* GetExtension(
Profile& profile,
const extensions::ExtensionId& extension_id) {
return extensions::ExtensionRegistry::Get(&profile)
->enabled_extensions()
.GetByID(extension_id);
}
// Returns whether user can select the site access for `extension` on
// `web_contents`.
bool CanUserCustomizeExtensionSiteAccess(
const extensions::Extension& extension,
Profile& profile,
const ToolbarActionsModel& toolbar_model,
content::WebContents& web_contents) {
const GURL& url = web_contents.GetLastCommittedURL();
if (toolbar_model.IsRestrictedUrl(url)) {
// We don't allow customization of restricted sites (e.g.
// chrome://settings).
return false;
}
if (extension.permissions_data()->IsPolicyBlockedHost(url)) {
// Users can't customize the site access of policy-blocked sites.
return false;
}
if (extensions::ExtensionSystem::Get(&profile)
->management_policy()
->HasEnterpriseForcedAccess(extension)) {
// Users can't customize the site access of enterprise-installed extensions.
return false;
}
// The extension wants site access if it at least wants "on click" access.
auto* permissions_manager = PermissionsManager::Get(&profile);
bool extension_wants_access = permissions_manager->CanUserSelectSiteAccess(
extension, url, PermissionsManager::UserSiteAccess::kOnClick);
if (!extension_wants_access) {
// Users can't customize site access of extensions that don't want access to
// begin with.
return false;
}
// Users can only customize site access when they have allowed all extensions
// to be customizable on the site.
return permissions_manager->GetUserSiteSetting(
web_contents.GetPrimaryMainFrame()->GetLastCommittedOrigin()) ==
PermissionsManager::UserSiteSetting::kCustomizeByExtension;
}
void LogSiteAccessUpdate(PermissionsManager::UserSiteAccess site_access) {
switch (site_access) {
case PermissionsManager::UserSiteAccess::kOnClick:
base::RecordAction(
base::UserMetricsAction("Extensions.Menu.OnClickSelected"));
break;
case PermissionsManager::UserSiteAccess::kOnSite:
base::RecordAction(
base::UserMetricsAction("Extensions.Menu.OnSiteSelected"));
break;
case PermissionsManager::UserSiteAccess::kOnAllSites:
base::RecordAction(
base::UserMetricsAction("Extensions.Menu.OnAllSitesSelected"));
break;
default:
NOTREACHED() << "Unknown site access";
}
}
void LogSiteSettingsUpdate(PermissionsManager::UserSiteSetting site_setting) {
switch (site_setting) {
case PermissionsManager::UserSiteSetting::kCustomizeByExtension:
base::RecordAction(
base::UserMetricsAction("Extensions.Menu.AllowByExtensionSelected"));
break;
case PermissionsManager::UserSiteSetting::kBlockAllExtensions:
base::RecordAction(
base::UserMetricsAction("Extensions.Menu.ExtensionsBlockedSelected"));
break;
case PermissionsManager::UserSiteSetting::kGrantAllExtensions:
default:
NOTREACHED() << "Invalid site setting update";
}
}
} // namespace
ExtensionsMenuViewModel::ExtensionsMenuViewModel(
BrowserWindowInterface* browser,
std::unique_ptr<ExtensionsMenuViewPlatformDelegate> platform_delegate)
: browser_(browser),
platform_delegate_(std::move(platform_delegate)),
toolbar_model_(ToolbarActionsModel::Get(browser_->GetProfile())) {
platform_delegate_->AttachToModel(this);
permissions_manager_observation_.Observe(
extensions::PermissionsManager::Get(browser_->GetProfile()));
toolbar_model_observation_.Observe(toolbar_model_.get());
}
ExtensionsMenuViewModel::~ExtensionsMenuViewModel() {
platform_delegate_->DetachFromModel();
}
void ExtensionsMenuViewModel::UpdateSiteAccess(
const extensions::ExtensionId& extension_id,
PermissionsManager::UserSiteAccess site_access) {
LogSiteAccessUpdate(site_access);
Profile* profile = browser_->GetProfile();
SitePermissionsHelper permissions(profile);
permissions.UpdateSiteAccess(*GetExtension(*profile, extension_id),
GetActiveWebContents(), site_access);
}
void ExtensionsMenuViewModel::GrantSiteAccess(
const extensions::ExtensionId& extension_id) {
auto* profile = browser_->GetProfile();
const extensions::Extension* extension = GetExtension(*profile, extension_id);
content::WebContents* web_contents = GetActiveWebContents();
auto* toolbar_model = ToolbarActionsModel::Get(profile);
auto url = web_contents->GetLastCommittedURL();
auto* permissions_manager = PermissionsManager::Get(profile);
// Can only grant site access when user can customize the extension's site
// access and it's currently on click.
auto current_site_access =
permissions_manager->GetUserSiteAccess(*extension, url);
CHECK(CanUserCustomizeExtensionSiteAccess(*extension, *profile,
*toolbar_model, *web_contents));
CHECK_EQ(current_site_access, PermissionsManager::UserSiteAccess::kOnClick);
// Update site access when extension requested host permissions for the
// current site (that is, site access was withheld).
PermissionsManager::ExtensionSiteAccess extension_site_access =
permissions_manager->GetSiteAccess(*extension, url);
if (extension_site_access.withheld_site_access ||
extension_site_access.withheld_all_sites_access) {
// Restore to previous access by looking whether broad site access was
// previously granted.
PermissionsManager::UserSiteAccess new_site_access =
permissions_manager->HasPreviousBroadSiteAccess(extension_id)
? PermissionsManager::UserSiteAccess::kOnAllSites
: PermissionsManager::UserSiteAccess::kOnSite;
SitePermissionsHelper permissions_helper(profile);
permissions_helper.UpdateSiteAccess(*extension, web_contents,
new_site_access);
return;
}
// Otherwise, grant one-time access (e.g. extension with activeTab is
// granted access).
extensions::ExtensionActionRunner* action_runner =
extensions::ExtensionActionRunner::GetForWebContents(web_contents);
if (action_runner) {
action_runner->GrantTabPermissions({extension});
}
}
void ExtensionsMenuViewModel::RevokeSiteAccess(
const extensions::ExtensionId& extension_id) {
auto* profile = browser_->GetProfile();
const extensions::Extension* extension = GetExtension(*profile, extension_id);
content::WebContents* web_contents = GetActiveWebContents();
auto* toolbar_model = ToolbarActionsModel::Get(profile);
// Can only revoke site access when user can customize the extension's site
// access.
CHECK(CanUserCustomizeExtensionSiteAccess(*extension, *profile,
*toolbar_model, *web_contents));
auto url = web_contents->GetLastCommittedURL();
auto* permissions_manager = PermissionsManager::Get(profile);
auto current_site_access =
permissions_manager->GetUserSiteAccess(*extension, url);
PermissionsManager::ExtensionSiteAccess extension_site_access =
permissions_manager->GetSiteAccess(*extension, url);
// Update site access to "on click" when extension requested, and was granted,
// host permissions for the current site (that is, extension has site access).
if (extension_site_access.has_site_access ||
extension_site_access.has_all_sites_access) {
CHECK_NE(current_site_access, PermissionsManager::UserSiteAccess::kOnClick);
SitePermissionsHelper permissions_helper(profile);
permissions_helper.UpdateSiteAccess(
*extension, web_contents, PermissionsManager::UserSiteAccess::kOnClick);
return;
}
// Otherwise, extension has one-time access and we need to clear tab
// permissions (e.g extension with activeTab was granted one-time access).
CHECK_EQ(current_site_access, PermissionsManager::UserSiteAccess::kOnClick);
extensions::ActiveTabPermissionGranter::FromWebContents(web_contents)
->ClearActiveExtensionAndNotify(extension_id);
auto* action_runner =
extensions::ExtensionActionRunner::GetForWebContents(web_contents);
if (action_runner) {
action_runner->ShowReloadPageBubble({GetExtension(*profile, extension_id)});
}
}
void ExtensionsMenuViewModel::UpdateSiteSetting(
extensions::PermissionsManager::UserSiteSetting site_setting) {
content::WebContents* web_contents = GetActiveWebContents();
const url::Origin& origin =
GetActiveWebContents()->GetPrimaryMainFrame()->GetLastCommittedOrigin();
extensions::TabHelper::FromWebContents(web_contents)
->SetReloadRequired(site_setting);
PermissionsManager::Get(browser_->GetProfile())
->UpdateUserSiteSetting(origin, site_setting);
LogSiteSettingsUpdate(site_setting);
}
void ExtensionsMenuViewModel::OnHostAccessRequestAdded(
const extensions::ExtensionId& extension_id,
int tab_id) {
// Ignore requests for other tabs.
auto* web_contents = GetActiveWebContents();
int current_tab_id = extensions::ExtensionTabUtil::GetTabId(web_contents);
if (tab_id != current_tab_id) {
return;
}
// Ignore requests that are not active.
auto* permissions_manager =
extensions::PermissionsManager::Get(browser_->GetProfile());
if (!permissions_manager->HasActiveHostAccessRequest(tab_id, extension_id)) {
return;
}
platform_delegate_->OnAccessRequestAdded(extension_id, web_contents);
}
void ExtensionsMenuViewModel::OnHostAccessRequestRemoved(
const extensions::ExtensionId& extension_id,
int tab_id) {
// Ignore requests for other tabs.
auto* web_contents = GetActiveWebContents();
int current_tab_id = extensions::ExtensionTabUtil::GetTabId(web_contents);
if (tab_id != current_tab_id) {
return;
}
platform_delegate_->OnAccessRequestRemoved(extension_id);
}
void ExtensionsMenuViewModel::OnToolbarActionAdded(
const ToolbarActionsModel::ActionId& action_id) {
platform_delegate_->OnActionAdded(action_id);
}
void ExtensionsMenuViewModel::OnToolbarActionRemoved(
const ToolbarActionsModel::ActionId& action_id) {
// TODO(crbug.com/449814184): implement and remove observer from
// ExtensionsMenuViewPlatformDelegateViews.
}
void ExtensionsMenuViewModel::OnToolbarActionUpdated(
const ToolbarActionsModel::ActionId& action_id) {
// TODO(crbug.com/449814184): implement and remove observer from
// ExtensionsMenuViewPlatformDelegateViews.
}
void ExtensionsMenuViewModel::OnToolbarModelInitialized() {
// TODO(crbug.com/449814184): implement and remove observer from
// ExtensionsMenuViewPlatformDelegateViews.
}
void ExtensionsMenuViewModel::OnToolbarPinnedActionsChanged() {
// TODO(crbug.com/449814184): implement and remove observer from
// ExtensionsMenuViewPlatformDelegateViews.
}
content::WebContents* ExtensionsMenuViewModel::GetActiveWebContents() {
return TabListInterface::From(browser_)->GetActiveTab()->GetContents();
}