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