Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / extensions / extension_action.cc
blob50ac1856d558a1f04279a5f8c61a4801c59b81ee
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.
5 #include "chrome/browser/extensions/extension_action.h"
7 #include <algorithm>
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/extensions/extension_constants.h"
14 #include "chrome/common/icon_with_badge_image_source.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/SkBitmapDevice.h"
19 #include "third_party/skia/include/core/SkCanvas.h"
20 #include "third_party/skia/include/core/SkPaint.h"
21 #include "third_party/skia/include/effects/SkGradientShader.h"
22 #include "ui/base/resource/resource_bundle.h"
23 #include "ui/gfx/animation/animation_delegate.h"
24 #include "ui/gfx/canvas.h"
25 #include "ui/gfx/color_utils.h"
26 #include "ui/gfx/image/image.h"
27 #include "ui/gfx/image/image_skia.h"
28 #include "ui/gfx/image/image_skia_source.h"
29 #include "ui/gfx/rect.h"
30 #include "ui/gfx/size.h"
31 #include "ui/gfx/skbitmap_operations.h"
32 #include "url/gurl.h"
34 namespace {
36 class GetAttentionImageSource : public gfx::ImageSkiaSource {
37 public:
38 explicit GetAttentionImageSource(const gfx::ImageSkia& icon)
39 : icon_(icon) {}
41 // gfx::ImageSkiaSource overrides:
42 virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
43 gfx::ImageSkiaRep icon_rep = icon_.GetRepresentation(scale);
44 color_utils::HSL shift = {-1, 0, 0.5};
45 return gfx::ImageSkiaRep(
46 SkBitmapOperations::CreateHSLShiftedBitmap(icon_rep.sk_bitmap(), shift),
47 icon_rep.scale());
50 private:
51 const gfx::ImageSkia icon_;
54 } // namespace
56 // TODO(tbarzic): Merge AnimationIconImageSource and IconAnimation together.
57 // Source for painting animated skia image.
58 class AnimatedIconImageSource : public gfx::ImageSkiaSource {
59 public:
60 AnimatedIconImageSource(
61 const gfx::ImageSkia& image,
62 base::WeakPtr<ExtensionAction::IconAnimation> animation)
63 : image_(image),
64 animation_(animation) {
67 private:
68 virtual ~AnimatedIconImageSource() {}
70 virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
71 gfx::ImageSkiaRep original_rep = image_.GetRepresentation(scale);
72 if (!animation_.get())
73 return original_rep;
75 // Original representation's scale factor may be different from scale
76 // factor passed to this method. We want to use the former (since we are
77 // using bitmap for that scale).
78 return gfx::ImageSkiaRep(
79 animation_->Apply(original_rep.sk_bitmap()), original_rep.scale());
82 gfx::ImageSkia image_;
83 base::WeakPtr<ExtensionAction::IconAnimation> animation_;
85 DISALLOW_COPY_AND_ASSIGN(AnimatedIconImageSource);
88 const int ExtensionAction::kDefaultTabId = -1;
89 // 100ms animation at 50fps (so 5 animation frames in total).
90 const int kIconFadeInDurationMs = 100;
91 const int kIconFadeInFramesPerSecond = 50;
93 ExtensionAction::IconAnimation::IconAnimation()
94 : gfx::LinearAnimation(kIconFadeInDurationMs, kIconFadeInFramesPerSecond,
95 NULL),
96 weak_ptr_factory_(this) {}
98 ExtensionAction::IconAnimation::~IconAnimation() {
99 // Make sure observers don't access *this after its destructor has started.
100 weak_ptr_factory_.InvalidateWeakPtrs();
101 // In case the animation was destroyed before it finished (likely due to
102 // delays in timer scheduling), make sure it's fully visible.
103 FOR_EACH_OBSERVER(Observer, observers_, OnIconChanged());
106 const SkBitmap& ExtensionAction::IconAnimation::Apply(
107 const SkBitmap& icon) const {
108 DCHECK_GT(icon.width(), 0);
109 DCHECK_GT(icon.height(), 0);
111 if (!device_.get() ||
112 (device_->width() != icon.width()) ||
113 (device_->height() != icon.height())) {
114 device_.reset(new SkBitmapDevice(
115 SkBitmap::kARGB_8888_Config, icon.width(), icon.height(), true));
118 SkCanvas canvas(device_.get());
119 canvas.clear(SK_ColorWHITE);
120 SkPaint paint;
121 paint.setAlpha(CurrentValueBetween(0, 255));
122 canvas.drawBitmap(icon, 0, 0, &paint);
123 return device_->accessBitmap(false);
126 base::WeakPtr<ExtensionAction::IconAnimation>
127 ExtensionAction::IconAnimation::AsWeakPtr() {
128 return weak_ptr_factory_.GetWeakPtr();
131 void ExtensionAction::IconAnimation::AddObserver(
132 ExtensionAction::IconAnimation::Observer* observer) {
133 observers_.AddObserver(observer);
136 void ExtensionAction::IconAnimation::RemoveObserver(
137 ExtensionAction::IconAnimation::Observer* observer) {
138 observers_.RemoveObserver(observer);
141 void ExtensionAction::IconAnimation::AnimateToState(double state) {
142 FOR_EACH_OBSERVER(Observer, observers_, OnIconChanged());
145 ExtensionAction::IconAnimation::ScopedObserver::ScopedObserver(
146 const base::WeakPtr<IconAnimation>& icon_animation,
147 Observer* observer)
148 : icon_animation_(icon_animation),
149 observer_(observer) {
150 if (icon_animation.get())
151 icon_animation->AddObserver(observer);
154 ExtensionAction::IconAnimation::ScopedObserver::~ScopedObserver() {
155 if (icon_animation_.get())
156 icon_animation_->RemoveObserver(observer_);
159 ExtensionAction::ExtensionAction(
160 const std::string& extension_id,
161 extensions::ActionInfo::Type action_type,
162 const extensions::ActionInfo& manifest_data)
163 : extension_id_(extension_id),
164 action_type_(action_type),
165 has_changed_(false) {
166 // Page/script actions are hidden/disabled by default, and browser actions are
167 // visible/enabled by default.
168 SetAppearance(kDefaultTabId,
169 action_type == extensions::ActionInfo::TYPE_BROWSER ?
170 ExtensionAction::ACTIVE : ExtensionAction::INVISIBLE);
171 SetTitle(kDefaultTabId, manifest_data.default_title);
172 SetPopupUrl(kDefaultTabId, manifest_data.default_popup_url);
173 if (!manifest_data.default_icon.empty()) {
174 set_default_icon(make_scoped_ptr(new ExtensionIconSet(
175 manifest_data.default_icon)));
177 set_id(manifest_data.id);
180 ExtensionAction::~ExtensionAction() {
183 scoped_ptr<ExtensionAction> ExtensionAction::CopyForTest() const {
184 scoped_ptr<ExtensionAction> copy(
185 new ExtensionAction(extension_id_, action_type_,
186 extensions::ActionInfo()));
187 copy->popup_url_ = popup_url_;
188 copy->title_ = title_;
189 copy->icon_ = icon_;
190 copy->badge_text_ = badge_text_;
191 copy->badge_background_color_ = badge_background_color_;
192 copy->badge_text_color_ = badge_text_color_;
193 copy->appearance_ = appearance_;
194 copy->icon_animation_ = icon_animation_;
195 copy->id_ = id_;
197 if (default_icon_)
198 copy->default_icon_.reset(new ExtensionIconSet(*default_icon_));
200 return copy.Pass();
203 // static
204 int ExtensionAction::GetIconSizeForType(
205 extensions::ActionInfo::Type type) {
206 switch (type) {
207 case extensions::ActionInfo::TYPE_BROWSER:
208 case extensions::ActionInfo::TYPE_PAGE:
209 case extensions::ActionInfo::TYPE_SYSTEM_INDICATOR:
210 // TODO(dewittj) Report the actual icon size of the system
211 // indicator.
212 return extension_misc::EXTENSION_ICON_ACTION;
213 case extensions::ActionInfo::TYPE_SCRIPT_BADGE:
214 return extension_misc::EXTENSION_ICON_BITTY;
215 default:
216 NOTREACHED();
217 return 0;
221 void ExtensionAction::SetPopupUrl(int tab_id, const GURL& url) {
222 // We store |url| even if it is empty, rather than removing a URL from the
223 // map. If an extension has a default popup, and removes it for a tab via
224 // the API, we must remember that there is no popup for that specific tab.
225 // If we removed the tab's URL, GetPopupURL would incorrectly return the
226 // default URL.
227 SetValue(&popup_url_, tab_id, url);
230 bool ExtensionAction::HasPopup(int tab_id) const {
231 return !GetPopupUrl(tab_id).is_empty();
234 GURL ExtensionAction::GetPopupUrl(int tab_id) const {
235 return GetValue(&popup_url_, tab_id);
238 void ExtensionAction::SetIcon(int tab_id, const gfx::Image& image) {
239 SetValue(&icon_, tab_id, image.AsImageSkia());
242 gfx::Image ExtensionAction::ApplyAttentionAndAnimation(
243 const gfx::ImageSkia& original_icon,
244 int tab_id) const {
245 gfx::ImageSkia icon = original_icon;
246 if (GetValue(&appearance_, tab_id) == WANTS_ATTENTION)
247 icon = gfx::ImageSkia(new GetAttentionImageSource(icon), icon.size());
249 return gfx::Image(ApplyIconAnimation(tab_id, icon));
252 gfx::ImageSkia ExtensionAction::GetExplicitlySetIcon(int tab_id) const {
253 return GetValue(&icon_, tab_id);
256 bool ExtensionAction::SetAppearance(int tab_id, Appearance new_appearance) {
257 const Appearance old_appearance = GetValue(&appearance_, tab_id);
259 if (old_appearance == new_appearance)
260 return false;
262 SetValue(&appearance_, tab_id, new_appearance);
264 // When showing a script badge for the first time on a web page, fade it in.
265 // Other transitions happen instantly.
266 if (old_appearance == INVISIBLE && tab_id != kDefaultTabId &&
267 action_type_ == extensions::ActionInfo::TYPE_SCRIPT_BADGE) {
268 RunIconAnimation(tab_id);
271 return true;
274 void ExtensionAction::DeclarativeShow(int tab_id) {
275 DCHECK_NE(tab_id, kDefaultTabId);
276 ++declarative_show_count_[tab_id]; // Use default initialization to 0.
279 void ExtensionAction::UndoDeclarativeShow(int tab_id) {
280 int& show_count = declarative_show_count_[tab_id];
281 DCHECK_GT(show_count, 0);
282 if (--show_count == 0)
283 declarative_show_count_.erase(tab_id);
286 void ExtensionAction::ClearAllValuesForTab(int tab_id) {
287 popup_url_.erase(tab_id);
288 title_.erase(tab_id);
289 icon_.erase(tab_id);
290 badge_text_.erase(tab_id);
291 badge_text_color_.erase(tab_id);
292 badge_background_color_.erase(tab_id);
293 appearance_.erase(tab_id);
294 // TODO(jyasskin): Erase the element from declarative_show_count_
295 // when the tab's closed. There's a race between the
296 // PageActionController and the ContentRulesRegistry on navigation,
297 // which prevents me from cleaning everything up now.
298 icon_animation_.erase(tab_id);
301 void ExtensionAction::PaintBadge(gfx::Canvas* canvas,
302 const gfx::Rect& bounds,
303 int tab_id) {
304 badge_util::PaintBadge(
305 canvas,
306 bounds,
307 GetBadgeText(tab_id),
308 GetBadgeTextColor(tab_id),
309 GetBadgeBackgroundColor(tab_id),
310 GetIconWidth(tab_id),
311 action_type());
314 gfx::ImageSkia ExtensionAction::GetIconWithBadge(
315 const gfx::ImageSkia& icon,
316 int tab_id,
317 const gfx::Size& spacing) const {
318 if (tab_id < 0)
319 return icon;
321 return gfx::ImageSkia(
322 new IconWithBadgeImageSource(icon,
323 icon.size(),
324 spacing,
325 GetBadgeText(tab_id),
326 GetBadgeTextColor(tab_id),
327 GetBadgeBackgroundColor(tab_id),
328 action_type()),
329 icon.size());
332 // Determines which icon would be returned by |GetIcon|, and returns its width.
333 int ExtensionAction::GetIconWidth(int tab_id) const {
334 // If icon has been set, return its width.
335 gfx::ImageSkia icon = GetValue(&icon_, tab_id);
336 if (!icon.isNull())
337 return icon.width();
338 // If there is a default icon, the icon width will be set depending on our
339 // action type.
340 if (default_icon_)
341 return GetIconSizeForType(action_type());
343 // If no icon has been set and there is no default icon, we need favicon
344 // width.
345 return ui::ResourceBundle::GetSharedInstance().GetImageNamed(
346 IDR_EXTENSIONS_FAVICON).ToImageSkia()->width();
349 base::WeakPtr<ExtensionAction::IconAnimation> ExtensionAction::GetIconAnimation(
350 int tab_id) const {
351 std::map<int, base::WeakPtr<IconAnimation> >::iterator it =
352 icon_animation_.find(tab_id);
353 if (it == icon_animation_.end())
354 return base::WeakPtr<ExtensionAction::IconAnimation>();
355 if (it->second.get())
356 return it->second;
358 // Take this opportunity to remove all the NULL IconAnimations from
359 // icon_animation_.
360 icon_animation_.erase(it);
361 for (it = icon_animation_.begin(); it != icon_animation_.end();) {
362 if (it->second.get()) {
363 ++it;
364 } else {
365 // The WeakPtr is null; remove it from the map.
366 icon_animation_.erase(it++);
369 return base::WeakPtr<ExtensionAction::IconAnimation>();
372 gfx::ImageSkia ExtensionAction::ApplyIconAnimation(
373 int tab_id,
374 const gfx::ImageSkia& icon) const {
375 base::WeakPtr<IconAnimation> animation = GetIconAnimation(tab_id);
376 if (animation.get() == NULL)
377 return icon;
379 return gfx::ImageSkia(new AnimatedIconImageSource(icon, animation),
380 icon.size());
383 namespace {
384 // Used to create a Callback owning an IconAnimation.
385 void DestroyIconAnimation(scoped_ptr<ExtensionAction::IconAnimation>) {}
387 void ExtensionAction::RunIconAnimation(int tab_id) {
388 scoped_ptr<IconAnimation> icon_animation(new IconAnimation());
389 icon_animation_[tab_id] = icon_animation->AsWeakPtr();
390 icon_animation->Start();
391 // After the icon is finished fading in (plus some padding to handle random
392 // timer delays), destroy it. We use a delayed task so that the Animation is
393 // deleted even if it hasn't finished by the time the MessageLoop is
394 // destroyed.
395 base::MessageLoop::current()->PostDelayedTask(
396 FROM_HERE,
397 base::Bind(&DestroyIconAnimation, base::Passed(&icon_animation)),
398 base::TimeDelta::FromMilliseconds(kIconFadeInDurationMs * 2));