Don't offer desktop PWA installation if the site is in-scope of an existing app.
Prior to this CL, desktop PWA installation only checked for the presence
of an existing app with the exact same start URL as the current site.
This means that once a site is installed, if it changes its start URL,
desktop Chrome will think the site is not installed, and allow it to be
installed again.
This CL makes the desktop behaviour match Android: check if there is an
existing app whose scope encloses the start URL of the current site, and
disallow installation if that is the case.
This means that "nested" PWAs can no longer be installed on desktop, but
we need a more robust solution involving more stable PWA ids to make
that work well. It is better to have consistent behaviour between
mobile and desktop for now.
BUG=1015317
Change-Id: I031e58dedbf4326aa9260cdb11ce1845e63b1ea9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2172188
Reviewed-by: Alan Cutter <[email protected]>
Reviewed-by: Daniel Murphy <[email protected]>
Commit-Queue: Dominick Ng <[email protected]>
Cr-Commit-Position: refs/heads/master@{#764971}
diff --git a/chrome/browser/android/shortcut_helper.cc b/chrome/browser/android/shortcut_helper.cc
index a20cc07..cd45a0a 100644
--- a/chrome/browser/android/shortcut_helper.cc
+++ b/chrome/browser/android/shortcut_helper.cc
@@ -330,11 +330,8 @@
// static
bool ShortcutHelper::IsWebApkInstalled(content::BrowserContext* browser_context,
- const GURL& start_url,
- const GURL& manifest_url) {
- return !QueryFirstWebApkPackage(start_url).empty() ||
- (!manifest_url.is_empty() && WebApkInstallService::Get(browser_context)
- ->IsInstallInProgress(manifest_url));
+ const GURL& url) {
+ return !QueryFirstWebApkPackage(url).empty();
}
// static
diff --git a/chrome/browser/android/shortcut_helper.h b/chrome/browser/android/shortcut_helper.h
index 0c072b1..561ff841 100644
--- a/chrome/browser/android/shortcut_helper.h
+++ b/chrome/browser/android/shortcut_helper.h
@@ -93,11 +93,9 @@
// Returns an empty string if there are no matches.
static std::string QueryFirstWebApkPackage(const GURL& url);
- // Returns true if WebAPKs are enabled and there is an installed WebAPK which
- // can handle |start_url|, or there is one is being installed.
+ // Returns true if there is an installed WebAPK which can handle |url|.
static bool IsWebApkInstalled(content::BrowserContext* browser_context,
- const GURL& start_url,
- const GURL& manifest_url);
+ const GURL& url);
// Returns true if there is a WebAPK installed under |origin|, and false
// otherwise.
diff --git a/chrome/browser/banners/app_banner_manager.cc b/chrome/browser/banners/app_banner_manager.cc
index d91b35a..d75db75d 100644
--- a/chrome/browser/banners/app_banner_manager.cc
+++ b/chrome/browser/banners/app_banner_manager.cc
@@ -17,6 +17,7 @@
#include "chrome/browser/banners/app_banner_settings_helper.h"
#include "chrome/browser/engagement/site_engagement_service.h"
#include "chrome/browser/installable/installable_metrics.h"
+#include "chrome/browser/installable/installable_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
@@ -289,7 +290,8 @@
}
bool AppBannerManager::IsWebAppConsideredInstalled() {
- return false;
+ return IsWebAppInstalledForUrl(web_contents()->GetBrowserContext(),
+ manifest_.start_url);
}
bool AppBannerManager::ShouldAllowWebAppReplacementInstall() {
diff --git a/chrome/browser/banners/app_banner_manager.h b/chrome/browser/banners/app_banner_manager.h
index 00bdb1c..e2bfa6c8 100644
--- a/chrome/browser/banners/app_banner_manager.h
+++ b/chrome/browser/banners/app_banner_manager.h
@@ -212,8 +212,7 @@
const blink::Manifest::RelatedApplication& related_app) const = 0;
// Returns whether the current page is already installed as a web app, or
- // should be considered installed. On Android, we rely on a heuristic that
- // may yield false negatives or false positives (crbug.com/786268).
+ // should be considered as installed.
virtual bool IsWebAppConsideredInstalled();
// Returns whether the installed web app at the current page can be
diff --git a/chrome/browser/banners/app_banner_manager_android.cc b/chrome/browser/banners/app_banner_manager_android.cc
index fdf39f0..99ba201 100644
--- a/chrome/browser/banners/app_banner_manager_android.cc
+++ b/chrome/browser/banners/app_banner_manager_android.cc
@@ -14,6 +14,7 @@
#include "chrome/browser/android/shortcut_helper.h"
#include "chrome/browser/android/tab_android.h"
#include "chrome/browser/android/tab_web_contents_delegate_android.h"
+#include "chrome/browser/android/webapk/webapk_install_service.h"
#include "chrome/browser/android/webapk/webapk_metrics.h"
#include "chrome/browser/android/webapk/webapk_ukm_recorder.h"
#include "chrome/browser/android/webapk/webapk_web_manifest_checker.h"
@@ -154,12 +155,12 @@
}
bool AppBannerManagerAndroid::IsWebAppConsideredInstalled() {
- // Whether a WebAPK is installed or is being installed. IsWebApkInstalled
- // will still detect the presence of a WebAPK even if Chrome's data is
- // cleared.
- DCHECK(!manifest_.IsEmpty());
- return ShortcutHelper::IsWebApkInstalled(web_contents()->GetBrowserContext(),
- manifest_.start_url, manifest_url_);
+ // Also check if a WebAPK is currently being installed. Installation may take
+ // some time, so ensure we don't accidentally allow a new installation whilst
+ // one is in flight for the current site.
+ return AppBannerManager::IsWebAppConsideredInstalled() ||
+ WebApkInstallService::Get(web_contents()->GetBrowserContext())
+ ->IsInstallInProgress(manifest_url_);
}
InstallableParams
diff --git a/chrome/browser/banners/app_banner_manager_desktop.cc b/chrome/browser/banners/app_banner_manager_desktop.cc
index 8ff1be7f..4fdf2e2c 100644
--- a/chrome/browser/banners/app_banner_manager_desktop.cc
+++ b/chrome/browser/banners/app_banner_manager_desktop.cc
@@ -170,14 +170,12 @@
return false;
}
-bool AppBannerManagerDesktop::IsWebAppConsideredInstalled() {
- DCHECK(!manifest_.IsEmpty());
- return registrar().IsLocallyInstalled(manifest_.start_url);
-}
-
bool AppBannerManagerDesktop::ShouldAllowWebAppReplacementInstall() {
+ // Only allow replacement install if this specific app is already installed.
web_app::AppId app_id = web_app::GenerateAppIdFromURL(manifest_.start_url);
- DCHECK(registrar().IsLocallyInstalled(app_id));
+ if (!registrar().IsLocallyInstalled(app_id))
+ return false;
+
if (IsExternallyInstalledWebApp())
return false;
auto display_mode = registrar().GetAppUserDisplayMode(app_id);
diff --git a/chrome/browser/banners/app_banner_manager_desktop.h b/chrome/browser/banners/app_banner_manager_desktop.h
index 43392bd..fcb53e1c 100644
--- a/chrome/browser/banners/app_banner_manager_desktop.h
+++ b/chrome/browser/banners/app_banner_manager_desktop.h
@@ -72,7 +72,6 @@
web_app::AppRegistrar& registrar();
// AppBannerManager overrides.
- bool IsWebAppConsideredInstalled() override;
bool ShouldAllowWebAppReplacementInstall() override;
void ShowBannerUi(WebappInstallSource install_source) override;
diff --git a/chrome/browser/installable/installable_utils.cc b/chrome/browser/installable/installable_utils.cc
index 5dda8e0..1ae8234 100644
--- a/chrome/browser/installable/installable_utils.cc
+++ b/chrome/browser/installable/installable_utils.cc
@@ -20,8 +20,9 @@
bool IsWebAppInstalledForUrl(content::BrowserContext* browser_context,
const GURL& url) {
#if defined(OS_ANDROID)
- return ShortcutHelper::IsWebApkInstalled(browser_context, url,
- GURL::EmptyGURL());
+ // This will still detect the presence of a WebAPK even if Chrome's data is
+ // cleared
+ return ShortcutHelper::IsWebApkInstalled(browser_context, url);
#else
return web_app::FindInstalledAppWithUrlInScope(
Profile::FromBrowserContext(browser_context), url)
diff --git a/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc b/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc
index 9ff63049..f938f3c 100644
--- a/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_action/pwa_install_view_browsertest.cc
@@ -180,6 +180,10 @@
return https_server_.GetURL("/banners/manifest_test_page.html");
}
+ GURL GetNestedInstallableAppURL() {
+ return https_server_.GetURL("/banners/scope_a/page_1.html");
+ }
+
GURL GetNonInstallableAppURL() {
return https_server_.GetURL("app.com", "/simple.html");
}
@@ -280,6 +284,29 @@
EXPECT_FALSE(pwa_install_view_->GetVisible());
}
+// Tests that the plus icon is not shown when an outer app is installed and we
+// navigate to a nested app.
+IN_PROC_BROWSER_TEST_P(PwaInstallViewBrowserTest,
+ NestedPwaIsNotInstallableWhenOuterPwaIsInstalled) {
+ // When nothing is installed, the nested PWA should be installable.
+ StartNavigateToUrl(GetNestedInstallableAppURL());
+ ASSERT_TRUE(app_banner_manager_->WaitForInstallableCheck());
+ EXPECT_TRUE(pwa_install_view_->GetVisible());
+
+ // Install the outer PWA.
+ ASSERT_TRUE(OpenTab(GetInstallableAppURL()).installable);
+ ExecutePwaInstallIcon();
+
+ // Use a new tab because installed app may have opened in new window.
+ OpenTabResult result = OpenTab(GetNestedInstallableAppURL());
+
+ // The nested PWA should now not be installable.
+ EXPECT_EQ(
+ result.app_banner_manager->GetInstallableWebAppCheckResultForTesting(),
+ banners::AppBannerManager::InstallableWebAppCheckResult::kNo);
+ EXPECT_FALSE(pwa_install_view_->GetVisible());
+}
+
// Tests that the plus icon is shown when an existing app is installed and set
// to open in a tab.
IN_PROC_BROWSER_TEST_P(PwaInstallViewBrowserTest,