| // 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 |