1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "extensions/browser/extension_action.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/base64.h"
11 #include "base/check_op.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/values.h"
14 #include "extensions/browser/extension_icon_image.h"
15 #include "extensions/browser/extension_icon_placeholder.h"
16 #include "extensions/common/constants.h"
17 #include "extensions/common/extension_icon_set.h"
18 #include "extensions/common/manifest_handlers/icons_handler.h"
19 #include "extensions/grit/extensions_browser_resources.h"
20 #include "skia/ext/skia_utils_base.h"
21 #include "skia/public/mojom/bitmap.mojom.h"
22 #include "third_party/skia/include/core/SkBitmap.h"
23 #include "third_party/skia/include/core/SkCanvas.h"
24 #include "third_party/skia/include/core/SkPaint.h"
25 #include "third_party/skia/include/effects/SkGradientShader.h"
26 #include "ui/base/resource/resource_bundle.h"
27 #include "ui/gfx/animation/animation_delegate.h"
28 #include "ui/gfx/canvas.h"
29 #include "ui/gfx/color_utils.h"
30 #include "ui/gfx/geometry/rect.h"
31 #include "ui/gfx/geometry/size.h"
32 #include "ui/gfx/image/image.h"
33 #include "ui/gfx/image/image_skia.h"
34 #include "ui/gfx/image/image_skia_source.h"
35 #include "ui/gfx/skbitmap_operations.h"
36 #include "url/gurl.h"
37 
38 namespace extensions {
39 
40 namespace {
41 
42 class GetAttentionImageSource : public gfx::ImageSkiaSource {
43  public:
GetAttentionImageSource(const gfx::ImageSkia & icon)44   explicit GetAttentionImageSource(const gfx::ImageSkia& icon) : icon_(icon) {}
45 
46   // gfx::ImageSkiaSource overrides:
GetImageForScale(float scale)47   gfx::ImageSkiaRep GetImageForScale(float scale) override {
48     gfx::ImageSkiaRep icon_rep = icon_.GetRepresentation(scale);
49     color_utils::HSL shift = {-1, 0, 0.5};
50     return gfx::ImageSkiaRep(
51         SkBitmapOperations::CreateHSLShiftedBitmap(icon_rep.GetBitmap(), shift),
52         icon_rep.scale());
53   }
54 
55  private:
56   const gfx::ImageSkia icon_;
57 };
58 
59 struct IconRepresentationInfo {
60   // Size as a string that will be used to retrieve a representation value from
61   // SetIcon function arguments.
62   const char* size_string;
63   // Scale factor for which the represantion should be used.
64   ui::ScaleFactor scale;
65 };
66 
67 template <class T>
HasValue(const std::map<int,T> & map,int tab_id)68 bool HasValue(const std::map<int, T>& map, int tab_id) {
69   return map.find(tab_id) != map.end();
70 }
71 
72 }  // namespace
73 
74 // static
ActionIconSize()75 extension_misc::ExtensionIcons ExtensionAction::ActionIconSize() {
76   return extension_misc::EXTENSION_ICON_BITTY;
77 }
78 
79 // static
FallbackIcon()80 gfx::Image ExtensionAction::FallbackIcon() {
81   return ui::ResourceBundle::GetSharedInstance().GetImageNamed(
82       IDR_EXTENSIONS_FAVICON);
83 }
84 
85 const int ExtensionAction::kDefaultTabId = -1;
86 
ExtensionAction(const Extension & extension,const ActionInfo & manifest_data)87 ExtensionAction::ExtensionAction(const Extension& extension,
88                                  const ActionInfo& manifest_data)
89     : extension_id_(extension.id()),
90       extension_name_(extension.name()),
91       action_type_(manifest_data.type),
92       default_state_(manifest_data.default_state) {
93   SetIsVisible(kDefaultTabId, default_state_ == ActionInfo::STATE_ENABLED);
94   Populate(extension, manifest_data);
95 }
96 
~ExtensionAction()97 ExtensionAction::~ExtensionAction() {}
98 
SetPopupUrl(int tab_id,const GURL & url)99 void ExtensionAction::SetPopupUrl(int tab_id, const GURL& url) {
100   // We store |url| even if it is empty, rather than removing a URL from the
101   // map.  If an extension has a default popup, and removes it for a tab via
102   // the API, we must remember that there is no popup for that specific tab.
103   // If we removed the tab's URL, GetPopupURL would incorrectly return the
104   // default URL.
105   SetValue(&popup_url_, tab_id, url);
106 }
107 
HasPopup(int tab_id) const108 bool ExtensionAction::HasPopup(int tab_id) const {
109   return !GetPopupUrl(tab_id).is_empty();
110 }
111 
GetPopupUrl(int tab_id) const112 GURL ExtensionAction::GetPopupUrl(int tab_id) const {
113   return GetValue(&popup_url_, tab_id);
114 }
115 
SetIcon(int tab_id,const gfx::Image & image)116 void ExtensionAction::SetIcon(int tab_id, const gfx::Image& image) {
117   SetValue(&icon_, tab_id, image);
118 }
119 
ParseIconFromCanvasDictionary(const base::DictionaryValue & dict,gfx::ImageSkia * icon)120 ExtensionAction::IconParseResult ExtensionAction::ParseIconFromCanvasDictionary(
121     const base::DictionaryValue& dict,
122     gfx::ImageSkia* icon) {
123   for (base::DictionaryValue::Iterator iter(dict); !iter.IsAtEnd();
124        iter.Advance()) {
125     std::string byte_string;
126     std::string base64_string;
127     const void* bytes = nullptr;
128     size_t num_bytes = 0;
129     if (iter.value().is_blob()) {
130       bytes = iter.value().GetBlob().data();
131       num_bytes = iter.value().GetBlob().size();
132     } else if (iter.value().GetAsString(&base64_string)) {
133       if (!base::Base64Decode(base64_string, &byte_string))
134         return IconParseResult::kDecodeFailure;
135       bytes = byte_string.c_str();
136       num_bytes = byte_string.length();
137     } else {
138       continue;
139     }
140     SkBitmap unsafe_bitmap;
141     if (!skia::mojom::InlineBitmap::Deserialize(bytes, num_bytes,
142                                                 &unsafe_bitmap)) {
143       return IconParseResult::kUnpickleFailure;
144     }
145     CHECK(!unsafe_bitmap.isNull());
146     // On receipt of an arbitrary bitmap from the renderer, we convert to an N32
147     // 32bpp bitmap. Other pixel sizes can lead to out-of-bounds mistakes when
148     // transferring the pixels out of the/ bitmap into other buffers.
149     SkBitmap bitmap;
150     if (!skia::SkBitmapToN32OpaqueOrPremul(unsafe_bitmap, &bitmap)) {
151       NOTREACHED() << "Unable to convert bitmap for icon";
152       return IconParseResult::kUnpickleFailure;
153     }
154 
155     // Chrome helpfully scales the provided icon(s), but let's not go overboard.
156     const int kActionIconMaxSize = 10 * ActionIconSize();
157     if (bitmap.drawsNothing() || bitmap.width() > kActionIconMaxSize)
158       continue;
159 
160     float scale = static_cast<float>(bitmap.width()) / ActionIconSize();
161     icon->AddRepresentation(gfx::ImageSkiaRep(bitmap, scale));
162   }
163   return IconParseResult::kSuccess;
164 }
165 
GetExplicitlySetIcon(int tab_id) const166 gfx::Image ExtensionAction::GetExplicitlySetIcon(int tab_id) const {
167   return GetValue(&icon_, tab_id);
168 }
169 
SetIsVisible(int tab_id,bool new_visibility)170 bool ExtensionAction::SetIsVisible(int tab_id, bool new_visibility) {
171   const bool old_visibility = GetValue(&is_visible_, tab_id);
172 
173   if (old_visibility == new_visibility)
174     return false;
175 
176   SetValue(&is_visible_, tab_id, new_visibility);
177 
178   return true;
179 }
180 
DeclarativeShow(int tab_id)181 void ExtensionAction::DeclarativeShow(int tab_id) {
182   DCHECK_NE(tab_id, kDefaultTabId);
183   ++declarative_show_count_[tab_id];  // Use default initialization to 0.
184 }
185 
UndoDeclarativeShow(int tab_id)186 void ExtensionAction::UndoDeclarativeShow(int tab_id) {
187   int& show_count = declarative_show_count_[tab_id];
188   DCHECK_GT(show_count, 0);
189   if (--show_count == 0)
190     declarative_show_count_.erase(tab_id);
191 }
192 
DeclarativeSetIcon(int tab_id,int priority,const gfx::Image & icon)193 void ExtensionAction::DeclarativeSetIcon(int tab_id,
194                                          int priority,
195                                          const gfx::Image& icon) {
196   DCHECK_NE(tab_id, kDefaultTabId);
197   declarative_icon_[tab_id][priority].push_back(icon);
198 }
199 
UndoDeclarativeSetIcon(int tab_id,int priority,const gfx::Image & icon)200 void ExtensionAction::UndoDeclarativeSetIcon(int tab_id,
201                                              int priority,
202                                              const gfx::Image& icon) {
203   std::vector<gfx::Image>& icons = declarative_icon_[tab_id][priority];
204   for (auto it = icons.begin(); it != icons.end(); ++it) {
205     if (it->AsImageSkia().BackedBySameObjectAs(icon.AsImageSkia())) {
206       icons.erase(it);
207       return;
208     }
209   }
210 }
211 
GetDeclarativeIcon(int tab_id) const212 const gfx::Image ExtensionAction::GetDeclarativeIcon(int tab_id) const {
213   if (declarative_icon_.find(tab_id) != declarative_icon_.end() &&
214       !declarative_icon_.find(tab_id)->second.rbegin()->second.empty()) {
215     return declarative_icon_.find(tab_id)->second.rbegin()->second.back();
216   }
217   return gfx::Image();
218 }
219 
ClearAllValuesForTab(int tab_id)220 void ExtensionAction::ClearAllValuesForTab(int tab_id) {
221   popup_url_.erase(tab_id);
222   title_.erase(tab_id);
223   icon_.erase(tab_id);
224   badge_text_.erase(tab_id);
225   dnr_action_count_.erase(tab_id);
226   badge_text_color_.erase(tab_id);
227   badge_background_color_.erase(tab_id);
228   is_visible_.erase(tab_id);
229   // TODO(jyasskin): Erase the element from declarative_show_count_
230   // when the tab's closed.  There's a race between the
231   // LocationBarController and the ContentRulesRegistry on navigation,
232   // which prevents me from cleaning everything up now.
233 }
234 
SetDefaultIconImage(std::unique_ptr<IconImage> icon_image)235 void ExtensionAction::SetDefaultIconImage(
236     std::unique_ptr<IconImage> icon_image) {
237   default_icon_image_ = std::move(icon_image);
238 }
239 
GetDefaultIconImage() const240 gfx::Image ExtensionAction::GetDefaultIconImage() const {
241   // If we have a default icon, it should be loaded before trying to use it.
242   DCHECK(!default_icon_image_ == !default_icon_);
243   if (default_icon_image_)
244     return default_icon_image_->image();
245 
246   return GetPlaceholderIconImage();
247 }
248 
GetPlaceholderIconImage() const249 gfx::Image ExtensionAction::GetPlaceholderIconImage() const {
250   if (placeholder_icon_image_.IsEmpty()) {
251     // For extension actions, we use a special placeholder icon (with the first
252     // letter of the extension name) rather than the default (puzzle piece).
253     // Note that this is only if we can't find any better image (e.g. a product
254     // icon).
255     placeholder_icon_image_ = ExtensionIconPlaceholder::CreateImage(
256         ActionIconSize(), extension_name_);
257   }
258 
259   return placeholder_icon_image_;
260 }
261 
GetDisplayBadgeText(int tab_id) const262 std::string ExtensionAction::GetDisplayBadgeText(int tab_id) const {
263   return UseDNRActionCountAsBadgeText(tab_id)
264              ? base::NumberToString(GetDNRActionCount(tab_id))
265              : GetExplicitlySetBadgeText(tab_id);
266 }
267 
UseDNRActionCountAsBadgeText(int tab_id) const268 bool ExtensionAction::UseDNRActionCountAsBadgeText(int tab_id) const {
269   // Tab specific badge text set by an extension overrides the automatically set
270   // action count. Action count should only be shown if at least one action is
271   // matched.
272   return !HasBadgeText(tab_id) && GetDNRActionCount(tab_id) > 0;
273 }
274 
HasPopupUrl(int tab_id) const275 bool ExtensionAction::HasPopupUrl(int tab_id) const {
276   return HasValue(popup_url_, tab_id);
277 }
278 
HasTitle(int tab_id) const279 bool ExtensionAction::HasTitle(int tab_id) const {
280   return HasValue(title_, tab_id);
281 }
282 
HasBadgeText(int tab_id) const283 bool ExtensionAction::HasBadgeText(int tab_id) const {
284   return HasValue(badge_text_, tab_id);
285 }
286 
HasBadgeBackgroundColor(int tab_id) const287 bool ExtensionAction::HasBadgeBackgroundColor(int tab_id) const {
288   return HasValue(badge_background_color_, tab_id);
289 }
290 
HasBadgeTextColor(int tab_id) const291 bool ExtensionAction::HasBadgeTextColor(int tab_id) const {
292   return HasValue(badge_text_color_, tab_id);
293 }
294 
HasIsVisible(int tab_id) const295 bool ExtensionAction::HasIsVisible(int tab_id) const {
296   return HasValue(is_visible_, tab_id);
297 }
298 
HasIcon(int tab_id) const299 bool ExtensionAction::HasIcon(int tab_id) const {
300   return HasValue(icon_, tab_id);
301 }
302 
HasDNRActionCount(int tab_id) const303 bool ExtensionAction::HasDNRActionCount(int tab_id) const {
304   return HasValue(dnr_action_count_, tab_id);
305 }
306 
SetDefaultIconForTest(std::unique_ptr<ExtensionIconSet> default_icon)307 void ExtensionAction::SetDefaultIconForTest(
308     std::unique_ptr<ExtensionIconSet> default_icon) {
309   default_icon_ = std::move(default_icon);
310 }
311 
Populate(const Extension & extension,const ActionInfo & manifest_data)312 void ExtensionAction::Populate(const Extension& extension,
313                                const ActionInfo& manifest_data) {
314   // If the manifest doesn't specify a title, set it to |extension|'s name.
315   const std::string& title = !manifest_data.default_title.empty()
316                                  ? manifest_data.default_title
317                                  : extension.name();
318   SetTitle(kDefaultTabId, title);
319   SetPopupUrl(kDefaultTabId, manifest_data.default_popup_url);
320 
321   // Initialize the specified icon set.
322   if (!manifest_data.default_icon.empty()) {
323     default_icon_.reset(new ExtensionIconSet(manifest_data.default_icon));
324   } else {
325     // Fall back to the product icons if no action icon exists.
326     const ExtensionIconSet& product_icons = IconsInfo::GetIcons(&extension);
327     if (!product_icons.empty())
328       default_icon_.reset(new ExtensionIconSet(product_icons));
329   }
330 }
331 
332 // Determines which icon would be returned by |GetIcon|, and returns its width.
GetIconWidth(int tab_id) const333 int ExtensionAction::GetIconWidth(int tab_id) const {
334   // If icon has been set, return its width.
335   gfx::Image icon = GetValue(&icon_, tab_id);
336   if (!icon.IsEmpty())
337     return icon.Width();
338   // If there is a default icon, the icon width will be set depending on our
339   // action type.
340   if (default_icon_)
341     return ActionIconSize();
342 
343   // If no icon has been set and there is no default icon, we need favicon
344   // width.
345   return FallbackIcon().Width();
346 }
347 
348 }  // namespace extensions
349