blob: 044bc94d0379742262fd0620ab50224c92a788f0 [file] [log] [blame]
// Copyright 2023 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/views/extensions/extensions_menu_view_controller.h"
#include <algorithm>
#include "base/functional/bind.h"
#include "base/i18n/case_conversion.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/notreached.h"
#include "chrome/browser/extensions/extension_action_runner.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/extension_ui_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.h"
#include "chrome/browser/ui/extensions/extension_action_view_controller.h"
#include "chrome/browser/ui/extensions/extensions_container.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/extensions/extension_view_utils.h"
#include "chrome/browser/ui/views/extensions/extensions_menu_item_view.h"
#include "chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h"
#include "chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/permissions_manager.h"
#include "extensions/common/extension_id.h"
#include "ui/base/metadata/metadata_types.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"
namespace {
using PermissionsManager = extensions::PermissionsManager;
using SitePermissionsHelper = extensions::SitePermissionsHelper;
// Returns the state for the main page in the menu.
enum class MainPageState {
// Site is restricted to all extensions.
kRestrictedSite,
// Site is restricted all non-enterprise extensions by policy.
kPolicyBlockedSite,
// User blocked all extensions access to the site.
kUserBlockedSite,
// User can customize each extension's access to the site.
kUserCustomizedSite,
};
MainPageState GetMainPageState(Profile& profile,
const ToolbarActionsModel& toolbar_model,
content::WebContents& web_contents) {
const GURL& url = web_contents.GetLastCommittedURL();
if (toolbar_model.IsRestrictedUrl(url)) {
return MainPageState::kRestrictedSite;
}
if (toolbar_model.IsPolicyBlockedHost(url)) {
return MainPageState::kPolicyBlockedSite;
}
PermissionsManager::UserSiteSetting site_setting =
PermissionsManager::Get(&profile)->GetUserSiteSetting(
web_contents.GetPrimaryMainFrame()->GetLastCommittedOrigin());
if (site_setting ==
PermissionsManager::UserSiteSetting::kBlockAllExtensions) {
return MainPageState::kUserBlockedSite;
}
return MainPageState::kUserCustomizedSite;
}
// Returns the extension for `extension_id`.
const extensions::Extension* GetExtension(
Browser* browser,
extensions::ExtensionId extension_id) {
return extensions::ExtensionRegistry::Get(browser->profile())
->enabled_extensions()
.GetByID(extension_id);
}
// Returns sorted extension ids based on their extensions name.
std::vector<std::string> SortExtensionsByName(
ToolbarActionsModel& toolbar_model) {
auto sort_by_name = [&toolbar_model](const ToolbarActionsModel::ActionId a,
const ToolbarActionsModel::ActionId b) {
return base::i18n::ToLower(toolbar_model.GetExtensionName(a)) <
base::i18n::ToLower(toolbar_model.GetExtensionName(b));
};
std::vector<std::string> sorted_ids(toolbar_model.action_ids().begin(),
toolbar_model.action_ids().end());
std::sort(sorted_ids.begin(), sorted_ids.end(), sort_by_name);
return sorted_ids;
}
// Returns the index of `action_id` in the toolbar model actions based on the
// extensions name alphabetical order.
size_t FindIndex(ToolbarActionsModel& toolbar_model,
const ToolbarActionsModel::ActionId& action_id) {
std::u16string extension_name =
base::i18n::ToLower(toolbar_model.GetExtensionName(action_id));
auto sorted_action_ids = SortExtensionsByName(toolbar_model);
return static_cast<size_t>(
std::ranges::lower_bound(sorted_action_ids, extension_name, {},
[&toolbar_model](std::string id) {
return base::i18n::ToLower(
toolbar_model.GetExtensionName(id));
}) -
sorted_action_ids.begin());
}
// Returns the main page, if it is the correct type.
ExtensionsMenuMainPageView* GetMainPage(views::View* page) {
return views::AsViewClass<ExtensionsMenuMainPageView>(page);
}
// Returns the site permissions page, if it is the correct type.
ExtensionsMenuSitePermissionsPageView* GetSitePermissionsPage(
views::View* page) {
return views::AsViewClass<ExtensionsMenuSitePermissionsPageView>(page);
}
// Returns whether the site permissions button should be visible.
bool IsSitePermissionsButtonVisible(const extensions::Extension& extension,
Profile& profile,
const ToolbarActionsModel& toolbar_model,
content::WebContents& web_contents) {
// Button is never visible when site is restricted.
if (toolbar_model.IsRestrictedUrl(web_contents.GetLastCommittedURL())) {
return false;
}
PermissionsManager::UserSiteSetting user_site_setting =
PermissionsManager::Get(&profile)->GetUserSiteSetting(
web_contents.GetPrimaryMainFrame()->GetLastCommittedOrigin());
switch (user_site_setting) {
case PermissionsManager::UserSiteSetting::kCustomizeByExtension: {
// Extensions should always display the button.
return true;
}
case PermissionsManager::UserSiteSetting::kBlockAllExtensions: {
// Extension should only display the button when it's an enterprise
// extension and has granted access.
bool enterprise_forced_access =
extensions::ExtensionSystem::Get(&profile)
->management_policy()
->HasEnterpriseForcedAccess(extension);
SitePermissionsHelper::SiteInteraction site_interaction =
SitePermissionsHelper(&profile).GetSiteInteraction(extension,
&web_contents);
return enterprise_forced_access &&
site_interaction ==
SitePermissionsHelper::SiteInteraction::kGranted;
}
case PermissionsManager::UserSiteSetting::kGrantAllExtensions: {
NOTREACHED();
}
}
}
// 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;
}
// Returns the state for the `extension`'s site permissions button.
ExtensionMenuItemView::SitePermissionsButtonState GetSitePermissionsButtonState(
const extensions::Extension& extension,
Profile& profile,
const ToolbarActionsModel& toolbar_model,
content::WebContents& web_contents) {
bool is_site_permissions_button_visible = IsSitePermissionsButtonVisible(
extension, profile, toolbar_model, web_contents);
if (!is_site_permissions_button_visible) {
return ExtensionMenuItemView::SitePermissionsButtonState::kHidden;
}
bool is_site_permissions_button_enabled = CanUserCustomizeExtensionSiteAccess(
extension, profile, toolbar_model, web_contents);
return is_site_permissions_button_enabled
? ExtensionMenuItemView::SitePermissionsButtonState::kEnabled
: ExtensionMenuItemView::SitePermissionsButtonState::kDisabled;
}
// Returns the sites access displayed by the `extension`'s site permissions
// button.
ExtensionMenuItemView::SitePermissionsButtonAccess
GetSitePermissionsButtonAccess(const extensions::Extension& extension,
Profile& profile,
const ToolbarActionsModel& toolbar_model,
content::WebContents& web_contents) {
auto site_interaction = SitePermissionsHelper(&profile).GetSiteInteraction(
extension, &web_contents);
if (site_interaction == SitePermissionsHelper::SiteInteraction::kNone) {
return ExtensionMenuItemView::SitePermissionsButtonAccess::kNone;
}
auto site_access = PermissionsManager::Get(&profile)->GetUserSiteAccess(
extension, web_contents.GetLastCommittedURL());
switch (site_access) {
case PermissionsManager::UserSiteAccess::kOnClick:
return ExtensionMenuItemView::SitePermissionsButtonAccess::kOnClick;
case PermissionsManager::UserSiteAccess::kOnSite:
return ExtensionMenuItemView::SitePermissionsButtonAccess::kOnSite;
case PermissionsManager::UserSiteAccess::kOnAllSites:
return ExtensionMenuItemView::SitePermissionsButtonAccess::kOnAllSites;
}
}
// Returns the state for the `extension`'s site access toggle button.
ExtensionMenuItemView::SiteAccessToggleState GetSiteAccessToggleState(
const extensions::Extension& extension,
Profile& profile,
const ToolbarActionsModel& toolbar_model,
content::WebContents& web_contents) {
if (!CanUserCustomizeExtensionSiteAccess(extension, profile, toolbar_model,
web_contents)) {
return ExtensionMenuItemView::SiteAccessToggleState::kHidden;
}
// Button is on iff the extension has access to the site.
auto site_interaction = SitePermissionsHelper(&profile).GetSiteInteraction(
extension, &web_contents);
return site_interaction == SitePermissionsHelper::SiteInteraction::kGranted
? ExtensionMenuItemView::SiteAccessToggleState::kOn
: ExtensionMenuItemView::SiteAccessToggleState::kOff;
}
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";
}
}
} // namespace
ExtensionsMenuViewController::ExtensionsMenuViewController(
Browser* browser,
ExtensionsContainer* extensions_container,
views::View* bubble_contents)
: browser_(browser),
extensions_container_(extensions_container),
bubble_contents_(bubble_contents),
toolbar_model_(ToolbarActionsModel::Get(browser_->profile())) {
browser_->tab_strip_model()->AddObserver(this);
toolbar_model_observation_.Observe(toolbar_model_.get());
permissions_manager_observation_.Observe(
PermissionsManager::Get(browser_->profile()));
}
// Note: No need to call TabStripModel::RemoveObserver(), because it's handled
// directly within TabStripModelObserver::~TabStripModelObserver().
ExtensionsMenuViewController::~ExtensionsMenuViewController() = default;
void ExtensionsMenuViewController::OpenMainPage() {
auto main_page = std::make_unique<ExtensionsMenuMainPageView>(browser_, this);
UpdateMainPage(main_page.get(), GetActiveWebContents());
PopulateMainPage(main_page.get());
SwitchToPage(std::move(main_page));
}
void ExtensionsMenuViewController::OpenSitePermissionsPage(
const extensions::ExtensionId& extension_id) {
CHECK(CanUserCustomizeExtensionSiteAccess(
*GetExtension(browser_, extension_id), *browser_->profile(),
*toolbar_model_, *GetActiveWebContents()));
auto site_permissions_page =
std::make_unique<ExtensionsMenuSitePermissionsPageView>(
browser_, extension_id, this);
UpdateSitePermissionsPage(site_permissions_page.get(),
GetActiveWebContents());
SwitchToPage(std::move(site_permissions_page));
base::RecordAction(
base::UserMetricsAction("Extensions.Menu.SitePermissionsPageOpened"));
}
void ExtensionsMenuViewController::CloseBubble() {
bubble_contents_->GetWidget()->CloseWithReason(
views::Widget::ClosedReason::kCloseButtonClicked);
}
void ExtensionsMenuViewController::OnSiteAccessSelected(
const extensions::ExtensionId& extension_id,
PermissionsManager::UserSiteAccess site_access) {
LogSiteAccessUpdate(site_access);
SitePermissionsHelper permissions(browser_->profile());
permissions.UpdateSiteAccess(*GetExtension(browser_, extension_id),
GetActiveWebContents(), site_access);
}
void ExtensionsMenuViewController::OnSiteSettingsToggleButtonPressed(
bool is_on) {
content::WebContents* web_contents = GetActiveWebContents();
const url::Origin& origin =
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin();
PermissionsManager::UserSiteSetting site_setting =
is_on ? PermissionsManager::UserSiteSetting::kCustomizeByExtension
: PermissionsManager::UserSiteSetting::kBlockAllExtensions;
extensions::TabHelper::FromWebContents(web_contents)
->SetReloadRequired(site_setting);
PermissionsManager::Get(browser_->profile())
->UpdateUserSiteSetting(origin, site_setting);
if (is_on) {
base::RecordAction(
base::UserMetricsAction("Extensions.Menu.AllowByExtensionSelected"));
} else {
base::RecordAction(
base::UserMetricsAction("Extensions.Menu.ExtensionsBlockedSelected"));
}
}
void ExtensionsMenuViewController::OnExtensionToggleSelected(
const extensions::ExtensionId& extension_id,
bool is_on) {
const extensions::Extension* extension = GetExtension(browser_, extension_id);
content::WebContents* web_contents = GetActiveWebContents();
CHECK(CanUserCustomizeExtensionSiteAccess(*extension, *browser_->profile(),
*toolbar_model_, *web_contents));
SitePermissionsHelper permissions_helper(browser_->profile());
auto* permissions_manager = PermissionsManager::Get(browser_->profile());
auto current_site_access = permissions_manager->GetUserSiteAccess(
*extension, web_contents->GetLastCommittedURL());
PermissionsManager::ExtensionSiteAccess extension_site_access =
permissions_manager->GetSiteAccess(*extension,
web_contents->GetLastCommittedURL());
// Grant extension site access when extension is toggled on.
if (is_on) {
DCHECK_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).
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;
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});
}
return;
}
// Revoke extension's site access when extension is toggled off.
// 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) {
DCHECK_NE(current_site_access,
PermissionsManager::UserSiteAccess::kOnClick);
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).
DCHECK_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(browser_, extension_id)});
}
}
void ExtensionsMenuViewController::OnReloadPageButtonClicked() {
GetActiveWebContents()->GetController().Reload(content::ReloadType::NORMAL,
false);
}
void ExtensionsMenuViewController::OnAllowExtensionClicked(
const extensions::ExtensionId& extension_id) {
content::WebContents* web_contents = GetActiveWebContents();
extensions::ExtensionActionRunner* action_runner =
extensions::ExtensionActionRunner::GetForWebContents(web_contents);
if (!action_runner) {
return;
}
// Accepting a site access request grants always access to the site.
extensions::SitePermissionsHelper(browser_->profile())
.UpdateSiteAccess(
*GetExtension(browser_, extension_id), web_contents,
extensions::PermissionsManager::UserSiteAccess::kOnSite);
base::RecordAction(base::UserMetricsAction(
"Extensions.Toolbar.ExtensionActivatedFromAllowingRequestAccessInMenu"));
}
void ExtensionsMenuViewController::OnDismissExtensionClicked(
const extensions::ExtensionId& extension_id) {
auto* permissions_manager = PermissionsManager::Get(browser_->profile());
CHECK(permissions_manager);
content::WebContents* web_contents = GetActiveWebContents();
int tab_id = extensions::ExtensionTabUtil::GetTabId(web_contents);
permissions_manager->UserDismissedHostAccessRequest(web_contents, tab_id,
extension_id);
base::RecordAction(base::UserMetricsAction(
"Extensions.Toolbar.ExtensionRequestDismissedFromMenu"));
}
void ExtensionsMenuViewController::OnShowRequestsTogglePressed(
const extensions::ExtensionId& extension_id,
bool is_on) {
extensions::SitePermissionsHelper(browser_->profile())
.SetShowAccessRequestsInToolbar(extension_id, is_on);
if (is_on) {
base::RecordAction(base::UserMetricsAction(
"Extensions.Menu.ShowRequestsInToolbarPressed"));
} else {
base::RecordAction(base::UserMetricsAction(
"Extensions.Menu.HideRequestsInToolbarPressed"));
}
}
void ExtensionsMenuViewController::TabChangedAt(content::WebContents* contents,
int index,
TabChangeType change_type) {
bool should_update_page = false;
switch (change_type) {
case TabChangeType::kAll:
should_update_page = true;
break;
case TabChangeType::kLoadingOnly:
should_update_page = false;
break;
}
if (!should_update_page || GetActiveWebContents() != contents) {
return;
}
UpdatePage(contents);
}
void ExtensionsMenuViewController::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
CHECK_EQ(tab_strip_model, browser_->tab_strip_model());
content::WebContents* web_contents = GetActiveWebContents();
if (!selection.active_tab_changed() || !web_contents) {
return;
}
UpdatePage(web_contents);
}
void ExtensionsMenuViewController::UpdatePage(
content::WebContents* web_contents) {
DCHECK(current_page_);
if (!web_contents) {
return;
}
auto* site_permissions_page = GetSitePermissionsPage(current_page_.view());
if (site_permissions_page) {
// Update site permissions page if the extension can have one.
if (CanUserCustomizeExtensionSiteAccess(
*GetExtension(browser_, site_permissions_page->extension_id()),
*browser_->profile(), *toolbar_model_, *web_contents)) {
UpdateSitePermissionsPage(site_permissions_page, web_contents);
return;
}
// Otherwise navigate back to the main page.
OpenMainPage();
return;
}
ExtensionsMenuMainPageView* main_page = GetMainPage(current_page_.view());
DCHECK(main_page);
UpdateMainPage(main_page, web_contents);
}
void ExtensionsMenuViewController::UpdateMainPage(
ExtensionsMenuMainPageView* main_page,
content::WebContents* web_contents) {
CHECK(web_contents);
auto has_enterprise_extensions = [&]() {
return std::any_of(
toolbar_model_->action_ids().begin(),
toolbar_model_->action_ids().end(),
[this](const ToolbarActionsModel::ActionId extension_id) {
auto* extension = GetExtension(browser_, extension_id);
return extensions::ExtensionSystem::Get(browser_->profile())
->management_policy()
->HasEnterpriseForcedAccess(*extension);
});
};
auto reload_required = [web_contents]() {
return extensions::TabHelper::FromWebContents(web_contents)
->IsReloadRequired();
};
std::u16string current_site =
extensions::ui_util::GetFormattedHostForDisplay(*web_contents);
int site_settings_label_id;
bool is_site_settings_toggle_visible = false;
bool is_site_settings_toggle_on = false;
bool is_site_settings_tooltip_visible = false;
bool is_reload_required = false;
bool can_have_requests = false;
MainPageState state =
GetMainPageState(*browser_->profile(), *toolbar_model_, *web_contents);
switch (state) {
case MainPageState::kRestrictedSite:
site_settings_label_id =
IDS_EXTENSIONS_MENU_SITE_SETTINGS_NOT_ALLOWED_LABEL;
is_site_settings_toggle_visible = false;
is_site_settings_toggle_on = false;
break;
case MainPageState::kPolicyBlockedSite:
site_settings_label_id =
IDS_EXTENSIONS_MENU_SITE_SETTINGS_NOT_ALLOWED_LABEL;
is_site_settings_toggle_visible = false;
is_site_settings_toggle_on = false;
is_site_settings_tooltip_visible = has_enterprise_extensions();
break;
case MainPageState::kUserBlockedSite:
site_settings_label_id = IDS_EXTENSIONS_MENU_SITE_SETTINGS_LABEL;
is_site_settings_toggle_visible = true;
is_site_settings_toggle_on = false;
is_site_settings_tooltip_visible = has_enterprise_extensions();
is_reload_required = reload_required();
break;
case MainPageState::kUserCustomizedSite:
site_settings_label_id = IDS_EXTENSIONS_MENU_SITE_SETTINGS_LABEL;
is_site_settings_toggle_visible = true;
is_site_settings_toggle_on = true;
is_reload_required = reload_required();
can_have_requests = true;
break;
}
main_page->UpdateSiteSettings(
current_site, site_settings_label_id, is_site_settings_tooltip_visible,
is_site_settings_toggle_visible, is_site_settings_toggle_on);
if (is_reload_required) {
main_page->ShowReloadSection();
} else if (can_have_requests) {
int tab_id = extensions::ExtensionTabUtil::GetTabId(web_contents);
auto* permissions_manager = PermissionsManager::Get(browser_->profile());
int index = 0;
std::vector<std::string> extension_ids =
SortExtensionsByName(*toolbar_model_);
for (const auto& extension_id : extension_ids) {
if (permissions_manager->HasActiveHostAccessRequest(tab_id,
extension_id)) {
AddOrUpdateExtensionRequestingAccess(main_page, extension_id, index,
web_contents);
++index;
} else {
// Otherwise remove its entry, if existent.
main_page->RemoveExtensionRequestingAccess(extension_id);
}
}
main_page->MaybeShowRequestsSection();
}
// Update menu items.
// TODO(crbug.com/40879945): Reorder the extensions after updating them, since
// their names can change.
std::vector<ExtensionMenuItemView*> menu_items = main_page->GetMenuItems();
for (auto* menu_item : menu_items) {
const extensions::Extension* extension =
GetExtension(browser_, menu_item->view_controller()->GetId());
CHECK(extension);
ExtensionMenuItemView::SiteAccessToggleState site_access_toggle_state =
GetSiteAccessToggleState(*extension, *browser_->profile(),
*toolbar_model_, *web_contents);
ExtensionMenuItemView::SitePermissionsButtonState
site_permissions_button_state = GetSitePermissionsButtonState(
*extension, *browser_->profile(), *toolbar_model_, *web_contents);
ExtensionMenuItemView::SitePermissionsButtonAccess
site_permissions_button_access = GetSitePermissionsButtonAccess(
*extension, *browser_->profile(), *toolbar_model_, *web_contents);
bool is_enterprise = extensions::ExtensionSystem::Get(browser_->profile())
->management_policy()
->HasEnterpriseForcedAccess(*extension);
menu_item->Update(site_access_toggle_state, site_permissions_button_state,
site_permissions_button_access, is_enterprise);
}
}
void ExtensionsMenuViewController::UpdateSitePermissionsPage(
ExtensionsMenuSitePermissionsPageView* site_permissions_page,
content::WebContents* web_contents) {
CHECK(web_contents);
auto* permissions_manager = PermissionsManager::Get(browser_->profile());
SitePermissionsHelper permissions_helper(browser_->profile());
extensions::ExtensionId extension_id = site_permissions_page->extension_id();
const extensions::Extension* extension = GetExtension(browser_, extension_id);
const GURL& url = web_contents->GetLastCommittedURL();
const int icon_size = ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_EXTENSIONS_MENU_EXTENSION_ICON_SIZE);
ToolbarActionViewController* action_controller =
extensions_container_->GetActionForId(extension_id);
std::u16string extension_name = action_controller->GetActionName();
ui::ImageModel extension_icon =
action_controller->GetIcon(web_contents, gfx::Size(icon_size, icon_size));
std::u16string current_site =
extensions::ui_util::GetFormattedHostForDisplay(*web_contents);
PermissionsManager::UserSiteAccess user_site_access =
permissions_manager->GetUserSiteAccess(*extension, url);
bool is_show_requests_toggle_on =
permissions_helper.ShowAccessRequestsInToolbar(extension_id);
bool is_on_site_enabled = permissions_manager->CanUserSelectSiteAccess(
*extension, url, PermissionsManager::UserSiteAccess::kOnSite);
bool is_on_all_sites_enabled = permissions_manager->CanUserSelectSiteAccess(
*extension, url, PermissionsManager::UserSiteAccess::kOnAllSites);
site_permissions_page->Update(extension_name, extension_icon, current_site,
user_site_access, is_show_requests_toggle_on,
is_on_site_enabled, is_on_all_sites_enabled);
}
void ExtensionsMenuViewController::OnToolbarActionAdded(
const ToolbarActionsModel::ActionId& action_id) {
DCHECK(current_page_);
// Do nothing when site permission page is opened as a new extension doesn't
// affect the site permissions page of another extension.
if (GetSitePermissionsPage(current_page_.view())) {
return;
}
// Insert a menu item for the extension when main page is opened.
auto* main_page = GetMainPage(current_page_.view());
DCHECK(main_page);
int index = FindIndex(*toolbar_model_, action_id);
InsertMenuItemMainPage(main_page, action_id, index);
}
void ExtensionsMenuViewController::OnToolbarActionRemoved(
const ToolbarActionsModel::ActionId& action_id) {
DCHECK(current_page_);
auto* site_permissions_page = GetSitePermissionsPage(current_page_.view());
if (site_permissions_page) {
// Return to the main page if site permissions page belongs to the extension
// removed.
if (site_permissions_page->extension_id() == action_id) {
OpenMainPage();
}
return;
}
// Remove the menu item for the extension when main page is opened.
auto* main_page = GetMainPage(current_page_.view());
DCHECK(main_page);
main_page->RemoveMenuItem(action_id);
}
void ExtensionsMenuViewController::OnToolbarActionUpdated(
const ToolbarActionsModel::ActionId& action_id) {
UpdatePage(GetActiveWebContents());
}
void ExtensionsMenuViewController::OnToolbarModelInitialized() {
DCHECK(current_page_);
// Toolbar model should have been initialized if site permissions page is
// open, since this page can only be reached after main page was populated
// after toolbar model was initialized.
CHECK(!GetSitePermissionsPage(current_page_.view()));
auto* main_page = GetMainPage(current_page_.view());
DCHECK(main_page);
PopulateMainPage(main_page);
}
void ExtensionsMenuViewController::OnToolbarPinnedActionsChanged() {
DCHECK(current_page_);
// Do nothing when site permissions page is opened as it doesn't have pin
// buttons.
if (GetSitePermissionsPage(current_page_.view())) {
return;
}
auto* main_page = GetMainPage(current_page_.view());
DCHECK(main_page);
std::vector<ExtensionMenuItemView*> menu_items = main_page->GetMenuItems();
for (auto* menu_item : menu_items) {
bool is_action_pinned =
toolbar_model_->IsActionPinned(menu_item->view_controller()->GetId());
menu_item->UpdateContextMenuButton(is_action_pinned);
}
}
void ExtensionsMenuViewController::OnUserPermissionsSettingsChanged(
const PermissionsManager::UserPermissionsSettings& settings) {
DCHECK(current_page_);
if (GetSitePermissionsPage(current_page_.view())) {
// Site permissions page can only be opened when site setting is set to
// "customize by extension". Thus, when site settings changed, we have to
// return to main page.
DCHECK_NE(PermissionsManager::Get(browser_->profile())
->GetUserSiteSetting(GetActiveWebContents()
->GetPrimaryMainFrame()
->GetLastCommittedOrigin()),
PermissionsManager::UserSiteSetting::kCustomizeByExtension);
OpenMainPage();
return;
}
ExtensionsMenuMainPageView* main_page = GetMainPage(current_page_.view());
DCHECK(main_page);
UpdateMainPage(main_page, GetActiveWebContents());
// TODO(crbug.com/40879945): Update the "highlighted section" based on the
// `site_setting` and whether a page refresh is needed.
// TODO(crbug.com/40879945): Run blocked actions for extensions that only have
// blocked actions that don't require a page refresh to run.
}
void ExtensionsMenuViewController::OnShowAccessRequestsInToolbarChanged(
const extensions::ExtensionId& extension_id,
bool can_show_requests) {
DCHECK(current_page_);
// Changing whether an extension can show requests access in the toolbar only
// affects the site permissions page for such extension.
auto* site_permissions_page = GetSitePermissionsPage(current_page_.view());
if (site_permissions_page &&
site_permissions_page->extension_id() == extension_id) {
site_permissions_page->UpdateShowRequestsToggle(can_show_requests);
}
}
void ExtensionsMenuViewController::OnHostAccessRequestDismissedByUser(
const extensions::ExtensionId& extension_id,
const url::Origin& origin) {
DCHECK(current_page_);
// Extension can only dismiss requests from the menu's main page. if it has
// navigated to another site in between, do nothing (navigation listeners will
// handle menu updates).
auto* main_page = GetMainPage(current_page_.view());
if (!main_page ||
GetActiveWebContents()->GetPrimaryMainFrame()->GetLastCommittedOrigin() !=
origin) {
return;
}
main_page->RemoveExtensionRequestingAccess(extension_id);
main_page->MaybeShowRequestsSection();
}
void ExtensionsMenuViewController::OnHostAccessRequestAdded(
const extensions::ExtensionId& extension_id,
int tab_id) {
DCHECK(current_page_);
// Ignore requests for other tabs.
int current_tab_id =
extensions::ExtensionTabUtil::GetTabId(GetActiveWebContents());
if (tab_id != current_tab_id) {
return;
}
// Site access requests only affect the main page.
ExtensionsMenuMainPageView* main_page = GetMainPage(current_page_.view());
if (!main_page) {
return;
}
// Add the request iff it's an active one.
auto* permissions_manager =
extensions::PermissionsManager::Get(browser_->profile());
if (permissions_manager->HasActiveHostAccessRequest(tab_id, extension_id)) {
// TODO(crbug.com/330588494): Add to correct index based on alphabetic
// order.
int index = 0;
AddOrUpdateExtensionRequestingAccess(main_page, extension_id, index,
GetActiveWebContents());
main_page->MaybeShowRequestsSection();
}
}
void ExtensionsMenuViewController::OnHostAccessRequestUpdated(
const extensions::ExtensionId& extension_id,
int tab_id) {
DCHECK(current_page_);
// Ignore requests for other tabs.
int current_tab_id =
extensions::ExtensionTabUtil::GetTabId(GetActiveWebContents());
if (tab_id != current_tab_id) {
return;
}
// Site access requests only affect the main page.
ExtensionsMenuMainPageView* main_page = GetMainPage(current_page_.view());
if (!main_page) {
return;
}
// Update the request iff it's an active one.
auto* permissions_manager =
extensions::PermissionsManager::Get(browser_->profile());
if (permissions_manager->HasActiveHostAccessRequest(tab_id, extension_id)) {
// TODO(crbug.com/330588494): Add to correct index based on alphabetic
// order.
int index = 0;
AddOrUpdateExtensionRequestingAccess(main_page, extension_id, index,
GetActiveWebContents());
main_page->MaybeShowRequestsSection();
return;
}
// Otherwise, remove the request if existent.
main_page->RemoveExtensionRequestingAccess(extension_id);
main_page->MaybeShowRequestsSection();
}
void ExtensionsMenuViewController::OnHostAccessRequestRemoved(
const extensions::ExtensionId& extension_id,
int tab_id) {
DCHECK(current_page_);
// Ignore requests for other tabs.
int current_tab_id =
extensions::ExtensionTabUtil::GetTabId(GetActiveWebContents());
if (tab_id != current_tab_id) {
return;
}
// Site access requests only affect the main page.
ExtensionsMenuMainPageView* main_page = GetMainPage(current_page_.view());
if (!main_page) {
return;
}
main_page->RemoveExtensionRequestingAccess(extension_id);
main_page->MaybeShowRequestsSection();
}
void ExtensionsMenuViewController::OnHostAccessRequestsCleared(int tab_id) {
DCHECK(current_page_);
// Ignore requests for other tabs.
int current_tab_id =
extensions::ExtensionTabUtil::GetTabId(GetActiveWebContents());
if (tab_id != current_tab_id) {
return;
}
// Site access requests only affect the 'user customized access' section in
// the main page.
ExtensionsMenuMainPageView* main_page = GetMainPage(current_page_.view());
if (!main_page) {
return;
}
main_page->ClearExtensionsRequestingAccess();
main_page->MaybeShowRequestsSection();
}
ExtensionsMenuMainPageView*
ExtensionsMenuViewController::GetMainPageViewForTesting() {
DCHECK(current_page_);
return GetMainPage(current_page_.view());
}
ExtensionsMenuSitePermissionsPageView*
ExtensionsMenuViewController::GetSitePermissionsPageForTesting() {
DCHECK(current_page_);
return GetSitePermissionsPage(current_page_.view());
}
void ExtensionsMenuViewController::SwitchToPage(
std::unique_ptr<views::View> page) {
if (current_page_) {
bubble_contents_->RemoveChildViewT(current_page_.view());
}
DCHECK(!current_page_);
current_page_.SetView(bubble_contents_->AddChildView(std::move(page)));
}
void ExtensionsMenuViewController::PopulateMainPage(
ExtensionsMenuMainPageView* main_page) {
// TODO(crbug.com/40879945): We should update the subheader here since it
// depends on `toolbar_model_`.
std::vector<std::string> sorted_ids = SortExtensionsByName(*toolbar_model_);
for (size_t i = 0; i < sorted_ids.size(); ++i) {
InsertMenuItemMainPage(main_page, sorted_ids[i], i);
}
}
void ExtensionsMenuViewController::InsertMenuItemMainPage(
ExtensionsMenuMainPageView* main_page,
const extensions::ExtensionId& extension_id,
int index) {
// TODO(emiliapaz): Under MVC architecture, view should not own the view
// controller. However, the current extensions structure depends on this
// thus a major restructure is needed.
std::unique_ptr<ExtensionActionViewController> action_controller =
ExtensionActionViewController::Create(extension_id, browser_,
extensions_container_);
const extensions::Extension* extension = action_controller->extension();
Profile* profile = browser_->profile();
content::WebContents* web_contents = GetActiveWebContents();
bool is_enterprise = extensions::ExtensionSystem::Get(profile)
->management_policy()
->HasEnterpriseForcedAccess(*extension);
ExtensionMenuItemView::SiteAccessToggleState site_access_toggle_state =
GetSiteAccessToggleState(*extension, *profile, *toolbar_model_,
*web_contents);
ExtensionMenuItemView::SitePermissionsButtonState
site_permissions_button_state = GetSitePermissionsButtonState(
*extension, *profile, *toolbar_model_, *web_contents);
ExtensionMenuItemView::SitePermissionsButtonAccess
site_permissions_button_access = GetSitePermissionsButtonAccess(
*extension, *profile, *toolbar_model_, *web_contents);
main_page->CreateAndInsertMenuItem(std::move(action_controller), extension_id,
is_enterprise, site_access_toggle_state,
site_permissions_button_state,
site_permissions_button_access, index);
}
void ExtensionsMenuViewController::AddOrUpdateExtensionRequestingAccess(
ExtensionsMenuMainPageView* main_page,
const extensions::ExtensionId& extension_id,
int index,
content::WebContents* web_contents) {
ToolbarActionViewController* action_controller =
extensions_container_->GetActionForId(extension_id);
std::u16string name = action_controller->GetActionName();
const int icon_size = ChromeLayoutProvider::Get()->GetDistanceMetric(
DISTANCE_EXTENSIONS_MENU_EXTENSION_ICON_SIZE);
ui::ImageModel icon =
action_controller->GetIcon(web_contents, gfx::Size(icon_size, icon_size));
main_page->AddOrUpdateExtensionRequestingAccess(extension_id, name, icon,
index);
}
content::WebContents* ExtensionsMenuViewController::GetActiveWebContents()
const {
return browser_->tab_strip_model()->GetActiveWebContents();
}