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/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