1 // Copyright 2019 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/web_applications/manifest_update_manager.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "base/callback_helpers.h"
11 #include "base/memory/scoped_refptr.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/test/bind.h"
15 #include "base/test/metrics/histogram_tester.h"
16 #include "base/test/scoped_feature_list.h"
17 #include "base/time/time.h"
18 #include "chrome/app/chrome_command_ids.h"
19 #include "chrome/browser/installable/installable_metrics.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/browser_commands.h"
23 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
24 #include "chrome/browser/web_applications/components/app_icon_manager.h"
25 #include "chrome/browser/web_applications/components/app_registry_controller.h"
26 #include "chrome/browser/web_applications/components/app_shortcut_manager.h"
27 #include "chrome/browser/web_applications/components/install_finalizer.h"
28 #include "chrome/browser/web_applications/components/install_manager.h"
29 #include "chrome/browser/web_applications/components/os_integration_manager.h"
30 #include "chrome/browser/web_applications/components/pending_app_manager.h"
31 #include "chrome/browser/web_applications/components/web_app_constants.h"
32 #include "chrome/browser/web_applications/components/web_app_provider_base.h"
33 #include "chrome/browser/web_applications/extensions/bookmark_app_registrar.h"
34 #include "chrome/browser/web_applications/system_web_app_manager.h"
35 #include "chrome/browser/web_applications/test/test_system_web_app_installation.h"
36 #include "chrome/browser/web_applications/test/web_app_install_observer.h"
37 #include "chrome/browser/web_applications/test/web_app_test.h"
38 #include "chrome/browser/web_applications/web_app.h"
39 #include "chrome/browser/web_applications/web_app_registrar.h"
40 #include "chrome/common/chrome_features.h"
41 #include "chrome/test/base/in_process_browser_test.h"
42 #include "chrome/test/base/ui_test_utils.h"
43 #include "content/public/common/content_features.h"
44 #include "content/public/test/browser_test.h"
45 #include "content/public/test/url_loader_interceptor.h"
46 #include "extensions/browser/extension_registry.h"
47 #include "extensions/browser/test_extension_registry_observer.h"
48 #include "net/test/embedded_test_server/http_request.h"
49 #include "net/test/embedded_test_server/http_response.h"
50 #include "testing/gtest/include/gtest/gtest.h"
51 #include "third_party/blink/public/common/manifest/manifest.h"
52 #include "third_party/skia/include/core/SkColor.h"
53
54 namespace web_app {
55
56 namespace {
57
58 constexpr char kUpdateHistogramName[] = "Webapp.Update.ManifestUpdateResult";
59
60 constexpr char kInstallableIconList[] = R"(
61 [
62 {
63 "src": "launcher-icon-4x.png",
64 "sizes": "192x192",
65 "type": "image/png"
66 }
67 ]
68 )";
69 constexpr SkColor kInstallableIconTopLeftColor =
70 SkColorSetRGB(0x15, 0x96, 0xE0);
71
72 constexpr char kAnotherInstallableIconList[] = R"(
73 [
74 {
75 "src": "/banners/image-512px.png",
76 "sizes": "512x512",
77 "type": "image/png"
78 }
79 ]
80 )";
81
82 constexpr char kAnotherShortcutsItemName[] = "Timeline";
83 constexpr char kAnotherShortcutsItemUrl[] = "/shortcut";
84 constexpr char kAnotherShortcutsItemShortName[] = "H";
85 constexpr char kAnotherShortcutsItemDescription[] = "Navigate home";
86 constexpr char kAnotherIconSrc[] = "/launcher-icon-4x.png";
87 constexpr int kAnotherIconSize = 192;
88
89 constexpr char kShortcutsItem[] = R"(
90 [
91 {
92 "name": "Home",
93 "short_name": "HM",
94 "description": "Go home",
95 "url": ".",
96 "icons": [
97 {
98 "src": "/banners/image-512px.png",
99 "sizes": "512x512",
100 "type": "image/png"
101 }
102 ]
103 }
104 ]
105 )";
106
107 constexpr char kShortcutsItems[] = R"(
108 [
109 {
110 "name": "Home",
111 "short_name": "HM",
112 "description": "Go home",
113 "url": ".",
114 "icons": [
115 {
116 "src": "/banners/image-512px.png",
117 "sizes": "512x512",
118 "type": "image/png"
119 }
120 ]
121 },
122 {
123 "name": "Settings",
124 "short_name": "ST",
125 "description": "App settings",
126 "url": "/settings",
127 "icons": [
128 {
129 "src": "launcher-icon-4x.png",
130 "sizes": "192x192",
131 "type": "image/png"
132 }
133 ]
134 }
135 ]
136 )";
137
138 constexpr SkColor kAnotherInstallableIconTopLeftColor =
139 SkColorSetRGB(0x5C, 0x5C, 0x5C);
140
GetManifestUpdateManager(Browser * browser)141 ManifestUpdateManager& GetManifestUpdateManager(Browser* browser) {
142 return WebAppProviderBase::GetProviderBase(browser->profile())
143 ->manifest_update_manager();
144 }
145
146 class UpdateCheckResultAwaiter {
147 public:
UpdateCheckResultAwaiter(Browser * browser,const GURL & url)148 explicit UpdateCheckResultAwaiter(Browser* browser, const GURL& url)
149 : browser_(browser), url_(url) {
150 SetCallback();
151 }
152
SetCallback()153 void SetCallback() {
154 GetManifestUpdateManager(browser_).SetResultCallbackForTesting(
155 base::BindOnce(&UpdateCheckResultAwaiter::OnResult,
156 base::Unretained(this)));
157 }
158
AwaitNextResult()159 ManifestUpdateResult AwaitNextResult() && {
160 run_loop_.Run();
161 return *result_;
162 }
163
OnResult(const GURL & url,ManifestUpdateResult result)164 void OnResult(const GURL& url, ManifestUpdateResult result) {
165 if (url != url_) {
166 SetCallback();
167 return;
168 }
169 result_ = result;
170 run_loop_.Quit();
171 }
172
173 private:
174 Browser* browser_ = nullptr;
175 const GURL& url_;
176 base::RunLoop run_loop_;
177 base::Optional<ManifestUpdateResult> result_;
178 };
179
180 } // namespace
181
182 class ManifestUpdateManagerBrowserTest : public InProcessBrowserTest {
183 public:
ManifestUpdateManagerBrowserTest()184 ManifestUpdateManagerBrowserTest() {
185 scoped_feature_list_.InitAndEnableFeature(
186 features::kDesktopPWAsLocalUpdating);
187 }
188 ManifestUpdateManagerBrowserTest(const ManifestUpdateManagerBrowserTest&) =
189 delete;
190 ManifestUpdateManagerBrowserTest& operator=(
191 const ManifestUpdateManagerBrowserTest&) = delete;
192
193 ~ManifestUpdateManagerBrowserTest() override = default;
194
SetUp()195 void SetUp() override {
196 http_server_.AddDefaultHandlers(GetChromeTestDataDir());
197 http_server_.RegisterRequestHandler(base::BindRepeating(
198 &ManifestUpdateManagerBrowserTest::RequestHandlerOverride,
199 base::Unretained(this)));
200 ASSERT_TRUE(http_server_.Start());
201 // Suppress globally to avoid OS hooks deployed for system web app during
202 // WebAppProvider setup.
203 os_hooks_suppress_ =
204 OsIntegrationManager::ScopedSuppressOsHooksForTesting();
205 InProcessBrowserTest::SetUp();
206 }
207
SetUpOnMainThread()208 void SetUpOnMainThread() override {
209 // Cannot construct RunLoop in constructor due to threading restrictions.
210 shortcut_run_loop_.emplace();
211 }
212
OnShortcutInfoRetrieved(std::unique_ptr<ShortcutInfo> shortcut_info)213 void OnShortcutInfoRetrieved(std::unique_ptr<ShortcutInfo> shortcut_info) {
214 if (shortcut_info) {
215 updated_shortcut_top_left_color_ =
216 shortcut_info->favicon.begin()->AsBitmap().getColor(0, 0);
217 }
218 shortcut_run_loop_->Quit();
219 }
220
CheckShortcutInfoUpdated(const AppId & app_id,SkColor top_left_color)221 void CheckShortcutInfoUpdated(const AppId& app_id, SkColor top_left_color) {
222 GetProvider().os_integration_manager().GetShortcutInfoForApp(
223 app_id, base::BindOnce(
224 &ManifestUpdateManagerBrowserTest::OnShortcutInfoRetrieved,
225 base::Unretained(this)));
226 shortcut_run_loop_->Run();
227 EXPECT_EQ(updated_shortcut_top_left_color_, top_left_color);
228 }
229
RequestHandlerOverride(const net::test_server::HttpRequest & request)230 std::unique_ptr<net::test_server::HttpResponse> RequestHandlerOverride(
231 const net::test_server::HttpRequest& request) {
232 if (request_override_)
233 return request_override_.Run(request);
234 return nullptr;
235 }
236
OverrideManifest(const char * manifest_template,const std::vector<std::string> & substitutions)237 void OverrideManifest(const char* manifest_template,
238 const std::vector<std::string>& substitutions) {
239 std::string content = base::ReplaceStringPlaceholders(
240 manifest_template, substitutions, nullptr);
241 request_override_ = base::BindLambdaForTesting(
242 [this, content = std::move(content)](
243 const net::test_server::HttpRequest& request)
244 -> std::unique_ptr<net::test_server::HttpResponse> {
245 if (request.GetURL() != GetManifestURL())
246 return nullptr;
247 auto http_response =
248 std::make_unique<net::test_server::BasicHttpResponse>();
249 http_response->set_code(net::HTTP_FOUND);
250 http_response->set_content(content);
251 return std::move(http_response);
252 });
253 }
254
GetAppURL() const255 GURL GetAppURL() const {
256 return http_server_.GetURL("/banners/manifest_test_page.html");
257 }
258
GetManifestURL() const259 GURL GetManifestURL() const {
260 return http_server_.GetURL("/banners/manifest.json");
261 }
262
InstallWebApp()263 AppId InstallWebApp() {
264 GURL app_url = GetAppURL();
265 ui_test_utils::NavigateToURL(browser(), app_url);
266
267 AppId app_id;
268 base::RunLoop run_loop;
269 GetProvider().install_manager().InstallWebAppFromManifestWithFallback(
270 browser()->tab_strip_model()->GetActiveWebContents(),
271 /*force_shortcut_app=*/false, WebappInstallSource::OMNIBOX_INSTALL_ICON,
272 base::BindOnce(TestAcceptDialogCallback),
273 base::BindLambdaForTesting(
274 [&](const AppId& new_app_id, InstallResultCode code) {
275 EXPECT_EQ(code, InstallResultCode::kSuccessNewInstall);
276 app_id = new_app_id;
277 run_loop.Quit();
278 }));
279 run_loop.Run();
280 return app_id;
281 }
282
InstallPolicyApp()283 AppId InstallPolicyApp() {
284 const GURL app_url = GetAppURL();
285 base::RunLoop run_loop;
286 ExternalInstallOptions install_options(
287 app_url, DisplayMode::kStandalone,
288 ExternalInstallSource::kExternalPolicy);
289 install_options.add_to_applications_menu = false;
290 install_options.add_to_desktop = false;
291 install_options.add_to_quick_launch_bar = false;
292 install_options.install_placeholder = true;
293 GetProvider().pending_app_manager().Install(
294 std::move(install_options),
295 base::BindLambdaForTesting(
296 [&](const GURL& installed_app_url, InstallResultCode code) {
297 EXPECT_EQ(installed_app_url, app_url);
298 EXPECT_EQ(code, InstallResultCode::kSuccessNewInstall);
299 run_loop.Quit();
300 }));
301 run_loop.Run();
302 return GetProvider().registrar().LookupExternalAppId(app_url).value();
303 }
304
SetTimeOverride(base::Time time_override)305 void SetTimeOverride(base::Time time_override) {
306 GetManifestUpdateManager(browser()).set_time_override_for_testing(
307 time_override);
308 }
309
GetResultAfterPageLoad(const GURL & url,const AppId * app_id)310 ManifestUpdateResult GetResultAfterPageLoad(const GURL& url,
311 const AppId* app_id) {
312 UpdateCheckResultAwaiter awaiter(browser(), url);
313 ui_test_utils::NavigateToURL(browser(), url);
314 return std::move(awaiter).AwaitNextResult();
315 }
316
GetProvider()317 WebAppProviderBase& GetProvider() {
318 return *WebAppProviderBase::GetProviderBase(browser()->profile());
319 }
320
321 protected:
322 net::EmbeddedTestServer::HandleRequestCallback request_override_;
323
324 base::HistogramTester histogram_tester_;
325
326 net::EmbeddedTestServer http_server_;
327
328 private:
329 base::test::ScopedFeatureList scoped_feature_list_;
330
331 base::Optional<base::RunLoop> shortcut_run_loop_;
332 base::Optional<SkColor> updated_shortcut_top_left_color_;
333 ScopedOsHooksSuppress os_hooks_suppress_;
334 };
335
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckOutOfScopeNavigation)336 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
337 CheckOutOfScopeNavigation) {
338 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), nullptr),
339 ManifestUpdateResult::kNoAppInScope);
340
341 AppId app_id = InstallWebApp();
342
343 EXPECT_EQ(GetResultAfterPageLoad(GURL("http://example.org"), nullptr),
344 ManifestUpdateResult::kNoAppInScope);
345
346 histogram_tester_.ExpectTotalCount(kUpdateHistogramName, 0);
347 }
348
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckIsThrottled)349 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest, CheckIsThrottled) {
350 constexpr base::TimeDelta kDelayBetweenChecks = base::TimeDelta::FromDays(1);
351 base::Time time_override = base::Time::UnixEpoch();
352 SetTimeOverride(time_override);
353
354 AppId app_id = InstallWebApp();
355 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
356 ManifestUpdateResult::kAppUpToDate);
357
358 time_override += kDelayBetweenChecks / 2;
359 SetTimeOverride(time_override);
360 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
361 ManifestUpdateResult::kThrottled);
362
363 time_override += kDelayBetweenChecks;
364 SetTimeOverride(time_override);
365 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
366 ManifestUpdateResult::kAppUpToDate);
367
368 time_override += kDelayBetweenChecks / 2;
369 SetTimeOverride(time_override);
370 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
371 ManifestUpdateResult::kThrottled);
372
373 time_override += kDelayBetweenChecks * 2;
374 SetTimeOverride(time_override);
375 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
376 ManifestUpdateResult::kAppUpToDate);
377
378 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
379 ManifestUpdateResult::kThrottled, 2);
380 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
381 ManifestUpdateResult::kAppUpToDate, 3);
382 }
383
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckCancelledByWebContentsDestroyed)384 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
385 CheckCancelledByWebContentsDestroyed) {
386 AppId app_id = InstallWebApp();
387 GetManifestUpdateManager(browser()).hang_update_checks_for_testing();
388
389 GURL url = GetAppURL();
390 UpdateCheckResultAwaiter awaiter(browser(), url);
391 ui_test_utils::NavigateToURL(browser(), url);
392 chrome::CloseTab(browser());
393 EXPECT_EQ(std::move(awaiter).AwaitNextResult(),
394 ManifestUpdateResult::kWebContentsDestroyed);
395 histogram_tester_.ExpectBucketCount(
396 kUpdateHistogramName, ManifestUpdateResult::kWebContentsDestroyed, 1);
397 }
398
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckCancelledByAppUninstalled)399 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
400 CheckCancelledByAppUninstalled) {
401 AppId app_id = InstallWebApp();
402 GetManifestUpdateManager(browser()).hang_update_checks_for_testing();
403
404 GURL url = GetAppURL();
405 UpdateCheckResultAwaiter awaiter(browser(), url);
406 ui_test_utils::NavigateToURL(browser(), url);
407 GetProvider().install_finalizer().UninstallWebAppFromSyncByUser(
408 app_id, base::DoNothing());
409 EXPECT_EQ(std::move(awaiter).AwaitNextResult(),
410 ManifestUpdateResult::kAppUninstalled);
411 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
412 ManifestUpdateResult::kAppUninstalled, 1);
413 }
414
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckIgnoresWhitespaceDifferences)415 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
416 CheckIgnoresWhitespaceDifferences) {
417 constexpr char kManifestTemplate[] = R"(
418 {
419 "name": "Test app",
420 "start_url": ".",
421 "scope": "/",
422 "display": "standalone",
423 "icons": $1
424 $2
425 }
426 )";
427 OverrideManifest(kManifestTemplate, {kInstallableIconList, ""});
428 AppId app_id = InstallWebApp();
429
430 OverrideManifest(kManifestTemplate, {kInstallableIconList, "\n\n\n\n"});
431 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
432 ManifestUpdateResult::kAppUpToDate);
433 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
434 ManifestUpdateResult::kAppUpToDate, 1);
435 }
436
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckIgnoresNameChange)437 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
438 CheckIgnoresNameChange) {
439 constexpr char kManifestTemplate[] = R"(
440 {
441 "name": "$1",
442 "start_url": ".",
443 "scope": "/",
444 "display": "standalone",
445 "icons": $2
446 }
447 )";
448 OverrideManifest(kManifestTemplate, {"Test app name", kInstallableIconList});
449 AppId app_id = InstallWebApp();
450
451 OverrideManifest(kManifestTemplate,
452 {"Different app name", kInstallableIconList});
453 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
454 ManifestUpdateResult::kAppUpToDate);
455 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
456 ManifestUpdateResult::kAppUpToDate, 1);
457 }
458
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckIgnoresShortNameChange)459 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
460 CheckIgnoresShortNameChange) {
461 constexpr char kManifestTemplate[] = R"(
462 {
463 "name": "Test app name",
464 "short_name": "$1",
465 "start_url": ".",
466 "scope": "/",
467 "display": "standalone",
468 "icons": $2
469 }
470 )";
471 OverrideManifest(kManifestTemplate,
472 {"Short test app name", kInstallableIconList});
473 AppId app_id = InstallWebApp();
474
475 OverrideManifest(kManifestTemplate,
476 {"Different short test app name", kInstallableIconList});
477 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
478 ManifestUpdateResult::kAppUpToDate);
479 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
480 ManifestUpdateResult::kAppUpToDate, 1);
481 }
482
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckIgnoresStartUrlChange)483 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
484 CheckIgnoresStartUrlChange) {
485 constexpr char kManifestTemplate[] = R"(
486 {
487 "name": "Test app name",
488 "start_url": "$1",
489 "scope": "/",
490 "display": "standalone",
491 "icons": $2
492 }
493 )";
494 OverrideManifest(kManifestTemplate, {"a.html", kInstallableIconList});
495 AppId app_id = InstallWebApp();
496
497 OverrideManifest(kManifestTemplate, {"b.html", kInstallableIconList});
498 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
499 ManifestUpdateResult::kAppUpToDate);
500 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
501 ManifestUpdateResult::kAppUpToDate, 1);
502 }
503
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckIgnoresNoManifestChange)504 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
505 CheckIgnoresNoManifestChange) {
506 constexpr char kManifestTemplate[] = R"(
507 {
508 "name": "Test app name",
509 "start_url": ".",
510 "scope": "/",
511 "display": "standalone",
512 "icons": $1
513 }
514 )";
515 OverrideManifest(kManifestTemplate, {kInstallableIconList});
516 AppId app_id = InstallWebApp();
517 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
518 ManifestUpdateResult::kAppUpToDate);
519 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
520 ManifestUpdateResult::kAppUpToDate, 1);
521 }
522
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckIgnoresInvalidManifest)523 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
524 CheckIgnoresInvalidManifest) {
525 constexpr char kManifestTemplate[] = R"(
526 {
527 "name": "Test app name",
528 "start_url": ".",
529 "scope": "/",
530 "display": "standalone",
531 "icons": $1,
532 $2
533 }
534 )";
535 OverrideManifest(kManifestTemplate, {kInstallableIconList, ""});
536 AppId app_id = InstallWebApp();
537 OverrideManifest(kManifestTemplate, {kInstallableIconList,
538 "invalid manifest syntax !@#$%^*&()"});
539 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
540 ManifestUpdateResult::kAppNotEligible);
541 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
542 ManifestUpdateResult::kAppNotEligible, 1);
543 }
544
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckIgnoresNonLocalApps)545 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
546 CheckIgnoresNonLocalApps) {
547 constexpr char kManifestTemplate[] = R"(
548 {
549 "name": "Test app name",
550 "start_url": ".",
551 "scope": "/",
552 "display": "standalone",
553 "icons": $1,
554 "theme_color": "$2"
555 }
556 )";
557 OverrideManifest(kManifestTemplate, {kInstallableIconList, "blue"});
558 AppId app_id = InstallWebApp();
559
560 GetProvider().registry_controller().SetAppIsLocallyInstalled(app_id, false);
561 EXPECT_FALSE(GetProvider().registrar().IsLocallyInstalled(app_id));
562
563 OverrideManifest(kManifestTemplate, {kInstallableIconList, "red"});
564 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
565 ManifestUpdateResult::kNoAppInScope);
566 histogram_tester_.ExpectTotalCount(kUpdateHistogramName, 0);
567 }
568
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckIgnoresPlaceholderApps)569 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
570 CheckIgnoresPlaceholderApps) {
571 // Set up app URL to redirect to force placeholder app to install.
572 const GURL app_url = GetAppURL();
573 request_override_ = base::BindLambdaForTesting(
574 [&app_url](const net::test_server::HttpRequest& request)
575 -> std::unique_ptr<net::test_server::HttpResponse> {
576 if (request.GetURL() != app_url)
577 return nullptr;
578 auto http_response =
579 std::make_unique<net::test_server::BasicHttpResponse>();
580 http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
581 http_response->AddCustomHeader(
582 "Location", "http://other-origin.com/defaultresponse");
583 http_response->set_content("redirect page");
584 return std::move(http_response);
585 });
586
587 // Install via PendingAppManager, the redirect to a different origin should
588 // cause it to install a placeholder app.
589 AppId app_id = InstallPolicyApp();
590 EXPECT_TRUE(GetProvider().registrar().IsPlaceholderApp(app_id));
591
592 // Manifest updating should ignore non-redirect loads for placeholder apps
593 // because the PendingAppManager will handle these.
594 constexpr char kManifestTemplate[] = R"(
595 {
596 "name": "Test app name",
597 "start_url": ".",
598 "scope": "/",
599 "display": "standalone",
600 "icons": $1
601 }
602 )";
603 OverrideManifest(kManifestTemplate, {kInstallableIconList});
604 EXPECT_EQ(GetResultAfterPageLoad(app_url, &app_id),
605 ManifestUpdateResult::kAppIsPlaceholder);
606 histogram_tester_.ExpectBucketCount(
607 kUpdateHistogramName, ManifestUpdateResult::kAppIsPlaceholder, 1);
608 }
609
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckFindsThemeColorChange)610 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
611 CheckFindsThemeColorChange) {
612 constexpr char kManifestTemplate[] = R"(
613 {
614 "name": "Test app name",
615 "start_url": ".",
616 "scope": "/",
617 "display": "standalone",
618 "icons": $1,
619 "theme_color": "$2"
620 }
621 )";
622 OverrideManifest(kManifestTemplate, {kInstallableIconList, "blue"});
623 AppId app_id = InstallWebApp();
624 EXPECT_EQ(GetProvider().registrar().GetAppThemeColor(app_id), SK_ColorBLUE);
625
626 // Check that OnWebAppInstalled and OnWebAppUninstalled are not called
627 // if in-place web app update happens.
628 WebAppInstallObserver install_observer(&GetProvider().registrar());
629 install_observer.SetWebAppInstalledDelegate(
630 base::BindLambdaForTesting([](const AppId& app_id) { NOTREACHED(); }));
631 install_observer.SetWebAppUninstalledDelegate(
632 base::BindLambdaForTesting([](const AppId& app_id) { NOTREACHED(); }));
633
634 // CSS #RRGGBBAA syntax.
635 OverrideManifest(kManifestTemplate, {kInstallableIconList, "#00FF00F0"});
636 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
637 ManifestUpdateResult::kAppUpdated);
638 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
639 ManifestUpdateResult::kAppUpdated, 1);
640 CheckShortcutInfoUpdated(app_id, kInstallableIconTopLeftColor);
641
642 // Updated theme_color loses any transparency.
643 EXPECT_EQ(GetProvider().registrar().GetAppThemeColor(app_id),
644 SkColorSetARGB(0xFF, 0x00, 0xFF, 0x00));
645 }
646
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckKeepsSameName)647 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest, CheckKeepsSameName) {
648 constexpr char kManifestTemplate[] = R"(
649 {
650 "name": "$1",
651 "start_url": ".",
652 "scope": "/",
653 "display": "standalone",
654 "icons": $2,
655 "theme_color": "$3"
656 }
657 )";
658 OverrideManifest(kManifestTemplate,
659 {"App name 1", kInstallableIconList, "blue"});
660 AppId app_id = InstallWebApp();
661 EXPECT_EQ(GetProvider().registrar().GetAppThemeColor(app_id), SK_ColorBLUE);
662 EXPECT_EQ(GetProvider().registrar().GetAppShortName(app_id), "App name 1");
663
664 OverrideManifest(kManifestTemplate,
665 {"App name 2", kInstallableIconList, "red"});
666 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
667 ManifestUpdateResult::kAppUpdated);
668 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
669 ManifestUpdateResult::kAppUpdated, 1);
670 CheckShortcutInfoUpdated(app_id, kInstallableIconTopLeftColor);
671 EXPECT_EQ(GetProvider().registrar().GetAppThemeColor(app_id), SK_ColorRED);
672 // The app name must not change without user confirmation.
673 EXPECT_EQ(GetProvider().registrar().GetAppShortName(app_id), "App name 1");
674 }
675
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckFindsIconUrlChange)676 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
677 CheckFindsIconUrlChange) {
678 constexpr char kManifestTemplate[] = R"(
679 {
680 "name": "Test app name",
681 "start_url": ".",
682 "scope": "/",
683 "display": "standalone",
684 "icons": $1
685 }
686 )";
687 OverrideManifest(kManifestTemplate, {kInstallableIconList});
688 AppId app_id = InstallWebApp();
689
690 OverrideManifest(kManifestTemplate, {kAnotherInstallableIconList});
691 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
692 ManifestUpdateResult::kAppUpdated);
693 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
694 ManifestUpdateResult::kAppUpdated, 1);
695 CheckShortcutInfoUpdated(app_id, kAnotherInstallableIconTopLeftColor);
696 }
697
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckUpdatedPolicyAppsNotUninstallable)698 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
699 CheckUpdatedPolicyAppsNotUninstallable) {
700 constexpr char kManifestTemplate[] = R"(
701 {
702 "name": "Test app name",
703 "start_url": ".",
704 "scope": "/",
705 "display": "standalone",
706 "theme_color": "$1",
707 "icons": $2
708 }
709 )";
710 OverrideManifest(kManifestTemplate, {"blue", kInstallableIconList});
711 AppId app_id = InstallPolicyApp();
712 EXPECT_FALSE(
713 GetProvider().install_finalizer().CanUserUninstallFromSync(app_id));
714
715 OverrideManifest(kManifestTemplate, {"red", kInstallableIconList});
716 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
717 ManifestUpdateResult::kAppUpdated);
718 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
719 ManifestUpdateResult::kAppUpdated, 1);
720 CheckShortcutInfoUpdated(app_id, kInstallableIconTopLeftColor);
721
722 // Policy installed apps should continue to be not uninstallable by the user
723 // after updating.
724 EXPECT_FALSE(
725 GetProvider().install_finalizer().CanUserUninstallFromSync(app_id));
726 }
727
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckFindsScopeChange)728 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
729 CheckFindsScopeChange) {
730 constexpr char kManifestTemplate[] = R"(
731 {
732 "name": "Test app name",
733 "start_url": ".",
734 "scope": "$1",
735 "display": "standalone",
736 "icons": $2
737 }
738 )";
739 OverrideManifest(kManifestTemplate, {"/banners/", kInstallableIconList});
740 AppId app_id = InstallWebApp();
741
742 OverrideManifest(kManifestTemplate, {"/", kInstallableIconList});
743 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
744 ManifestUpdateResult::kAppUpdated);
745 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
746 ManifestUpdateResult::kAppUpdated, 1);
747 CheckShortcutInfoUpdated(app_id, kInstallableIconTopLeftColor);
748 EXPECT_EQ(GetProvider().registrar().GetAppScope(app_id),
749 http_server_.GetURL("/"));
750 }
751
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckFindsDisplayChange)752 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
753 CheckFindsDisplayChange) {
754 constexpr char kManifestTemplate[] = R"(
755 {
756 "name": "Test app name",
757 "start_url": ".",
758 "scope": "/",
759 "display": "$1",
760 "icons": $2
761 }
762 )";
763 OverrideManifest(kManifestTemplate, {"minimal-ui", kInstallableIconList});
764 AppId app_id = InstallWebApp();
765
766 OverrideManifest(kManifestTemplate, {"standalone", kInstallableIconList});
767 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
768 ManifestUpdateResult::kAppUpdated);
769 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
770 ManifestUpdateResult::kAppUpdated, 1);
771 CheckShortcutInfoUpdated(app_id, kInstallableIconTopLeftColor);
772 EXPECT_EQ(GetProvider().registrar().GetAppDisplayMode(app_id),
773 DisplayMode::kStandalone);
774 }
775
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckFindsDisplayBrowserChange)776 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
777 CheckFindsDisplayBrowserChange) {
778 constexpr char kManifestTemplate[] = R"(
779 {
780 "name": "Test app name",
781 "start_url": ".",
782 "scope": "/",
783 "display": "$1",
784 "icons": $2
785 }
786 )";
787 OverrideManifest(kManifestTemplate, {"standalone", kInstallableIconList});
788 AppId app_id = InstallWebApp();
789 GetProvider().registry_controller().SetAppUserDisplayMode(
790 app_id, DisplayMode::kStandalone, /*is_user_action=*/false);
791
792 OverrideManifest(kManifestTemplate, {"browser", kInstallableIconList});
793 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
794 ManifestUpdateResult::kAppUpdated);
795 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
796 ManifestUpdateResult::kAppUpdated, 1);
797 EXPECT_EQ(GetProvider().registrar().GetAppDisplayMode(app_id),
798 DisplayMode::kBrowser);
799
800 // We don't touch the user's launch preference even if the app display mode
801 // changes. Instead the effective display mode changes.
802 EXPECT_EQ(GetProvider().registrar().GetAppUserDisplayMode(app_id),
803 DisplayMode::kStandalone);
804 EXPECT_EQ(GetProvider().registrar().GetAppEffectiveDisplayMode(app_id),
805 DisplayMode::kMinimalUi);
806 }
807
808 // A dedicated test fixture for DisplayOverride, which is supported
809 // only for the new web apps mode, and requires a command line switch
810 // to enable manifest parsing.
811 class ManifestUpdateManagerBrowserTest_DisplayOverride
812 : public ManifestUpdateManagerBrowserTest {
813 public:
ManifestUpdateManagerBrowserTest_DisplayOverride()814 ManifestUpdateManagerBrowserTest_DisplayOverride() {
815 scoped_feature_list_.InitAndEnableFeature(
816 features::kWebAppManifestDisplayOverride);
817 }
818
819 private:
820 base::test::ScopedFeatureList scoped_feature_list_;
821 };
822
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_DisplayOverride,CheckFindsDisplayOverrideChange)823 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_DisplayOverride,
824 CheckFindsDisplayOverrideChange) {
825 constexpr char kManifestTemplate[] = R"(
826 {
827 "name": "Test app name",
828 "start_url": ".",
829 "scope": "/",
830 "display": "standalone",
831 "display_override": $1,
832 "icons": $2
833 }
834 )";
835
836 OverrideManifest(kManifestTemplate,
837 {R"([ "fullscreen", "standalone" ])", kInstallableIconList});
838 AppId app_id = InstallWebApp();
839
840 OverrideManifest(kManifestTemplate,
841 {R"([ "fullscreen", "minimal-ui" ])", kInstallableIconList});
842 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
843 ManifestUpdateResult::kAppUpdated);
844 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
845 ManifestUpdateResult::kAppUpdated, 1);
846 CheckShortcutInfoUpdated(app_id, kInstallableIconTopLeftColor);
847
848 std::vector<DisplayMode> app_display_mode_override =
849 GetProvider().registrar().GetAppDisplayModeOverride(app_id);
850
851 ASSERT_EQ(2u, app_display_mode_override.size());
852 EXPECT_EQ(DisplayMode::kFullscreen, app_display_mode_override[0]);
853 EXPECT_EQ(DisplayMode::kMinimalUi, app_display_mode_override[1]);
854 }
855
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_DisplayOverride,CheckFindsNewDisplayOverride)856 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_DisplayOverride,
857 CheckFindsNewDisplayOverride) {
858 constexpr char kManifestTemplate[] = R"(
859 {
860 "name": "Test app name",
861 "start_url": ".",
862 "scope": "/",
863 "display": "standalone",
864 $1
865 "icons": $2
866 }
867 )";
868
869 // No display_override in manifest
870 OverrideManifest(kManifestTemplate, {"", kInstallableIconList});
871 AppId app_id = InstallWebApp();
872
873 // Add display_override field
874 OverrideManifest(kManifestTemplate,
875 {R"("display_override": [ "minimal-ui", "standalone" ],)",
876 kInstallableIconList});
877 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
878 ManifestUpdateResult::kAppUpdated);
879 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
880 ManifestUpdateResult::kAppUpdated, 1);
881 CheckShortcutInfoUpdated(app_id, kInstallableIconTopLeftColor);
882
883 std::vector<DisplayMode> app_display_mode_override =
884 GetProvider().registrar().GetAppDisplayModeOverride(app_id);
885
886 ASSERT_EQ(2u, app_display_mode_override.size());
887 EXPECT_EQ(DisplayMode::kMinimalUi, app_display_mode_override[0]);
888 EXPECT_EQ(DisplayMode::kStandalone, app_display_mode_override[1]);
889 }
890
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_DisplayOverride,CheckFindsDeletedDisplayOverride)891 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_DisplayOverride,
892 CheckFindsDeletedDisplayOverride) {
893 constexpr char kManifestTemplate[] = R"(
894 {
895 "name": "Test app name",
896 "start_url": ".",
897 "scope": "/",
898 "display": "standalone",
899 $1
900 "icons": $2
901 }
902 )";
903
904 // Ensure display_override exists in initial manifest
905 OverrideManifest(kManifestTemplate,
906 {R"("display_override": [ "fullscreen", "minimal-ui" ],)",
907 kInstallableIconList});
908 AppId app_id = InstallWebApp();
909
910 // Remove display_override from manifest
911 OverrideManifest(kManifestTemplate, {"", kInstallableIconList});
912 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
913 ManifestUpdateResult::kAppUpdated);
914 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
915 ManifestUpdateResult::kAppUpdated, 1);
916 CheckShortcutInfoUpdated(app_id, kInstallableIconTopLeftColor);
917
918 std::vector<DisplayMode> app_display_mode_override =
919 GetProvider().registrar().GetAppDisplayModeOverride(app_id);
920
921 ASSERT_EQ(0u, app_display_mode_override.size());
922 }
923
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_DisplayOverride,CheckFindsInvalidDisplayOverride)924 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_DisplayOverride,
925 CheckFindsInvalidDisplayOverride) {
926 constexpr char kManifestTemplate[] = R"(
927 {
928 "name": "Test app name",
929 "start_url": ".",
930 "scope": "/",
931 "display": "standalone",
932 "display_override": $1,
933 "icons": $2
934 }
935 )";
936
937 OverrideManifest(kManifestTemplate,
938 {R"([ "browser", "fullscreen" ])", kInstallableIconList});
939 AppId app_id = InstallWebApp();
940
941 ASSERT_EQ(2u,
942 GetProvider().registrar().GetAppDisplayModeOverride(app_id).size());
943
944 // display_override contains only invalid values
945 OverrideManifest(kManifestTemplate,
946 {R"( [ "invalid", 7 ])", kInstallableIconList});
947 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
948 ManifestUpdateResult::kAppUpdated);
949 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
950 ManifestUpdateResult::kAppUpdated, 1);
951 CheckShortcutInfoUpdated(app_id, kInstallableIconTopLeftColor);
952
953 std::vector<DisplayMode> app_display_mode_override =
954 GetProvider().registrar().GetAppDisplayModeOverride(app_id);
955
956 ASSERT_EQ(0u, app_display_mode_override.size());
957 }
958
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_DisplayOverride,CheckIgnoresDisplayOverrideInvalidChange)959 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_DisplayOverride,
960 CheckIgnoresDisplayOverrideInvalidChange) {
961 constexpr char kManifestTemplate[] = R"(
962 {
963 "name": "Test app name",
964 "start_url": ".",
965 "scope": "/",
966 "display": "standalone",
967 $1
968 "icons": $2
969 }
970 )";
971
972 // No display_override in manifest
973 OverrideManifest(kManifestTemplate, {"", kInstallableIconList});
974 AppId app_id = InstallWebApp();
975
976 // display_override contains only invalid values
977 OverrideManifest(
978 kManifestTemplate,
979 {R"("display_override": [ "invalid", 7 ],)", kInstallableIconList});
980 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
981 ManifestUpdateResult::kAppUpToDate);
982 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
983 ManifestUpdateResult::kAppUpToDate, 1);
984 }
985
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_DisplayOverride,CheckIgnoresDisplayOverrideChange)986 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest_DisplayOverride,
987 CheckIgnoresDisplayOverrideChange) {
988 constexpr char kManifestTemplate[] = R"(
989 {
990 "name": "Test app name",
991 "start_url": ".",
992 "scope": "/",
993 "display": "standalone",
994 "display_override": $1,
995 "icons": $2
996 }
997 )";
998
999 OverrideManifest(kManifestTemplate,
1000 {R"([ "standard", "fullscreen" ])", kInstallableIconList});
1001 AppId app_id = InstallWebApp();
1002
1003 // display_override contains an additional invalid value
1004 OverrideManifest(
1005 kManifestTemplate,
1006 {R"([ "invalid", "standard", "fullscreen" ])", kInstallableIconList});
1007 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1008 ManifestUpdateResult::kAppUpToDate);
1009 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
1010 ManifestUpdateResult::kAppUpToDate, 1);
1011 }
1012
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckFindsIconContentChange)1013 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
1014 CheckFindsIconContentChange) {
1015 constexpr char kManifest[] = R"(
1016 {
1017 "name": "Test app name",
1018 "start_url": ".",
1019 "scope": "/",
1020 "display": "standalone",
1021 "icons": [
1022 {
1023 "src": "/web_apps/basic-192.png?ignore",
1024 "sizes": "192x192",
1025 "type": "image/png"
1026 }
1027 ]
1028 }
1029 )";
1030 OverrideManifest(kManifest, {});
1031 AppId app_id = InstallWebApp();
1032
1033 // Replace the contents of basic-192.png with blue-192.png without changing
1034 // the URL.
1035 content::URLLoaderInterceptor url_interceptor(base::BindLambdaForTesting(
1036 [this](content::URLLoaderInterceptor::RequestParams* params)
1037 -> bool /*intercepted*/ {
1038 if (params->url_request.url ==
1039 http_server_.GetURL("/web_apps/basic-192.png?ignore")) {
1040 content::URLLoaderInterceptor::WriteResponse(
1041 "chrome/test/data/web_apps/blue-192.png", params->client.get());
1042 return true;
1043 }
1044 return false;
1045 }));
1046
1047 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1048 ManifestUpdateResult::kAppUpdated);
1049 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
1050 ManifestUpdateResult::kAppUpdated, 1);
1051 CheckShortcutInfoUpdated(app_id, SK_ColorBLUE);
1052
1053 EXPECT_EQ(ReadAppIconPixel(browser()->profile(), app_id, /*size=*/192,
1054 /*x=*/0, /*y=*/0),
1055 SK_ColorBLUE);
1056 }
1057
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,CheckIgnoresIconDownloadFail)1058 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTest,
1059 CheckIgnoresIconDownloadFail) {
1060 constexpr char kManifest[] = R"(
1061 {
1062 "name": "Test app name",
1063 "start_url": ".",
1064 "scope": "/",
1065 "display": "standalone",
1066 "icons": [
1067 {
1068 "src": "/web_apps/basic-48.png?ignore",
1069 "sizes": "48x48",
1070 "type": "image/png"
1071 },
1072 {
1073 "src": "/web_apps/basic-192.png?ignore",
1074 "sizes": "192x192",
1075 "type": "image/png"
1076 }
1077 ]
1078 }
1079 )";
1080 OverrideManifest(kManifest, {});
1081 AppId app_id = InstallWebApp();
1082
1083 // Make basic-48.png fail to download.
1084 // Replace the contents of basic-192.png with blue-192.png without changing
1085 // the URL.
1086 content::URLLoaderInterceptor url_interceptor(base::BindLambdaForTesting(
1087 [this](content::URLLoaderInterceptor::RequestParams* params)
1088 -> bool /*intercepted*/ {
1089 if (params->url_request.url ==
1090 http_server_.GetURL("/web_apps/basic-48.png?ignore")) {
1091 content::URLLoaderInterceptor::WriteResponse("malformed response", "",
1092 params->client.get());
1093 return true;
1094 }
1095 if (params->url_request.url ==
1096 http_server_.GetURL("/web_apps/basic-192.png?ignore")) {
1097 content::URLLoaderInterceptor::WriteResponse(
1098 "chrome/test/data/web_apps/blue-192.png", params->client.get());
1099 return true;
1100 }
1101 return false;
1102 }));
1103
1104 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1105 ManifestUpdateResult::kIconDownloadFailed);
1106 histogram_tester_.ExpectBucketCount(
1107 kUpdateHistogramName, ManifestUpdateResult::kIconDownloadFailed, 1);
1108
1109 EXPECT_EQ(ReadAppIconPixel(browser()->profile(), app_id, /*size=*/48, /*x=*/0,
1110 /*y=*/0),
1111 SK_ColorBLACK);
1112 EXPECT_EQ(ReadAppIconPixel(browser()->profile(), app_id, /*size=*/192,
1113 /*x=*/0, /*y=*/0),
1114 SK_ColorBLACK);
1115 }
1116
1117 class ManifestUpdateManagerSystemAppBrowserTest
1118 : public ManifestUpdateManagerBrowserTest {
1119 public:
1120 static constexpr char kSystemAppManifestText[] =
1121 R"({
1122 "name": "Test System App",
1123 "display": "standalone",
1124 "icons": [
1125 {
1126 "src": "icon-256.png",
1127 "sizes": "256x256",
1128 "type": "image/png"
1129 }
1130 ],
1131 "start_url": "/pwa.html",
1132 "theme_color": "$1"
1133 })";
1134
ManifestUpdateManagerSystemAppBrowserTest()1135 ManifestUpdateManagerSystemAppBrowserTest()
1136 : system_app_(
1137 TestSystemWebAppInstallation::SetUpStandaloneSingleWindowApp(
1138 false)) {
1139 system_app_->SetManifest(base::ReplaceStringPlaceholders(
1140 kSystemAppManifestText, {"#0f0"}, nullptr));
1141 }
1142
SetUpOnMainThread()1143 void SetUpOnMainThread() override { system_app_->WaitForAppInstall(); }
1144
1145 protected:
1146 std::unique_ptr<TestSystemWebAppInstallation> system_app_;
1147 };
1148
1149 constexpr char
1150 ManifestUpdateManagerSystemAppBrowserTest::kSystemAppManifestText[];
1151
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerSystemAppBrowserTest,CheckUpdateSkipped)1152 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerSystemAppBrowserTest,
1153 CheckUpdateSkipped) {
1154 system_app_->SetManifest(base::ReplaceStringPlaceholders(
1155 kSystemAppManifestText, {"#f00"}, nullptr));
1156
1157 AppId app_id = system_app_->GetAppId();
1158 EXPECT_EQ(GetResultAfterPageLoad(system_app_->GetAppUrl(), &app_id),
1159 ManifestUpdateResult::kAppIsSystemWebApp);
1160
1161 histogram_tester_.ExpectBucketCount(
1162 kUpdateHistogramName, ManifestUpdateResult::kAppIsSystemWebApp, 1);
1163 EXPECT_EQ(GetProvider().registrar().GetAppThemeColor(app_id), SK_ColorGREEN);
1164 }
1165
1166 using ManifestUpdateManagerWebAppsBrowserTest =
1167 ManifestUpdateManagerBrowserTest;
1168
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerWebAppsBrowserTest,CheckFindsThemeColorChangeForShadowBookmarkApp)1169 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerWebAppsBrowserTest,
1170 CheckFindsThemeColorChangeForShadowBookmarkApp) {
1171 auto* extensions_registry =
1172 extensions::ExtensionRegistry::Get(browser()->profile());
1173 extensions::TestExtensionRegistryObserver extensions_registry_observer(
1174 extensions_registry);
1175
1176 extensions::BookmarkAppRegistrar bookmark_app_registrar{browser()->profile()};
1177
1178 constexpr char kManifestTemplate[] = R"(
1179 {
1180 "name": "Test app name",
1181 "start_url": ".",
1182 "scope": "/",
1183 "display": "standalone",
1184 "icons": $1,
1185 "theme_color": "$2"
1186 }
1187 )";
1188 OverrideManifest(kManifestTemplate, {kInstallableIconList, "blue"});
1189 AppId app_id = InstallWebApp();
1190 EXPECT_EQ(GetProvider().registrar().GetAppThemeColor(app_id), SK_ColorBLUE);
1191
1192 scoped_refptr<const extensions::Extension> extension =
1193 extensions_registry_observer.WaitForExtensionInstalled();
1194 EXPECT_EQ(extension->id(), app_id);
1195 EXPECT_EQ(bookmark_app_registrar.GetAppThemeColor(app_id).value(),
1196 SK_ColorBLUE);
1197
1198 OverrideManifest(kManifestTemplate, {kInstallableIconList, "red"});
1199 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1200 ManifestUpdateResult::kAppUpdated);
1201 CheckShortcutInfoUpdated(app_id, kInstallableIconTopLeftColor);
1202 EXPECT_EQ(GetProvider().registrar().GetAppThemeColor(app_id), SK_ColorRED);
1203
1204 // Wait for all update events sequentially. Otherwise the test is flaky.
1205 extensions_registry_observer.WaitForExtensionUnloaded();
1206 extensions_registry_observer.WaitForExtensionLoaded();
1207 extension = extensions_registry_observer.WaitForExtensionReady();
1208
1209 EXPECT_EQ(extension->id(), app_id);
1210 EXPECT_EQ(bookmark_app_registrar.GetAppThemeColor(app_id).value(),
1211 SK_ColorRED);
1212 }
1213
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerWebAppsBrowserTest,CheckFindsAddedShareTarget)1214 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerWebAppsBrowserTest,
1215 CheckFindsAddedShareTarget) {
1216 constexpr char kManifestTemplate[] = R"(
1217 {
1218 "name": "Test app name",
1219 "start_url": ".",
1220 "scope": "/",
1221 "display": "minimal-ui",
1222 "icons": $1
1223 }
1224 )";
1225
1226 constexpr char kShareTargetManifestTemplate[] = R"(
1227 {
1228 "name": "Test app name",
1229 "start_url": ".",
1230 "scope": "/",
1231 "display": "minimal-ui",
1232 "share_target": {
1233 "action": "/web_share_target/share.html",
1234 "method": "GET",
1235 "params": {
1236 "url": "link"
1237 }
1238 },
1239 "icons": $1
1240 }
1241 )";
1242
1243 OverrideManifest(kManifestTemplate, {kInstallableIconList});
1244 AppId app_id = InstallWebApp();
1245
1246 OverrideManifest(kShareTargetManifestTemplate, {kInstallableIconList});
1247 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1248 ManifestUpdateResult::kAppUpdated);
1249 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
1250 ManifestUpdateResult::kAppUpdated, 1);
1251
1252 const WebApp* web_app =
1253 GetProvider().registrar().AsWebAppRegistrar()->GetAppById(app_id);
1254 EXPECT_TRUE(web_app->share_target().has_value());
1255 EXPECT_EQ(web_app->share_target()->method, apps::ShareTarget::Method::kGet);
1256 }
1257
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerWebAppsBrowserTest,CheckFindsShareTargetChange)1258 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerWebAppsBrowserTest,
1259 CheckFindsShareTargetChange) {
1260 constexpr char kShareTargetManifestTemplate[] = R"(
1261 {
1262 "name": "Test app name",
1263 "start_url": ".",
1264 "scope": "/",
1265 "display": "minimal-ui",
1266 "share_target": {
1267 "action": "/web_share_target/share.html",
1268 "method": "$1",
1269 "params": {
1270 "url": "link"
1271 }
1272 },
1273 "icons": $2
1274 }
1275 )";
1276 OverrideManifest(kShareTargetManifestTemplate, {"GET", kInstallableIconList});
1277 AppId app_id = InstallWebApp();
1278
1279 OverrideManifest(kShareTargetManifestTemplate,
1280 {"POST", kInstallableIconList});
1281 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1282 ManifestUpdateResult::kAppUpdated);
1283 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
1284 ManifestUpdateResult::kAppUpdated, 1);
1285
1286 const WebApp* web_app =
1287 GetProvider().registrar().AsWebAppRegistrar()->GetAppById(app_id);
1288 EXPECT_TRUE(web_app->share_target().has_value());
1289 EXPECT_EQ(web_app->share_target()->method, apps::ShareTarget::Method::kPost);
1290 }
1291
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerWebAppsBrowserTest,CheckFindsDeletedShareTarget)1292 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerWebAppsBrowserTest,
1293 CheckFindsDeletedShareTarget) {
1294 constexpr char kShareTargetManifestTemplate[] = R"(
1295 {
1296 "name": "Test app name",
1297 "start_url": ".",
1298 "scope": "/",
1299 "display": "minimal-ui",
1300 "share_target": {
1301 "action": "/web_share_target/share.html",
1302 "method": "GET",
1303 "params": {
1304 "url": "link"
1305 }
1306 },
1307 "icons": $1
1308 }
1309 )";
1310
1311 constexpr char kManifestTemplate[] = R"(
1312 {
1313 "name": "Test app name",
1314 "start_url": ".",
1315 "scope": "/",
1316 "display": "minimal-ui",
1317 "icons": $1
1318 }
1319 )";
1320
1321 OverrideManifest(kShareTargetManifestTemplate, {kInstallableIconList});
1322 AppId app_id = InstallWebApp();
1323
1324 OverrideManifest(kManifestTemplate, {kInstallableIconList});
1325 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1326 ManifestUpdateResult::kAppUpdated);
1327 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
1328 ManifestUpdateResult::kAppUpdated, 1);
1329
1330 const WebApp* web_app =
1331 GetProvider().registrar().AsWebAppRegistrar()->GetAppById(app_id);
1332 EXPECT_FALSE(web_app->share_target().has_value());
1333 }
1334
1335 class ManifestUpdateManagerBrowserTestWithShortcutsMenu
1336 : public ManifestUpdateManagerBrowserTest {
1337 public:
ManifestUpdateManagerBrowserTestWithShortcutsMenu()1338 ManifestUpdateManagerBrowserTestWithShortcutsMenu() {
1339 scoped_feature_list_.InitAndEnableFeature(
1340 features::kDesktopPWAsAppIconShortcutsMenu);
1341 }
1342
1343 private:
1344 base::test::ScopedFeatureList scoped_feature_list_;
1345 };
1346
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,CheckFindsShortcutsMenuUpdated)1347 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,
1348 CheckFindsShortcutsMenuUpdated) {
1349 constexpr char kManifestTemplate[] = R"(
1350 {
1351 "name": "Test app name",
1352 "start_url": ".",
1353 "scope": "/",
1354 "display": "standalone",
1355 "icons": $1,
1356 "shortcuts": $2
1357 }
1358 )";
1359 OverrideManifest(kManifestTemplate, {kInstallableIconList, kShortcutsItem});
1360 AppId app_id = InstallWebApp();
1361
1362 OverrideManifest(kManifestTemplate, {kInstallableIconList, kShortcutsItems});
1363 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1364 ManifestUpdateResult::kAppUpdated);
1365 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
1366 ManifestUpdateResult::kAppUpdated, 1);
1367 EXPECT_EQ(
1368 GetProvider().registrar().GetAppShortcutsMenuItemInfos(app_id).size(),
1369 2u);
1370 }
1371
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,CheckFindsItemNameUpdated)1372 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,
1373 CheckFindsItemNameUpdated) {
1374 constexpr char kManifestTemplate[] = R"(
1375 {
1376 "name": "Test app name",
1377 "start_url": ".",
1378 "scope": "/",
1379 "display": "standalone",
1380 "icons": $1,
1381 "shortcuts": [
1382 {
1383 "name": "$2",
1384 "short_name": "HM",
1385 "description": "Go home",
1386 "url": ".",
1387 "icons": [
1388 {
1389 "src": "/banners/image-512px.png",
1390 "sizes": "512x512",
1391 "type": "image/png"
1392 }
1393 ]
1394 }
1395 ]
1396 }
1397 )";
1398 OverrideManifest(kManifestTemplate, {kInstallableIconList, "Home"});
1399 AppId app_id = InstallWebApp();
1400
1401 OverrideManifest(kManifestTemplate,
1402 {kInstallableIconList, kAnotherShortcutsItemName});
1403 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1404 ManifestUpdateResult::kAppUpdated);
1405 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
1406 ManifestUpdateResult::kAppUpdated, 1);
1407 EXPECT_EQ(
1408 GetProvider().registrar().GetAppShortcutsMenuItemInfos(app_id)[0].name,
1409 base::UTF8ToUTF16(kAnotherShortcutsItemName));
1410 }
1411
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,CheckIgnoresShortNameAndDescriptionChange)1412 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,
1413 CheckIgnoresShortNameAndDescriptionChange) {
1414 constexpr char kManifestTemplate[] = R"(
1415 {
1416 "name": "Test app name",
1417 "start_url": ".",
1418 "scope": "/",
1419 "display": "standalone",
1420 "icons": $1,
1421 "shortcuts": [
1422 {
1423 "name": "Home",
1424 "short_name": "$2",
1425 "description": "$3",
1426 "url": ".",
1427 "icons": [
1428 {
1429 "src": "/banners/image-512px.png",
1430 "sizes": "512x512",
1431 "type": "image/png"
1432 }
1433 ]
1434 }
1435 ]
1436 }
1437 )";
1438 OverrideManifest(kManifestTemplate, {kInstallableIconList, "HM", "Go home"});
1439 AppId app_id = InstallWebApp();
1440
1441 OverrideManifest(kManifestTemplate,
1442 {kInstallableIconList, kAnotherShortcutsItemShortName,
1443 kAnotherShortcutsItemDescription});
1444 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1445 ManifestUpdateResult::kAppUpToDate);
1446 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
1447 ManifestUpdateResult::kAppUpToDate, 1);
1448 }
1449
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,CheckFindsItemUrlUpdated)1450 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,
1451 CheckFindsItemUrlUpdated) {
1452 constexpr char kManifestTemplate[] = R"(
1453 {
1454 "name": "Test app name",
1455 "start_url": ".",
1456 "scope": "/",
1457 "display": "standalone",
1458 "icons": $1,
1459 "shortcuts": [
1460 {
1461 "name": "Home",
1462 "short_name": "HM",
1463 "description": "Go home",
1464 "url": "$2",
1465 "icons": [
1466 {
1467 "src": "/banners/image-512px.png",
1468 "sizes": "512x512",
1469 "type": "image/png"
1470 }
1471 ]
1472 }
1473 ]
1474 }
1475 )";
1476 OverrideManifest(kManifestTemplate, {kInstallableIconList, "/"});
1477 AppId app_id = InstallWebApp();
1478
1479 OverrideManifest(kManifestTemplate,
1480 {kInstallableIconList, kAnotherShortcutsItemUrl});
1481 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1482 ManifestUpdateResult::kAppUpdated);
1483 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
1484 ManifestUpdateResult::kAppUpdated, 1);
1485 EXPECT_EQ(
1486 GetProvider().registrar().GetAppShortcutsMenuItemInfos(app_id)[0].url,
1487 http_server_.GetURL(kAnotherShortcutsItemUrl));
1488 }
1489
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,CheckFindsShortcutIconContentChange)1490 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,
1491 CheckFindsShortcutIconContentChange) {
1492 constexpr char kManifest[] = R"(
1493 {
1494 "name": "Test app name",
1495 "start_url": ".",
1496 "scope": "/",
1497 "display": "standalone",
1498 "icons": $1,
1499 "shortcuts": [
1500 {
1501 "name": "Home",
1502 "short_name": "HM",
1503 "description": "Go home",
1504 "url": "/",
1505 "icons": [
1506 {
1507 "src": "/web_apps/basic-192.png?ignore",
1508 "sizes": "192x192",
1509 "type": "image/png"
1510 }
1511 ]
1512 }
1513 ]
1514 }
1515 )";
1516 OverrideManifest(kManifest, {kInstallableIconList});
1517 AppId app_id = InstallWebApp();
1518
1519 // Replace the contents of basic-192.png with blue-192.png without changing
1520 // the URL.
1521 content::URLLoaderInterceptor url_interceptor(base::BindLambdaForTesting(
1522 [this](content::URLLoaderInterceptor::RequestParams* params)
1523 -> bool /*intercepted*/ {
1524 if (params->url_request.url ==
1525 http_server_.GetURL("/web_apps/basic-192.png?ignore")) {
1526 content::URLLoaderInterceptor::WriteResponse(
1527 "chrome/test/data/web_apps/blue-192.png", params->client.get());
1528 return true;
1529 }
1530 return false;
1531 }));
1532
1533 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1534 ManifestUpdateResult::kAppUpdated);
1535 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
1536 ManifestUpdateResult::kAppUpdated, 1);
1537
1538 // Check that the installed icon is now blue.
1539 base::RunLoop run_loop;
1540 GetProvider().icon_manager().ReadAllShortcutsMenuIcons(
1541 app_id,
1542 base::BindLambdaForTesting(
1543 [&run_loop](ShortcutsMenuIconsBitmaps shortcuts_menu_icons_bitmaps) {
1544 run_loop.Quit();
1545 EXPECT_EQ(shortcuts_menu_icons_bitmaps[0].at(192).getColor(0, 0),
1546 SK_ColorBLUE);
1547 }));
1548 run_loop.Run();
1549 }
1550
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,CheckFindsShortcutIconSrcUpdated)1551 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,
1552 CheckFindsShortcutIconSrcUpdated) {
1553 constexpr char kManifestTemplate[] = R"(
1554 {
1555 "name": "Test app name",
1556 "start_url": ".",
1557 "scope": "/",
1558 "display": "standalone",
1559 "icons": $1,
1560 "shortcuts": [
1561 {
1562 "name": "Home",
1563 "short_name": "HM",
1564 "description": "Go home",
1565 "url": ".",
1566 "icons": [
1567 {
1568 "src": "$2",
1569 "sizes": "512x512",
1570 "type": "image/png"
1571 }
1572 ]
1573 }
1574 ]
1575 }
1576 )";
1577 OverrideManifest(kManifestTemplate,
1578 {kInstallableIconList, "/banners/image-512px.png"});
1579 AppId app_id = InstallWebApp();
1580
1581 OverrideManifest(kManifestTemplate, {kInstallableIconList, kAnotherIconSrc});
1582 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1583 ManifestUpdateResult::kAppUpdated);
1584 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
1585 ManifestUpdateResult::kAppUpdated, 1);
1586 EXPECT_EQ(GetProvider()
1587 .registrar()
1588 .GetAppShortcutsMenuItemInfos(app_id)[0]
1589 .shortcut_icon_infos[0]
1590 .url,
1591 http_server_.GetURL(kAnotherIconSrc));
1592 }
1593
IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,CheckFindsShortcutIconSizesUpdated)1594 IN_PROC_BROWSER_TEST_F(ManifestUpdateManagerBrowserTestWithShortcutsMenu,
1595 CheckFindsShortcutIconSizesUpdated) {
1596 constexpr char kManifestTemplate[] = R"(
1597 {
1598 "name": "Test app name",
1599 "start_url": ".",
1600 "scope": "/",
1601 "display": "standalone",
1602 "icons": $1,
1603 "shortcuts": [
1604 {
1605 "name": "Home",
1606 "short_name": "HM",
1607 "description": "Go home",
1608 "url": ".",
1609 "icons": [
1610 {
1611 "src": "/banners/image-512px.png",
1612 "sizes": "$2",
1613 "type": "image/png"
1614 }
1615 ]
1616 }
1617 ]
1618 }
1619 )";
1620 OverrideManifest(kManifestTemplate, {kInstallableIconList, "512x512"});
1621 AppId app_id = InstallWebApp();
1622
1623 OverrideManifest(kManifestTemplate,
1624 {kInstallableIconList,
1625 gfx::Size(kAnotherIconSize, kAnotherIconSize).ToString()});
1626 EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
1627 ManifestUpdateResult::kAppUpdated);
1628 histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
1629 ManifestUpdateResult::kAppUpdated, 1);
1630 EXPECT_EQ(GetProvider()
1631 .registrar()
1632 .GetAppShortcutsMenuItemInfos(app_id)[0]
1633 .shortcut_icon_infos[0]
1634 .square_size_px,
1635 kAnotherIconSize);
1636 }
1637
1638 } // namespace web_app
1639