15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/browser/extensions/extension_action.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <algorithm>
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/bind.h"
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/logging.h"
11ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include "base/message_loop/message_loop.h"
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/common/badge_util.h"
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/common/extensions/extension_constants.h"
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/common/icon_with_badge_image_source.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/theme_resources.h"
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/ui_resources.h"
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "third_party/skia/include/core/SkBitmap.h"
18424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)#include "third_party/skia/include/core/SkBitmapDevice.h"
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "third_party/skia/include/core/SkCanvas.h"
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "third_party/skia/include/core/SkPaint.h"
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "third_party/skia/include/effects/SkGradientShader.h"
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/resource/resource_bundle.h"
23d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)#include "ui/gfx/animation/animation_delegate.h"
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/canvas.h"
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/color_utils.h"
262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/gfx/image/image.h"
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/image/image_skia.h"
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/image/image_skia_source.h"
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/rect.h"
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/size.h"
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/skbitmap_operations.h"
327dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "url/gurl.h"
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace {
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class GetAttentionImageSource : public gfx::ImageSkiaSource {
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public:
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  explicit GetAttentionImageSource(const gfx::ImageSkia& icon)
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      : icon_(icon) {}
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // gfx::ImageSkiaSource overrides:
4268043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
4368043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)    gfx::ImageSkiaRep icon_rep = icon_.GetRepresentation(scale);
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    color_utils::HSL shift = {-1, 0, 0.5};
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return gfx::ImageSkiaRep(
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        SkBitmapOperations::CreateHSLShiftedBitmap(icon_rep.sk_bitmap(), shift),
4768043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        icon_rep.scale());
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private:
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const gfx::ImageSkia icon_;
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// TODO(tbarzic): Merge AnimationIconImageSource and IconAnimation together.
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Source for painting animated skia image.
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class AnimatedIconImageSource : public gfx::ImageSkiaSource {
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public:
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  AnimatedIconImageSource(
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      const gfx::ImageSkia& image,
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      base::WeakPtr<ExtensionAction::IconAnimation> animation)
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      : image_(image),
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        animation_(animation) {
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private:
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  virtual ~AnimatedIconImageSource() {}
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7068043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)  virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    gfx::ImageSkiaRep original_rep = image_.GetRepresentation(scale);
72868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    if (!animation_.get())
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return original_rep;
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Original representation's scale factor may be different from scale
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // factor passed to this method. We want to use the former (since we are
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // using bitmap for that scale).
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return gfx::ImageSkiaRep(
7968043e1e95eeb07d5cae7aca370b26518b0867d6Torne (Richard Coles)        animation_->Apply(original_rep.sk_bitmap()), original_rep.scale());
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gfx::ImageSkia image_;
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::WeakPtr<ExtensionAction::IconAnimation> animation_;
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(AnimatedIconImageSource);
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const int ExtensionAction::kDefaultTabId = -1;
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// 100ms animation at 50fps (so 5 animation frames in total).
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const int kIconFadeInDurationMs = 100;
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const int kIconFadeInFramesPerSecond = 50;
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ExtensionAction::IconAnimation::IconAnimation()
94d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    : gfx::LinearAnimation(kIconFadeInDurationMs, kIconFadeInFramesPerSecond,
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          NULL),
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      weak_ptr_factory_(this) {}
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ExtensionAction::IconAnimation::~IconAnimation() {
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Make sure observers don't access *this after its destructor has started.
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  weak_ptr_factory_.InvalidateWeakPtrs();
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // In case the animation was destroyed before it finished (likely due to
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // delays in timer scheduling), make sure it's fully visible.
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  FOR_EACH_OBSERVER(Observer, observers_, OnIconChanged());
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const SkBitmap& ExtensionAction::IconAnimation::Apply(
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const SkBitmap& icon) const {
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK_GT(icon.width(), 0);
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  DCHECK_GT(icon.height(), 0);
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!device_.get() ||
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      (device_->width() != icon.width()) ||
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      (device_->height() != icon.height())) {
114424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    device_.reset(new SkBitmapDevice(
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      SkBitmap::kARGB_8888_Config, icon.width(), icon.height(), true));
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  SkCanvas canvas(device_.get());
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  canvas.clear(SK_ColorWHITE);
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  SkPaint paint;
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  paint.setAlpha(CurrentValueBetween(0, 255));
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  canvas.drawBitmap(icon, 0, 0, &paint);
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return device_->accessBitmap(false);
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)base::WeakPtr<ExtensionAction::IconAnimation>
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ExtensionAction::IconAnimation::AsWeakPtr() {
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return weak_ptr_factory_.GetWeakPtr();
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void ExtensionAction::IconAnimation::AddObserver(
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ExtensionAction::IconAnimation::Observer* observer) {
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  observers_.AddObserver(observer);
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void ExtensionAction::IconAnimation::RemoveObserver(
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ExtensionAction::IconAnimation::Observer* observer) {
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  observers_.RemoveObserver(observer);
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void ExtensionAction::IconAnimation::AnimateToState(double state) {
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  FOR_EACH_OBSERVER(Observer, observers_, OnIconChanged());
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ExtensionAction::IconAnimation::ScopedObserver::ScopedObserver(
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const base::WeakPtr<IconAnimation>& icon_animation,
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Observer* observer)
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : icon_animation_(icon_animation),
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      observer_(observer) {
150868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (icon_animation.get())
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    icon_animation->AddObserver(observer);
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ExtensionAction::IconAnimation::ScopedObserver::~ScopedObserver() {
155868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (icon_animation_.get())
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    icon_animation_->RemoveObserver(observer_);
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ExtensionAction::ExtensionAction(
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const std::string& extension_id,
1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    extensions::ActionInfo::Type action_type,
1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    const extensions::ActionInfo& manifest_data)
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    : extension_id_(extension_id),
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      action_type_(action_type),
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      has_changed_(false) {
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Page/script actions are hidden/disabled by default, and browser actions are
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // visible/enabled by default.
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  SetAppearance(kDefaultTabId,
1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                action_type == extensions::ActionInfo::TYPE_BROWSER ?
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                ExtensionAction::ACTIVE : ExtensionAction::INVISIBLE);
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  SetTitle(kDefaultTabId, manifest_data.default_title);
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  SetPopupUrl(kDefaultTabId, manifest_data.default_popup_url);
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!manifest_data.default_icon.empty()) {
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    set_default_icon(make_scoped_ptr(new ExtensionIconSet(
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        manifest_data.default_icon)));
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  set_id(manifest_data.id);
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ExtensionAction::~ExtensionAction() {
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)scoped_ptr<ExtensionAction> ExtensionAction::CopyForTest() const {
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  scoped_ptr<ExtensionAction> copy(
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      new ExtensionAction(extension_id_, action_type_,
1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                          extensions::ActionInfo()));
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  copy->popup_url_ = popup_url_;
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  copy->title_ = title_;
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  copy->icon_ = icon_;
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  copy->badge_text_ = badge_text_;
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  copy->badge_background_color_ = badge_background_color_;
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  copy->badge_text_color_ = badge_text_color_;
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  copy->appearance_ = appearance_;
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  copy->icon_animation_ = icon_animation_;
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  copy->id_ = id_;
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
197c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if (default_icon_)
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    copy->default_icon_.reset(new ExtensionIconSet(*default_icon_));
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return copy.Pass();
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// static
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)int ExtensionAction::GetIconSizeForType(
2052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    extensions::ActionInfo::Type type) {
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  switch (type) {
2072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    case extensions::ActionInfo::TYPE_BROWSER:
2082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    case extensions::ActionInfo::TYPE_PAGE:
2092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    case extensions::ActionInfo::TYPE_SYSTEM_INDICATOR:
2102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      // TODO(dewittj) Report the actual icon size of the system
2112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      // indicator.
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return extension_misc::EXTENSION_ICON_ACTION;
2132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    case extensions::ActionInfo::TYPE_SCRIPT_BADGE:
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return extension_misc::EXTENSION_ICON_BITTY;
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    default:
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      NOTREACHED();
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return 0;
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void ExtensionAction::SetPopupUrl(int tab_id, const GURL& url) {
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // We store |url| even if it is empty, rather than removing a URL from the
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // map.  If an extension has a default popup, and removes it for a tab via
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // the API, we must remember that there is no popup for that specific tab.
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If we removed the tab's URL, GetPopupURL would incorrectly return the
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // default URL.
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  SetValue(&popup_url_, tab_id, url);
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool ExtensionAction::HasPopup(int tab_id) const {
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return !GetPopupUrl(tab_id).is_empty();
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)GURL ExtensionAction::GetPopupUrl(int tab_id) const {
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return GetValue(&popup_url_, tab_id);
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void ExtensionAction::SetIcon(int tab_id, const gfx::Image& image) {
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  SetValue(&icon_, tab_id, image.AsImageSkia());
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)gfx::Image ExtensionAction::ApplyAttentionAndAnimation(
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const gfx::ImageSkia& original_icon,
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    int tab_id) const {
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gfx::ImageSkia icon = original_icon;
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (GetValue(&appearance_, tab_id) == WANTS_ATTENTION)
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    icon = gfx::ImageSkia(new GetAttentionImageSource(icon), icon.size());
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return gfx::Image(ApplyIconAnimation(tab_id, icon));
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)gfx::ImageSkia ExtensionAction::GetExplicitlySetIcon(int tab_id) const {
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return GetValue(&icon_, tab_id);
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool ExtensionAction::SetAppearance(int tab_id, Appearance new_appearance) {
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  const Appearance old_appearance = GetValue(&appearance_, tab_id);
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (old_appearance == new_appearance)
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return false;
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  SetValue(&appearance_, tab_id, new_appearance);
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // When showing a script badge for the first time on a web page, fade it in.
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Other transitions happen instantly.
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (old_appearance == INVISIBLE && tab_id != kDefaultTabId &&
2672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      action_type_ == extensions::ActionInfo::TYPE_SCRIPT_BADGE) {
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    RunIconAnimation(tab_id);
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return true;
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void ExtensionAction::DeclarativeShow(int tab_id) {
2752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  DCHECK_NE(tab_id, kDefaultTabId);
2762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  ++declarative_show_count_[tab_id];  // Use default initialization to 0.
2772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
2782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void ExtensionAction::UndoDeclarativeShow(int tab_id) {
2802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  int& show_count = declarative_show_count_[tab_id];
2812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  DCHECK_GT(show_count, 0);
2822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if (--show_count == 0)
2832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    declarative_show_count_.erase(tab_id);
2842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
2852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void ExtensionAction::ClearAllValuesForTab(int tab_id) {
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  popup_url_.erase(tab_id);
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  title_.erase(tab_id);
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  icon_.erase(tab_id);
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  badge_text_.erase(tab_id);
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  badge_text_color_.erase(tab_id);
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  badge_background_color_.erase(tab_id);
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  appearance_.erase(tab_id);
2942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // TODO(jyasskin): Erase the element from declarative_show_count_
2952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // when the tab's closed.  There's a race between the
2962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // PageActionController and the ContentRulesRegistry on navigation,
2972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // which prevents me from cleaning everything up now.
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  icon_animation_.erase(tab_id);
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void ExtensionAction::PaintBadge(gfx::Canvas* canvas,
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                 const gfx::Rect& bounds,
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                 int tab_id) {
3042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  badge_util::PaintBadge(
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      canvas,
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      bounds,
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      GetBadgeText(tab_id),
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      GetBadgeTextColor(tab_id),
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      GetBadgeBackgroundColor(tab_id),
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      GetIconWidth(tab_id),
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      action_type());
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)gfx::ImageSkia ExtensionAction::GetIconWithBadge(
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const gfx::ImageSkia& icon,
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    int tab_id,
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const gfx::Size& spacing) const {
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (tab_id < 0)
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return icon;
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return gfx::ImageSkia(
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      new IconWithBadgeImageSource(icon,
3232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                                   icon.size(),
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                   spacing,
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                   GetBadgeText(tab_id),
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                   GetBadgeTextColor(tab_id),
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                   GetBadgeBackgroundColor(tab_id),
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                   action_type()),
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     icon.size());
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Determines which icon would be returned by |GetIcon|, and returns its width.
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)int ExtensionAction::GetIconWidth(int tab_id) const {
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If icon has been set, return its width.
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gfx::ImageSkia icon = GetValue(&icon_, tab_id);
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!icon.isNull())
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return icon.width();
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If there is a default icon, the icon width will be set depending on our
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // action type.
340c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  if (default_icon_)
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return GetIconSizeForType(action_type());
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If no icon has been set and there is no default icon, we need favicon
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // width.
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return ui::ResourceBundle::GetSharedInstance().GetImageNamed(
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          IDR_EXTENSIONS_FAVICON).ToImageSkia()->width();
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)base::WeakPtr<ExtensionAction::IconAnimation> ExtensionAction::GetIconAnimation(
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    int tab_id) const {
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  std::map<int, base::WeakPtr<IconAnimation> >::iterator it =
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      icon_animation_.find(tab_id);
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (it == icon_animation_.end())
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return base::WeakPtr<ExtensionAction::IconAnimation>();
355868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (it->second.get())
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return it->second;
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Take this opportunity to remove all the NULL IconAnimations from
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // icon_animation_.
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  icon_animation_.erase(it);
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (it = icon_animation_.begin(); it != icon_animation_.end();) {
362868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)    if (it->second.get()) {
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ++it;
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // The WeakPtr is null; remove it from the map.
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      icon_animation_.erase(it++);
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return base::WeakPtr<ExtensionAction::IconAnimation>();
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)gfx::ImageSkia ExtensionAction::ApplyIconAnimation(
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    int tab_id,
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    const gfx::ImageSkia& icon) const {
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  base::WeakPtr<IconAnimation> animation = GetIconAnimation(tab_id);
376868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)  if (animation.get() == NULL)
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return icon;
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return gfx::ImageSkia(new AnimatedIconImageSource(icon, animation),
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        icon.size());
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace {
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Used to create a Callback owning an IconAnimation.
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void DestroyIconAnimation(scoped_ptr<ExtensionAction::IconAnimation>) {}
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void ExtensionAction::RunIconAnimation(int tab_id) {
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  scoped_ptr<IconAnimation> icon_animation(new IconAnimation());
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  icon_animation_[tab_id] = icon_animation->AsWeakPtr();
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  icon_animation->Start();
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // After the icon is finished fading in (plus some padding to handle random
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // timer delays), destroy it. We use a delayed task so that the Animation is
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // deleted even if it hasn't finished by the time the MessageLoop is
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // destroyed.
39590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  base::MessageLoop::current()->PostDelayedTask(
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      FROM_HERE,
3972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      base::Bind(&DestroyIconAnimation, base::Passed(&icon_animation)),
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      base::TimeDelta::FromMilliseconds(kIconFadeInDurationMs * 2));
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
400