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/image_loader.h"
6
7#include "base/json/json_file_value_serializer.h"
8#include "base/message_loop/message_loop.h"
9#include "base/path_service.h"
10#include "chrome/browser/chrome_notification_types.h"
11#include "chrome/common/chrome_paths.h"
12#include "chrome/common/extensions/extension_constants.h"
13#include "chrome/common/extensions/extension_icon_set.h"
14#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
15#include "content/public/browser/notification_service.h"
16#include "content/public/test/test_browser_thread.h"
17#include "extensions/common/extension.h"
18#include "extensions/common/extension_resource.h"
19#include "extensions/common/manifest.h"
20#include "grit/component_extension_resources.h"
21#include "testing/gtest/include/gtest/gtest.h"
22#include "third_party/skia/include/core/SkBitmap.h"
23#include "ui/gfx/image/image.h"
24#include "ui/gfx/image/image_skia.h"
25#include "ui/gfx/size.h"
26
27using content::BrowserThread;
28using extensions::Extension;
29using extensions::ExtensionResource;
30using extensions::ImageLoader;
31using extensions::Manifest;
32using extensions::UnloadedExtensionInfo;
33
34class ImageLoaderTest : public testing::Test {
35 public:
36  ImageLoaderTest()
37      : image_loaded_count_(0),
38        quit_in_image_loaded_(false),
39        ui_thread_(BrowserThread::UI, &ui_loop_),
40        file_thread_(BrowserThread::FILE),
41        io_thread_(BrowserThread::IO) {
42  }
43
44  void OnImageLoaded(const gfx::Image& image) {
45    image_loaded_count_++;
46    if (quit_in_image_loaded_)
47      base::MessageLoop::current()->Quit();
48    image_ = image;
49  }
50
51  void WaitForImageLoad() {
52    quit_in_image_loaded_ = true;
53    base::MessageLoop::current()->Run();
54    quit_in_image_loaded_ = false;
55  }
56
57  int image_loaded_count() {
58    int result = image_loaded_count_;
59    image_loaded_count_ = 0;
60    return result;
61  }
62
63  scoped_refptr<Extension> CreateExtension(const char* name,
64                                           Manifest::Location location) {
65    // Create and load an extension.
66    base::FilePath test_file;
67    if (!PathService::Get(chrome::DIR_TEST_DATA, &test_file)) {
68      EXPECT_FALSE(true);
69      return NULL;
70    }
71    test_file = test_file.AppendASCII("extensions")
72                         .AppendASCII(name);
73    int error_code = 0;
74    std::string error;
75    JSONFileValueSerializer serializer(test_file.AppendASCII("app.json"));
76    scoped_ptr<DictionaryValue> valid_value(
77        static_cast<DictionaryValue*>(serializer.Deserialize(&error_code,
78                                                             &error)));
79    EXPECT_EQ(0, error_code) << error;
80    if (error_code != 0)
81      return NULL;
82
83    EXPECT_TRUE(valid_value.get());
84    if (!valid_value)
85      return NULL;
86
87    if (location == Manifest::COMPONENT) {
88      if (!PathService::Get(chrome::DIR_RESOURCES, &test_file)) {
89        EXPECT_FALSE(true);
90        return NULL;
91      }
92      test_file = test_file.AppendASCII(name);
93    }
94    return Extension::Create(test_file, location, *valid_value,
95                             Extension::NO_FLAGS, &error);
96  }
97
98  gfx::Image image_;
99
100 private:
101  virtual void SetUp() OVERRIDE {
102    testing::Test::SetUp();
103    file_thread_.Start();
104    io_thread_.Start();
105  }
106
107  int image_loaded_count_;
108  bool quit_in_image_loaded_;
109  base::MessageLoop ui_loop_;
110  content::TestBrowserThread ui_thread_;
111  content::TestBrowserThread file_thread_;
112  content::TestBrowserThread io_thread_;
113};
114
115// Tests loading an image works correctly.
116TEST_F(ImageLoaderTest, LoadImage) {
117  scoped_refptr<Extension> extension(CreateExtension(
118      "image_loading_tracker", Manifest::INVALID_LOCATION));
119  ASSERT_TRUE(extension.get() != NULL);
120
121  ExtensionResource image_resource = extensions::IconsInfo::GetIconResource(
122      extension.get(),
123      extension_misc::EXTENSION_ICON_SMALLISH,
124      ExtensionIconSet::MATCH_EXACTLY);
125  gfx::Size max_size(extension_misc::EXTENSION_ICON_SMALLISH,
126                     extension_misc::EXTENSION_ICON_SMALLISH);
127  ImageLoader loader;
128  loader.LoadImageAsync(extension.get(),
129                        image_resource,
130                        max_size,
131                        base::Bind(&ImageLoaderTest::OnImageLoaded,
132                                   base::Unretained(this)));
133
134  // The image isn't cached, so we should not have received notification.
135  EXPECT_EQ(0, image_loaded_count());
136
137  WaitForImageLoad();
138
139  // We should have gotten the image.
140  EXPECT_EQ(1, image_loaded_count());
141
142  // Check that the image was loaded.
143  EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH,
144            image_.ToSkBitmap()->width());
145}
146
147// Tests deleting an extension while waiting for the image to load doesn't cause
148// problems.
149TEST_F(ImageLoaderTest, DeleteExtensionWhileWaitingForCache) {
150  scoped_refptr<Extension> extension(CreateExtension(
151      "image_loading_tracker", Manifest::INVALID_LOCATION));
152  ASSERT_TRUE(extension.get() != NULL);
153
154  ExtensionResource image_resource = extensions::IconsInfo::GetIconResource(
155      extension.get(),
156      extension_misc::EXTENSION_ICON_SMALLISH,
157      ExtensionIconSet::MATCH_EXACTLY);
158  gfx::Size max_size(extension_misc::EXTENSION_ICON_SMALLISH,
159                     extension_misc::EXTENSION_ICON_SMALLISH);
160  ImageLoader loader;
161  std::set<int> sizes;
162  sizes.insert(extension_misc::EXTENSION_ICON_SMALLISH);
163  loader.LoadImageAsync(extension.get(),
164                        image_resource,
165                        max_size,
166                        base::Bind(&ImageLoaderTest::OnImageLoaded,
167                                   base::Unretained(this)));
168
169  // The image isn't cached, so we should not have received notification.
170  EXPECT_EQ(0, image_loaded_count());
171
172  // Send out notification the extension was uninstalled.
173  UnloadedExtensionInfo details(extension.get(),
174                                UnloadedExtensionInfo::REASON_UNINSTALL);
175  content::NotificationService::current()->Notify(
176      chrome::NOTIFICATION_EXTENSION_UNLOADED,
177      content::NotificationService::AllSources(),
178      content::Details<UnloadedExtensionInfo>(&details));
179
180  // Chuck the extension, that way if anyone tries to access it we should crash
181  // or get valgrind errors.
182  extension = NULL;
183
184  WaitForImageLoad();
185
186  // Even though we deleted the extension, we should still get the image.
187  // We should still have gotten the image.
188  EXPECT_EQ(1, image_loaded_count());
189
190  // Check that the image was loaded.
191  EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH,
192            image_.ToSkBitmap()->width());
193}
194
195// Tests loading multiple dimensions of the same image.
196TEST_F(ImageLoaderTest, MultipleImages) {
197  scoped_refptr<Extension> extension(CreateExtension(
198      "image_loading_tracker", Manifest::INVALID_LOCATION));
199  ASSERT_TRUE(extension.get() != NULL);
200
201  std::vector<ImageLoader::ImageRepresentation> info_list;
202  int sizes[] = {extension_misc::EXTENSION_ICON_SMALLISH,
203                 extension_misc::EXTENSION_ICON_BITTY};
204  for (size_t i = 0; i < arraysize(sizes); ++i) {
205    ExtensionResource resource = extensions::IconsInfo::GetIconResource(
206        extension.get(), sizes[i], ExtensionIconSet::MATCH_EXACTLY);
207    info_list.push_back(ImageLoader::ImageRepresentation(
208        resource,
209        ImageLoader::ImageRepresentation::RESIZE_WHEN_LARGER,
210        gfx::Size(sizes[i], sizes[i]),
211        ui::SCALE_FACTOR_NONE));
212  }
213
214  ImageLoader loader;
215  loader.LoadImagesAsync(extension.get(), info_list,
216                         base::Bind(&ImageLoaderTest::OnImageLoaded,
217                                    base::Unretained(this)));
218
219  // The image isn't cached, so we should not have received notification.
220  EXPECT_EQ(0, image_loaded_count());
221
222  WaitForImageLoad();
223
224  // We should have gotten the image.
225  EXPECT_EQ(1, image_loaded_count());
226
227  // Check that all images were loaded.
228  std::vector<gfx::ImageSkiaRep> image_reps =
229      image_.ToImageSkia()->image_reps();
230  ASSERT_EQ(2u, image_reps.size());
231  const gfx::ImageSkiaRep* img_rep1 = &image_reps[0];
232  const gfx::ImageSkiaRep* img_rep2 = &image_reps[1];
233  if (img_rep1->pixel_width() > img_rep2->pixel_width()) {
234    std::swap(img_rep1, img_rep2);
235  }
236  EXPECT_EQ(extension_misc::EXTENSION_ICON_BITTY,
237            img_rep1->pixel_width());
238  EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH,
239            img_rep2->pixel_width());
240}
241
242// Tests IsComponentExtensionResource function.
243TEST_F(ImageLoaderTest, IsComponentExtensionResource) {
244  scoped_refptr<Extension> extension(CreateExtension(
245      "file_manager", Manifest::COMPONENT));
246  ASSERT_TRUE(extension.get() != NULL);
247
248  ExtensionResource resource = extensions::IconsInfo::GetIconResource(
249      extension.get(),
250      extension_misc::EXTENSION_ICON_BITTY,
251      ExtensionIconSet::MATCH_EXACTLY);
252
253#if defined(FILE_MANAGER_EXTENSION)
254  int resource_id;
255  ASSERT_EQ(true,
256            ImageLoader::IsComponentExtensionResource(extension->path(),
257                                                      resource.relative_path(),
258                                                      &resource_id));
259  ASSERT_EQ(IDR_FILE_MANAGER_ICON_16, resource_id);
260#endif
261}
262