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 "chrome/browser/extensions/extension_action.h"
6
7 #include <algorithm>
8
9 #include "base/base64.h"
10 #include "base/bind.h"
11 #include "base/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "chrome/common/badge_util.h"
14 #include "chrome/common/icon_with_badge_image_source.h"
15 #include "extensions/common/constants.h"
16 #include "grit/theme_resources.h"
17 #include "grit/ui_resources.h"
18 #include "ipc/ipc_message.h"
19 #include "ipc/ipc_message_utils.h"
20 #include "third_party/skia/include/core/SkBitmap.h"
21 #include "third_party/skia/include/core/SkCanvas.h"
22 #include "third_party/skia/include/core/SkPaint.h"
23 #include "third_party/skia/include/effects/SkGradientShader.h"
24 #include "ui/base/resource/resource_bundle.h"
25 #include "ui/gfx/animation/animation_delegate.h"
26 #include "ui/gfx/canvas.h"
27 #include "ui/gfx/color_utils.h"
28 #include "ui/gfx/image/image.h"
29 #include "ui/gfx/image/image_skia.h"
30 #include "ui/gfx/image/image_skia_source.h"
31 #include "ui/gfx/ipc/gfx_param_traits.h"
32 #include "ui/gfx/rect.h"
33 #include "ui/gfx/size.h"
34 #include "ui/gfx/skbitmap_operations.h"
35 #include "url/gurl.h"
36
37 namespace {
38
39 class GetAttentionImageSource : public gfx::ImageSkiaSource {
40 public:
GetAttentionImageSource(const gfx::ImageSkia & icon)41 explicit GetAttentionImageSource(const gfx::ImageSkia& icon)
42 : icon_(icon) {}
43
44 // gfx::ImageSkiaSource overrides:
GetImageForScale(float scale)45 virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
46 gfx::ImageSkiaRep icon_rep = icon_.GetRepresentation(scale);
47 color_utils::HSL shift = {-1, 0, 0.5};
48 return gfx::ImageSkiaRep(
49 SkBitmapOperations::CreateHSLShiftedBitmap(icon_rep.sk_bitmap(), shift),
50 icon_rep.scale());
51 }
52
53 private:
54 const gfx::ImageSkia icon_;
55 };
56
57 struct IconRepresentationInfo {
58 // Size as a string that will be used to retrieve a representation value from
59 // SetIcon function arguments.
60 const char* size_string;
61 // Scale factor for which the represantion should be used.
62 ui::ScaleFactor scale;
63 };
64
65 const IconRepresentationInfo kIconSizes[] = {{"19", ui::SCALE_FACTOR_100P},
66 {"38", ui::SCALE_FACTOR_200P}};
67
68 template <class T>
HasValue(const std::map<int,T> & map,int tab_id)69 bool HasValue(const std::map<int, T>& map, int tab_id) {
70 return map.find(tab_id) != map.end();
71 }
72
73 } // namespace
74
75 const int ExtensionAction::kDefaultTabId = -1;
76 const int ExtensionAction::kPageActionIconMaxSize =
77 extension_misc::EXTENSION_ICON_ACTION;
78
ExtensionAction(const std::string & extension_id,extensions::ActionInfo::Type action_type,const extensions::ActionInfo & manifest_data)79 ExtensionAction::ExtensionAction(const std::string& extension_id,
80 extensions::ActionInfo::Type action_type,
81 const extensions::ActionInfo& manifest_data)
82 : extension_id_(extension_id), action_type_(action_type) {
83 // Page/script actions are hidden/disabled by default, and browser actions are
84 // visible/enabled by default.
85 SetIsVisible(kDefaultTabId,
86 action_type == extensions::ActionInfo::TYPE_BROWSER);
87 SetTitle(kDefaultTabId, manifest_data.default_title);
88 SetPopupUrl(kDefaultTabId, manifest_data.default_popup_url);
89 if (!manifest_data.default_icon.empty()) {
90 set_default_icon(make_scoped_ptr(new ExtensionIconSet(
91 manifest_data.default_icon)));
92 }
93 set_id(manifest_data.id);
94 }
95
~ExtensionAction()96 ExtensionAction::~ExtensionAction() {
97 }
98
CopyForTest() const99 scoped_ptr<ExtensionAction> ExtensionAction::CopyForTest() const {
100 scoped_ptr<ExtensionAction> copy(
101 new ExtensionAction(extension_id_, action_type_,
102 extensions::ActionInfo()));
103 copy->popup_url_ = popup_url_;
104 copy->title_ = title_;
105 copy->icon_ = icon_;
106 copy->badge_text_ = badge_text_;
107 copy->badge_background_color_ = badge_background_color_;
108 copy->badge_text_color_ = badge_text_color_;
109 copy->is_visible_ = is_visible_;
110 copy->id_ = id_;
111
112 if (default_icon_)
113 copy->default_icon_.reset(new ExtensionIconSet(*default_icon_));
114
115 return copy.Pass();
116 }
117
118 // static
GetIconSizeForType(extensions::ActionInfo::Type type)119 int ExtensionAction::GetIconSizeForType(
120 extensions::ActionInfo::Type type) {
121 switch (type) {
122 case extensions::ActionInfo::TYPE_BROWSER:
123 case extensions::ActionInfo::TYPE_PAGE:
124 case extensions::ActionInfo::TYPE_SYSTEM_INDICATOR:
125 // TODO(dewittj) Report the actual icon size of the system
126 // indicator.
127 return extension_misc::EXTENSION_ICON_ACTION;
128 default:
129 NOTREACHED();
130 return 0;
131 }
132 }
133
SetPopupUrl(int tab_id,const GURL & url)134 void ExtensionAction::SetPopupUrl(int tab_id, const GURL& url) {
135 // We store |url| even if it is empty, rather than removing a URL from the
136 // map. If an extension has a default popup, and removes it for a tab via
137 // the API, we must remember that there is no popup for that specific tab.
138 // If we removed the tab's URL, GetPopupURL would incorrectly return the
139 // default URL.
140 SetValue(&popup_url_, tab_id, url);
141 }
142
HasPopup(int tab_id) const143 bool ExtensionAction::HasPopup(int tab_id) const {
144 return !GetPopupUrl(tab_id).is_empty();
145 }
146
GetPopupUrl(int tab_id) const147 GURL ExtensionAction::GetPopupUrl(int tab_id) const {
148 return GetValue(&popup_url_, tab_id);
149 }
150
SetIcon(int tab_id,const gfx::Image & image)151 void ExtensionAction::SetIcon(int tab_id, const gfx::Image& image) {
152 SetValue(&icon_, tab_id, image.AsImageSkia());
153 }
154
ParseIconFromCanvasDictionary(const base::DictionaryValue & dict,gfx::ImageSkia * icon)155 bool ExtensionAction::ParseIconFromCanvasDictionary(
156 const base::DictionaryValue& dict,
157 gfx::ImageSkia* icon) {
158 // Try to extract an icon for each known scale.
159 for (size_t i = 0; i < arraysize(kIconSizes); i++) {
160 const base::BinaryValue* image_data;
161 std::string binary_string64;
162 IPC::Message pickle;
163 if (dict.GetBinary(kIconSizes[i].size_string, &image_data)) {
164 pickle = IPC::Message(image_data->GetBuffer(), image_data->GetSize());
165 } else if (dict.GetString(kIconSizes[i].size_string, &binary_string64)) {
166 std::string binary_string;
167 if (!base::Base64Decode(binary_string64, &binary_string))
168 return false;
169 pickle = IPC::Message(binary_string.c_str(), binary_string.length());
170 } else {
171 continue;
172 }
173 PickleIterator iter(pickle);
174 SkBitmap bitmap;
175 if (!IPC::ReadParam(&pickle, &iter, &bitmap))
176 return false;
177 CHECK(!bitmap.isNull());
178 float scale = ui::GetScaleForScaleFactor(kIconSizes[i].scale);
179 icon->AddRepresentation(gfx::ImageSkiaRep(bitmap, scale));
180 }
181 return true;
182 }
183
GetExplicitlySetIcon(int tab_id) const184 gfx::ImageSkia ExtensionAction::GetExplicitlySetIcon(int tab_id) const {
185 return GetValue(&icon_, tab_id);
186 }
187
SetIsVisible(int tab_id,bool new_visibility)188 bool ExtensionAction::SetIsVisible(int tab_id, bool new_visibility) {
189 const bool old_visibility = GetValue(&is_visible_, tab_id);
190
191 if (old_visibility == new_visibility)
192 return false;
193
194 SetValue(&is_visible_, tab_id, new_visibility);
195
196 return true;
197 }
198
DeclarativeShow(int tab_id)199 void ExtensionAction::DeclarativeShow(int tab_id) {
200 DCHECK_NE(tab_id, kDefaultTabId);
201 ++declarative_show_count_[tab_id]; // Use default initialization to 0.
202 }
203
UndoDeclarativeShow(int tab_id)204 void ExtensionAction::UndoDeclarativeShow(int tab_id) {
205 int& show_count = declarative_show_count_[tab_id];
206 DCHECK_GT(show_count, 0);
207 if (--show_count == 0)
208 declarative_show_count_.erase(tab_id);
209 }
210
DeclarativeSetIcon(int tab_id,int priority,const gfx::Image & icon)211 void ExtensionAction::DeclarativeSetIcon(int tab_id,
212 int priority,
213 const gfx::Image& icon) {
214 DCHECK_NE(tab_id, kDefaultTabId);
215 declarative_icon_[tab_id][priority].push_back(icon);
216 }
217
UndoDeclarativeSetIcon(int tab_id,int priority,const gfx::Image & icon)218 void ExtensionAction::UndoDeclarativeSetIcon(int tab_id,
219 int priority,
220 const gfx::Image& icon) {
221 std::vector<gfx::Image>& icons = declarative_icon_[tab_id][priority];
222 for (std::vector<gfx::Image>::iterator it = icons.begin(); it != icons.end();
223 ++it) {
224 if (it->AsImageSkia().BackedBySameObjectAs(icon.AsImageSkia())) {
225 icons.erase(it);
226 return;
227 }
228 }
229 }
230
GetDeclarativeIcon(int tab_id) const231 const gfx::ImageSkia ExtensionAction::GetDeclarativeIcon(int tab_id) const {
232 if (declarative_icon_.find(tab_id) != declarative_icon_.end() &&
233 !declarative_icon_.find(tab_id)->second.rbegin()->second.empty()) {
234 return declarative_icon_.find(tab_id)->second.rbegin()
235 ->second.back().AsImageSkia();
236 }
237 return gfx::ImageSkia();
238 }
239
ClearAllValuesForTab(int tab_id)240 void ExtensionAction::ClearAllValuesForTab(int tab_id) {
241 popup_url_.erase(tab_id);
242 title_.erase(tab_id);
243 icon_.erase(tab_id);
244 badge_text_.erase(tab_id);
245 badge_text_color_.erase(tab_id);
246 badge_background_color_.erase(tab_id);
247 is_visible_.erase(tab_id);
248 // TODO(jyasskin): Erase the element from declarative_show_count_
249 // when the tab's closed. There's a race between the
250 // LocationBarController and the ContentRulesRegistry on navigation,
251 // which prevents me from cleaning everything up now.
252 }
253
PaintBadge(gfx::Canvas * canvas,const gfx::Rect & bounds,int tab_id)254 void ExtensionAction::PaintBadge(gfx::Canvas* canvas,
255 const gfx::Rect& bounds,
256 int tab_id) {
257 badge_util::PaintBadge(
258 canvas,
259 bounds,
260 GetBadgeText(tab_id),
261 GetBadgeTextColor(tab_id),
262 GetBadgeBackgroundColor(tab_id),
263 GetIconWidth(tab_id),
264 action_type());
265 }
266
GetIconWithBadge(const gfx::ImageSkia & icon,int tab_id,const gfx::Size & spacing) const267 gfx::ImageSkia ExtensionAction::GetIconWithBadge(
268 const gfx::ImageSkia& icon,
269 int tab_id,
270 const gfx::Size& spacing) const {
271 if (tab_id < 0)
272 return icon;
273
274 return gfx::ImageSkia(
275 new IconWithBadgeImageSource(icon,
276 icon.size(),
277 spacing,
278 GetBadgeText(tab_id),
279 GetBadgeTextColor(tab_id),
280 GetBadgeBackgroundColor(tab_id),
281 action_type()),
282 icon.size());
283 }
284
HasPopupUrl(int tab_id) const285 bool ExtensionAction::HasPopupUrl(int tab_id) const {
286 return HasValue(popup_url_, tab_id);
287 }
288
HasTitle(int tab_id) const289 bool ExtensionAction::HasTitle(int tab_id) const {
290 return HasValue(title_, tab_id);
291 }
292
HasBadgeText(int tab_id) const293 bool ExtensionAction::HasBadgeText(int tab_id) const {
294 return HasValue(badge_text_, tab_id);
295 }
296
HasBadgeBackgroundColor(int tab_id) const297 bool ExtensionAction::HasBadgeBackgroundColor(int tab_id) const {
298 return HasValue(badge_background_color_, tab_id);
299 }
300
HasBadgeTextColor(int tab_id) const301 bool ExtensionAction::HasBadgeTextColor(int tab_id) const {
302 return HasValue(badge_text_color_, tab_id);
303 }
304
HasIsVisible(int tab_id) const305 bool ExtensionAction::HasIsVisible(int tab_id) const {
306 return HasValue(is_visible_, tab_id);
307 }
308
HasIcon(int tab_id) const309 bool ExtensionAction::HasIcon(int tab_id) const {
310 return HasValue(icon_, tab_id);
311 }
312
313 // Determines which icon would be returned by |GetIcon|, and returns its width.
GetIconWidth(int tab_id) const314 int ExtensionAction::GetIconWidth(int tab_id) const {
315 // If icon has been set, return its width.
316 gfx::ImageSkia icon = GetValue(&icon_, tab_id);
317 if (!icon.isNull())
318 return icon.width();
319 // If there is a default icon, the icon width will be set depending on our
320 // action type.
321 if (default_icon_)
322 return GetIconSizeForType(action_type());
323
324 // If no icon has been set and there is no default icon, we need favicon
325 // width.
326 return ui::ResourceBundle::GetSharedInstance().GetImageNamed(
327 IDR_EXTENSIONS_FAVICON).ToImageSkia()->width();
328 }
329