|  | // Copyright 2012 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "extensions/browser/extension_action.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <memory> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/check_op.h" | 
|  | #include "base/containers/contains.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "extensions/browser/extension_icon_image.h" | 
|  | #include "extensions/browser/extension_icon_placeholder.h" | 
|  | #include "extensions/common/constants.h" | 
|  | #include "extensions/common/icons/extension_icon_set.h" | 
|  | #include "extensions/common/manifest_handlers/icons_handler.h" | 
|  | #include "extensions/grit/extensions_browser_resources.h" | 
|  | #include "third_party/skia/include/core/SkBitmap.h" | 
|  | #include "third_party/skia/include/core/SkCanvas.h" | 
|  | #include "third_party/skia/include/core/SkPaint.h" | 
|  | #include "third_party/skia/include/effects/SkGradientShader.h" | 
|  | #include "ui/base/resource/resource_bundle.h" | 
|  | #include "ui/gfx/animation/animation_delegate.h" | 
|  | #include "ui/gfx/canvas.h" | 
|  | #include "ui/gfx/color_utils.h" | 
|  | #include "ui/gfx/geometry/rect.h" | 
|  | #include "ui/gfx/geometry/size.h" | 
|  | #include "ui/gfx/image/image.h" | 
|  | #include "ui/gfx/image/image_skia.h" | 
|  | #include "ui/gfx/image/image_skia_rep.h" | 
|  | #include "ui/gfx/image/image_skia_source.h" | 
|  | #include "ui/gfx/skbitmap_operations.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | namespace extensions { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class GetAttentionImageSource : public gfx::ImageSkiaSource { | 
|  | public: | 
|  | explicit GetAttentionImageSource(const gfx::ImageSkia& icon) : icon_(icon) {} | 
|  |  | 
|  | // gfx::ImageSkiaSource overrides: | 
|  | gfx::ImageSkiaRep GetImageForScale(float scale) override { | 
|  | gfx::ImageSkiaRep icon_rep = icon_.GetRepresentation(scale); | 
|  | color_utils::HSL shift = {-1, 0, 0.5}; | 
|  | return gfx::ImageSkiaRep( | 
|  | SkBitmapOperations::CreateHSLShiftedBitmap(icon_rep.GetBitmap(), shift), | 
|  | icon_rep.scale()); | 
|  | } | 
|  |  | 
|  | private: | 
|  | const gfx::ImageSkia icon_; | 
|  | }; | 
|  |  | 
|  | struct IconRepresentationInfo { | 
|  | // Size as a string that will be used to retrieve a representation value from | 
|  | // SetIcon function arguments. | 
|  | const char* size_string; | 
|  | // Scale factor for which the representation should be used. | 
|  | ui::ResourceScaleFactor scale; | 
|  | }; | 
|  |  | 
|  | template <class T> | 
|  | bool HasValue(const std::map<int, T>& map, ExtensionAction::TabID tab_id) { | 
|  | return base::Contains(map, tab_id); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // static | 
|  | // LINT.IfChange(ActionIconSize) | 
|  | extension_misc::ExtensionIcons ExtensionAction::ActionIconSize() { | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | return extension_misc::EXTENSION_ICON_SMALLISH; | 
|  | #else | 
|  | return extension_misc::EXTENSION_ICON_BITTY; | 
|  | #endif | 
|  | } | 
|  | // LINT.ThenChange(/extensions/browser/icon_util.cc:ActionIconSize) | 
|  |  | 
|  | // static | 
|  | gfx::Image ExtensionAction::FallbackIcon() { | 
|  | return ui::ResourceBundle::GetSharedInstance().GetImageNamed( | 
|  | IDR_EXTENSIONS_FAVICON); | 
|  | } | 
|  |  | 
|  | const int ExtensionAction::kDefaultTabId = -1; | 
|  |  | 
|  | ExtensionAction::ExtensionAction(const Extension& extension, | 
|  | const ActionInfo& manifest_data) | 
|  | : extension_id_(extension.id()), | 
|  | extension_name_(extension.name()), | 
|  | action_type_(manifest_data.type), | 
|  | default_state_(manifest_data.default_state) { | 
|  | SetIsVisible(kDefaultTabId, | 
|  | default_state_ == ActionInfo::DefaultState::kEnabled); | 
|  | Populate(extension, manifest_data); | 
|  | } | 
|  |  | 
|  | ExtensionAction::~ExtensionAction() = default; | 
|  |  | 
|  | void ExtensionAction::SetPopupUrl(TabID tab_id, const GURL& url) { | 
|  | // We store |url| even if it is empty, rather than removing a URL from the | 
|  | // map.  If an extension has a default popup, and removes it for a tab via | 
|  | // the API, we must remember that there is no popup for that specific tab. | 
|  | // If we removed the tab's URL, GetPopupURL would incorrectly return the | 
|  | // default URL. | 
|  | SetValue(&popup_url_, tab_id, url); | 
|  | } | 
|  |  | 
|  | bool ExtensionAction::HasPopup(TabID tab_id) const { | 
|  | return !GetPopupUrl(tab_id).is_empty(); | 
|  | } | 
|  |  | 
|  | GURL ExtensionAction::GetPopupUrl(TabID tab_id) const { | 
|  | return GetValue(popup_url_, tab_id); | 
|  | } | 
|  |  | 
|  | void ExtensionAction::SetIcon(TabID tab_id, const gfx::Image& image) { | 
|  | SetValue(&icon_, tab_id, image); | 
|  | } | 
|  |  | 
|  | gfx::Image ExtensionAction::GetExplicitlySetIcon(TabID tab_id) const { | 
|  | return GetValue(icon_, tab_id); | 
|  | } | 
|  |  | 
|  | bool ExtensionAction::SetIsVisible(TabID tab_id, bool new_visibility) { | 
|  | const bool old_visibility = GetValue(is_visible_, tab_id); | 
|  |  | 
|  | if (old_visibility == new_visibility) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | SetValue(&is_visible_, tab_id, new_visibility); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void ExtensionAction::DeclarativeShow(TabID tab_id) { | 
|  | DCHECK_NE(tab_id, kDefaultTabId); | 
|  | ++declarative_show_count_[tab_id];  // Use default initialization to 0. | 
|  | } | 
|  |  | 
|  | void ExtensionAction::UndoDeclarativeShow(TabID tab_id) { | 
|  | int& show_count = declarative_show_count_[tab_id]; | 
|  | DCHECK_GT(show_count, 0); | 
|  | if (--show_count == 0) { | 
|  | declarative_show_count_.erase(tab_id); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ExtensionAction::DeclarativeSetIcon(TabID tab_id, | 
|  | int priority, | 
|  | const gfx::Image& icon) { | 
|  | DCHECK_NE(tab_id, kDefaultTabId); | 
|  | declarative_icon_[tab_id][priority].push_back(icon); | 
|  | } | 
|  |  | 
|  | void ExtensionAction::UndoDeclarativeSetIcon(TabID tab_id, | 
|  | int priority, | 
|  | const gfx::Image& icon) { | 
|  | std::vector<gfx::Image>& icons = declarative_icon_[tab_id][priority]; | 
|  | for (auto it = icons.begin(); it != icons.end(); ++it) { | 
|  | if (it->AsImageSkia().BackedBySameObjectAs(icon.AsImageSkia())) { | 
|  | icons.erase(it); | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | const gfx::Image ExtensionAction::GetDeclarativeIcon(TabID tab_id) const { | 
|  | auto it = declarative_icon_.find(tab_id); | 
|  | if (it != declarative_icon_.end() && !it->second.rbegin()->second.empty()) { | 
|  | return it->second.rbegin()->second.back(); | 
|  | } | 
|  | return gfx::Image(); | 
|  | } | 
|  |  | 
|  | void ExtensionAction::ClearAllValuesForTab(TabID tab_id) { | 
|  | popup_url_.erase(tab_id); | 
|  | title_.erase(tab_id); | 
|  | icon_.erase(tab_id); | 
|  | badge_text_.erase(tab_id); | 
|  | dnr_action_count_.erase(tab_id); | 
|  | badge_text_color_.erase(tab_id); | 
|  | badge_background_color_.erase(tab_id); | 
|  | is_visible_.erase(tab_id); | 
|  | // TODO(jyasskin): Erase the element from declarative_show_count_ | 
|  | // when the tab's closed.  There's a race between the | 
|  | // LocationBarController and the ContentRulesRegistry on navigation, | 
|  | // which prevents me from cleaning everything up now. | 
|  | } | 
|  |  | 
|  | void ExtensionAction::SetDefaultIconImage( | 
|  | std::unique_ptr<IconImage> icon_image) { | 
|  | default_icon_image_ = std::move(icon_image); | 
|  | } | 
|  |  | 
|  | gfx::Image ExtensionAction::GetDefaultIconImage() const { | 
|  | // If we have a default icon, it should be loaded before trying to use it. | 
|  | DCHECK(!default_icon_image_ == !default_icon_); | 
|  | if (default_icon_image_) { | 
|  | return default_icon_image_->image(); | 
|  | } | 
|  |  | 
|  | return GetPlaceholderIconImage(); | 
|  | } | 
|  |  | 
|  | gfx::Image ExtensionAction::GetPlaceholderIconImage() const { | 
|  | if (placeholder_icon_image_.IsEmpty()) { | 
|  | // For extension actions, we use a special placeholder icon (with the first | 
|  | // letter of the extension name) rather than the default (puzzle piece). | 
|  | // Note that this is only if we can't find any better image (e.g. a product | 
|  | // icon). | 
|  | placeholder_icon_image_ = ExtensionIconPlaceholder::CreateImage( | 
|  | ActionIconSize(), extension_name_); | 
|  | } | 
|  |  | 
|  | return placeholder_icon_image_; | 
|  | } | 
|  |  | 
|  | std::string ExtensionAction::GetDisplayBadgeText(TabID tab_id) const { | 
|  | // Tab specific badge text set by an extension overrides the automatically set | 
|  | // action count. Action count should only be shown if at least one action is | 
|  | // matched. | 
|  | bool use_dnr_action_count = | 
|  | !HasBadgeText(tab_id) && GetDNRActionCount(tab_id) > 0; | 
|  | return use_dnr_action_count ? base::NumberToString(GetDNRActionCount(tab_id)) | 
|  | : GetExplicitlySetBadgeText(tab_id); | 
|  | } | 
|  |  | 
|  | bool ExtensionAction::HasPopupUrl(TabID tab_id) const { | 
|  | return HasValue(popup_url_, tab_id); | 
|  | } | 
|  |  | 
|  | bool ExtensionAction::HasTitle(TabID tab_id) const { | 
|  | return HasValue(title_, tab_id); | 
|  | } | 
|  |  | 
|  | bool ExtensionAction::HasBadgeText(TabID tab_id) const { | 
|  | return HasValue(badge_text_, tab_id); | 
|  | } | 
|  |  | 
|  | bool ExtensionAction::HasBadgeBackgroundColor(TabID tab_id) const { | 
|  | return HasValue(badge_background_color_, tab_id); | 
|  | } | 
|  |  | 
|  | bool ExtensionAction::HasBadgeTextColor(TabID tab_id) const { | 
|  | return HasValue(badge_text_color_, tab_id); | 
|  | } | 
|  |  | 
|  | bool ExtensionAction::HasIsVisible(TabID tab_id) const { | 
|  | return HasValue(is_visible_, tab_id); | 
|  | } | 
|  |  | 
|  | bool ExtensionAction::HasIcon(TabID tab_id) const { | 
|  | return HasValue(icon_, tab_id); | 
|  | } | 
|  |  | 
|  | bool ExtensionAction::HasDNRActionCount(TabID tab_id) const { | 
|  | return HasValue(dnr_action_count_, tab_id); | 
|  | } | 
|  |  | 
|  | void ExtensionAction::SetDefaultIconForTest( | 
|  | std::unique_ptr<ExtensionIconSet> default_icon) { | 
|  | default_icon_ = std::move(default_icon); | 
|  | } | 
|  |  | 
|  | void ExtensionAction::Populate(const Extension& extension, | 
|  | const ActionInfo& manifest_data) { | 
|  | // If the manifest doesn't specify a title, set it to |extension|'s name. | 
|  | const std::string& title = !manifest_data.default_title.empty() | 
|  | ? manifest_data.default_title | 
|  | : extension.name(); | 
|  | SetTitle(kDefaultTabId, title); | 
|  | SetPopupUrl(kDefaultTabId, manifest_data.default_popup_url); | 
|  |  | 
|  | // Initialize the specified icon set. | 
|  | if (!manifest_data.default_icon.empty()) { | 
|  | default_icon_ = | 
|  | std::make_unique<ExtensionIconSet>(manifest_data.default_icon); | 
|  | } else { | 
|  | // Fall back to the product icons if no action icon exists. | 
|  | const ExtensionIconSet& product_icons = IconsInfo::GetIcons(&extension); | 
|  | if (!product_icons.empty()) { | 
|  | default_icon_ = std::make_unique<ExtensionIconSet>(product_icons); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Determines which icon would be returned by |GetIcon|, and returns its width. | 
|  | int ExtensionAction::GetIconWidth(TabID tab_id) const { | 
|  | // If icon has been set, return its width. | 
|  | gfx::Image icon = GetValue(icon_, tab_id); | 
|  | if (!icon.IsEmpty()) { | 
|  | return icon.Width(); | 
|  | } | 
|  | // If there is a default icon, the icon width will be set depending on our | 
|  | // action type. | 
|  | if (default_icon_) { | 
|  | return ActionIconSize(); | 
|  | } | 
|  |  | 
|  | // If no icon has been set and there is no default icon, we need favicon | 
|  | // width. | 
|  | return FallbackIcon().Width(); | 
|  | } | 
|  |  | 
|  | bool ExtensionAction::GetIsVisibleInternal(TabID tab_id, | 
|  | bool include_declarative) const { | 
|  | if (const bool* tab_is_visible = base::FindOrNull(is_visible_, tab_id)) { | 
|  | return *tab_is_visible; | 
|  | } | 
|  |  | 
|  | if (include_declarative && base::Contains(declarative_show_count_, tab_id)) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | if (const bool* default_is_visible = | 
|  | base::FindOrNull(is_visible_, kDefaultTabId)) { | 
|  | return *default_is_visible; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |