| // 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/bind.h" | 
 | #include "base/check_op.h" | 
 | #include "base/feature_list.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/metrics/histogram_functions.h" | 
 | #include "base/strings/strcat.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "chrome/browser/extensions/api/commands/command_service.h" | 
 | #include "chrome/browser/extensions/api/extension_action/extension_action_api.h" | 
 | #include "chrome/browser/extensions/extension_action_runner.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/site_permissions_helper.h" | 
 | #include "chrome/browser/profiles/profile.h" | 
 | #include "chrome/browser/ui/browser.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/extensions_container.h" | 
 | #include "chrome/browser/ui/extensions/icon_with_badge_image_source.h" | 
 | #include "chrome/browser/ui/toolbar/toolbar_action_view_delegate.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/blocked_action_type.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 "extensions/common/permissions/api_permission.h" | 
 | #include "ui/base/l10n/l10n_util.h" | 
 | #include "ui/color/color_provider_manager.h" | 
 | #include "ui/gfx/image/image_skia.h" | 
 | #include "ui/gfx/image/image_skia_operations.h" | 
 | #include "ui/native_theme/native_theme.h" | 
 |  | 
 | using extensions::ActionInfo; | 
 | using extensions::CommandService; | 
 | using extensions::ExtensionActionRunner; | 
 |  | 
 | namespace { | 
 |  | 
 | void RecordInvocationSource( | 
 |     ToolbarActionViewController::InvocationSource source) { | 
 |   base::UmaHistogramEnumeration("Extensions.Toolbar.InvocationSource", source); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | // static | 
 | std::unique_ptr<ExtensionActionViewController> | 
 | ExtensionActionViewController::Create( | 
 |     const extensions::ExtensionId& extension_id, | 
 |     Browser* browser, | 
 |     ExtensionsContainer* extensions_container) { | 
 |   DCHECK(browser); | 
 |   DCHECK(extensions_container); | 
 |  | 
 |   auto* registry = extensions::ExtensionRegistry::Get(browser->profile()); | 
 |   scoped_refptr<const extensions::Extension> extension = | 
 |       registry->enabled_extensions().GetByID(extension_id); | 
 |   DCHECK(extension); | 
 |   extensions::ExtensionAction* extension_action = | 
 |       extensions::ExtensionActionManager::Get(browser->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)); | 
 | } | 
 |  | 
 | // 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, | 
 |     Browser* browser, | 
 |     extensions::ExtensionAction* extension_action, | 
 |     extensions::ExtensionRegistry* extension_registry, | 
 |     ExtensionsContainer* extensions_container) | 
 |     : extension_(std::move(extension)), | 
 |       browser_(browser), | 
 |       extension_action_(extension_action), | 
 |       extensions_container_(extensions_container), | 
 |       popup_host_(nullptr), | 
 |       view_delegate_(nullptr), | 
 |       platform_delegate_(ExtensionActionPlatformDelegate::Create(this)), | 
 |       icon_factory_(browser->profile(), | 
 |                     extension_.get(), | 
 |                     extension_action, | 
 |                     this), | 
 |       extension_registry_(extension_registry) {} | 
 |  | 
 | ExtensionActionViewController::~ExtensionActionViewController() { | 
 |   DCHECK(!IsShowingPopup()); | 
 | } | 
 |  | 
 | 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(); | 
 |     platform_delegate_.reset(); | 
 |     view_delegate_ = nullptr; | 
 |   } | 
 | } | 
 |  | 
 | gfx::Image ExtensionActionViewController::GetIcon( | 
 |     content::WebContents* web_contents, | 
 |     const gfx::Size& size) { | 
 |   if (!ExtensionIsValid()) | 
 |     return gfx::Image(); | 
 |  | 
 |   return gfx::Image( | 
 |       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::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::string title = extension_action()->GetTitle( | 
 |       sessions::SessionTabHelper::IdForTab(web_contents).id()); | 
 |  | 
 |   std::u16string title_utf16 = | 
 |       base::UTF8ToUTF16(title.empty() ? extension()->name() : 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) { | 
 |     title_utf16 = base::StrCat( | 
 |         {title_utf16, u"\n", | 
 |          l10n_util::GetStringUTF16(site_interaction_description_id)}); | 
 |   } | 
 |  | 
 |   return title_utf16; | 
 | } | 
 |  | 
 | std::u16string ExtensionActionViewController::GetTooltip( | 
 |     content::WebContents* web_contents) const { | 
 |   return GetAccessibleName(web_contents); | 
 | } | 
 |  | 
 | bool ExtensionActionViewController::IsEnabled( | 
 |     content::WebContents* web_contents) const { | 
 |   if (!ExtensionIsValid()) | 
 |     return false; | 
 |  | 
 |   extensions::SitePermissionsHelper::SiteInteraction site_interaction = | 
 |       GetSiteInteraction(web_contents); | 
 |  | 
 |   return extension_action_->GetIsVisible( | 
 |              sessions::SessionTabHelper::IdForTab(web_contents).id()) || | 
 |          site_interaction == | 
 |              extensions::SitePermissionsHelper::SiteInteraction::kWithheld || | 
 |          site_interaction == | 
 |              extensions::SitePermissionsHelper::SiteInteraction::kActiveTab; | 
 | } | 
 |  | 
 | bool ExtensionActionViewController::IsShowingPopup() const { | 
 |   return popup_host_ != nullptr; | 
 | } | 
 |  | 
 | bool ExtensionActionViewController::IsRequestingSiteAccess( | 
 |     content::WebContents* web_contents) const { | 
 |   return GetSiteInteraction(web_contents) == | 
 |          extensions::SitePermissionsHelper::SiteInteraction::kWithheld; | 
 | } | 
 |  | 
 | void ExtensionActionViewController::HidePopup() { | 
 |   if (IsShowingPopup()) { | 
 |     // Only call Close() on the popup if it's been shown; otherwise, the popup | 
 |     // will be cleaned up in ShowPopup(). | 
 |     if (has_opened_popup_) | 
 |       popup_host_->Close(); | 
 |     // We need to do these actions synchronously (instead of closing and then | 
 |     // performing the rest of the cleanup in OnExtensionHostDestroyed()) because | 
 |     // the extension host may close asynchronously, and we need to keep the view | 
 |     // delegate up to date. | 
 |     if (popup_host_) | 
 |       OnPopupClosed(); | 
 |   } | 
 | } | 
 |  | 
 | gfx::NativeView ExtensionActionViewController::GetPopupNativeView() { | 
 |   return popup_host_ ? popup_host_->view()->GetNativeView() : nullptr; | 
 | } | 
 |  | 
 | ui::MenuModel* ExtensionActionViewController::GetContextMenu( | 
 |     extensions::ExtensionContextMenuModel::ContextMenuSource | 
 |         context_menu_source) { | 
 |   if (!ExtensionIsValid()) | 
 |     return nullptr; | 
 |  | 
 |   ToolbarActionViewController* const action = | 
 |       extensions_container_->GetActionForId(GetId()); | 
 |   extensions::ExtensionContextMenuModel::ButtonVisibility visibility = | 
 |       extensions_container_->GetActionVisibility(action); | 
 |  | 
 |   // Reconstruct the menu every time because the menu's contents are dynamic. | 
 |   context_menu_model_ = std::make_unique<extensions::ExtensionContextMenuModel>( | 
 |       extension(), browser_, visibility, this, | 
 |       view_delegate_->CanShowIconInToolbar(), context_menu_source); | 
 |   return context_menu_model_.get(); | 
 | } | 
 |  | 
 | void ExtensionActionViewController::OnContextMenuShown() { | 
 |   extensions_container_->OnContextMenuShown(this); | 
 | } | 
 |  | 
 | void ExtensionActionViewController::OnContextMenuClosed() { | 
 |   extensions_container_->OnContextMenuClosed(this); | 
 | } | 
 |  | 
 | void ExtensionActionViewController::ExecuteUserAction(InvocationSource source) { | 
 |   if (!ExtensionIsValid()) | 
 |     return; | 
 |  | 
 |   if (!IsEnabled(view_delegate_->GetCurrentWebContents())) { | 
 |     GetPreferredPopupViewController() | 
 |         ->view_delegate_->ShowContextMenuAsFallback(); | 
 |     return; | 
 |   } | 
 |  | 
 |   content::WebContents* const web_contents = | 
 |       view_delegate_->GetCurrentWebContents(); | 
 |   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; | 
 |   if (action_runner->RunAction(extension(), kGrantTabPermissions) == | 
 |       extensions::ExtensionAction::ACTION_SHOW_POPUP) { | 
 |     constexpr bool kByUser = true; | 
 |     GetPreferredPopupViewController()->TriggerPopup( | 
 |         PopupShowAction::kShow, kByUser, ShowPopupCallback()); | 
 |   } | 
 | } | 
 |  | 
 | 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::UpdateState() { | 
 |   if (!ExtensionIsValid()) | 
 |     return; | 
 |  | 
 |   view_delegate_->UpdateState(); | 
 | } | 
 |  | 
 | 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::InspectPopup() { | 
 |   // This method is only triggered through user action (clicking on the context | 
 |   // menu entry). | 
 |   constexpr bool kByUser = true; | 
 |   GetPreferredPopupViewController()->TriggerPopup( | 
 |       PopupShowAction::kShowAndInspect, kByUser, ShowPopupCallback()); | 
 | } | 
 |  | 
 | void ExtensionActionViewController::OnIconUpdated() { | 
 |   // We update the view first, so that if the observer relies on its UI it can | 
 |   // be ready. | 
 |   if (view_delegate_) | 
 |     view_delegate_->UpdateState(); | 
 | } | 
 |  | 
 | void ExtensionActionViewController::OnExtensionHostDestroyed( | 
 |     extensions::ExtensionHost* host) { | 
 |   OnPopupClosed(); | 
 | } | 
 |  | 
 | extensions::SitePermissionsHelper::SiteInteraction | 
 | ExtensionActionViewController::GetSiteInteraction( | 
 |     content::WebContents* web_contents) const { | 
 |   return extensions::SitePermissionsHelper(browser_->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(browser_->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(browser_->profile()) | 
 |           ->GetUserSiteSetting(origin); | 
 |  | 
 |   // Compute hover card 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". | 
 |   auto site_interaction = GetSiteInteraction(web_contents); | 
 |   switch (site_interaction) { | 
 |     case extensions::SitePermissionsHelper::SiteInteraction::kGranted: | 
 |       return site_setting == extensions::PermissionsManager::UserSiteSetting:: | 
 |                                  kGrantAllExtensions | 
 |                  ? HoverCardState::kAllExtensionsAllowed | 
 |                  : HoverCardState::kExtensionHasAccess; | 
 |  | 
 |     case extensions::SitePermissionsHelper::SiteInteraction::kWithheld: | 
 |     case extensions::SitePermissionsHelper::SiteInteraction::kActiveTab: | 
 |       return site_setting == extensions::PermissionsManager::UserSiteSetting:: | 
 |                                  kBlockAllExtensions | 
 |                  ? HoverCardState::kAllExtensionsBlocked | 
 |                  : HoverCardState::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 | 
 |                  ? HoverCardState::kAllExtensionsBlocked | 
 |                  : HoverCardState::kExtensionDoesNotWantAccess; | 
 |   } | 
 | } | 
 |  | 
 | 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_PAGE) | 
 |     return IsEnabled(view_delegate_->GetCurrentWebContents()); | 
 |   return true; | 
 | } | 
 |  | 
 | std::unique_ptr<IconWithBadgeImageSource> | 
 | ExtensionActionViewController::GetIconImageSourceForTesting( | 
 |     content::WebContents* web_contents, | 
 |     const gfx::Size& size) { | 
 |   return GetIconImageSource(web_contents, size); | 
 | } | 
 |  | 
 | bool ExtensionActionViewController::HasBeenBlockedForTesting( | 
 |     content::WebContents* web_contents) const { | 
 |   return extensions::SitePermissionsHelper(browser_->profile()) | 
 |       .HasBeenBlocked(*extension(), web_contents); | 
 | } | 
 |  | 
 | ExtensionActionViewController* | 
 | ExtensionActionViewController::GetPreferredPopupViewController() { | 
 |   return static_cast<ExtensionActionViewController*>( | 
 |       extensions_container_->GetActionForId(GetId())); | 
 | } | 
 |  | 
 | void ExtensionActionViewController::TriggerPopup(PopupShowAction show_action, | 
 |                                                  bool by_user, | 
 |                                                  ShowPopupCallback callback) { | 
 |   DCHECK(ExtensionIsValid()); | 
 |   DCHECK_EQ(this, GetPreferredPopupViewController()); | 
 |  | 
 |   content::WebContents* const web_contents = | 
 |       view_delegate_->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_); | 
 |   // Creating a host should never fail in this case, since the extension is | 
 |   // valid and has a valid popup URL. | 
 |   CHECK(host); | 
 |  | 
 |   // Always hide the current popup, even if it's not owned by this extension. | 
 |   // Only one popup should be visible at a time. | 
 |   extensions_container_->HideActivePopup(); | 
 |  | 
 |   extensions_container_->CloseOverflowMenuIfOpen(); | 
 |  | 
 |   popup_host_ = host.get(); | 
 |   popup_host_observation_.Observe(popup_host_.get()); | 
 |   extensions_container_->SetPopupOwner(this); | 
 |  | 
 |   extensions_container_->PopOutAction( | 
 |       this, base::BindOnce(&ExtensionActionViewController::ShowPopup, | 
 |                            weak_factory_.GetWeakPtr(), std::move(host), by_user, | 
 |                            show_action, std::move(callback))); | 
 | } | 
 |  | 
 | void ExtensionActionViewController::ShowPopup( | 
 |     std::unique_ptr<extensions::ExtensionViewHost> popup_host, | 
 |     bool grant_tab_permissions, | 
 |     PopupShowAction show_action, | 
 |     ShowPopupCallback callback) { | 
 |   // It's possible that the popup should be closed before it finishes opening | 
 |   // (since it can open asynchronously). Check before proceeding. | 
 |   if (!popup_host_) { | 
 |     if (callback) | 
 |       std::move(callback).Run(nullptr); | 
 |     return; | 
 |   } | 
 |   // NOTE: Today, ShowPopup() always synchronously creates the platform-specific | 
 |   // popup class, which is what we care most about (since `has_opened_popup_` | 
 |   // is used to determine whether we need to manually close the | 
 |   // ExtensionViewHost). This doesn't necessarily mean that the popup has | 
 |   // completed rendering on the screen. | 
 |   has_opened_popup_ = true; | 
 |   platform_delegate_->ShowPopup(std::move(popup_host), show_action, | 
 |                                 std::move(callback)); | 
 |   view_delegate_->OnPopupShown(grant_tab_permissions); | 
 | } | 
 |  | 
 | void ExtensionActionViewController::OnPopupClosed() { | 
 |   DCHECK(popup_host_observation_.IsObservingSource(popup_host_.get())); | 
 |   popup_host_observation_.Reset(); | 
 |   popup_host_ = nullptr; | 
 |   has_opened_popup_ = false; | 
 |   extensions_container_->SetPopupOwner(nullptr); | 
 |   if (extensions_container_->GetPoppedOutAction() == this) | 
 |     extensions_container_->UndoPopOut(); | 
 |   view_delegate_->OnPopupClosed(); | 
 | } | 
 |  | 
 | 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); | 
 |   bool grayscale = | 
 |       GetSiteInteraction(web_contents) == | 
 |           extensions::SitePermissionsHelper::SiteInteraction::kNone && | 
 |       !action_is_visible; | 
 |   image_source->set_grayscale(grayscale); | 
 |  | 
 |   if (base::FeatureList::IsEnabled( | 
 |           extensions_features::kExtensionsMenuAccessControl)) { | 
 |     return image_source; | 
 |   } | 
 |  | 
 |   bool was_blocked = extensions::SitePermissionsHelper(browser_->profile()) | 
 |                          .HasBeenBlocked(*extension(), web_contents); | 
 |   image_source->set_paint_blocked_actions_decoration(was_blocked); | 
 |  | 
 |   return image_source; | 
 | } |