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/convert_web_app.h"
6
7 #include <stddef.h>
8
9 #include <map>
10 #include <memory>
11 #include <string>
12 #include <utility>
13 #include <vector>
14
15 #include "base/files/file_path.h"
16 #include "base/files/file_util.h"
17 #include "base/files/scoped_temp_dir.h"
18 #include "base/path_service.h"
19 #include "base/run_loop.h"
20 #include "base/stl_util.h"
21 #include "base/strings/string16.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/test/scoped_feature_list.h"
25 #include "base/time/time.h"
26 #include "base/version.h"
27 #include "chrome/browser/extensions/extension_service.h"
28 #include "chrome/browser/extensions/extension_service_test_base.h"
29 #include "chrome/browser/web_applications/components/web_application_info.h"
30 #include "chrome/common/chrome_features.h"
31 #include "chrome/common/chrome_paths.h"
32 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
33 #include "chrome/common/extensions/manifest_handlers/linked_app_icons.h"
34 #include "components/services/app_service/public/cpp/file_handler.h"
35 #include "components/services/app_service/public/cpp/file_handler_info.h"
36 #include "extensions/browser/extension_system.h"
37 #include "extensions/common/extension.h"
38 #include "extensions/common/extension_icon_set.h"
39 #include "extensions/common/extension_resource.h"
40 #include "extensions/common/manifest_constants.h"
41 #include "extensions/common/manifest_handlers/file_handler_info.h"
42 #include "extensions/common/manifest_handlers/icons_handler.h"
43 #include "extensions/common/manifest_handlers/web_app_file_handler.h"
44 #include "extensions/common/manifest_handlers/web_app_linked_shortcut_items.h"
45 #include "extensions/common/manifest_handlers/web_app_shortcut_icons_handler.h"
46 #include "extensions/common/permissions/permission_set.h"
47 #include "extensions/common/permissions/permissions_data.h"
48 #include "extensions/common/url_pattern.h"
49 #include "testing/gmock/include/gmock/gmock-matchers.h"
50 #include "testing/gtest/include/gtest/gtest.h"
51 #include "ui/gfx/codec/png_codec.h"
52 #include "url/gurl.h"
53
54 namespace extensions {
55
56 namespace keys = manifest_keys;
57
58 namespace {
59
60 // Returns an icon bitmap corresponding to a canned icon size.
GetIconBitmap(int size)61 SkBitmap GetIconBitmap(int size) {
62 SkBitmap result;
63
64 base::FilePath icon_file;
65 if (!base::PathService::Get(chrome::DIR_TEST_DATA, &icon_file)) {
66 ADD_FAILURE() << "Could not get test data directory.";
67 return result;
68 }
69
70 icon_file = icon_file.AppendASCII("extensions")
71 .AppendASCII("convert_web_app")
72 .AppendASCII(base::StringPrintf("%i.png", size));
73
74 std::string icon_data;
75 if (!base::ReadFileToString(icon_file, &icon_data)) {
76 ADD_FAILURE() << "Could not read test icon.";
77 return result;
78 }
79
80 if (!gfx::PNGCodec::Decode(
81 reinterpret_cast<const unsigned char*>(icon_data.c_str()),
82 icon_data.size(), &result)) {
83 ADD_FAILURE() << "Could not decode test icon.";
84 return result;
85 }
86
87 return result;
88 }
89
GetTestTime(int year,int month,int day,int hour,int minute,int second,int millisecond)90 base::Time GetTestTime(int year, int month, int day, int hour, int minute,
91 int second, int millisecond) {
92 base::Time::Exploded exploded = {0};
93 exploded.year = year;
94 exploded.month = month;
95 exploded.day_of_month = day;
96 exploded.hour = hour;
97 exploded.minute = minute;
98 exploded.second = second;
99 exploded.millisecond = millisecond;
100 base::Time out_time;
101 EXPECT_TRUE(base::Time::FromUTCExploded(exploded, &out_time));
102 return out_time;
103 }
104
105 } // namespace
106
107 class ExtensionFromWebApp : public extensions::ExtensionServiceTestBase {
108 public:
SetUp()109 void SetUp() override {
110 extensions::ExtensionServiceTestBase::SetUp();
111 ASSERT_TRUE(extensions_dir_.CreateUniqueTempDir());
112 }
113
StartExtensionService()114 void StartExtensionService() {
115 InitializeEmptyExtensionService();
116 service()->Init();
117 base::RunLoop().RunUntilIdle();
118 ASSERT_TRUE(ExtensionSystem::Get(service()->profile())->is_ready());
119 }
120
ExtensionPath() const121 const base::FilePath& ExtensionPath() const {
122 return extensions_dir_.GetPath();
123 }
124
125 private:
126 base::ScopedTempDir extensions_dir_;
127 };
128
129 class ExtensionFromWebAppWithShortcutsMenu : public ExtensionFromWebApp {
130 public:
ExtensionFromWebAppWithShortcutsMenu()131 ExtensionFromWebAppWithShortcutsMenu() {
132 scoped_feature_list.InitAndEnableFeature(
133 features::kDesktopPWAsAppIconShortcutsMenu);
134 }
135
136 private:
137 base::test::ScopedFeatureList scoped_feature_list;
138 };
139
TEST_F(ExtensionFromWebApp,GetScopeURLFromBookmarkApp)140 TEST_F(ExtensionFromWebApp, GetScopeURLFromBookmarkApp) {
141 StartExtensionService();
142 base::DictionaryValue manifest;
143 manifest.SetString(keys::kName, "Test App");
144 manifest.SetString(keys::kVersion, "0");
145 manifest.SetString(keys::kLaunchWebURL, "http://aaronboodman.com/gearpad/");
146
147 // Create a "url_handlers" dictionary with one URL handler generated from
148 // the scope.
149 // {
150 // "scope": {
151 // "matches": [ "http://aaronboodman.com/gearpad/*" ],
152 // "title": "Test App"
153 // },
154 // }
155 GURL scope_url = GURL("http://aaronboodman.com/gearpad/");
156 manifest.SetDictionary(keys::kUrlHandlers,
157 CreateURLHandlersForBookmarkApp(
158 scope_url, base::ASCIIToUTF16("Test App")));
159
160 std::string error;
161 scoped_refptr<Extension> bookmark_app =
162 Extension::Create(ExtensionPath(), Manifest::INTERNAL, manifest,
163 Extension::FROM_BOOKMARK, &error);
164 ASSERT_TRUE(bookmark_app.get());
165
166 EXPECT_EQ(scope_url, GetScopeURLFromBookmarkApp(bookmark_app.get()));
167 }
168
TEST_F(ExtensionFromWebApp,GetScopeURLFromBookmarkApp_NoURLHandlers)169 TEST_F(ExtensionFromWebApp, GetScopeURLFromBookmarkApp_NoURLHandlers) {
170 StartExtensionService();
171 base::DictionaryValue manifest;
172 manifest.SetString(keys::kName, "Test App");
173 manifest.SetString(keys::kVersion, "0");
174 manifest.SetString(keys::kLaunchWebURL, "http://aaronboodman.com/gearpad/");
175 manifest.SetDictionary(keys::kUrlHandlers,
176 std::make_unique<base::DictionaryValue>());
177
178 std::string error;
179 scoped_refptr<Extension> bookmark_app =
180 Extension::Create(ExtensionPath(), Manifest::INTERNAL, manifest,
181 Extension::FROM_BOOKMARK, &error);
182 ASSERT_TRUE(bookmark_app.get());
183
184 EXPECT_EQ(GURL(), GetScopeURLFromBookmarkApp(bookmark_app.get()));
185 }
186
TEST_F(ExtensionFromWebApp,GetScopeURLFromBookmarkApp_WrongURLHandler)187 TEST_F(ExtensionFromWebApp, GetScopeURLFromBookmarkApp_WrongURLHandler) {
188 StartExtensionService();
189 base::DictionaryValue manifest;
190 manifest.SetString(keys::kName, "Test App");
191 manifest.SetString(keys::kVersion, "0");
192 manifest.SetString(keys::kLaunchWebURL, "http://aaronboodman.com/gearpad/");
193
194 // Create a "url_handlers" dictionary with one URL handler not generated
195 // from the scope.
196 // {
197 // "test_url_handler": {
198 // "matches": [ "http://*.aaronboodman.com/" ],
199 // "title": "test handler"
200 // }
201 // }
202 auto test_matches = std::make_unique<base::ListValue>();
203 test_matches->AppendString("http://*.aaronboodman.com/");
204
205 auto test_handler = std::make_unique<base::DictionaryValue>();
206 test_handler->SetList(keys::kMatches, std::move(test_matches));
207 test_handler->SetString(keys::kUrlHandlerTitle, "test handler");
208
209 auto url_handlers = std::make_unique<base::DictionaryValue>();
210 url_handlers->SetDictionary("test_url_handler", std::move(test_handler));
211 manifest.SetDictionary(keys::kUrlHandlers, std::move(url_handlers));
212
213 std::string error;
214 scoped_refptr<Extension> bookmark_app =
215 Extension::Create(ExtensionPath(), Manifest::INTERNAL, manifest,
216 Extension::FROM_BOOKMARK, &error);
217 ASSERT_TRUE(bookmark_app.get());
218
219 EXPECT_EQ(GURL(), GetScopeURLFromBookmarkApp(bookmark_app.get()));
220 }
221
TEST_F(ExtensionFromWebApp,GetScopeURLFromBookmarkApp_ExtraURLHandler)222 TEST_F(ExtensionFromWebApp, GetScopeURLFromBookmarkApp_ExtraURLHandler) {
223 StartExtensionService();
224 base::DictionaryValue manifest;
225 manifest.SetString(keys::kName, "Test App");
226 manifest.SetString(keys::kVersion, "0");
227 manifest.SetString(keys::kLaunchWebURL, "http://aaronboodman.com/gearpad/");
228
229 // Create a "url_handlers" dictionary with two URL handlers. One for
230 // the scope and and extra one for testing.
231 // {
232 // "scope": {
233 // "matches": [ "http://aaronboodman.com/gearpad/*" ],
234 // "title": "Test App"
235 // },
236 // "test_url_handler": {
237 // "matches": [ "http://*.aaronboodman.com/" ],
238 // "title": "test handler"
239 // }
240 // }
241 GURL scope_url = GURL("http://aaronboodman.com/gearpad/");
242 std::unique_ptr<base::DictionaryValue> url_handlers =
243 CreateURLHandlersForBookmarkApp(scope_url,
244 base::ASCIIToUTF16("Test App"));
245
246 auto test_matches = std::make_unique<base::ListValue>();
247 test_matches->AppendString("http://*.aaronboodman.com/");
248
249 auto test_handler = std::make_unique<base::DictionaryValue>();
250 test_handler->SetList(keys::kMatches, std::move(test_matches));
251 test_handler->SetString(keys::kUrlHandlerTitle, "test handler");
252
253 url_handlers->SetDictionary("test_url_handler", std::move(test_handler));
254 manifest.SetDictionary(keys::kUrlHandlers, std::move(url_handlers));
255
256 std::string error;
257 scoped_refptr<Extension> bookmark_app =
258 Extension::Create(ExtensionPath(), Manifest::INTERNAL, manifest,
259 Extension::FROM_BOOKMARK, &error);
260 ASSERT_TRUE(bookmark_app.get());
261
262 // Check that we can retrieve the scope even if there is an extra
263 // url handler.
264 EXPECT_EQ(scope_url, GetScopeURLFromBookmarkApp(bookmark_app.get()));
265 }
266
TEST_F(ExtensionFromWebApp,GenerateVersion)267 TEST_F(ExtensionFromWebApp, GenerateVersion) {
268 StartExtensionService();
269 EXPECT_EQ("2010.1.1.0",
270 ConvertTimeToExtensionVersion(
271 GetTestTime(2010, 1, 1, 0, 0, 0, 0)));
272 EXPECT_EQ("2010.12.31.22111",
273 ConvertTimeToExtensionVersion(
274 GetTestTime(2010, 12, 31, 8, 5, 50, 500)));
275 EXPECT_EQ("2010.10.1.65535",
276 ConvertTimeToExtensionVersion(
277 GetTestTime(2010, 10, 1, 23, 59, 59, 999)));
278 }
279
TEST_F(ExtensionFromWebApp,Basic)280 TEST_F(ExtensionFromWebApp, Basic) {
281 StartExtensionService();
282 WebApplicationInfo web_app;
283 web_app.title = base::ASCIIToUTF16("Gearpad");
284 web_app.description =
285 base::ASCIIToUTF16("The best text editor in the universe!");
286 web_app.start_url = GURL("http://aaronboodman.com/gearpad/");
287 web_app.scope = GURL("http://aaronboodman.com/gearpad/");
288
289 const int sizes[] = {16, 48, 128};
290 for (size_t i = 0; i < base::size(sizes); ++i) {
291 WebApplicationIconInfo icon_info;
292 icon_info.url =
293 web_app.start_url.Resolve(base::StringPrintf("%i.png", sizes[i]));
294 icon_info.square_size_px = sizes[i];
295 web_app.icon_infos.push_back(std::move(icon_info));
296 web_app.icon_bitmaps_any[sizes[i]] = GetIconBitmap(sizes[i]);
297 }
298
299 scoped_refptr<Extension> extension = ConvertWebAppToExtension(
300 web_app, GetTestTime(1978, 12, 11, 0, 0, 0, 0), ExtensionPath(),
301 Extension::NO_FLAGS, Manifest::INTERNAL);
302 ASSERT_TRUE(extension.get());
303
304 base::ScopedTempDir extension_dir;
305 EXPECT_TRUE(extension_dir.Set(extension->path()));
306
307 EXPECT_TRUE(extension->is_app());
308 EXPECT_TRUE(extension->is_hosted_app());
309 EXPECT_TRUE(extension->from_bookmark());
310 EXPECT_FALSE(extension->is_legacy_packaged_app());
311
312 EXPECT_FALSE(extension->was_installed_by_default());
313 EXPECT_FALSE(extension->was_installed_by_oem());
314 EXPECT_FALSE(extension->from_webstore());
315 EXPECT_EQ(Manifest::INTERNAL, extension->location());
316
317 EXPECT_EQ("zVvdNZy3Mp7CFU8JVSyXNlDuHdVLbP7fDO3TGVzj/0w=",
318 extension->public_key());
319 EXPECT_EQ("oplhagaaipaimkjlbekcdjkffijdockj", extension->id());
320 EXPECT_EQ("1978.12.11.0", extension->version().GetString());
321 EXPECT_EQ(base::UTF16ToUTF8(web_app.title), extension->name());
322 EXPECT_EQ(base::UTF16ToUTF8(web_app.description), extension->description());
323 EXPECT_EQ(web_app.start_url,
324 AppLaunchInfo::GetFullLaunchURL(extension.get()));
325 EXPECT_EQ(web_app.scope, GetScopeURLFromBookmarkApp(extension.get()));
326 EXPECT_EQ(0u,
327 extension->permissions_data()->active_permissions().apis().size());
328 ASSERT_EQ(0u, extension->web_extent().patterns().size());
329
330 const LinkedAppIcons& linked_icons =
331 LinkedAppIcons::GetLinkedAppIcons(extension.get());
332 EXPECT_EQ(web_app.icon_infos.size(), linked_icons.icons.size());
333 for (size_t i = 0; i < web_app.icon_infos.size(); ++i) {
334 EXPECT_EQ(web_app.icon_infos[i].url, linked_icons.icons[i].url);
335 EXPECT_EQ(web_app.icon_infos[i].square_size_px, linked_icons.icons[i].size);
336 }
337
338 EXPECT_EQ(web_app.icon_bitmaps_any.size(),
339 IconsInfo::GetIcons(extension.get()).map().size());
340 for (const std::pair<const SquareSizePx, SkBitmap>& icon :
341 web_app.icon_bitmaps_any) {
342 int size = icon.first;
343 EXPECT_EQ(base::StringPrintf("icons/%i.png", size),
344 IconsInfo::GetIcons(extension.get())
345 .Get(size, ExtensionIconSet::MATCH_EXACTLY));
346 ExtensionResource resource = IconsInfo::GetIconResource(
347 extension.get(), size, ExtensionIconSet::MATCH_EXACTLY);
348 ASSERT_TRUE(!resource.empty());
349 EXPECT_TRUE(base::PathExists(resource.GetFilePath()));
350 }
351 }
352
TEST_F(ExtensionFromWebApp,Minimal)353 TEST_F(ExtensionFromWebApp, Minimal) {
354 StartExtensionService();
355 WebApplicationInfo web_app;
356 web_app.title = base::ASCIIToUTF16("Gearpad");
357 web_app.start_url = GURL("http://aaronboodman.com/gearpad/");
358
359 scoped_refptr<Extension> extension = ConvertWebAppToExtension(
360 web_app, GetTestTime(1978, 12, 11, 0, 0, 0, 0), ExtensionPath(),
361 Extension::NO_FLAGS, Manifest::INTERNAL);
362 ASSERT_TRUE(extension.get());
363
364 base::ScopedTempDir extension_dir;
365 EXPECT_TRUE(extension_dir.Set(extension->path()));
366
367 EXPECT_TRUE(extension->is_app());
368 EXPECT_TRUE(extension->is_hosted_app());
369 EXPECT_TRUE(extension->from_bookmark());
370 EXPECT_FALSE(extension->is_legacy_packaged_app());
371
372 EXPECT_FALSE(extension->was_installed_by_default());
373 EXPECT_FALSE(extension->was_installed_by_oem());
374 EXPECT_FALSE(extension->from_webstore());
375 EXPECT_EQ(Manifest::INTERNAL, extension->location());
376
377 EXPECT_EQ("zVvdNZy3Mp7CFU8JVSyXNlDuHdVLbP7fDO3TGVzj/0w=",
378 extension->public_key());
379 EXPECT_EQ("oplhagaaipaimkjlbekcdjkffijdockj", extension->id());
380 EXPECT_EQ("1978.12.11.0", extension->version().GetString());
381 EXPECT_EQ(base::UTF16ToUTF8(web_app.title), extension->name());
382 EXPECT_EQ("", extension->description());
383 EXPECT_EQ(web_app.start_url,
384 AppLaunchInfo::GetFullLaunchURL(extension.get()));
385 EXPECT_TRUE(GetScopeURLFromBookmarkApp(extension.get()).is_empty());
386 EXPECT_EQ(0u, IconsInfo::GetIcons(extension.get()).map().size());
387 EXPECT_EQ(0u,
388 extension->permissions_data()->active_permissions().apis().size());
389 ASSERT_EQ(0u, extension->web_extent().patterns().size());
390 }
391
TEST_F(ExtensionFromWebApp,ExtraInstallationFlags)392 TEST_F(ExtensionFromWebApp, ExtraInstallationFlags) {
393 StartExtensionService();
394 WebApplicationInfo web_app;
395 web_app.title = base::ASCIIToUTF16("Gearpad");
396 web_app.start_url = GURL("http://aaronboodman.com/gearpad/");
397
398 scoped_refptr<Extension> extension = ConvertWebAppToExtension(
399 web_app, GetTestTime(1978, 12, 11, 0, 0, 0, 0), ExtensionPath(),
400 Extension::FROM_WEBSTORE | Extension::WAS_INSTALLED_BY_OEM,
401 Manifest::INTERNAL);
402 ASSERT_TRUE(extension.get());
403
404 EXPECT_TRUE(extension->is_app());
405 EXPECT_TRUE(extension->is_hosted_app());
406 EXPECT_TRUE(extension->from_bookmark());
407 EXPECT_FALSE(extension->is_legacy_packaged_app());
408
409 EXPECT_TRUE(extension->was_installed_by_oem());
410 EXPECT_TRUE(extension->from_webstore());
411 EXPECT_FALSE(extension->was_installed_by_default());
412 EXPECT_EQ(Manifest::INTERNAL, extension->location());
413 }
414
TEST_F(ExtensionFromWebApp,ExternalPolicyLocation)415 TEST_F(ExtensionFromWebApp, ExternalPolicyLocation) {
416 StartExtensionService();
417 WebApplicationInfo web_app;
418 web_app.title = base::ASCIIToUTF16("Gearpad");
419 web_app.start_url = GURL("http://aaronboodman.com/gearpad/");
420
421 scoped_refptr<Extension> extension = ConvertWebAppToExtension(
422 web_app, GetTestTime(1978, 12, 11, 0, 0, 0, 0), ExtensionPath(),
423 Extension::NO_FLAGS, Manifest::EXTERNAL_POLICY);
424 ASSERT_TRUE(extension.get());
425
426 EXPECT_TRUE(extension->is_app());
427 EXPECT_TRUE(extension->is_hosted_app());
428 EXPECT_TRUE(extension->from_bookmark());
429 EXPECT_FALSE(extension->is_legacy_packaged_app());
430
431 EXPECT_EQ(Manifest::EXTERNAL_POLICY, extension->location());
432 }
433
434 // Tests that a scope not ending in "/" works correctly.
435 // The tested behavior is unexpected but is working correctly according
436 // to the Web Manifest spec. https://github.com/w3c/manifest/issues/554
TEST_F(ExtensionFromWebApp,ScopeDoesNotEndInSlash)437 TEST_F(ExtensionFromWebApp, ScopeDoesNotEndInSlash) {
438 StartExtensionService();
439 WebApplicationInfo web_app;
440 web_app.title = base::ASCIIToUTF16("Gearpad");
441 web_app.description =
442 base::ASCIIToUTF16("The best text editor in the universe!");
443 web_app.start_url = GURL("http://aaronboodman.com/gearpad/");
444 web_app.scope = GURL("http://aaronboodman.com/gear");
445
446 scoped_refptr<Extension> extension = ConvertWebAppToExtension(
447 web_app, GetTestTime(1978, 12, 11, 0, 0, 0, 0), ExtensionPath(),
448 Extension::NO_FLAGS, Manifest::INTERNAL);
449 ASSERT_TRUE(extension.get());
450 EXPECT_EQ(web_app.scope, GetScopeURLFromBookmarkApp(extension.get()));
451 }
452
453 // Tests that |file_handler| on the WebAppManifest is correctly converted
454 // to |file_handlers| on an extension manifest.
TEST_F(ExtensionFromWebApp,FileHandlersAreCorrectlyConverted)455 TEST_F(ExtensionFromWebApp, FileHandlersAreCorrectlyConverted) {
456 StartExtensionService();
457 WebApplicationInfo web_app;
458 web_app.title = base::ASCIIToUTF16("Graphr");
459 web_app.description = base::ASCIIToUTF16("A magical graphy thing");
460 web_app.start_url = GURL("https://graphr.n/");
461 web_app.scope = GURL("https://graphr.n/");
462
463 {
464 blink::Manifest::FileHandler graph;
465 graph.action = GURL("https://graphr.n/open-graph/");
466 graph.name = base::ASCIIToUTF16("Graph");
467 graph.accept[base::ASCIIToUTF16("text/svg+xml")].push_back(
468 base::ASCIIToUTF16(""));
469 graph.accept[base::ASCIIToUTF16("text/svg+xml")].push_back(
470 base::ASCIIToUTF16(".svg"));
471 web_app.file_handlers.push_back(graph);
472
473 blink::Manifest::FileHandler raw;
474 raw.action = GURL("https://graphr.n/open-raw/");
475 raw.name = base::ASCIIToUTF16("Raw");
476 raw.accept[base::ASCIIToUTF16("text/csv")].push_back(
477 base::ASCIIToUTF16(".csv"));
478 web_app.file_handlers.push_back(raw);
479 }
480
481 scoped_refptr<Extension> extension = ConvertWebAppToExtension(
482 web_app, GetTestTime(1978, 12, 11, 0, 0, 0, 0), ExtensionPath(),
483 Extension::NO_FLAGS, Manifest::INTERNAL);
484
485 ASSERT_TRUE(extension.get());
486
487 const std::vector<apps::FileHandlerInfo>* file_handler_infos =
488 extensions::FileHandlers::GetFileHandlers(extension.get());
489
490 ASSERT_TRUE(file_handler_infos);
491 EXPECT_EQ(2u, file_handler_infos->size());
492
493 {
494 const apps::FileHandlerInfo& info = file_handler_infos->at(0);
495 EXPECT_EQ("https://graphr.n/open-graph/", info.id);
496 EXPECT_FALSE(info.include_directories);
497 EXPECT_EQ(apps::file_handler_verbs::kOpenWith, info.verb);
498 // Extensions should contain SVG, and only SVG
499 EXPECT_THAT(info.extensions, testing::UnorderedElementsAre("svg"));
500 // Mime types should contain text/svg+xml and only text/svg+xml
501 EXPECT_THAT(info.types, testing::UnorderedElementsAre("text/svg+xml"));
502 }
503 {
504 const apps::FileHandlerInfo& info = file_handler_infos->at(1);
505 EXPECT_EQ("https://graphr.n/open-raw/", info.id);
506 EXPECT_FALSE(info.include_directories);
507 EXPECT_EQ(apps::file_handler_verbs::kOpenWith, info.verb);
508 // Extensions should contain csv, and only csv
509 EXPECT_THAT(info.extensions, testing::UnorderedElementsAre("csv"));
510 // Mime types should contain text/csv and only text/csv
511 EXPECT_THAT(info.types, testing::UnorderedElementsAre("text/csv"));
512 }
513 }
514
515 // Tests that |file_handler| on the WebAppManifest is correctly converted
516 // to |web_app_file_handlers| on an extension manifest.
TEST_F(ExtensionFromWebApp,WebAppFileHandlersAreCorrectlyConverted)517 TEST_F(ExtensionFromWebApp, WebAppFileHandlersAreCorrectlyConverted) {
518 StartExtensionService();
519 WebApplicationInfo web_app;
520 web_app.title = base::ASCIIToUTF16("Graphr");
521 web_app.description = base::ASCIIToUTF16("A magical graphy thing.");
522 web_app.start_url = GURL("https://graphr.n/");
523 web_app.scope = GURL("https://graphr.n");
524
525 {
526 blink::Manifest::FileHandler file_handler;
527 file_handler.action = GURL("https://graphr.n/open-graph/");
528 file_handler.name = base::ASCIIToUTF16("Graph");
529 file_handler.accept[base::ASCIIToUTF16("text/svg+xml")].push_back(
530 base::ASCIIToUTF16(""));
531 file_handler.accept[base::ASCIIToUTF16("text/svg+xml")].push_back(
532 base::ASCIIToUTF16(".svg"));
533 web_app.file_handlers.push_back(file_handler);
534 }
535 {
536 blink::Manifest::FileHandler file_handler;
537 file_handler.action = GURL("https://graphr.n/open-raw/");
538 file_handler.name = base::ASCIIToUTF16("Raw");
539 file_handler.accept[base::ASCIIToUTF16("text/csv")].push_back(
540 base::ASCIIToUTF16(".csv"));
541 web_app.file_handlers.push_back(file_handler);
542 }
543
544 scoped_refptr<Extension> extension = ConvertWebAppToExtension(
545 web_app, GetTestTime(1978, 12, 11, 0, 0, 0, 0), ExtensionPath(),
546 Extension::NO_FLAGS, Manifest::INTERNAL);
547
548 ASSERT_TRUE(extension.get());
549
550 const apps::FileHandlers* file_handlers =
551 extensions::WebAppFileHandlers::GetWebAppFileHandlers(extension.get());
552
553 ASSERT_TRUE(file_handlers);
554 EXPECT_EQ(2u, file_handlers->size());
555
556 {
557 const apps::FileHandler& file_handler = file_handlers->at(0);
558 EXPECT_EQ("https://graphr.n/open-graph/", file_handler.action);
559 EXPECT_EQ(1u, file_handler.accept.size());
560 EXPECT_EQ("text/svg+xml", file_handler.accept[0].mime_type);
561 EXPECT_THAT(file_handler.accept[0].file_extensions,
562 testing::UnorderedElementsAre(".svg"));
563 }
564 {
565 const apps::FileHandler& file_handler = file_handlers->at(1);
566 EXPECT_EQ("https://graphr.n/open-raw/", file_handler.action);
567 EXPECT_EQ(1u, file_handler.accept.size());
568 EXPECT_EQ("text/csv", file_handler.accept[0].mime_type);
569 EXPECT_THAT(file_handler.accept[0].file_extensions,
570 testing::UnorderedElementsAre(".csv"));
571 }
572 }
573
574 // Tests that |shortcuts_menu_item_infos| on the WebAppManifest is correctly
575 // converted to |web_app_shortcut_icons| and |web_app_linked_shortcut_items| on
576 // an extension manifest.
TEST_F(ExtensionFromWebAppWithShortcutsMenu,WebAppShortcutIconsAreCorrectlyConverted)577 TEST_F(ExtensionFromWebAppWithShortcutsMenu,
578 WebAppShortcutIconsAreCorrectlyConverted) {
579 StartExtensionService();
580 WebApplicationInfo web_app;
581 WebApplicationShortcutsMenuItemInfo shortcut_item;
582 std::map<SquareSizePx, SkBitmap> shortcut_icon_bitmaps;
583 web_app.title = base::ASCIIToUTF16("Shortcut App");
584 web_app.description = base::ASCIIToUTF16("We have shortcuts.");
585 web_app.start_url = GURL("https://shortcut-app.io/");
586 web_app.scope = GURL("https://shortcut-app.io");
587
588 shortcut_item.name = base::ASCIIToUTF16("Shortcut 1");
589 shortcut_item.url = GURL("https://shortcut-app.io/shortcuts/shortcut1");
590 {
591 const int sizes[] = {16, 128};
592 for (const auto& size : sizes) {
593 WebApplicationShortcutsMenuItemInfo::Icon icon_info;
594 icon_info.url = web_app.start_url.Resolve(
595 base::StringPrintf("shortcut1/%i.png", size));
596 icon_info.square_size_px = size;
597 shortcut_item.shortcut_icon_infos.push_back(std::move(icon_info));
598 shortcut_icon_bitmaps[size] = GetIconBitmap(size);
599 }
600 web_app.shortcuts_menu_icons_bitmaps.emplace_back(
601 std::move(shortcut_icon_bitmaps));
602 }
603 web_app.shortcuts_menu_item_infos.push_back(std::move(shortcut_item));
604
605 shortcut_item.name = base::ASCIIToUTF16("Shortcut 2");
606 shortcut_item.url = GURL("https://shortcut-app.io/shortcuts/shortcut2");
607 {
608 const int sizes[] = {16, 48};
609 for (const auto& size : sizes) {
610 WebApplicationShortcutsMenuItemInfo::Icon icon_info;
611 icon_info.url =
612 web_app.start_url.Resolve(base::StringPrintf("0/%i.png", size));
613 icon_info.square_size_px = size;
614 shortcut_item.shortcut_icon_infos.push_back(std::move(icon_info));
615 shortcut_icon_bitmaps[size] = GetIconBitmap(size);
616 }
617 web_app.shortcuts_menu_icons_bitmaps.emplace_back(
618 std::move(shortcut_icon_bitmaps));
619 }
620 web_app.shortcuts_menu_item_infos.push_back(std::move(shortcut_item));
621
622 scoped_refptr<Extension> extension = ConvertWebAppToExtension(
623 web_app, GetTestTime(1978, 12, 11, 0, 0, 0, 0), ExtensionPath(),
624 Extension::FROM_BOOKMARK, Manifest::INTERNAL);
625
626 ASSERT_TRUE(extension.get());
627
628 const WebAppLinkedShortcutItems& linked_shortcut_items =
629 WebAppLinkedShortcutItems::GetWebAppLinkedShortcutItems(extension.get());
630 const std::map<int, ExtensionIconSet>& shortcut_icons =
631 WebAppShortcutIconsInfo::GetShortcutIcons(extension.get());
632 for (size_t i = 0; i < web_app.shortcuts_menu_item_infos.size(); ++i) {
633 const std::vector<WebApplicationShortcutsMenuItemInfo::Icon>& icon_infos =
634 web_app.shortcuts_menu_item_infos[i].shortcut_icon_infos;
635 const std::vector<WebAppLinkedShortcutItems::ShortcutItemInfo::IconInfo>&
636 linked_shortcut_icons_info =
637 linked_shortcut_items.shortcut_item_infos[i]
638 .shortcut_item_icon_infos;
639 ASSERT_EQ(icon_infos.size(), linked_shortcut_icons_info.size());
640 for (size_t j = 0; j < icon_infos.size(); ++j) {
641 EXPECT_EQ(linked_shortcut_icons_info[j].url, icon_infos[j].url);
642 EXPECT_EQ(linked_shortcut_icons_info[j].size,
643 icon_infos[j].square_size_px);
644 }
645
646 const std::map<SquareSizePx, SkBitmap>& icon_bitmaps =
647 web_app.shortcuts_menu_icons_bitmaps[i];
648 EXPECT_EQ(icon_bitmaps.size(), shortcut_icons.at(i).map().size());
649 for (const std::pair<const SquareSizePx, SkBitmap>& icon : icon_bitmaps) {
650 int size = icon.first;
651 EXPECT_EQ(
652 base::StringPrintf("shortcut_icons/%i/%i.png", static_cast<int>(i),
653 size),
654 shortcut_icons.at(i).Get(size, ExtensionIconSet::MATCH_EXACTLY));
655
656 ExtensionResource resource = WebAppShortcutIconsInfo::GetIconResource(
657 extension.get(), i, size, ExtensionIconSet::MATCH_EXACTLY);
658 EXPECT_TRUE(base::PathExists(resource.GetFilePath()));
659 ASSERT_TRUE(!resource.empty());
660 }
661 }
662 }
663
664 } // namespace extensions
665