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 "chrome/browser/extensions/bookmark_app_helper.h"
6
7#include "base/strings/utf_string_conversions.h"
8#include "chrome/browser/extensions/extension_service.h"
9#include "chrome/browser/extensions/extension_service_test_base.h"
10#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
11#include "chrome/test/base/testing_profile.h"
12#include "content/public/browser/render_process_host.h"
13#include "content/public/browser/web_contents.h"
14#include "content/public/test/web_contents_tester.h"
15#include "extensions/browser/extension_registry.h"
16#include "extensions/common/constants.h"
17#include "extensions/common/extension_icon_set.h"
18#include "extensions/common/manifest_handlers/icons_handler.h"
19#include "testing/gtest/include/gtest/gtest.h"
20#include "third_party/skia/include/core/SkBitmap.h"
21#include "ui/gfx/skia_util.h"
22
23namespace {
24
25#if !defined(OS_ANDROID)
26const char kAppUrl[] = "http://www.chromium.org";
27const char kAppTitle[] = "Test title";
28const char kAlternativeAppTitle[] = "Different test title";
29const char kAppDescription[] = "Test description";
30
31const int kIconSizeTiny = extension_misc::EXTENSION_ICON_BITTY;
32const int kIconSizeSmall = extension_misc::EXTENSION_ICON_SMALL;
33const int kIconSizeMedium = extension_misc::EXTENSION_ICON_MEDIUM;
34const int kIconSizeLarge = extension_misc::EXTENSION_ICON_LARGE;
35#endif
36
37class BookmarkAppHelperTest : public testing::Test {
38 public:
39  BookmarkAppHelperTest() {}
40  virtual ~BookmarkAppHelperTest() {}
41
42 private:
43  DISALLOW_COPY_AND_ASSIGN(BookmarkAppHelperTest);
44};
45
46class BookmarkAppHelperExtensionServiceTest
47    : public extensions::ExtensionServiceTestBase {
48 public:
49  BookmarkAppHelperExtensionServiceTest() {}
50  virtual ~BookmarkAppHelperExtensionServiceTest() {}
51
52  virtual void SetUp() OVERRIDE {
53    extensions::ExtensionServiceTestBase::SetUp();
54    InitializeEmptyExtensionService();
55    service_->Init();
56    EXPECT_EQ(0u, service_->extensions()->size());
57  }
58
59  virtual void TearDown() OVERRIDE {
60    ExtensionServiceTestBase::TearDown();
61    for (content::RenderProcessHost::iterator i(
62             content::RenderProcessHost::AllHostsIterator());
63         !i.IsAtEnd();
64         i.Advance()) {
65      content::RenderProcessHost* host = i.GetCurrentValue();
66      if (Profile::FromBrowserContext(host->GetBrowserContext()) ==
67          profile_.get())
68        host->Cleanup();
69    }
70  }
71
72 private:
73  DISALLOW_COPY_AND_ASSIGN(BookmarkAppHelperExtensionServiceTest);
74};
75
76SkBitmap CreateSquareBitmapWithColor(int size, SkColor color) {
77  SkBitmap bitmap;
78  bitmap.setConfig(SkBitmap::kARGB_8888_Config, size, size);
79  bitmap.allocPixels();
80  bitmap.eraseColor(color);
81  return bitmap;
82}
83
84void ValidateBitmapSizeAndColor(SkBitmap bitmap, int size, SkColor color) {
85  // Obtain pixel lock to access pixels.
86  SkAutoLockPixels lock(bitmap);
87  EXPECT_EQ(color, bitmap.getColor(0, 0));
88  EXPECT_EQ(size, bitmap.width());
89  EXPECT_EQ(size, bitmap.height());
90}
91
92#if !defined(OS_ANDROID)
93WebApplicationInfo::IconInfo CreateIconInfoWithBitmap(int size, SkColor color) {
94  WebApplicationInfo::IconInfo icon_info;
95  icon_info.width = size;
96  icon_info.height = size;
97  icon_info.data = CreateSquareBitmapWithColor(size, color);
98  return icon_info;
99}
100
101void ValidateWebApplicationInfo(base::Closure callback,
102                                const WebApplicationInfo& expected,
103                                const WebApplicationInfo& actual) {
104  EXPECT_EQ(expected.title, actual.title);
105  EXPECT_EQ(expected.description, actual.description);
106  EXPECT_EQ(expected.app_url, actual.app_url);
107  EXPECT_EQ(expected.icons.size(), actual.icons.size());
108  for (size_t i = 0; i < expected.icons.size(); ++i) {
109    EXPECT_EQ(expected.icons[i].width, actual.icons[i].width);
110    EXPECT_EQ(expected.icons[i].height, actual.icons[i].height);
111    EXPECT_EQ(expected.icons[i].url, actual.icons[i].url);
112    EXPECT_TRUE(
113        gfx::BitmapsAreEqual(expected.icons[i].data, actual.icons[i].data));
114  }
115  callback.Run();
116}
117#endif
118
119}  // namespace
120
121namespace extensions {
122
123class TestBookmarkAppHelper : public BookmarkAppHelper {
124 public:
125  TestBookmarkAppHelper(ExtensionService* service,
126                        WebApplicationInfo web_app_info,
127                        content::WebContents* contents)
128      : BookmarkAppHelper(service, web_app_info, contents) {}
129
130  virtual ~TestBookmarkAppHelper() {}
131
132  void CreationComplete(const extensions::Extension* extension,
133                        const WebApplicationInfo& web_app_info) {
134    extension_ = extension;
135  }
136
137  void CompleteIconDownload(
138      bool success,
139      const std::map<GURL, std::vector<SkBitmap> >& bitmaps) {
140    BookmarkAppHelper::OnIconsDownloaded(success, bitmaps);
141  }
142
143  const Extension* extension() { return extension_; }
144
145 private:
146  const Extension* extension_;
147
148  DISALLOW_COPY_AND_ASSIGN(TestBookmarkAppHelper);
149};
150
151// Android doesn't support extensions.
152#if !defined(OS_ANDROID)
153TEST_F(BookmarkAppHelperExtensionServiceTest, CreateBookmarkApp) {
154  WebApplicationInfo web_app_info;
155  web_app_info.app_url = GURL(kAppUrl);
156  web_app_info.title = base::UTF8ToUTF16(kAppTitle);
157  web_app_info.description = base::UTF8ToUTF16(kAppDescription);
158
159  scoped_ptr<content::WebContents> contents(
160      content::WebContentsTester::CreateTestWebContents(profile_.get(), NULL));
161  TestBookmarkAppHelper helper(service_, web_app_info, contents.get());
162  helper.Create(base::Bind(&TestBookmarkAppHelper::CreationComplete,
163                           base::Unretained(&helper)));
164
165  std::map<GURL, std::vector<SkBitmap> > icon_map;
166  icon_map[GURL(kAppUrl)].push_back(
167      CreateSquareBitmapWithColor(kIconSizeSmall, SK_ColorRED));
168  helper.CompleteIconDownload(true, icon_map);
169
170  base::RunLoop().RunUntilIdle();
171  EXPECT_TRUE(helper.extension());
172  const Extension* extension =
173      service_->GetInstalledExtension(helper.extension()->id());
174  EXPECT_TRUE(extension);
175  EXPECT_EQ(1u, service_->extensions()->size());
176  EXPECT_TRUE(extension->from_bookmark());
177  EXPECT_EQ(kAppTitle, extension->name());
178  EXPECT_EQ(kAppDescription, extension->description());
179  EXPECT_EQ(GURL(kAppUrl), AppLaunchInfo::GetLaunchWebURL(extension));
180  EXPECT_FALSE(
181      IconsInfo::GetIconResource(
182          extension, kIconSizeSmall, ExtensionIconSet::MATCH_EXACTLY).empty());
183}
184
185TEST_F(BookmarkAppHelperExtensionServiceTest, CreateBookmarkAppNoContents) {
186  WebApplicationInfo web_app_info;
187  web_app_info.app_url = GURL(kAppUrl);
188  web_app_info.title = base::UTF8ToUTF16(kAppTitle);
189  web_app_info.description = base::UTF8ToUTF16(kAppDescription);
190  web_app_info.icons.push_back(
191      CreateIconInfoWithBitmap(kIconSizeTiny, SK_ColorRED));
192
193  TestBookmarkAppHelper helper(service_, web_app_info, NULL);
194  helper.Create(base::Bind(&TestBookmarkAppHelper::CreationComplete,
195                           base::Unretained(&helper)));
196
197  base::RunLoop().RunUntilIdle();
198  EXPECT_TRUE(helper.extension());
199  const Extension* extension =
200      service_->GetInstalledExtension(helper.extension()->id());
201  EXPECT_TRUE(extension);
202  EXPECT_EQ(1u, service_->extensions()->size());
203  EXPECT_TRUE(extension->from_bookmark());
204  EXPECT_EQ(kAppTitle, extension->name());
205  EXPECT_EQ(kAppDescription, extension->description());
206  EXPECT_EQ(GURL(kAppUrl), AppLaunchInfo::GetLaunchWebURL(extension));
207  EXPECT_FALSE(
208      IconsInfo::GetIconResource(
209          extension, kIconSizeTiny, ExtensionIconSet::MATCH_EXACTLY).empty());
210  EXPECT_FALSE(
211      IconsInfo::GetIconResource(
212          extension, kIconSizeSmall, ExtensionIconSet::MATCH_EXACTLY).empty());
213  EXPECT_FALSE(
214      IconsInfo::GetIconResource(extension,
215                                 kIconSizeSmall * 2,
216                                 ExtensionIconSet::MATCH_EXACTLY).empty());
217  EXPECT_FALSE(
218      IconsInfo::GetIconResource(
219          extension, kIconSizeMedium, ExtensionIconSet::MATCH_EXACTLY).empty());
220  EXPECT_FALSE(
221      IconsInfo::GetIconResource(extension,
222                                 kIconSizeMedium * 2,
223                                 ExtensionIconSet::MATCH_EXACTLY).empty());
224}
225
226TEST_F(BookmarkAppHelperExtensionServiceTest, CreateAndUpdateBookmarkApp) {
227  EXPECT_EQ(0u, registry()->enabled_extensions().size());
228  WebApplicationInfo web_app_info;
229  web_app_info.app_url = GURL(kAppUrl);
230  web_app_info.title = base::UTF8ToUTF16(kAppTitle);
231  web_app_info.description = base::UTF8ToUTF16(kAppDescription);
232  web_app_info.icons.push_back(
233      CreateIconInfoWithBitmap(kIconSizeSmall, SK_ColorRED));
234
235  extensions::CreateOrUpdateBookmarkApp(service_, web_app_info);
236  base::RunLoop().RunUntilIdle();
237
238  {
239    EXPECT_EQ(1u, registry()->enabled_extensions().size());
240    const Extension* extension = service_->extensions()->begin()->get();
241    EXPECT_TRUE(extension->from_bookmark());
242    EXPECT_EQ(kAppTitle, extension->name());
243    EXPECT_EQ(kAppDescription, extension->description());
244    EXPECT_EQ(GURL(kAppUrl), AppLaunchInfo::GetLaunchWebURL(extension));
245    EXPECT_FALSE(extensions::IconsInfo::GetIconResource(
246                     extension, kIconSizeSmall, ExtensionIconSet::MATCH_EXACTLY)
247                     .empty());
248  }
249
250  web_app_info.title = base::UTF8ToUTF16(kAlternativeAppTitle);
251  web_app_info.icons[0] = CreateIconInfoWithBitmap(kIconSizeLarge, SK_ColorRED);
252
253  extensions::CreateOrUpdateBookmarkApp(service_, web_app_info);
254  base::RunLoop().RunUntilIdle();
255
256  {
257    EXPECT_EQ(1u, registry()->enabled_extensions().size());
258    const Extension* extension = service_->extensions()->begin()->get();
259    EXPECT_TRUE(extension->from_bookmark());
260    EXPECT_EQ(kAlternativeAppTitle, extension->name());
261    EXPECT_EQ(kAppDescription, extension->description());
262    EXPECT_EQ(GURL(kAppUrl), AppLaunchInfo::GetLaunchWebURL(extension));
263    EXPECT_TRUE(extensions::IconsInfo::GetIconResource(
264                    extension, kIconSizeSmall, ExtensionIconSet::MATCH_EXACTLY)
265                    .empty());
266    EXPECT_FALSE(extensions::IconsInfo::GetIconResource(
267                     extension, kIconSizeLarge, ExtensionIconSet::MATCH_EXACTLY)
268                     .empty());
269  }
270}
271
272TEST_F(BookmarkAppHelperExtensionServiceTest, GetWebApplicationInfo) {
273  WebApplicationInfo web_app_info;
274  web_app_info.app_url = GURL(kAppUrl);
275  web_app_info.title = base::UTF8ToUTF16(kAppTitle);
276  web_app_info.description = base::UTF8ToUTF16(kAppDescription);
277
278  web_app_info.icons.push_back(
279      CreateIconInfoWithBitmap(kIconSizeSmall, SK_ColorRED));
280  web_app_info.icons.push_back(
281      CreateIconInfoWithBitmap(kIconSizeLarge, SK_ColorRED));
282
283  extensions::CreateOrUpdateBookmarkApp(service_, web_app_info);
284  base::RunLoop().RunUntilIdle();
285
286  EXPECT_EQ(1u, registry()->enabled_extensions().size());
287  base::RunLoop run_loop;
288  extensions::GetWebApplicationInfoFromApp(
289      profile_.get(),
290      service_->extensions()->begin()->get(),
291      base::Bind(
292          &ValidateWebApplicationInfo, run_loop.QuitClosure(), web_app_info));
293  run_loop.Run();
294}
295#endif
296
297TEST_F(BookmarkAppHelperTest, ConstrainBitmapsToSizes) {
298  std::set<int> desired_sizes;
299  desired_sizes.insert(16);
300  desired_sizes.insert(32);
301  desired_sizes.insert(128);
302  desired_sizes.insert(256);
303
304  {
305    std::vector<SkBitmap> bitmaps;
306    bitmaps.push_back(CreateSquareBitmapWithColor(16, SK_ColorRED));
307    bitmaps.push_back(CreateSquareBitmapWithColor(32, SK_ColorGREEN));
308    bitmaps.push_back(CreateSquareBitmapWithColor(48, SK_ColorBLUE));
309    bitmaps.push_back(CreateSquareBitmapWithColor(144, SK_ColorYELLOW));
310
311    std::map<int, SkBitmap> results(
312        BookmarkAppHelper::ConstrainBitmapsToSizes(bitmaps, desired_sizes));
313
314    EXPECT_EQ(3u, results.size());
315    ValidateBitmapSizeAndColor(results[16], 16, SK_ColorRED);
316    ValidateBitmapSizeAndColor(results[32], 32, SK_ColorGREEN);
317    ValidateBitmapSizeAndColor(results[128], 128, SK_ColorYELLOW);
318  }
319  {
320    std::vector<SkBitmap> bitmaps;
321    bitmaps.push_back(CreateSquareBitmapWithColor(512, SK_ColorRED));
322    bitmaps.push_back(CreateSquareBitmapWithColor(18, SK_ColorGREEN));
323    bitmaps.push_back(CreateSquareBitmapWithColor(33, SK_ColorBLUE));
324    bitmaps.push_back(CreateSquareBitmapWithColor(17, SK_ColorYELLOW));
325
326    std::map<int, SkBitmap> results(
327        BookmarkAppHelper::ConstrainBitmapsToSizes(bitmaps, desired_sizes));
328
329    EXPECT_EQ(3u, results.size());
330    ValidateBitmapSizeAndColor(results[16], 16, SK_ColorYELLOW);
331    ValidateBitmapSizeAndColor(results[32], 32, SK_ColorBLUE);
332    ValidateBitmapSizeAndColor(results[256], 256, SK_ColorRED);
333  }
334}
335
336TEST_F(BookmarkAppHelperTest, IsValidBookmarkAppUrl) {
337  EXPECT_TRUE(IsValidBookmarkAppUrl(GURL("https://www.chromium.org")));
338  EXPECT_TRUE(IsValidBookmarkAppUrl(GURL("http://www.chromium.org/path")));
339  EXPECT_FALSE(IsValidBookmarkAppUrl(GURL("ftp://www.chromium.org")));
340  EXPECT_FALSE(IsValidBookmarkAppUrl(GURL("chrome://flags")));
341}
342
343}  // namespace extensions
344