Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(235)

Side by Side Diff: chrome/common/extensions/extension_action.cc

Issue 10911300: Move ExtensionAction from common/ to browser/. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: proof of concept Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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/common/extensions/extension_action.h"
6
7 #include <algorithm>
8
9 #include "base/bind.h"
10 #include "base/logging.h"
11 #include "base/message_loop.h"
12 #include "chrome/common/badge_util.h"
13 #include "googleurl/src/gurl.h"
14 #include "grit/theme_resources.h"
15 #include "grit/ui_resources.h"
16 #include "third_party/skia/include/core/SkBitmap.h"
17 #include "third_party/skia/include/core/SkCanvas.h"
18 #include "third_party/skia/include/core/SkDevice.h"
19 #include "third_party/skia/include/core/SkPaint.h"
20 #include "third_party/skia/include/effects/SkGradientShader.h"
21 #include "ui/base/animation/animation_delegate.h"
22 #include "ui/base/resource/resource_bundle.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/gfx/color_utils.h"
25 #include "ui/gfx/image/canvas_image_source.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/image/image_skia_source.h"
31 #include "ui/gfx/skbitmap_operations.h"
32
33 namespace {
34
35 // Different platforms need slightly different constants to look good.
36 #if defined(OS_LINUX) && !defined(TOOLKIT_VIEWS)
37 const float kTextSize = 9.0;
38 const int kBottomMargin = 0;
39 const int kPadding = 2;
40 const int kTopTextPadding = 0;
41 #elif defined(OS_LINUX) && defined(TOOLKIT_VIEWS)
42 const float kTextSize = 8.0;
43 const int kBottomMargin = 5;
44 const int kPadding = 2;
45 const int kTopTextPadding = 1;
46 #elif defined(OS_MACOSX)
47 const float kTextSize = 9.0;
48 const int kBottomMargin = 5;
49 const int kPadding = 2;
50 const int kTopTextPadding = 0;
51 #else
52 const float kTextSize = 10;
53 const int kBottomMargin = 5;
54 const int kPadding = 2;
55 // The padding between the top of the badge and the top of the text.
56 const int kTopTextPadding = -1;
57 #endif
58
59 const int kBadgeHeight = 11;
60 const int kMaxTextWidth = 23;
61 // The minimum width for center-aligning the badge.
62 const int kCenterAlignThreshold = 20;
63
64 class GetAttentionImageSource : public gfx::ImageSkiaSource {
65 public:
66 explicit GetAttentionImageSource(const gfx::ImageSkia& icon)
67 : icon_(icon) {}
68
69 // gfx::ImageSkiaSource overrides:
70 virtual gfx::ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor)
71 OVERRIDE {
72 gfx::ImageSkiaRep icon_rep = icon_.GetRepresentation(scale_factor);
73 color_utils::HSL shift = {-1, 0, 0.5};
74 return gfx::ImageSkiaRep(
75 SkBitmapOperations::CreateHSLShiftedBitmap(icon_rep.sk_bitmap(), shift),
76 icon_rep.scale_factor());
77 }
78
79 private:
80 const gfx::ImageSkia icon_;
81 };
82
83 } // namespace
84
85 // TODO(tbarzic): Merge AnimationIconImageSource and IconAnimation together.
86 // Source for painting animated skia image.
87 class AnimatedIconImageSource : public gfx::ImageSkiaSource {
88 public:
89 AnimatedIconImageSource(
90 const gfx::ImageSkia& image,
91 base::WeakPtr<ExtensionAction::IconAnimation> animation)
92 : image_(image),
93 animation_(animation) {
94 }
95
96 private:
97 virtual ~AnimatedIconImageSource() {}
98
99 virtual gfx::ImageSkiaRep GetImageForScale(ui::ScaleFactor scale) OVERRIDE {
100 gfx::ImageSkiaRep original_rep = image_.GetRepresentation(scale);
101 if (!animation_)
102 return original_rep;
103
104 // Original representation's scale factor may be different from scale
105 // factor passed to this method. We want to use the former (since we are
106 // using bitmap for that scale).
107 return gfx::ImageSkiaRep(
108 animation_->Apply(original_rep.sk_bitmap()),
109 original_rep.scale_factor());
110 }
111
112 gfx::ImageSkia image_;
113 base::WeakPtr<ExtensionAction::IconAnimation> animation_;
114
115 DISALLOW_COPY_AND_ASSIGN(AnimatedIconImageSource);
116 };
117
118 // CanvasImageSource for creating browser action icon with a badge.
119 class ExtensionAction::IconWithBadgeImageSource
120 : public gfx::CanvasImageSource {
121 public:
122 IconWithBadgeImageSource(const gfx::ImageSkia& icon,
123 const gfx::Size& spacing,
124 const std::string& text,
125 const SkColor& text_color,
126 const SkColor& background_color)
127 : gfx::CanvasImageSource(icon.size(), false),
128 icon_(icon),
129 spacing_(spacing),
130 text_(text),
131 text_color_(text_color),
132 background_color_(background_color) {
133 }
134
135 virtual ~IconWithBadgeImageSource() {}
136
137 private:
138 virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
139 canvas->DrawImageInt(icon_, 0, 0, SkPaint());
140
141 gfx::Rect bounds(size_.width() + spacing_.width(),
142 size_.height() + spacing_.height());
143
144 // Draw a badge on the provided browser action icon's canvas.
145 ExtensionAction::DoPaintBadge(canvas, bounds, text_, text_color_,
146 background_color_, size_.width());
147 }
148
149 // Browser action icon image.
150 gfx::ImageSkia icon_;
151 // Extra spacing for badge compared to icon bounds.
152 gfx::Size spacing_;
153 // Text to be displayed on the badge.
154 std::string text_;
155 // Color of badge text.
156 SkColor text_color_;
157 // Color of the badge.
158 SkColor background_color_;
159
160 DISALLOW_COPY_AND_ASSIGN(IconWithBadgeImageSource);
161 };
162
163
164 const int ExtensionAction::kDefaultTabId = -1;
165 // 100ms animation at 50fps (so 5 animation frames in total).
166 const int kIconFadeInDurationMs = 100;
167 const int kIconFadeInFramesPerSecond = 50;
168
169 ExtensionAction::IconAnimation::IconAnimation()
170 : ui::LinearAnimation(kIconFadeInDurationMs, kIconFadeInFramesPerSecond,
171 NULL),
172 weak_ptr_factory_(this) {}
173
174 ExtensionAction::IconAnimation::~IconAnimation() {
175 // Make sure observers don't access *this after its destructor has started.
176 weak_ptr_factory_.InvalidateWeakPtrs();
177 // In case the animation was destroyed before it finished (likely due to
178 // delays in timer scheduling), make sure it's fully visible.
179 FOR_EACH_OBSERVER(Observer, observers_, OnIconChanged());
180 }
181
182 const SkBitmap& ExtensionAction::IconAnimation::Apply(
183 const SkBitmap& icon) const {
184 DCHECK_GT(icon.width(), 0);
185 DCHECK_GT(icon.height(), 0);
186
187 if (!device_.get() ||
188 (device_->width() != icon.width()) ||
189 (device_->height() != icon.height())) {
190 device_.reset(new SkDevice(
191 SkBitmap::kARGB_8888_Config, icon.width(), icon.height(), true));
192 }
193
194 SkCanvas canvas(device_.get());
195 canvas.clear(SK_ColorWHITE);
196 SkPaint paint;
197 paint.setAlpha(CurrentValueBetween(0, 255));
198 canvas.drawBitmap(icon, 0, 0, &paint);
199 return device_->accessBitmap(false);
200 }
201
202 base::WeakPtr<ExtensionAction::IconAnimation>
203 ExtensionAction::IconAnimation::AsWeakPtr() {
204 return weak_ptr_factory_.GetWeakPtr();
205 }
206
207 void ExtensionAction::IconAnimation::AddObserver(
208 ExtensionAction::IconAnimation::Observer* observer) {
209 observers_.AddObserver(observer);
210 }
211
212 void ExtensionAction::IconAnimation::RemoveObserver(
213 ExtensionAction::IconAnimation::Observer* observer) {
214 observers_.RemoveObserver(observer);
215 }
216
217 void ExtensionAction::IconAnimation::AnimateToState(double state) {
218 FOR_EACH_OBSERVER(Observer, observers_, OnIconChanged());
219 }
220
221 ExtensionAction::IconAnimation::ScopedObserver::ScopedObserver(
222 const base::WeakPtr<IconAnimation>& icon_animation,
223 Observer* observer)
224 : icon_animation_(icon_animation),
225 observer_(observer) {
226 if (icon_animation.get())
227 icon_animation->AddObserver(observer);
228 }
229
230 ExtensionAction::IconAnimation::ScopedObserver::~ScopedObserver() {
231 if (icon_animation_.get())
232 icon_animation_->RemoveObserver(observer_);
233 }
234
235 ExtensionAction::ExtensionAction(const std::string& extension_id,
236 Type action_type)
237 : extension_id_(extension_id),
238 action_type_(action_type),
239 has_changed_(false) {
240 }
241
242 ExtensionAction::~ExtensionAction() {
243 }
244
245 scoped_ptr<ExtensionAction> ExtensionAction::CopyForTest() const {
246 scoped_ptr<ExtensionAction> copy(
247 new ExtensionAction(extension_id_, action_type_));
248 copy->popup_url_ = popup_url_;
249 copy->title_ = title_;
250 copy->icon_ = icon_;
251 copy->icon_index_ = icon_index_;
252 copy->badge_text_ = badge_text_;
253 copy->badge_background_color_ = badge_background_color_;
254 copy->badge_text_color_ = badge_text_color_;
255 copy->appearance_ = appearance_;
256 copy->icon_animation_ = icon_animation_;
257 copy->default_icon_path_ = default_icon_path_;
258 copy->id_ = id_;
259 copy->icon_paths_ = icon_paths_;
260 return copy.Pass();
261 }
262
263 void ExtensionAction::SetPopupUrl(int tab_id, const GURL& url) {
264 // We store |url| even if it is empty, rather than removing a URL from the
265 // map. If an extension has a default popup, and removes it for a tab via
266 // the API, we must remember that there is no popup for that specific tab.
267 // If we removed the tab's URL, GetPopupURL would incorrectly return the
268 // default URL.
269 SetValue(&popup_url_, tab_id, url);
270 }
271
272 bool ExtensionAction::HasPopup(int tab_id) const {
273 return !GetPopupUrl(tab_id).is_empty();
274 }
275
276 GURL ExtensionAction::GetPopupUrl(int tab_id) const {
277 return GetValue(&popup_url_, tab_id);
278 }
279
280 void ExtensionAction::CacheIcon(const std::string& path,
281 const gfx::Image& icon) {
282 if (!icon.IsEmpty())
283 path_to_icon_cache_.insert(std::make_pair(path, *icon.ToImageSkia()));
284 }
285
286 void ExtensionAction::SetIcon(int tab_id, const gfx::Image& image) {
287 SetValue(&icon_, tab_id, image.AsImageSkia());
288 }
289
290 gfx::Image ExtensionAction::GetIcon(int tab_id) const {
291 // Check if a specific icon is set for this tab.
292 gfx::ImageSkia icon = GetExplicitlySetIcon(tab_id);
293 if (icon.isNull()) {
294 // Need to find an icon from a path.
295 const std::string* path = NULL;
296 // Check if one of the elements of icon_path() was selected.
297 int icon_index = GetIconIndex(tab_id);
298 if (icon_index >= 0) {
299 path = &icon_paths()->at(icon_index);
300 } else {
301 // Otherwise, use the default icon.
302 path = &default_icon_path();
303 }
304
305 std::map<std::string, gfx::ImageSkia>::const_iterator cached_icon =
306 path_to_icon_cache_.find(*path);
307 if (cached_icon != path_to_icon_cache_.end()) {
308 icon = cached_icon->second;
309 } else {
310 icon = *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
311 IDR_EXTENSIONS_FAVICON);
312 }
313 }
314
315 if (GetValue(&appearance_, tab_id) == WANTS_ATTENTION)
316 icon = gfx::ImageSkia(new GetAttentionImageSource(icon), icon.size());
317
318 return gfx::Image(ApplyIconAnimation(tab_id, icon));
319 }
320
321 gfx::ImageSkia ExtensionAction::GetExplicitlySetIcon(int tab_id) const {
322 return GetValue(&icon_, tab_id);
323 }
324
325 void ExtensionAction::SetIconIndex(int tab_id, int index) {
326 if (static_cast<size_t>(index) >= icon_paths_.size()) {
327 NOTREACHED();
328 return;
329 }
330 SetValue(&icon_index_, tab_id, index);
331 }
332
333 bool ExtensionAction::SetAppearance(int tab_id, Appearance new_appearance) {
334 const Appearance old_appearance = GetValue(&appearance_, tab_id);
335
336 if (old_appearance == new_appearance)
337 return false;
338
339 SetValue(&appearance_, tab_id, new_appearance);
340
341 // When showing a badge for the first time on a web page, fade it
342 // in. Other transitions happen instantly.
343 if (old_appearance == INVISIBLE && tab_id != kDefaultTabId) {
344 RunIconAnimation(tab_id);
345 }
346
347 return true;
348 }
349
350 void ExtensionAction::ClearAllValuesForTab(int tab_id) {
351 popup_url_.erase(tab_id);
352 title_.erase(tab_id);
353 icon_.erase(tab_id);
354 icon_index_.erase(tab_id);
355 badge_text_.erase(tab_id);
356 badge_text_color_.erase(tab_id);
357 badge_background_color_.erase(tab_id);
358 appearance_.erase(tab_id);
359 icon_animation_.erase(tab_id);
360 }
361
362 void ExtensionAction::PaintBadge(gfx::Canvas* canvas,
363 const gfx::Rect& bounds,
364 int tab_id) {
365 ExtensionAction::DoPaintBadge(
366 canvas,
367 bounds,
368 GetBadgeText(tab_id),
369 GetBadgeTextColor(tab_id),
370 GetBadgeBackgroundColor(tab_id),
371 GetValue(&icon_, tab_id).size().width());
372 }
373
374 gfx::ImageSkia ExtensionAction::GetIconWithBadge(
375 const gfx::ImageSkia& icon,
376 int tab_id,
377 const gfx::Size& spacing) const {
378 if (tab_id < 0)
379 return icon;
380
381 return gfx::ImageSkia(
382 new IconWithBadgeImageSource(icon,
383 spacing,
384 GetBadgeText(tab_id),
385 GetBadgeTextColor(tab_id),
386 GetBadgeBackgroundColor(tab_id)),
387 icon.size());
388 }
389
390 // static
391 void ExtensionAction::DoPaintBadge(gfx::Canvas* canvas,
392 const gfx::Rect& bounds,
393 const std::string& text,
394 const SkColor& text_color_in,
395 const SkColor& background_color_in,
396 int icon_width) {
397 if (text.empty())
398 return;
399
400 SkColor text_color = text_color_in;
401 if (SkColorGetA(text_color_in) == 0x00)
402 text_color = SK_ColorWHITE;
403
404 SkColor background_color = background_color_in;
405 if (SkColorGetA(background_color_in) == 0x00)
406 background_color = SkColorSetARGB(255, 218, 0, 24);
407
408 canvas->Save();
409
410 SkPaint* text_paint = badge_util::GetBadgeTextPaintSingleton();
411 text_paint->setTextSize(SkFloatToScalar(kTextSize));
412 text_paint->setColor(text_color);
413
414 // Calculate text width. We clamp it to a max size.
415 SkScalar sk_text_width = text_paint->measureText(text.c_str(), text.size());
416 int text_width = std::min(kMaxTextWidth, SkScalarFloor(sk_text_width));
417
418 // Calculate badge size. It is clamped to a min width just because it looks
419 // silly if it is too skinny.
420 int badge_width = text_width + kPadding * 2;
421 // Force the pixel width of badge to be either odd (if the icon width is odd)
422 // or even otherwise. If there is a mismatch you get http://crbug.com/26400.
423 if (icon_width != 0 && (badge_width % 2 != icon_width % 2))
424 badge_width += 1;
425 badge_width = std::max(kBadgeHeight, badge_width);
426
427 // Paint the badge background color in the right location. It is usually
428 // right-aligned, but it can also be center-aligned if it is large.
429 int rect_height = kBadgeHeight;
430 int rect_y = bounds.bottom() - kBottomMargin - kBadgeHeight;
431 int rect_width = badge_width;
432 int rect_x = (badge_width >= kCenterAlignThreshold) ?
433 (bounds.x() + bounds.width() - badge_width) / 2 :
434 bounds.right() - badge_width;
435 gfx::Rect rect(rect_x, rect_y, rect_width, rect_height);
436
437 SkPaint rect_paint;
438 rect_paint.setStyle(SkPaint::kFill_Style);
439 rect_paint.setAntiAlias(true);
440 rect_paint.setColor(background_color);
441 canvas->DrawRoundRect(rect, 2, rect_paint);
442
443 // Overlay the gradient. It is stretchy, so we do this in three parts.
444 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
445 gfx::ImageSkia* gradient_left = rb.GetImageSkiaNamed(
446 IDR_BROWSER_ACTION_BADGE_LEFT);
447 gfx::ImageSkia* gradient_right = rb.GetImageSkiaNamed(
448 IDR_BROWSER_ACTION_BADGE_RIGHT);
449 gfx::ImageSkia* gradient_center = rb.GetImageSkiaNamed(
450 IDR_BROWSER_ACTION_BADGE_CENTER);
451
452 canvas->DrawImageInt(*gradient_left, rect.x(), rect.y());
453 canvas->TileImageInt(*gradient_center,
454 rect.x() + gradient_left->width(),
455 rect.y(),
456 rect.width() - gradient_left->width() - gradient_right->width(),
457 rect.height());
458 canvas->DrawImageInt(*gradient_right,
459 rect.right() - gradient_right->width(), rect.y());
460
461 // Finally, draw the text centered within the badge. We set a clip in case the
462 // text was too large.
463 rect.Inset(kPadding, 0);
464 canvas->ClipRect(rect);
465 canvas->sk_canvas()->drawText(
466 text.c_str(), text.size(),
467 SkFloatToScalar(rect.x() +
468 static_cast<float>(rect.width() - text_width) / 2),
469 SkFloatToScalar(rect.y() + kTextSize + kTopTextPadding),
470 *text_paint);
471 canvas->Restore();
472 }
473
474 base::WeakPtr<ExtensionAction::IconAnimation> ExtensionAction::GetIconAnimation(
475 int tab_id) const {
476 std::map<int, base::WeakPtr<IconAnimation> >::iterator it =
477 icon_animation_.find(tab_id);
478 if (it == icon_animation_.end())
479 return base::WeakPtr<ExtensionAction::IconAnimation>();
480 if (it->second)
481 return it->second;
482
483 // Take this opportunity to remove all the NULL IconAnimations from
484 // icon_animation_.
485 icon_animation_.erase(it);
486 for (it = icon_animation_.begin(); it != icon_animation_.end();) {
487 if (it->second) {
488 ++it;
489 } else {
490 // The WeakPtr is null; remove it from the map.
491 icon_animation_.erase(it++);
492 }
493 }
494 return base::WeakPtr<ExtensionAction::IconAnimation>();
495 }
496
497 gfx::ImageSkia ExtensionAction::ApplyIconAnimation(
498 int tab_id,
499 const gfx::ImageSkia& icon) const {
500 base::WeakPtr<IconAnimation> animation = GetIconAnimation(tab_id);
501 if (animation == NULL)
502 return icon;
503
504 return gfx::ImageSkia(new AnimatedIconImageSource(icon, animation),
505 icon.size());
506 }
507
508 namespace {
509 // Used to create a Callback owning an IconAnimation.
510 void DestroyIconAnimation(scoped_ptr<ExtensionAction::IconAnimation>) {}
511 }
512 void ExtensionAction::RunIconAnimation(int tab_id) {
513 scoped_ptr<IconAnimation> icon_animation(new IconAnimation());
514 icon_animation_[tab_id] = icon_animation->AsWeakPtr();
515 icon_animation->Start();
516 // After the icon is finished fading in (plus some padding to handle random
517 // timer delays), destroy it. We use a delayed task so that the Animation is
518 // deleted even if it hasn't finished by the time the MessageLoop is
519 // destroyed.
520 MessageLoop::current()->PostDelayedTask(
521 FROM_HERE,
522 base::Bind(&DestroyIconAnimation, base::Passed(icon_animation.Pass())),
523 base::TimeDelta::FromMilliseconds(kIconFadeInDurationMs * 2));
524 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698