// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/extension_action.h" #include <algorithm> #include "base/bind.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "chrome/common/badge_util.h" #include "chrome/common/icon_with_badge_image_source.h" #include "extensions/common/constants.h" #include "grit/theme_resources.h" #include "grit/ui_resources.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkPaint.h" #include "third_party/skia/include/effects/SkGradientShader.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/animation/animation_delegate.h" #include "ui/gfx/canvas.h" #include "ui/gfx/color_utils.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia_source.h" #include "ui/gfx/rect.h" #include "ui/gfx/size.h" #include "ui/gfx/skbitmap_operations.h" #include "url/gurl.h" namespace { class GetAttentionImageSource : public gfx::ImageSkiaSource { public: explicit GetAttentionImageSource(const gfx::ImageSkia& icon) : icon_(icon) {} // gfx::ImageSkiaSource overrides: virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE { gfx::ImageSkiaRep icon_rep = icon_.GetRepresentation(scale); color_utils::HSL shift = {-1, 0, 0.5}; return gfx::ImageSkiaRep( SkBitmapOperations::CreateHSLShiftedBitmap(icon_rep.sk_bitmap(), shift), icon_rep.scale()); } private: const gfx::ImageSkia icon_; }; template <class T> bool HasValue(const std::map<int, T>& map, int tab_id) { return map.find(tab_id) != map.end(); } } // namespace const int ExtensionAction::kDefaultTabId = -1; const int ExtensionAction::kPageActionIconMaxSize = 19; ExtensionAction::ExtensionAction(const std::string& extension_id, extensions::ActionInfo::Type action_type, const extensions::ActionInfo& manifest_data) : extension_id_(extension_id), action_type_(action_type) { // Page/script actions are hidden/disabled by default, and browser actions are // visible/enabled by default. SetIsVisible(kDefaultTabId, action_type == extensions::ActionInfo::TYPE_BROWSER); SetTitle(kDefaultTabId, manifest_data.default_title); SetPopupUrl(kDefaultTabId, manifest_data.default_popup_url); if (!manifest_data.default_icon.empty()) { set_default_icon(make_scoped_ptr(new ExtensionIconSet( manifest_data.default_icon))); } set_id(manifest_data.id); } ExtensionAction::~ExtensionAction() { } scoped_ptr<ExtensionAction> ExtensionAction::CopyForTest() const { scoped_ptr<ExtensionAction> copy( new ExtensionAction(extension_id_, action_type_, extensions::ActionInfo())); copy->popup_url_ = popup_url_; copy->title_ = title_; copy->icon_ = icon_; copy->badge_text_ = badge_text_; copy->badge_background_color_ = badge_background_color_; copy->badge_text_color_ = badge_text_color_; copy->is_visible_ = is_visible_; copy->id_ = id_; if (default_icon_) copy->default_icon_.reset(new ExtensionIconSet(*default_icon_)); return copy.Pass(); } // static int ExtensionAction::GetIconSizeForType( extensions::ActionInfo::Type type) { switch (type) { case extensions::ActionInfo::TYPE_BROWSER: case extensions::ActionInfo::TYPE_PAGE: case extensions::ActionInfo::TYPE_SYSTEM_INDICATOR: // TODO(dewittj) Report the actual icon size of the system // indicator. return extension_misc::EXTENSION_ICON_ACTION; default: NOTREACHED(); return 0; } } void ExtensionAction::SetPopupUrl(int tab_id, const GURL& url) { // We store |url| even if it is empty, rather than removing a URL from the // map. If an extension has a default popup, and removes it for a tab via // the API, we must remember that there is no popup for that specific tab. // If we removed the tab's URL, GetPopupURL would incorrectly return the // default URL. SetValue(&popup_url_, tab_id, url); } bool ExtensionAction::HasPopup(int tab_id) const { return !GetPopupUrl(tab_id).is_empty(); } GURL ExtensionAction::GetPopupUrl(int tab_id) const { return GetValue(&popup_url_, tab_id); } void ExtensionAction::SetIcon(int tab_id, const gfx::Image& image) { SetValue(&icon_, tab_id, image.AsImageSkia()); } gfx::ImageSkia ExtensionAction::GetExplicitlySetIcon(int tab_id) const { return GetValue(&icon_, tab_id); } bool ExtensionAction::SetIsVisible(int tab_id, bool new_visibility) { const bool old_visibility = GetValue(&is_visible_, tab_id); if (old_visibility == new_visibility) return false; SetValue(&is_visible_, tab_id, new_visibility); return true; } void ExtensionAction::DeclarativeShow(int tab_id) { DCHECK_NE(tab_id, kDefaultTabId); ++declarative_show_count_[tab_id]; // Use default initialization to 0. } void ExtensionAction::UndoDeclarativeShow(int tab_id) { int& show_count = declarative_show_count_[tab_id]; DCHECK_GT(show_count, 0); if (--show_count == 0) declarative_show_count_.erase(tab_id); } void ExtensionAction::ClearAllValuesForTab(int tab_id) { popup_url_.erase(tab_id); title_.erase(tab_id); icon_.erase(tab_id); badge_text_.erase(tab_id); badge_text_color_.erase(tab_id); badge_background_color_.erase(tab_id); is_visible_.erase(tab_id); // TODO(jyasskin): Erase the element from declarative_show_count_ // when the tab's closed. There's a race between the // PageActionController and the ContentRulesRegistry on navigation, // which prevents me from cleaning everything up now. } void ExtensionAction::PaintBadge(gfx::Canvas* canvas, const gfx::Rect& bounds, int tab_id) { badge_util::PaintBadge( canvas, bounds, GetBadgeText(tab_id), GetBadgeTextColor(tab_id), GetBadgeBackgroundColor(tab_id), GetIconWidth(tab_id), action_type()); } gfx::ImageSkia ExtensionAction::GetIconWithBadge( const gfx::ImageSkia& icon, int tab_id, const gfx::Size& spacing) const { if (tab_id < 0) return icon; return gfx::ImageSkia( new IconWithBadgeImageSource(icon, icon.size(), spacing, GetBadgeText(tab_id), GetBadgeTextColor(tab_id), GetBadgeBackgroundColor(tab_id), action_type()), icon.size()); } bool ExtensionAction::HasPopupUrl(int tab_id) const { return HasValue(popup_url_, tab_id); } bool ExtensionAction::HasTitle(int tab_id) const { return HasValue(title_, tab_id); } bool ExtensionAction::HasBadgeText(int tab_id) const { return HasValue(badge_text_, tab_id); } bool ExtensionAction::HasBadgeBackgroundColor(int tab_id) const { return HasValue(badge_background_color_, tab_id); } bool ExtensionAction::HasBadgeTextColor(int tab_id) const { return HasValue(badge_text_color_, tab_id); } bool ExtensionAction::HasIsVisible(int tab_id) const { return HasValue(is_visible_, tab_id); } bool ExtensionAction::HasIcon(int tab_id) const { return HasValue(icon_, tab_id); } // Determines which icon would be returned by |GetIcon|, and returns its width. int ExtensionAction::GetIconWidth(int tab_id) const { // If icon has been set, return its width. gfx::ImageSkia icon = GetValue(&icon_, tab_id); if (!icon.isNull()) return icon.width(); // If there is a default icon, the icon width will be set depending on our // action type. if (default_icon_) return GetIconSizeForType(action_type()); // If no icon has been set and there is no default icon, we need favicon // width. return ui::ResourceBundle::GetSharedInstance().GetImageNamed( IDR_EXTENSIONS_FAVICON).ToImageSkia()->width(); }