blob: 25dc3dd9d1deb6a8c3ceb3b1848eb8543f6d4dde [file] [log] [blame]
// Copyright 2014 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/extension_action_view_controller.h"
#include <memory>
#include <string>
#include <utility>
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/api/side_panel/side_panel_service.h"
#include "chrome/browser/extensions/commands/command_service.h"
#include "chrome/browser/extensions/extension_action_runner.h"
#include "chrome/browser/extensions/extension_context_menu_model.h"
#include "chrome/browser/extensions/extension_view.h"
#include "chrome/browser/extensions/extension_view_host.h"
#include "chrome/browser/extensions/extension_view_host_factory.h"
#include "chrome/browser/extensions/permissions/site_permissions_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/extensions/extension_action_platform_delegate.h"
#include "chrome/browser/ui/extensions/extension_popup_types.h"
#include "chrome/browser/ui/extensions/extension_side_panel_utils.h"
#include "chrome/browser/ui/extensions/extensions_container.h"
#include "chrome/browser/ui/extensions/icon_with_badge_image_source.h"
#include "chrome/browser/ui/tabs/tab_list_interface.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_delegate.h"
#include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
#include "chrome/grit/generated_resources.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_action.h"
#include "extensions/browser/extension_action_manager.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/api/extension_action/action_info.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/manifest_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/color/color_provider_manager.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/native_theme/native_theme.h"
using extensions::ActionInfo;
using extensions::CommandService;
using extensions::ExtensionActionRunner;
using extensions::PermissionsManager;
namespace {
void RecordInvocationSource(
ToolbarActionViewController::InvocationSource source) {
base::UmaHistogramEnumeration("Extensions.Toolbar.InvocationSource", source);
}
// Computes hover card site access status based on:
// 1. Extension wants site access: user site settings takes precedence
// over the extension's site access.
// 2. Extension does not want access: if all extensions are blocked display
// such message because a) user could wrongly infer that an extension that
// does not want access has access if we only show the blocked message for
// extensions that want access; and b) it helps us work around tricky
// calculations where we get into collisions between withheld and denied
// permission. Otherwise, it should display "does not want access".
ExtensionActionViewController::HoverCardState::SiteAccess
GetHoverCardSiteAccessState(
extensions::PermissionsManager::UserSiteSetting site_setting,
extensions::SitePermissionsHelper::SiteInteraction site_interaction) {
switch (site_interaction) {
case extensions::SitePermissionsHelper::SiteInteraction::kGranted:
return site_setting == extensions::PermissionsManager::UserSiteSetting::
kGrantAllExtensions
? ExtensionActionViewController::HoverCardState::SiteAccess::
kAllExtensionsAllowed
: ExtensionActionViewController::HoverCardState::SiteAccess::
kExtensionHasAccess;
case extensions::SitePermissionsHelper::SiteInteraction::kWithheld:
case extensions::SitePermissionsHelper::SiteInteraction::kActiveTab:
return site_setting == extensions::PermissionsManager::UserSiteSetting::
kBlockAllExtensions
? ExtensionActionViewController::HoverCardState::SiteAccess::
kAllExtensionsBlocked
: ExtensionActionViewController::HoverCardState::SiteAccess::
kExtensionRequestsAccess;
case extensions::SitePermissionsHelper::SiteInteraction::kNone:
// kNone site interaction includes extensions that don't want access when
// user site setting is "block all extensions".
return site_setting == extensions::PermissionsManager::UserSiteSetting::
kBlockAllExtensions
? ExtensionActionViewController::HoverCardState::SiteAccess::
kAllExtensionsBlocked
: ExtensionActionViewController::HoverCardState::SiteAccess::
kExtensionDoesNotWantAccess;
}
}
// Computes hover card policy status based on admin policy. Note that an
// extension pinned by admin is also installed by admin. Thus, "pinned by admin"
// has preference.
ExtensionActionViewController::HoverCardState::AdminPolicy
GetHoverCardPolicyState(Profile& profile,
const extensions::ExtensionId& extension_id) {
auto* const model = ToolbarActionsModel::Get(&profile);
if (model->IsActionForcePinned(extension_id)) {
return ExtensionActionViewController::HoverCardState::AdminPolicy::
kPinnedByAdmin;
}
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionRegistry::Get(&profile)
->enabled_extensions()
.GetByID(extension_id);
if (extensions::Manifest::IsPolicyLocation(extension->location())) {
return ExtensionActionViewController::HoverCardState::AdminPolicy::
kInstalledByAdmin;
}
return ExtensionActionViewController::HoverCardState::AdminPolicy::kNone;
}
} // namespace
// static
std::unique_ptr<ExtensionActionViewController>
ExtensionActionViewController::Create(
const extensions::ExtensionId& extension_id,
BrowserWindowInterface* browser,
ExtensionsContainer* extensions_container,
std::unique_ptr<ExtensionActionPlatformDelegate> platform_delegate) {
DCHECK(browser);
DCHECK(extensions_container);
Profile* profile = browser->GetProfile();
auto* registry = extensions::ExtensionRegistry::Get(profile);
scoped_refptr<const extensions::Extension> extension =
registry->enabled_extensions().GetByID(extension_id);
DCHECK(extension);
extensions::ExtensionAction* extension_action =
extensions::ExtensionActionManager::Get(profile)->GetExtensionAction(
*extension);
DCHECK(extension_action);
// WrapUnique() because the constructor is private.
return base::WrapUnique(new ExtensionActionViewController(
std::move(extension), browser, extension_action, registry,
extensions_container, std::move(platform_delegate)));
}
// static
bool ExtensionActionViewController::AnyActionHasCurrentSiteAccess(
const std::vector<std::unique_ptr<ToolbarActionViewController>>& actions,
content::WebContents* web_contents) {
for (const auto& action : actions) {
if (action->GetSiteInteraction(web_contents) ==
extensions::SitePermissionsHelper::SiteInteraction::kGranted) {
return true;
}
}
return false;
}
ExtensionActionViewController::ExtensionActionViewController(
scoped_refptr<const extensions::Extension> extension,
BrowserWindowInterface* browser,
extensions::ExtensionAction* extension_action,
extensions::ExtensionRegistry* extension_registry,
ExtensionsContainer* extensions_container,
std::unique_ptr<ExtensionActionPlatformDelegate> platform_delegate)
: extension_(std::move(extension)),
browser_(browser),
profile_(browser->GetProfile()),
extension_action_(extension_action),
extensions_container_(extensions_container),
view_delegate_(nullptr),
platform_delegate_(std::move(platform_delegate)),
icon_factory_(extension_.get(), extension_action, this),
extension_registry_(extension_registry) {
platform_delegate_->AttachToController(this);
// TODO(crbug.com/448199168): Get rid of the dependency to TabStripModel that
// is not available on Android.
browser_->GetTabStripModel()->AddObserver(this);
toolbar_model_observation_.Observe(ToolbarActionsModel::Get(profile_));
command_service_observation_.Observe(
extensions::CommandService::Get(profile_));
}
ExtensionActionViewController::~ExtensionActionViewController() {
DCHECK(!IsShowingPopup());
platform_delegate_->DetachFromController();
}
std::string ExtensionActionViewController::GetId() const {
return extension_->id();
}
void ExtensionActionViewController::SetDelegate(
ToolbarActionViewDelegate* delegate) {
DCHECK((delegate == nullptr) ^ (view_delegate_ == nullptr));
if (delegate) {
view_delegate_ = delegate;
} else {
HidePopup();
view_delegate_ = nullptr;
}
}
ui::ImageModel ExtensionActionViewController::GetIcon(
content::WebContents* web_contents,
const gfx::Size& size) {
if (!ExtensionIsValid()) {
return ui::ImageModel();
}
return ui::ImageModel::FromImageSkia(
gfx::ImageSkia(GetIconImageSource(web_contents, size), size));
}
std::u16string ExtensionActionViewController::GetActionName() const {
if (!ExtensionIsValid()) {
return std::u16string();
}
return base::UTF8ToUTF16(extension_->name());
}
std::u16string ExtensionActionViewController::GetActionTitle(
content::WebContents* web_contents) const {
if (!ExtensionIsValid()) {
return std::u16string();
}
std::string title = extension_action_->GetTitle(
sessions::SessionTabHelper::IdForTab(web_contents).id());
return base::UTF8ToUTF16(title);
}
std::u16string ExtensionActionViewController::GetAccessibleName(
content::WebContents* web_contents) const {
if (!ExtensionIsValid()) {
return std::u16string();
}
// GetAccessibleName() can (surprisingly) be called during browser
// teardown. Handle this gracefully.
if (!web_contents) {
return base::UTF8ToUTF16(extension()->name());
}
std::u16string action_title = GetActionTitle(web_contents);
std::u16string accessible_name =
action_title.empty() ? GetActionName() : action_title;
// Include a "host access" portion of the tooltip if the extension has active
// or pending interaction with the site.
auto site_interaction = GetSiteInteraction(web_contents);
int site_interaction_description_id = -1;
switch (site_interaction) {
case extensions::SitePermissionsHelper::SiteInteraction::kNone:
// No string for neither having nor wanting access.
break;
case extensions::SitePermissionsHelper::SiteInteraction::kWithheld:
case extensions::SitePermissionsHelper::SiteInteraction::kActiveTab:
site_interaction_description_id = IDS_EXTENSIONS_WANTS_ACCESS_TO_SITE;
break;
case extensions::SitePermissionsHelper::SiteInteraction::kGranted:
site_interaction_description_id = IDS_EXTENSIONS_HAS_ACCESS_TO_SITE;
break;
}
if (site_interaction_description_id != -1) {
accessible_name = base::StrCat(
{accessible_name, u"\n",
l10n_util::GetStringUTF16(site_interaction_description_id)});
}
return accessible_name;
}
std::u16string ExtensionActionViewController::GetTooltip(
content::WebContents* web_contents) const {
if (base::FeatureList::IsEnabled(
extensions_features::kExtensionsMenuAccessControl)) {
std::u16string action_title = GetActionTitle(web_contents);
std::u16string tooltip =
action_title.empty() ? GetActionName() : action_title;
url::Origin origin =
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin();
auto* permissions_manager = extensions::PermissionsManager::Get(profile_);
ToolbarActionViewController::HoverCardState::SiteAccess site_access =
GetHoverCardSiteAccessState(
permissions_manager->GetUserSiteSetting(origin),
GetSiteInteraction(web_contents));
int tooltip_site_access_id;
switch (site_access) {
case HoverCardState::SiteAccess::kAllExtensionsAllowed:
case HoverCardState::SiteAccess::kExtensionHasAccess:
tooltip_site_access_id =
IDS_EXTENSIONS_MENU_MAIN_PAGE_EXTENSION_BUTTON_HAS_ACCESS_TOOLTIP;
break;
case HoverCardState::SiteAccess::kAllExtensionsBlocked:
tooltip_site_access_id =
IDS_EXTENSIONS_MENU_MAIN_PAGE_EXTENSION_BUTTON_BLOCKED_ACCESS_TOOLTIP;
break;
case HoverCardState::SiteAccess::kExtensionRequestsAccess:
tooltip_site_access_id =
IDS_EXTENSIONS_MENU_MAIN_PAGE_EXTENSION_BUTTON_REQUESTS_TOOLTIP;
break;
case HoverCardState::SiteAccess::kExtensionDoesNotWantAccess:
tooltip_site_access_id = -1;
}
return tooltip_site_access_id == -1
? tooltip
: base::JoinString({tooltip, l10n_util::GetStringUTF16(
tooltip_site_access_id)},
u"\n");
}
return GetAccessibleName(web_contents);
}
bool ExtensionActionViewController::IsEnabled(
content::WebContents* web_contents) const {
if (!ExtensionIsValid()) {
return false;
}
int tab_id = sessions::SessionTabHelper::IdForTab(web_contents).id();
if (extension_action_->GetIsVisible(tab_id)) {
return true;
}
extensions::SitePermissionsHelper::SiteInteraction site_interaction =
GetSiteInteraction(web_contents);
if (site_interaction ==
extensions::SitePermissionsHelper::SiteInteraction::kWithheld ||
site_interaction ==
extensions::SitePermissionsHelper::SiteInteraction::kActiveTab) {
return true;
}
extensions::SidePanelService* side_panel_service =
extensions::SidePanelService::Get(profile_);
return side_panel_service &&
side_panel_service->HasSidePanelActionForTab(*extension(), tab_id);
}
bool ExtensionActionViewController::IsShowingPopup() const {
return platform_delegate_->IsShowingPopup();
}
void ExtensionActionViewController::HidePopup() {
return platform_delegate_->HidePopup();
}
gfx::NativeView ExtensionActionViewController::GetPopupNativeView() {
return platform_delegate_->GetPopupNativeView();
}
ui::MenuModel* ExtensionActionViewController::GetContextMenu(
extensions::ExtensionContextMenuModel::ContextMenuSource
context_menu_source) {
if (!ExtensionIsValid()) {
return nullptr;
}
bool is_pinned = ToolbarActionsModel::Get(profile_)->IsActionPinned(GetId());
// Reconstruct the menu every time because the menu's contents are dynamic.
context_menu_model_ = std::make_unique<extensions::ExtensionContextMenuModel>(
extension(), browser_, is_pinned, this,
ToolbarActionsModel::CanShowActionsInToolbar(*browser_),
context_menu_source);
return context_menu_model_.get();
}
void ExtensionActionViewController::OnContextMenuShown(
extensions::ExtensionContextMenuModel::ContextMenuSource source) {
if (source == extensions::ExtensionContextMenuModel::ContextMenuSource::
kToolbarAction) {
extensions_container_->OnContextMenuShownFromToolbar(GetId());
}
}
void ExtensionActionViewController::OnContextMenuClosed(
extensions::ExtensionContextMenuModel::ContextMenuSource source) {
if (source == extensions::ExtensionContextMenuModel::ContextMenuSource::
kToolbarAction) {
extensions_container_->OnContextMenuClosedFromToolbar();
}
}
void ExtensionActionViewController::ExecuteUserAction(InvocationSource source) {
if (!ExtensionIsValid()) {
return;
}
content::WebContents* const web_contents = GetCurrentWebContents();
if (!IsEnabled(web_contents)) {
platform_delegate_->ShowContextMenuAsFallback();
return;
}
ExtensionActionRunner* action_runner =
ExtensionActionRunner::GetForWebContents(web_contents);
if (!action_runner) {
return;
}
RecordInvocationSource(source);
extensions_container_->CloseOverflowMenuIfOpen();
// This method is only called to execute an action by the user, so we can
// always grant tab permissions.
constexpr bool kGrantTabPermissions = true;
extensions::ExtensionAction::ShowAction action =
action_runner->RunAction(extension(), kGrantTabPermissions);
if (action == extensions::ExtensionAction::ShowAction::kShowPopup) {
constexpr bool kByUser = true;
TriggerPopup(PopupShowAction::kShow, kByUser, ShowPopupCallback());
} else if (action ==
extensions::ExtensionAction::ShowAction::kToggleSidePanel) {
extensions::side_panel_util::ToggleExtensionSidePanel(browser_,
extension()->id());
}
}
void ExtensionActionViewController::TriggerPopupForAPI(
ShowPopupCallback callback) {
RecordInvocationSource(InvocationSource::kApi);
// This method is called programmatically by an API; it should never be
// considered a user action.
constexpr bool kByUser = false;
TriggerPopup(PopupShowAction::kShow, kByUser, std::move(callback));
}
void ExtensionActionViewController::UpdateHoverCard(
ToolbarActionView* action_view,
ToolbarActionHoverCardUpdateType update_type) {
if (!ExtensionIsValid()) {
return;
}
extensions_container_->UpdateToolbarActionHoverCard(action_view, update_type);
}
void ExtensionActionViewController::RegisterCommand() {
if (!ExtensionIsValid()) {
return;
}
platform_delegate_->RegisterCommand();
}
void ExtensionActionViewController::UnregisterCommand() {
platform_delegate_->UnregisterCommand();
}
void ExtensionActionViewController::TabChangedAt(content::WebContents* contents,
int index,
TabChangeType change_type) {
if (change_type == TabChangeType::kLoadingOnly) {
return;
}
NotifyUpdateToDelegate();
}
void ExtensionActionViewController::OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) {
if (!selection.active_tab_changed()) {
return;
}
NotifyUpdateToDelegate();
}
void ExtensionActionViewController::OnToolbarActionAdded(
const ToolbarActionsModel::ActionId& action_id) {}
void ExtensionActionViewController::OnToolbarActionRemoved(
const ToolbarActionsModel::ActionId& action_id) {}
void ExtensionActionViewController::OnToolbarActionUpdated(
const ToolbarActionsModel::ActionId& action_id) {
if (action_id != extension()->id()) {
return;
}
NotifyUpdateToDelegate();
}
void ExtensionActionViewController::OnToolbarModelInitialized() {}
void ExtensionActionViewController::OnToolbarPinnedActionsChanged() {}
void ExtensionActionViewController::OnExtensionCommandAdded(
const std::string& extension_id,
const std::string& command_name) {
if (extension_id != extension()->id()) {
return; // Not this action's extension.
}
if (!extensions::Command::IsActionRelatedCommand(command_name)) {
return;
}
RegisterCommand();
}
void ExtensionActionViewController::OnExtensionCommandRemoved(
const std::string& extension_id,
const std::string& command_name) {
if (extension_id != extension()->id()) {
return;
}
if (!extensions::Command::IsActionRelatedCommand(command_name)) {
return;
}
extensions::Command extension_command;
if (GetExtensionCommand(&extension_command)) {
return; // Command has not been removed.
}
UnregisterCommand();
}
void ExtensionActionViewController::OnCommandServiceDestroying() {
DCHECK(command_service_observation_.IsObserving());
command_service_observation_.Reset();
}
void ExtensionActionViewController::InspectPopup() {
// This method is only triggered through user action (clicking on the context
// menu entry).
TriggerPopup(PopupShowAction::kShowAndInspect, /*by_user*/ true,
ShowPopupCallback());
}
content::WebContents* ExtensionActionViewController::GetCurrentWebContents()
const {
tabs::TabInterface* tab = TabListInterface::From(browser_)->GetActiveTab();
if (!tab) {
return nullptr;
}
return tab->GetContents();
}
void ExtensionActionViewController::NotifyUpdateToDelegate() {
if (!view_delegate_ || !browser_->GetActiveTabInterface()) {
return;
}
view_delegate_->UpdateState();
}
void ExtensionActionViewController::OnIconUpdated() {
NotifyUpdateToDelegate();
}
extensions::SitePermissionsHelper::SiteInteraction
ExtensionActionViewController::GetSiteInteraction(
content::WebContents* web_contents) const {
return extensions::SitePermissionsHelper(profile_).GetSiteInteraction(
*extension(), web_contents);
}
bool ExtensionActionViewController::ExtensionIsValid() const {
return extension_registry_->enabled_extensions().Contains(extension_->id());
}
bool ExtensionActionViewController::GetExtensionCommand(
extensions::Command* command) const {
DCHECK(command);
if (!ExtensionIsValid()) {
return false;
}
CommandService* command_service = CommandService::Get(profile_);
return command_service->GetExtensionActionCommand(
extension_->id(), extension_action_->action_type(),
CommandService::ACTIVE, command, nullptr);
}
ToolbarActionViewController::HoverCardState
ExtensionActionViewController::GetHoverCardState(
content::WebContents* web_contents) const {
DCHECK(ExtensionIsValid());
DCHECK(web_contents);
url::Origin origin =
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin();
extensions::PermissionsManager::UserSiteSetting site_setting =
extensions::PermissionsManager::Get(profile_)->GetUserSiteSetting(origin);
auto site_interaction = GetSiteInteraction(web_contents);
HoverCardState state;
state.site_access =
GetHoverCardSiteAccessState(site_setting, site_interaction);
state.policy = GetHoverCardPolicyState(*profile_, GetId());
return state;
}
bool ExtensionActionViewController::CanHandleAccelerators() const {
if (!ExtensionIsValid()) {
return false;
}
#if DCHECK_IS_ON()
{
extensions::Command command;
DCHECK(GetExtensionCommand(&command));
}
#endif
// Page action accelerators are enabled if and only if the page action is
// enabled ("visible" in legacy terms) on the given tab. Other actions can
// always accept accelerators.
// TODO(devlin): Have all actions behave similarly; this should likely mean
// always checking IsEnabled(). It's weird to use a keyboard shortcut on a
// disabled action (in most cases, this will result in opening the context
// menu).
if (extension_action_->action_type() == extensions::ActionInfo::Type::kPage) {
return IsEnabled(GetCurrentWebContents());
}
return true;
}
std::unique_ptr<IconWithBadgeImageSource>
ExtensionActionViewController::GetIconImageSourceForTesting(
content::WebContents* web_contents,
const gfx::Size& size) {
return GetIconImageSource(web_contents, size);
}
void ExtensionActionViewController::TriggerPopup(PopupShowAction show_action,
bool by_user,
ShowPopupCallback callback) {
DCHECK(ExtensionIsValid());
content::WebContents* const web_contents = GetCurrentWebContents();
const int tab_id = sessions::SessionTabHelper::IdForTab(web_contents).id();
DCHECK(extension_action_->GetIsVisible(tab_id));
DCHECK(extension_action_->HasPopup(tab_id));
const GURL popup_url = extension_action_->GetPopupUrl(tab_id);
std::unique_ptr<extensions::ExtensionViewHost> host =
extensions::ExtensionViewHostFactory::CreatePopupHost(
popup_url, browser_->GetBrowserForMigrationOnly());
// Creating a host should never fail in this case, since the extension is
// valid and has a valid popup URL.
CHECK(host);
platform_delegate_->TriggerPopup(std::move(host), show_action, by_user,
std::move(callback));
}
std::unique_ptr<IconWithBadgeImageSource>
ExtensionActionViewController::GetIconImageSource(
content::WebContents* web_contents,
const gfx::Size& size) {
// `web_contents` may be null during tab closure or in tests. Fall back on a
// generic color provider.
auto get_color_provider_callback = base::BindRepeating(
[](base::WeakPtr<content::WebContents> weak_web_contents) {
return weak_web_contents
? &weak_web_contents->GetColorProvider()
: ui::ColorProviderManager::Get().GetColorProviderFor(
ui::NativeTheme::GetInstanceForNativeUi()
->GetColorProviderKey(nullptr));
},
web_contents ? web_contents->GetWeakPtr()
: base::WeakPtr<content::WebContents>());
auto image_source = std::make_unique<IconWithBadgeImageSource>(
size, std::move(get_color_provider_callback));
int tab_id = sessions::SessionTabHelper::IdForTab(web_contents).id();
image_source->SetIcon(icon_factory_.GetIcon(tab_id));
std::unique_ptr<IconWithBadgeImageSource::Badge> badge;
std::string badge_text = extension_action_->GetDisplayBadgeText(tab_id);
if (!badge_text.empty()) {
badge = std::make_unique<IconWithBadgeImageSource::Badge>(
badge_text, extension_action_->GetBadgeTextColor(tab_id),
extension_action_->GetBadgeBackgroundColor(tab_id));
}
image_source->SetBadge(std::move(badge));
// We only grayscale the icon if it cannot interact with the page and the icon
// is disabled.
bool action_is_visible = extension_action_->GetIsVisible(tab_id);
extensions::SidePanelService* side_panel_service =
extensions::SidePanelService::Get(profile_);
bool has_side_panel_action =
side_panel_service &&
side_panel_service->HasSidePanelActionForTab(*extension(), tab_id);
bool is_grayscale =
GetSiteInteraction(web_contents) ==
extensions::SitePermissionsHelper::SiteInteraction::kNone &&
!action_is_visible && !has_side_panel_action;
image_source->set_grayscale(is_grayscale);
if (base::FeatureList::IsEnabled(
extensions_features::kExtensionsMenuAccessControl)) {
return image_source;
}
bool was_blocked = extensions::SitePermissionsHelper(profile_).HasBeenBlocked(
*extension(), web_contents);
image_source->set_paint_blocked_actions_decoration(was_blocked);
return image_source;
}