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.
5 #include "chrome/browser/extensions/extension_action.h"
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/geometry/rect.h"
29 #include "ui/gfx/geometry/size.h"
30 #include "ui/gfx/image/image.h"
31 #include "ui/gfx/image/image_skia.h"
32 #include "ui/gfx/image/image_skia_source.h"
33 #include "ui/gfx/ipc/gfx_param_traits.h"
34 #include "ui/gfx/skbitmap_operations.h"
39 class GetAttentionImageSource
: public gfx::ImageSkiaSource
{
41 explicit GetAttentionImageSource(const gfx::ImageSkia
& icon
)
44 // gfx::ImageSkiaSource overrides:
45 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
),
54 const gfx::ImageSkia icon_
;
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
;
65 const IconRepresentationInfo kIconSizes
[] = {{"19", ui::SCALE_FACTOR_100P
},
66 {"38", ui::SCALE_FACTOR_200P
}};
69 bool HasValue(const std::map
<int, T
>& map
, int tab_id
) {
70 return map
.find(tab_id
) != map
.end();
75 const int ExtensionAction::kDefaultTabId
= -1;
76 const int ExtensionAction::kPageActionIconMaxSize
=
77 extension_misc::EXTENSION_ICON_ACTION
;
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
)));
93 set_id(manifest_data
.id
);
96 ExtensionAction::~ExtensionAction() {
99 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_
;
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_
;
113 copy
->default_icon_
.reset(new ExtensionIconSet(*default_icon_
));
119 int ExtensionAction::GetIconSizeForType(
120 extensions::ActionInfo::Type 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
127 return extension_misc::EXTENSION_ICON_ACTION
;
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
140 SetValue(&popup_url_
, tab_id
, url
);
143 bool ExtensionAction::HasPopup(int tab_id
) const {
144 return !GetPopupUrl(tab_id
).is_empty();
147 GURL
ExtensionAction::GetPopupUrl(int tab_id
) const {
148 return GetValue(&popup_url_
, tab_id
);
151 void ExtensionAction::SetIcon(int tab_id
, const gfx::Image
& image
) {
152 SetValue(&icon_
, tab_id
, image
.AsImageSkia());
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
;
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
))
169 pickle
= IPC::Message(binary_string
.c_str(), binary_string
.length());
173 PickleIterator
iter(pickle
);
175 if (!IPC::ReadParam(&pickle
, &iter
, &bitmap
))
177 CHECK(!bitmap
.isNull());
178 float scale
= ui::GetScaleForScaleFactor(kIconSizes
[i
].scale
);
179 icon
->AddRepresentation(gfx::ImageSkiaRep(bitmap
, scale
));
184 gfx::ImageSkia
ExtensionAction::GetExplicitlySetIcon(int tab_id
) const {
185 return GetValue(&icon_
, tab_id
);
188 bool ExtensionAction::SetIsVisible(int tab_id
, bool new_visibility
) {
189 const bool old_visibility
= GetValue(&is_visible_
, tab_id
);
191 if (old_visibility
== new_visibility
)
194 SetValue(&is_visible_
, tab_id
, new_visibility
);
199 void ExtensionAction::DeclarativeShow(int tab_id
) {
200 DCHECK_NE(tab_id
, kDefaultTabId
);
201 ++declarative_show_count_
[tab_id
]; // Use default initialization to 0.
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
);
211 void ExtensionAction::DeclarativeSetIcon(int tab_id
,
213 const gfx::Image
& icon
) {
214 DCHECK_NE(tab_id
, kDefaultTabId
);
215 declarative_icon_
[tab_id
][priority
].push_back(icon
);
218 void ExtensionAction::UndoDeclarativeSetIcon(int tab_id
,
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();
224 if (it
->AsImageSkia().BackedBySameObjectAs(icon
.AsImageSkia())) {
231 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();
237 return gfx::ImageSkia();
240 void ExtensionAction::ClearAllValuesForTab(int tab_id
) {
241 popup_url_
.erase(tab_id
);
242 title_
.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.
254 void ExtensionAction::PaintBadge(gfx::Canvas
* canvas
,
255 const gfx::Rect
& bounds
,
257 badge_util::PaintBadge(
260 GetBadgeText(tab_id
),
261 GetBadgeTextColor(tab_id
),
262 GetBadgeBackgroundColor(tab_id
),
263 GetIconWidth(tab_id
),
267 gfx::ImageSkia
ExtensionAction::GetIconWithBadge(
268 const gfx::ImageSkia
& icon
,
270 const gfx::Size
& spacing
) const {
274 return gfx::ImageSkia(
275 new IconWithBadgeImageSource(icon
,
278 GetBadgeText(tab_id
),
279 GetBadgeTextColor(tab_id
),
280 GetBadgeBackgroundColor(tab_id
),
285 bool ExtensionAction::HasPopupUrl(int tab_id
) const {
286 return HasValue(popup_url_
, tab_id
);
289 bool ExtensionAction::HasTitle(int tab_id
) const {
290 return HasValue(title_
, tab_id
);
293 bool ExtensionAction::HasBadgeText(int tab_id
) const {
294 return HasValue(badge_text_
, tab_id
);
297 bool ExtensionAction::HasBadgeBackgroundColor(int tab_id
) const {
298 return HasValue(badge_background_color_
, tab_id
);
301 bool ExtensionAction::HasBadgeTextColor(int tab_id
) const {
302 return HasValue(badge_text_color_
, tab_id
);
305 bool ExtensionAction::HasIsVisible(int tab_id
) const {
306 return HasValue(is_visible_
, tab_id
);
309 bool ExtensionAction::HasIcon(int tab_id
) const {
310 return HasValue(icon_
, tab_id
);
313 // Determines which icon would be returned by |GetIcon|, and returns its width.
314 int ExtensionAction::GetIconWidth(int tab_id
) const {
315 // If icon has been set, return its width.
316 gfx::ImageSkia icon
= GetValue(&icon_
, tab_id
);
319 // If there is a default icon, the icon width will be set depending on our
322 return GetIconSizeForType(action_type());
324 // If no icon has been set and there is no default icon, we need favicon
326 return ui::ResourceBundle::GetSharedInstance().GetImageNamed(
327 IDR_EXTENSIONS_FAVICON
).ToImageSkia()->width();