Home | History | Annotate | Download | only in extensions
      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