| OLD | NEW |
| (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 } | |
| OLD | NEW |