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/bind.h" 10 #include "base/logging.h" 11 #include "base/message_loop/message_loop.h" 12 #include "chrome/common/badge_util.h" 13 #include "chrome/common/icon_with_badge_image_source.h" 14 #include "extensions/common/constants.h" 15 #include "grit/theme_resources.h" 16 #include "grit/ui_resources.h" 17 #include "third_party/skia/include/core/SkBitmap.h" 18 #include "third_party/skia/include/core/SkCanvas.h" 19 #include "third_party/skia/include/core/SkPaint.h" 20 #include "third_party/skia/include/effects/SkGradientShader.h" 21 #include "ui/base/resource/resource_bundle.h" 22 #include "ui/gfx/animation/animation_delegate.h" 23 #include "ui/gfx/canvas.h" 24 #include "ui/gfx/color_utils.h" 25 #include "ui/gfx/image/image.h" 26 #include "ui/gfx/image/image_skia.h" 27 #include "ui/gfx/image/image_skia_source.h" 28 #include "ui/gfx/rect.h" 29 #include "ui/gfx/size.h" 30 #include "ui/gfx/skbitmap_operations.h" 31 #include "url/gurl.h" 32 33 namespace { 34 35 class GetAttentionImageSource : public gfx::ImageSkiaSource { 36 public: 37 explicit GetAttentionImageSource(const gfx::ImageSkia& icon) 38 : icon_(icon) {} 39 40 // gfx::ImageSkiaSource overrides: 41 virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE { 42 gfx::ImageSkiaRep icon_rep = icon_.GetRepresentation(scale); 43 color_utils::HSL shift = {-1, 0, 0.5}; 44 return gfx::ImageSkiaRep( 45 SkBitmapOperations::CreateHSLShiftedBitmap(icon_rep.sk_bitmap(), shift), 46 icon_rep.scale()); 47 } 48 49 private: 50 const gfx::ImageSkia icon_; 51 }; 52 53 template <class T> 54 bool HasValue(const std::map<int, T>& map, int tab_id) { 55 return map.find(tab_id) != map.end(); 56 } 57 58 } // namespace 59 60 const int ExtensionAction::kDefaultTabId = -1; 61 const int ExtensionAction::kPageActionIconMaxSize = 19; 62 63 ExtensionAction::ExtensionAction(const std::string& extension_id, 64 extensions::ActionInfo::Type action_type, 65 const extensions::ActionInfo& manifest_data) 66 : extension_id_(extension_id), action_type_(action_type) { 67 // Page/script actions are hidden/disabled by default, and browser actions are 68 // visible/enabled by default. 69 SetIsVisible(kDefaultTabId, 70 action_type == extensions::ActionInfo::TYPE_BROWSER); 71 SetTitle(kDefaultTabId, manifest_data.default_title); 72 SetPopupUrl(kDefaultTabId, manifest_data.default_popup_url); 73 if (!manifest_data.default_icon.empty()) { 74 set_default_icon(make_scoped_ptr(new ExtensionIconSet( 75 manifest_data.default_icon))); 76 } 77 set_id(manifest_data.id); 78 } 79 80 ExtensionAction::~ExtensionAction() { 81 } 82 83 scoped_ptr<ExtensionAction> ExtensionAction::CopyForTest() const { 84 scoped_ptr<ExtensionAction> copy( 85 new ExtensionAction(extension_id_, action_type_, 86 extensions::ActionInfo())); 87 copy->popup_url_ = popup_url_; 88 copy->title_ = title_; 89 copy->icon_ = icon_; 90 copy->badge_text_ = badge_text_; 91 copy->badge_background_color_ = badge_background_color_; 92 copy->badge_text_color_ = badge_text_color_; 93 copy->is_visible_ = is_visible_; 94 copy->id_ = id_; 95 96 if (default_icon_) 97 copy->default_icon_.reset(new ExtensionIconSet(*default_icon_)); 98 99 return copy.Pass(); 100 } 101 102 // static 103 int ExtensionAction::GetIconSizeForType( 104 extensions::ActionInfo::Type type) { 105 switch (type) { 106 case extensions::ActionInfo::TYPE_BROWSER: 107 case extensions::ActionInfo::TYPE_PAGE: 108 case extensions::ActionInfo::TYPE_SYSTEM_INDICATOR: 109 // TODO(dewittj) Report the actual icon size of the system 110 // indicator. 111 return extension_misc::EXTENSION_ICON_ACTION; 112 default: 113 NOTREACHED(); 114 return 0; 115 } 116 } 117 118 void ExtensionAction::SetPopupUrl(int tab_id, const GURL& url) { 119 // We store |url| even if it is empty, rather than removing a URL from the 120 // map. If an extension has a default popup, and removes it for a tab via 121 // the API, we must remember that there is no popup for that specific tab. 122 // If we removed the tab's URL, GetPopupURL would incorrectly return the 123 // default URL. 124 SetValue(&popup_url_, tab_id, url); 125 } 126 127 bool ExtensionAction::HasPopup(int tab_id) const { 128 return !GetPopupUrl(tab_id).is_empty(); 129 } 130 131 GURL ExtensionAction::GetPopupUrl(int tab_id) const { 132 return GetValue(&popup_url_, tab_id); 133 } 134 135 void ExtensionAction::SetIcon(int tab_id, const gfx::Image& image) { 136 SetValue(&icon_, tab_id, image.AsImageSkia()); 137 } 138 139 gfx::ImageSkia ExtensionAction::GetExplicitlySetIcon(int tab_id) const { 140 return GetValue(&icon_, tab_id); 141 } 142 143 bool ExtensionAction::SetIsVisible(int tab_id, bool new_visibility) { 144 const bool old_visibility = GetValue(&is_visible_, tab_id); 145 146 if (old_visibility == new_visibility) 147 return false; 148 149 SetValue(&is_visible_, tab_id, new_visibility); 150 151 return true; 152 } 153 154 void ExtensionAction::DeclarativeShow(int tab_id) { 155 DCHECK_NE(tab_id, kDefaultTabId); 156 ++declarative_show_count_[tab_id]; // Use default initialization to 0. 157 } 158 159 void ExtensionAction::UndoDeclarativeShow(int tab_id) { 160 int& show_count = declarative_show_count_[tab_id]; 161 DCHECK_GT(show_count, 0); 162 if (--show_count == 0) 163 declarative_show_count_.erase(tab_id); 164 } 165 166 void ExtensionAction::ClearAllValuesForTab(int tab_id) { 167 popup_url_.erase(tab_id); 168 title_.erase(tab_id); 169 icon_.erase(tab_id); 170 badge_text_.erase(tab_id); 171 badge_text_color_.erase(tab_id); 172 badge_background_color_.erase(tab_id); 173 is_visible_.erase(tab_id); 174 // TODO(jyasskin): Erase the element from declarative_show_count_ 175 // when the tab's closed. There's a race between the 176 // PageActionController and the ContentRulesRegistry on navigation, 177 // which prevents me from cleaning everything up now. 178 } 179 180 void ExtensionAction::PaintBadge(gfx::Canvas* canvas, 181 const gfx::Rect& bounds, 182 int tab_id) { 183 badge_util::PaintBadge( 184 canvas, 185 bounds, 186 GetBadgeText(tab_id), 187 GetBadgeTextColor(tab_id), 188 GetBadgeBackgroundColor(tab_id), 189 GetIconWidth(tab_id), 190 action_type()); 191 } 192 193 gfx::ImageSkia ExtensionAction::GetIconWithBadge( 194 const gfx::ImageSkia& icon, 195 int tab_id, 196 const gfx::Size& spacing) const { 197 if (tab_id < 0) 198 return icon; 199 200 return gfx::ImageSkia( 201 new IconWithBadgeImageSource(icon, 202 icon.size(), 203 spacing, 204 GetBadgeText(tab_id), 205 GetBadgeTextColor(tab_id), 206 GetBadgeBackgroundColor(tab_id), 207 action_type()), 208 icon.size()); 209 } 210 211 bool ExtensionAction::HasPopupUrl(int tab_id) const { 212 return HasValue(popup_url_, tab_id); 213 } 214 215 bool ExtensionAction::HasTitle(int tab_id) const { 216 return HasValue(title_, tab_id); 217 } 218 219 bool ExtensionAction::HasBadgeText(int tab_id) const { 220 return HasValue(badge_text_, tab_id); 221 } 222 223 bool ExtensionAction::HasBadgeBackgroundColor(int tab_id) const { 224 return HasValue(badge_background_color_, tab_id); 225 } 226 227 bool ExtensionAction::HasBadgeTextColor(int tab_id) const { 228 return HasValue(badge_text_color_, tab_id); 229 } 230 231 bool ExtensionAction::HasIsVisible(int tab_id) const { 232 return HasValue(is_visible_, tab_id); 233 } 234 235 bool ExtensionAction::HasIcon(int tab_id) const { 236 return HasValue(icon_, tab_id); 237 } 238 239 // Determines which icon would be returned by |GetIcon|, and returns its width. 240 int ExtensionAction::GetIconWidth(int tab_id) const { 241 // If icon has been set, return its width. 242 gfx::ImageSkia icon = GetValue(&icon_, tab_id); 243 if (!icon.isNull()) 244 return icon.width(); 245 // If there is a default icon, the icon width will be set depending on our 246 // action type. 247 if (default_icon_) 248 return GetIconSizeForType(action_type()); 249 250 // If no icon has been set and there is no default icon, we need favicon 251 // width. 252 return ui::ResourceBundle::GetSharedInstance().GetImageNamed( 253 IDR_EXTENSIONS_FAVICON).ToImageSkia()->width(); 254 } 255