image_loader_unittest.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
1// Copyright 2014 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 "extensions/browser/image_loader.h"
6
7#include "base/files/file_path.h"
8#include "base/json/json_file_value_serializer.h"
9#include "base/message_loop/message_loop.h"
10#include "base/path_service.h"
11#include "base/strings/string_util.h"
12#include "chrome/browser/chrome_notification_types.h"
13#include "chrome/common/chrome_paths.h"
14#include "content/public/browser/notification_service.h"
15#include "content/public/test/test_browser_thread.h"
16#include "extensions/browser/component_extension_resource_manager.h"
17#include "extensions/browser/extensions_browser_client.h"
18#include "extensions/common/constants.h"
19#include "extensions/common/extension.h"
20#include "extensions/common/extension_icon_set.h"
21#include "extensions/common/extension_resource.h"
22#include "extensions/common/manifest.h"
23#include "extensions/common/manifest_handlers/icons_handler.h"
24#include "testing/gtest/include/gtest/gtest.h"
25#include "third_party/skia/include/core/SkBitmap.h"
26#include "ui/gfx/image/image.h"
27#include "ui/gfx/image/image_family.h"
28#include "ui/gfx/image/image_skia.h"
29#include "ui/gfx/size.h"
30
31#if defined(OS_CHROMEOS)
32#include "ui/file_manager/grit/file_manager_resources.h"
33#endif
34
35using content::BrowserThread;
36using extensions::Extension;
37using extensions::ExtensionResource;
38using extensions::ImageLoader;
39using extensions::Manifest;
40using extensions::UnloadedExtensionInfo;
41
42class ImageLoaderTest : public testing::Test {
43 public:
44  ImageLoaderTest()
45      : image_loaded_count_(0),
46        quit_in_image_loaded_(false),
47        ui_thread_(BrowserThread::UI, &ui_loop_),
48        file_thread_(BrowserThread::FILE),
49        io_thread_(BrowserThread::IO) {
50  }
51
52  void OnImageLoaded(const gfx::Image& image) {
53    image_loaded_count_++;
54    if (quit_in_image_loaded_)
55      base::MessageLoop::current()->Quit();
56    image_ = image;
57  }
58
59  void OnImageFamilyLoaded(const gfx::ImageFamily& image_family) {
60    image_loaded_count_++;
61    if (quit_in_image_loaded_)
62      base::MessageLoop::current()->Quit();
63    image_family_ = image_family;
64  }
65
66  void WaitForImageLoad() {
67    quit_in_image_loaded_ = true;
68    base::MessageLoop::current()->Run();
69    quit_in_image_loaded_ = false;
70  }
71
72  int image_loaded_count() {
73    int result = image_loaded_count_;
74    image_loaded_count_ = 0;
75    return result;
76  }
77
78  scoped_refptr<Extension> CreateExtension(const char* name,
79                                           Manifest::Location location) {
80    // Create and load an extension.
81    base::FilePath test_file;
82    if (!PathService::Get(chrome::DIR_TEST_DATA, &test_file)) {
83      EXPECT_FALSE(true);
84      return NULL;
85    }
86    test_file = test_file.AppendASCII("extensions")
87                         .AppendASCII(name);
88    int error_code = 0;
89    std::string error;
90    JSONFileValueSerializer serializer(test_file.AppendASCII("app.json"));
91    scoped_ptr<base::DictionaryValue> valid_value(
92        static_cast<base::DictionaryValue*>(serializer.Deserialize(&error_code,
93                                                                   &error)));
94    EXPECT_EQ(0, error_code) << error;
95    if (error_code != 0)
96      return NULL;
97
98    EXPECT_TRUE(valid_value.get());
99    if (!valid_value)
100      return NULL;
101
102    if (location == Manifest::COMPONENT) {
103      if (!PathService::Get(chrome::DIR_RESOURCES, &test_file)) {
104        EXPECT_FALSE(true);
105        return NULL;
106      }
107      test_file = test_file.AppendASCII(name);
108    }
109    return Extension::Create(test_file, location, *valid_value,
110                             Extension::NO_FLAGS, &error);
111  }
112
113  gfx::Image image_;
114  gfx::ImageFamily image_family_;
115
116 private:
117  virtual void SetUp() OVERRIDE {
118    testing::Test::SetUp();
119    file_thread_.Start();
120    io_thread_.Start();
121  }
122
123  int image_loaded_count_;
124  bool quit_in_image_loaded_;
125  base::MessageLoop ui_loop_;
126  content::TestBrowserThread ui_thread_;
127  content::TestBrowserThread file_thread_;
128  content::TestBrowserThread io_thread_;
129};
130
131// Tests loading an image works correctly.
132TEST_F(ImageLoaderTest, LoadImage) {
133  scoped_refptr<Extension> extension(CreateExtension(
134      "image_loading_tracker", Manifest::INVALID_LOCATION));
135  ASSERT_TRUE(extension.get() != NULL);
136
137  ExtensionResource image_resource = extensions::IconsInfo::GetIconResource(
138      extension.get(),
139      extension_misc::EXTENSION_ICON_SMALLISH,
140      ExtensionIconSet::MATCH_EXACTLY);
141  gfx::Size max_size(extension_misc::EXTENSION_ICON_SMALLISH,
142                     extension_misc::EXTENSION_ICON_SMALLISH);
143  ImageLoader loader;
144  loader.LoadImageAsync(extension.get(),
145                        image_resource,
146                        max_size,
147                        base::Bind(&ImageLoaderTest::OnImageLoaded,
148                                   base::Unretained(this)));
149
150  // The image isn't cached, so we should not have received notification.
151  EXPECT_EQ(0, image_loaded_count());
152
153  WaitForImageLoad();
154
155  // We should have gotten the image.
156  EXPECT_EQ(1, image_loaded_count());
157
158  // Check that the image was loaded.
159  EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH,
160            image_.ToSkBitmap()->width());
161}
162
163// Tests deleting an extension while waiting for the image to load doesn't cause
164// problems.
165TEST_F(ImageLoaderTest, DeleteExtensionWhileWaitingForCache) {
166  scoped_refptr<Extension> extension(CreateExtension(
167      "image_loading_tracker", Manifest::INVALID_LOCATION));
168  ASSERT_TRUE(extension.get() != NULL);
169
170  ExtensionResource image_resource = extensions::IconsInfo::GetIconResource(
171      extension.get(),
172      extension_misc::EXTENSION_ICON_SMALLISH,
173      ExtensionIconSet::MATCH_EXACTLY);
174  gfx::Size max_size(extension_misc::EXTENSION_ICON_SMALLISH,
175                     extension_misc::EXTENSION_ICON_SMALLISH);
176  ImageLoader loader;
177  std::set<int> sizes;
178  sizes.insert(extension_misc::EXTENSION_ICON_SMALLISH);
179  loader.LoadImageAsync(extension.get(),
180                        image_resource,
181                        max_size,
182                        base::Bind(&ImageLoaderTest::OnImageLoaded,
183                                   base::Unretained(this)));
184
185  // The image isn't cached, so we should not have received notification.
186  EXPECT_EQ(0, image_loaded_count());
187
188  // Send out notification the extension was uninstalled.
189  UnloadedExtensionInfo details(extension.get(),
190                                UnloadedExtensionInfo::REASON_UNINSTALL);
191  content::NotificationService::current()->Notify(
192      chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
193      content::NotificationService::AllSources(),
194      content::Details<UnloadedExtensionInfo>(&details));
195
196  // Chuck the extension, that way if anyone tries to access it we should crash
197  // or get valgrind errors.
198  extension = NULL;
199
200  WaitForImageLoad();
201
202  // Even though we deleted the extension, we should still get the image.
203  // We should still have gotten the image.
204  EXPECT_EQ(1, image_loaded_count());
205
206  // Check that the image was loaded.
207  EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH,
208            image_.ToSkBitmap()->width());
209}
210
211// Tests loading multiple dimensions of the same image.
212TEST_F(ImageLoaderTest, MultipleImages) {
213  scoped_refptr<Extension> extension(CreateExtension(
214      "image_loading_tracker", Manifest::INVALID_LOCATION));
215  ASSERT_TRUE(extension.get() != NULL);
216
217  std::vector<ImageLoader::ImageRepresentation> info_list;
218  int sizes[] = {extension_misc::EXTENSION_ICON_BITTY,
219                 extension_misc::EXTENSION_ICON_SMALLISH, };
220  for (size_t i = 0; i < arraysize(sizes); ++i) {
221    ExtensionResource resource = extensions::IconsInfo::GetIconResource(
222        extension.get(), sizes[i], ExtensionIconSet::MATCH_EXACTLY);
223    info_list.push_back(ImageLoader::ImageRepresentation(
224        resource,
225        ImageLoader::ImageRepresentation::RESIZE_WHEN_LARGER,
226        gfx::Size(sizes[i], sizes[i]),
227        ui::SCALE_FACTOR_NONE));
228  }
229
230  ImageLoader loader;
231  loader.LoadImagesAsync(extension.get(), info_list,
232                         base::Bind(&ImageLoaderTest::OnImageLoaded,
233                                    base::Unretained(this)));
234
235  // The image isn't cached, so we should not have received notification.
236  EXPECT_EQ(0, image_loaded_count());
237
238  WaitForImageLoad();
239
240  // We should have gotten the image.
241  EXPECT_EQ(1, image_loaded_count());
242
243  // Check that all images were loaded.
244  std::vector<gfx::ImageSkiaRep> image_reps =
245      image_.ToImageSkia()->image_reps();
246  ASSERT_EQ(2u, image_reps.size());
247
248  const gfx::ImageSkiaRep* img_rep1 = &image_reps[0];
249  const gfx::ImageSkiaRep* img_rep2 = &image_reps[1];
250  EXPECT_EQ(extension_misc::EXTENSION_ICON_BITTY,
251            img_rep1->pixel_width());
252  EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH,
253            img_rep2->pixel_width());
254}
255
256// Tests loading multiple dimensions of the same image into an image family.
257TEST_F(ImageLoaderTest, LoadImageFamily) {
258  scoped_refptr<Extension> extension(
259      CreateExtension("image_loading_tracker", Manifest::INVALID_LOCATION));
260  ASSERT_TRUE(extension.get() != NULL);
261
262  std::vector<ImageLoader::ImageRepresentation> info_list;
263  int sizes[] = {extension_misc::EXTENSION_ICON_BITTY,
264                 extension_misc::EXTENSION_ICON_SMALLISH, };
265  for (size_t i = 0; i < arraysize(sizes); ++i) {
266    ExtensionResource resource = extensions::IconsInfo::GetIconResource(
267        extension.get(), sizes[i], ExtensionIconSet::MATCH_EXACTLY);
268    info_list.push_back(ImageLoader::ImageRepresentation(
269        resource,
270        ImageLoader::ImageRepresentation::NEVER_RESIZE,
271        gfx::Size(sizes[i], sizes[i]),
272        ui::SCALE_FACTOR_100P));
273  }
274
275  // Add a second icon of 200P which should get grouped with the smaller icon's
276  // ImageSkia.
277  ExtensionResource resource = extensions::IconsInfo::GetIconResource(
278      extension.get(),
279      extension_misc::EXTENSION_ICON_SMALLISH,
280      ExtensionIconSet::MATCH_EXACTLY);
281  info_list.push_back(ImageLoader::ImageRepresentation(
282      resource,
283      ImageLoader::ImageRepresentation::NEVER_RESIZE,
284      gfx::Size(extension_misc::EXTENSION_ICON_BITTY,
285                extension_misc::EXTENSION_ICON_BITTY),
286      ui::SCALE_FACTOR_200P));
287
288  ImageLoader loader;
289  loader.LoadImageFamilyAsync(extension.get(),
290                              info_list,
291                              base::Bind(&ImageLoaderTest::OnImageFamilyLoaded,
292                                         base::Unretained(this)));
293
294  // The image isn't cached, so we should not have received notification.
295  EXPECT_EQ(0, image_loaded_count());
296
297  WaitForImageLoad();
298
299  // We should have gotten the image.
300  EXPECT_EQ(1, image_loaded_count());
301
302  // Check that all images were loaded.
303  for (size_t i = 0; i < arraysize(sizes); ++i) {
304    const gfx::Image* image = image_family_.GetBest(sizes[i], sizes[i]);
305    EXPECT_EQ(sizes[i], image->Width());
306  }
307
308  // Check the smaller image has 2 representations of different scale factors.
309  std::vector<gfx::ImageSkiaRep> image_reps =
310      image_family_.GetBest(extension_misc::EXTENSION_ICON_BITTY,
311                            extension_misc::EXTENSION_ICON_BITTY)
312          ->ToImageSkia()
313          ->image_reps();
314
315  ASSERT_EQ(2u, image_reps.size());
316
317  const gfx::ImageSkiaRep* img_rep1 = &image_reps[0];
318  const gfx::ImageSkiaRep* img_rep2 = &image_reps[1];
319  EXPECT_EQ(extension_misc::EXTENSION_ICON_BITTY, img_rep1->pixel_width());
320  EXPECT_EQ(1.0f, img_rep1->scale());
321  EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH, img_rep2->pixel_width());
322  EXPECT_EQ(2.0f, img_rep2->scale());
323}
324
325// Tests IsComponentExtensionResource function.
326// TODO(mukai): move this to ChromeComponentExtensionResourceManager's test.
327TEST_F(ImageLoaderTest, IsComponentExtensionResource) {
328  extensions::ComponentExtensionResourceManager* resource_manager =
329      extensions::ExtensionsBrowserClient::Get()->
330      GetComponentExtensionResourceManager();
331  if (!resource_manager)
332    return;
333
334  scoped_refptr<Extension> extension(CreateExtension(
335      "file_manager", Manifest::COMPONENT));
336  ASSERT_TRUE(extension.get() != NULL);
337
338  ExtensionResource resource = extensions::IconsInfo::GetIconResource(
339      extension.get(),
340      extension_misc::EXTENSION_ICON_BITTY,
341      ExtensionIconSet::MATCH_EXACTLY);
342
343#if defined(OS_CHROMEOS)
344  int resource_id;
345  ASSERT_TRUE(resource_manager->IsComponentExtensionResource(
346      extension->path(),
347      resource.relative_path(),
348      &resource_id));
349  ASSERT_EQ(IDR_FILE_MANAGER_ICON_16, resource_id);
350#endif
351}
352