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: 41 explicit GetAttentionImageSource(const gfx::ImageSkia& icon) 42 : icon_(icon) {} 43 44 // gfx::ImageSkiaSource overrides: 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> 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 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 96 ExtensionAction::~ExtensionAction() { 97 } 98 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_; 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 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 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 143 bool ExtensionAction::HasPopup(int tab_id) const { 144 return !GetPopupUrl(tab_id).is_empty(); 145 } 146 147 GURL ExtensionAction::GetPopupUrl(int tab_id) const { 148 return GetValue(&popup_url_, tab_id); 149 } 150 151 void ExtensionAction::SetIcon(int tab_id, const gfx::Image& image) { 152 SetValue(&icon_, tab_id, image.AsImageSkia()); 153 } 154 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 184 gfx::ImageSkia ExtensionAction::GetExplicitlySetIcon(int tab_id) const { 185 return GetValue(&icon_, tab_id); 186 } 187 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 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 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 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 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 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(); 236 } 237 return gfx::ImageSkia(); 238 } 239 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 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 267 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 285 bool ExtensionAction::HasPopupUrl(int tab_id) const { 286 return HasValue(popup_url_, tab_id); 287 } 288 289 bool ExtensionAction::HasTitle(int tab_id) const { 290 return HasValue(title_, tab_id); 291 } 292 293 bool ExtensionAction::HasBadgeText(int tab_id) const { 294 return HasValue(badge_text_, tab_id); 295 } 296 297 bool ExtensionAction::HasBadgeBackgroundColor(int tab_id) const { 298 return HasValue(badge_background_color_, tab_id); 299 } 300 301 bool ExtensionAction::HasBadgeTextColor(int tab_id) const { 302 return HasValue(badge_text_color_, tab_id); 303 } 304 305 bool ExtensionAction::HasIsVisible(int tab_id) const { 306 return HasValue(is_visible_, tab_id); 307 } 308 309 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. 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); 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