extension_action_icon_factory_unittest.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
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/browser/extensions/extension_action_icon_factory.h"
6
7#include "base/command_line.h"
8#include "base/file_util.h"
9#include "base/json/json_file_value_serializer.h"
10#include "base/message_loop/message_loop.h"
11#include "base/path_service.h"
12#include "chrome/browser/extensions/extension_action.h"
13#include "chrome/browser/extensions/extension_action_manager.h"
14#include "chrome/browser/extensions/extension_service.h"
15#include "chrome/browser/extensions/test_extension_system.h"
16#include "chrome/common/chrome_paths.h"
17#include "chrome/common/extensions/extension.h"
18#include "chrome/test/base/testing_profile.h"
19#include "content/public/test/test_browser_thread.h"
20#include "grit/theme_resources.h"
21#include "skia/ext/image_operations.h"
22#include "testing/gtest/include/gtest/gtest.h"
23#include "ui/base/resource/resource_bundle.h"
24#include "ui/gfx/codec/png_codec.h"
25#include "ui/gfx/image/image_skia.h"
26#include "ui/gfx/skia_util.h"
27
28#if defined(OS_CHROMEOS)
29#include "chrome/browser/chromeos/login/user_manager.h"
30#include "chrome/browser/chromeos/settings/cros_settings.h"
31#include "chrome/browser/chromeos/settings/device_settings_service.h"
32#endif
33
34using content::BrowserThread;
35
36namespace extensions {
37namespace {
38
39bool ImageRepsAreEqual(const gfx::ImageSkiaRep& image_rep1,
40                       const gfx::ImageSkiaRep& image_rep2) {
41  return image_rep1.scale_factor() == image_rep2.scale_factor() &&
42         gfx::BitmapsAreEqual(image_rep1.sk_bitmap(), image_rep2.sk_bitmap());
43}
44
45gfx::Image EnsureImageSize(const gfx::Image& original, int size) {
46  const SkBitmap* original_bitmap = original.ToSkBitmap();
47  if (original_bitmap->width() == size && original_bitmap->height() == size)
48    return original;
49
50  SkBitmap resized = skia::ImageOperations::Resize(
51      *original.ToSkBitmap(), skia::ImageOperations::RESIZE_LANCZOS3,
52      size, size);
53  return gfx::Image::CreateFrom1xBitmap(resized);
54}
55
56gfx::ImageSkiaRep CreateBlankRep(int size_dip, ui::ScaleFactor scale_factor) {
57    SkBitmap bitmap;
58    const float scale = ui::GetScaleFactorScale(scale_factor);
59    bitmap.setConfig(SkBitmap::kARGB_8888_Config,
60                     static_cast<int>(size_dip * scale),
61                     static_cast<int>(size_dip * scale));
62    bitmap.allocPixels();
63    bitmap.eraseColor(SkColorSetARGB(0, 0, 0, 0));
64    return gfx::ImageSkiaRep(bitmap, scale_factor);
65}
66
67gfx::Image LoadIcon(const std::string& filename) {
68  base::FilePath path;
69  PathService::Get(chrome::DIR_TEST_DATA, &path);
70  path = path.AppendASCII("extensions/api_test").AppendASCII(filename);
71
72  std::string file_contents;
73  file_util::ReadFileToString(path, &file_contents);
74  const unsigned char* data =
75      reinterpret_cast<const unsigned char*>(file_contents.data());
76
77  SkBitmap bitmap;
78  gfx::PNGCodec::Decode(data, file_contents.length(), &bitmap);
79
80  return gfx::Image::CreateFrom1xBitmap(bitmap);
81}
82
83class ExtensionActionIconFactoryTest
84    : public testing::Test,
85      public ExtensionActionIconFactory::Observer {
86 public:
87  ExtensionActionIconFactoryTest()
88      : quit_in_icon_updated_(false),
89        ui_thread_(BrowserThread::UI, &ui_loop_),
90        file_thread_(BrowserThread::FILE),
91        io_thread_(BrowserThread::IO) {
92  }
93
94  virtual ~ExtensionActionIconFactoryTest() {}
95
96  void WaitForIconUpdate() {
97    quit_in_icon_updated_ = true;
98    base::MessageLoop::current()->Run();
99    quit_in_icon_updated_ = false;
100  }
101
102  scoped_refptr<Extension> CreateExtension(const char* name,
103                                           Manifest::Location location) {
104    // Create and load an extension.
105    base::FilePath test_file;
106    if (!PathService::Get(chrome::DIR_TEST_DATA, &test_file)) {
107      EXPECT_FALSE(true);
108      return NULL;
109    }
110    test_file = test_file.AppendASCII("extensions/api_test").AppendASCII(name);
111    int error_code = 0;
112    std::string error;
113    JSONFileValueSerializer serializer(test_file.AppendASCII("manifest.json"));
114    scoped_ptr<DictionaryValue> valid_value(
115        static_cast<DictionaryValue*>(serializer.Deserialize(&error_code,
116                                                             &error)));
117    EXPECT_EQ(0, error_code) << error;
118    if (error_code != 0)
119      return NULL;
120
121    EXPECT_TRUE(valid_value.get());
122    if (!valid_value)
123      return NULL;
124
125    scoped_refptr<Extension> extension =
126        Extension::Create(test_file, location, *valid_value,
127                          Extension::NO_FLAGS, &error);
128    EXPECT_TRUE(extension.get()) << error;
129    if (extension.get())
130      extension_service_->AddExtension(extension.get());
131    return extension;
132  }
133
134  // testing::Test overrides:
135  virtual void SetUp() OVERRIDE {
136    file_thread_.Start();
137    io_thread_.Start();
138    profile_.reset(new TestingProfile);
139    CommandLine command_line(CommandLine::NO_PROGRAM);
140    extension_service_ = static_cast<extensions::TestExtensionSystem*>(
141        extensions::ExtensionSystem::Get(profile_.get()))->
142        CreateExtensionService(&command_line, base::FilePath(), false);
143  }
144
145  virtual void TearDown() OVERRIDE {
146    profile_.reset();  // Get all DeleteSoon calls sent to ui_loop_.
147    ui_loop_.RunUntilIdle();
148  }
149
150  // ExtensionActionIconFactory::Observer overrides:
151  virtual void OnIconUpdated() OVERRIDE {
152    if (quit_in_icon_updated_)
153      base::MessageLoop::current()->Quit();
154  }
155
156  gfx::ImageSkia GetFavicon() {
157    return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
158        IDR_EXTENSIONS_FAVICON);
159  }
160
161  ExtensionAction* GetBrowserAction(const Extension& extension) {
162    return ExtensionActionManager::Get(profile())->GetBrowserAction(extension);
163  }
164
165  TestingProfile* profile() { return profile_.get(); }
166
167 private:
168  bool quit_in_icon_updated_;
169  base::MessageLoop ui_loop_;
170  content::TestBrowserThread ui_thread_;
171  content::TestBrowserThread file_thread_;
172  content::TestBrowserThread io_thread_;
173  scoped_ptr<TestingProfile> profile_;
174  ExtensionService* extension_service_;
175
176#if defined OS_CHROMEOS
177  chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
178  chromeos::ScopedTestCrosSettings test_cros_settings_;
179  chromeos::ScopedTestUserManager test_user_manager_;
180#endif
181
182  DISALLOW_COPY_AND_ASSIGN(ExtensionActionIconFactoryTest);
183};
184
185// If there is no default icon, and the icon has not been set using |SetIcon|,
186// the factory should return favicon.
187TEST_F(ExtensionActionIconFactoryTest, NoIcons) {
188  // Load an extension that has browser action without default icon set in the
189  // manifest and does not call |SetIcon| by default.
190  scoped_refptr<Extension> extension(CreateExtension(
191      "browser_action/no_icon", Manifest::INVALID_LOCATION));
192  ASSERT_TRUE(extension.get() != NULL);
193  ExtensionAction* browser_action = GetBrowserAction(*extension.get());
194  ASSERT_TRUE(browser_action);
195  ASSERT_FALSE(browser_action->default_icon());
196  ASSERT_TRUE(browser_action->GetExplicitlySetIcon(0 /*tab id*/).isNull());
197
198  gfx::ImageSkia favicon = GetFavicon();
199
200  ExtensionActionIconFactory icon_factory(
201      profile(), extension.get(), browser_action, this);
202
203  gfx::Image icon = icon_factory.GetIcon(0);
204
205  EXPECT_TRUE(ImageRepsAreEqual(
206      favicon.GetRepresentation(ui::SCALE_FACTOR_100P),
207      icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P)));
208}
209
210// If the icon has been set using |SetIcon|, the factory should return that
211// icon.
212TEST_F(ExtensionActionIconFactoryTest, AfterSetIcon) {
213  // Load an extension that has browser action without default icon set in the
214  // manifest and does not call |SetIcon| by default (but has an browser action
215  // icon resource).
216  scoped_refptr<Extension> extension(CreateExtension(
217      "browser_action/no_icon", Manifest::INVALID_LOCATION));
218  ASSERT_TRUE(extension.get() != NULL);
219  ExtensionAction* browser_action = GetBrowserAction(*extension.get());
220  ASSERT_TRUE(browser_action);
221  ASSERT_FALSE(browser_action->default_icon());
222  ASSERT_TRUE(browser_action->GetExplicitlySetIcon(0 /*tab id*/).isNull());
223
224  gfx::Image set_icon = LoadIcon("browser_action/no_icon/icon.png");
225  ASSERT_FALSE(set_icon.IsEmpty());
226
227  browser_action->SetIcon(0, set_icon);
228
229  ASSERT_FALSE(browser_action->GetExplicitlySetIcon(0 /*tab id*/).isNull());
230
231  ExtensionActionIconFactory icon_factory(
232      profile(), extension.get(), browser_action, this);
233
234  gfx::Image icon = icon_factory.GetIcon(0);
235
236  EXPECT_TRUE(ImageRepsAreEqual(
237      set_icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P),
238      icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P)));
239
240  // It should still return favicon for another tabs.
241  icon = icon_factory.GetIcon(1);
242
243  EXPECT_TRUE(ImageRepsAreEqual(
244      GetFavicon().GetRepresentation(ui::SCALE_FACTOR_100P),
245      icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P)));
246}
247
248// If there is a default icon, and the icon has not been set using |SetIcon|,
249// the factory should return the default icon.
250TEST_F(ExtensionActionIconFactoryTest, DefaultIcon) {
251  // Load an extension that has browser action without default icon set in the
252  // manifest and does not call |SetIcon| by default (but has an browser action
253  // icon resource).
254  scoped_refptr<Extension> extension(CreateExtension(
255      "browser_action/no_icon", Manifest::INVALID_LOCATION));
256  ASSERT_TRUE(extension.get() != NULL);
257  ExtensionAction* browser_action = GetBrowserAction(*extension.get());
258  ASSERT_TRUE(browser_action);
259  ASSERT_FALSE(browser_action->default_icon());
260  ASSERT_TRUE(browser_action->GetExplicitlySetIcon(0 /*tab id*/).isNull());
261
262  gfx::Image default_icon =
263      EnsureImageSize(LoadIcon("browser_action/no_icon/icon.png"), 19);
264  ASSERT_FALSE(default_icon.IsEmpty());
265
266  scoped_ptr<ExtensionIconSet> default_icon_set(new ExtensionIconSet());
267  default_icon_set->Add(19, "icon.png");
268
269  browser_action->set_default_icon(default_icon_set.Pass());
270  ASSERT_TRUE(browser_action->default_icon());
271
272  ExtensionActionIconFactory icon_factory(
273      profile(), extension.get(), browser_action, this);
274
275  gfx::Image icon = icon_factory.GetIcon(0);
276
277  // The icon should be loaded asynchronously. Initially a transparent icon
278  // should be returned.
279  EXPECT_TRUE(ImageRepsAreEqual(
280      CreateBlankRep(19, ui::SCALE_FACTOR_100P),
281      icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P)));
282
283  WaitForIconUpdate();
284
285  icon = icon_factory.GetIcon(0);
286
287  // The default icon representation should be loaded at this point.
288  EXPECT_TRUE(ImageRepsAreEqual(
289      default_icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P),
290      icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P)));
291
292  // The same icon should be returned for the other tabs.
293  icon = icon_factory.GetIcon(1);
294
295  EXPECT_TRUE(ImageRepsAreEqual(
296      default_icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P),
297      icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P)));
298
299}
300
301}  // namespace
302}  // namespace extensions
303