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/logging.h"
11 #include "extensions/browser/extension_icon_image.h"
12 #include "extensions/browser/extension_icon_placeholder.h"
13 #include "extensions/common/constants.h"
14 #include "extensions/common/extension_icon_set.h"
15 #include "extensions/common/feature_switch.h"
16 #include "extensions/common/manifest_handlers/icons_handler.h"
17 #include "grit/theme_resources.h"
18 #include "grit/ui_resources.h"
19 #include "ipc/ipc_message.h"
20 #include "ipc/ipc_message_utils.h"
21 #include "third_party/skia/include/core/SkBitmap.h"
22 #include "third_party/skia/include/core/SkCanvas.h"
23 #include "third_party/skia/include/core/SkPaint.h"
24 #include "third_party/skia/include/effects/SkGradientShader.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/gfx/animation/animation_delegate.h"
27 #include "ui/gfx/canvas.h"
28 #include "ui/gfx/color_utils.h"
29 #include "ui/gfx/geometry/rect.h"
30 #include "ui/gfx/geometry/size.h"
31 #include "ui/gfx/image/image.h"
32 #include "ui/gfx/image/image_skia.h"
33 #include "ui/gfx/image/image_skia_source.h"
34 #include "ui/gfx/ipc/gfx_param_traits.h"
35 #include "ui/gfx/skbitmap_operations.h"
40 // Returns the default icon image for extensions.
41 gfx::Image
GetDefaultIcon() {
42 return ui::ResourceBundle::GetSharedInstance().GetImageNamed(
43 IDR_EXTENSIONS_FAVICON
);
46 // Given the extension action type, returns the size the extension action icon
47 // should have. The icon should be square, so only one dimension is
49 int GetIconSizeForType(extensions::ActionInfo::Type type
) {
51 case extensions::ActionInfo::TYPE_BROWSER
:
52 case extensions::ActionInfo::TYPE_PAGE
:
53 case extensions::ActionInfo::TYPE_SYSTEM_INDICATOR
:
54 // TODO(dewittj) Report the actual icon size of the system
56 return extension_misc::EXTENSION_ICON_ACTION
;
63 class GetAttentionImageSource
: public gfx::ImageSkiaSource
{
65 explicit GetAttentionImageSource(const gfx::ImageSkia
& icon
)
68 // gfx::ImageSkiaSource overrides:
69 gfx::ImageSkiaRep
GetImageForScale(float scale
) override
{
70 gfx::ImageSkiaRep icon_rep
= icon_
.GetRepresentation(scale
);
71 color_utils::HSL shift
= {-1, 0, 0.5};
72 return gfx::ImageSkiaRep(
73 SkBitmapOperations::CreateHSLShiftedBitmap(icon_rep
.sk_bitmap(), shift
),
78 const gfx::ImageSkia icon_
;
81 struct IconRepresentationInfo
{
82 // Size as a string that will be used to retrieve a representation value from
83 // SetIcon function arguments.
84 const char* size_string
;
85 // Scale factor for which the represantion should be used.
86 ui::ScaleFactor scale
;
89 const IconRepresentationInfo kIconSizes
[] = {{"19", ui::SCALE_FACTOR_100P
},
90 {"38", ui::SCALE_FACTOR_200P
}};
93 bool HasValue(const std::map
<int, T
>& map
, int tab_id
) {
94 return map
.find(tab_id
) != map
.end();
99 const int ExtensionAction::kDefaultTabId
= -1;
100 const int ExtensionAction::kPageActionIconMaxSize
=
101 extension_misc::EXTENSION_ICON_ACTION
;
103 ExtensionAction::ExtensionAction(const extensions::Extension
& extension
,
104 extensions::ActionInfo::Type action_type
,
105 const extensions::ActionInfo
& manifest_data
)
106 : extension_id_(extension
.id()),
107 extension_name_(extension
.name()),
108 action_type_(action_type
) {
109 // Page/script actions are hidden/disabled by default, and browser actions are
110 // visible/enabled by default.
111 SetIsVisible(kDefaultTabId
,
112 action_type
== extensions::ActionInfo::TYPE_BROWSER
);
113 Populate(extension
, manifest_data
);
116 ExtensionAction::~ExtensionAction() {
119 void ExtensionAction::SetPopupUrl(int tab_id
, const GURL
& url
) {
120 // We store |url| even if it is empty, rather than removing a URL from the
121 // map. If an extension has a default popup, and removes it for a tab via
122 // the API, we must remember that there is no popup for that specific tab.
123 // If we removed the tab's URL, GetPopupURL would incorrectly return the
125 SetValue(&popup_url_
, tab_id
, url
);
128 bool ExtensionAction::HasPopup(int tab_id
) const {
129 return !GetPopupUrl(tab_id
).is_empty();
132 GURL
ExtensionAction::GetPopupUrl(int tab_id
) const {
133 return GetValue(&popup_url_
, tab_id
);
136 void ExtensionAction::SetIcon(int tab_id
, const gfx::Image
& image
) {
137 SetValue(&icon_
, tab_id
, image
);
140 bool ExtensionAction::ParseIconFromCanvasDictionary(
141 const base::DictionaryValue
& dict
,
142 gfx::ImageSkia
* icon
) {
143 // Try to extract an icon for each known scale.
144 for (size_t i
= 0; i
< arraysize(kIconSizes
); i
++) {
145 const base::BinaryValue
* image_data
;
146 std::string binary_string64
;
148 if (dict
.GetBinary(kIconSizes
[i
].size_string
, &image_data
)) {
149 pickle
= IPC::Message(image_data
->GetBuffer(), image_data
->GetSize());
150 } else if (dict
.GetString(kIconSizes
[i
].size_string
, &binary_string64
)) {
151 std::string binary_string
;
152 if (!base::Base64Decode(binary_string64
, &binary_string
))
154 pickle
= IPC::Message(binary_string
.c_str(), binary_string
.length());
158 base::PickleIterator
iter(pickle
);
160 if (!IPC::ReadParam(&pickle
, &iter
, &bitmap
))
162 CHECK(!bitmap
.isNull());
163 float scale
= ui::GetScaleForScaleFactor(kIconSizes
[i
].scale
);
164 icon
->AddRepresentation(gfx::ImageSkiaRep(bitmap
, scale
));
169 gfx::Image
ExtensionAction::GetExplicitlySetIcon(int tab_id
) const {
170 return GetValue(&icon_
, tab_id
);
173 bool ExtensionAction::SetIsVisible(int tab_id
, bool new_visibility
) {
174 const bool old_visibility
= GetValue(&is_visible_
, tab_id
);
176 if (old_visibility
== new_visibility
)
179 SetValue(&is_visible_
, tab_id
, new_visibility
);
184 void ExtensionAction::DeclarativeShow(int tab_id
) {
185 DCHECK_NE(tab_id
, kDefaultTabId
);
186 ++declarative_show_count_
[tab_id
]; // Use default initialization to 0.
189 void ExtensionAction::UndoDeclarativeShow(int tab_id
) {
190 int& show_count
= declarative_show_count_
[tab_id
];
191 DCHECK_GT(show_count
, 0);
192 if (--show_count
== 0)
193 declarative_show_count_
.erase(tab_id
);
196 void ExtensionAction::DeclarativeSetIcon(int tab_id
,
198 const gfx::Image
& icon
) {
199 DCHECK_NE(tab_id
, kDefaultTabId
);
200 declarative_icon_
[tab_id
][priority
].push_back(icon
);
203 void ExtensionAction::UndoDeclarativeSetIcon(int tab_id
,
205 const gfx::Image
& icon
) {
206 std::vector
<gfx::Image
>& icons
= declarative_icon_
[tab_id
][priority
];
207 for (std::vector
<gfx::Image
>::iterator it
= icons
.begin(); it
!= icons
.end();
209 if (it
->AsImageSkia().BackedBySameObjectAs(icon
.AsImageSkia())) {
216 const gfx::Image
ExtensionAction::GetDeclarativeIcon(int tab_id
) const {
217 if (declarative_icon_
.find(tab_id
) != declarative_icon_
.end() &&
218 !declarative_icon_
.find(tab_id
)->second
.rbegin()->second
.empty()) {
219 return declarative_icon_
.find(tab_id
)->second
.rbegin()->second
.back();
224 void ExtensionAction::ClearAllValuesForTab(int tab_id
) {
225 popup_url_
.erase(tab_id
);
226 title_
.erase(tab_id
);
228 badge_text_
.erase(tab_id
);
229 badge_text_color_
.erase(tab_id
);
230 badge_background_color_
.erase(tab_id
);
231 is_visible_
.erase(tab_id
);
232 // TODO(jyasskin): Erase the element from declarative_show_count_
233 // when the tab's closed. There's a race between the
234 // LocationBarController and the ContentRulesRegistry on navigation,
235 // which prevents me from cleaning everything up now.
238 extensions::IconImage
* ExtensionAction::LoadDefaultIconImage(
239 const extensions::Extension
& extension
,
240 content::BrowserContext
* browser_context
) {
241 if (default_icon_
&& !default_icon_image_
) {
242 default_icon_image_
.reset(new extensions::IconImage(
246 GetIconSizeForType(action_type_
),
247 *GetDefaultIcon().ToImageSkia(),
250 return default_icon_image_
.get();
253 gfx::Image
ExtensionAction::GetDefaultIconImage() const {
254 // If we have a default icon, it should be loaded before trying to use it.
255 DCHECK(!default_icon_image_
== !default_icon_
);
256 if (default_icon_image_
)
257 return default_icon_image_
->image();
259 // If the extension action redesign is enabled, we use a special placeholder
260 // icon (with the first letter of the extension name) rather than the default
262 if (extensions::FeatureSwitch::extension_action_redesign()->IsEnabled()) {
263 return extensions::ExtensionIconPlaceholder::CreateImage(
264 extension_misc::EXTENSION_ICON_ACTION
, extension_name_
);
267 return GetDefaultIcon();
270 bool ExtensionAction::HasPopupUrl(int tab_id
) const {
271 return HasValue(popup_url_
, tab_id
);
274 bool ExtensionAction::HasTitle(int tab_id
) const {
275 return HasValue(title_
, tab_id
);
278 bool ExtensionAction::HasBadgeText(int tab_id
) const {
279 return HasValue(badge_text_
, tab_id
);
282 bool ExtensionAction::HasBadgeBackgroundColor(int tab_id
) const {
283 return HasValue(badge_background_color_
, tab_id
);
286 bool ExtensionAction::HasBadgeTextColor(int tab_id
) const {
287 return HasValue(badge_text_color_
, tab_id
);
290 bool ExtensionAction::HasIsVisible(int tab_id
) const {
291 return HasValue(is_visible_
, tab_id
);
294 bool ExtensionAction::HasIcon(int tab_id
) const {
295 return HasValue(icon_
, tab_id
);
298 void ExtensionAction::SetDefaultIconForTest(
299 scoped_ptr
<ExtensionIconSet
> default_icon
) {
300 default_icon_
= default_icon
.Pass();
303 void ExtensionAction::Populate(const extensions::Extension
& extension
,
304 const extensions::ActionInfo
& manifest_data
) {
305 // If the manifest doesn't specify a title, set it to |extension|'s name.
306 const std::string
& title
=
307 !manifest_data
.default_title
.empty() ? manifest_data
.default_title
:
309 SetTitle(kDefaultTabId
, title
);
310 SetPopupUrl(kDefaultTabId
, manifest_data
.default_popup_url
);
311 set_id(manifest_data
.id
);
313 // Initialize the specified icon set.
314 if (!manifest_data
.default_icon
.empty())
315 default_icon_
.reset(new ExtensionIconSet(manifest_data
.default_icon
));
317 const ExtensionIconSet
& extension_icons
=
318 extensions::IconsInfo::GetIcons(&extension
);
319 // Look for any other icons.
320 std::string largest_icon
= extension_icons
.Get(
321 extension_misc::EXTENSION_ICON_GIGANTOR
, ExtensionIconSet::MATCH_SMALLER
);
323 if (!largest_icon
.empty()) {
324 // We found an icon to use, so create an icon set if one doesn't exist.
326 default_icon_
.reset(new ExtensionIconSet());
327 int largest_icon_size
= extension_icons
.GetIconSizeFromPath(largest_icon
);
328 // Replace any missing extension action icons with the largest icon
329 // retrieved from |extension|'s manifest so long as the largest icon is
330 // larger than the current key.
331 for (int i
= extension_misc::kNumExtensionActionIconSizes
- 1; i
>= 0;
333 int size
= extension_misc::kExtensionActionIconSizes
[i
].size
;
334 if (default_icon_
->Get(size
, ExtensionIconSet::MATCH_BIGGER
).empty() &&
335 largest_icon_size
> size
) {
336 default_icon_
->Add(size
, largest_icon
);
343 // Determines which icon would be returned by |GetIcon|, and returns its width.
344 int ExtensionAction::GetIconWidth(int tab_id
) const {
345 // If icon has been set, return its width.
346 gfx::Image icon
= GetValue(&icon_
, tab_id
);
349 // If there is a default icon, the icon width will be set depending on our
352 return GetIconSizeForType(action_type());
354 // If no icon has been set and there is no default icon, we need favicon
356 return ui::ResourceBundle::GetSharedInstance().GetImageNamed(
357 IDR_EXTENSIONS_FAVICON
).Width();