blob: 202ee264ec095b9efbf07c502f7755baa846c4bc [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "chrome/browser/web_applications/commands/manifest_silent_update_command.h"
#include "chrome/browser/web_applications/test/fake_web_app_origin_association_manager.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/fake_web_contents_manager.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/browser/web_applications/test/web_app_test_observers.h"
#include "chrome/browser/web_applications/web_app_command_scheduler.h"
#include "chrome/browser/web_applications/web_app_origin_association_manager.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_scope.h"
#include "chrome/common/chrome_features.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace web_app {
namespace {
class WebAppScopeExtensionsTest : public WebAppTest {
public:
WebAppScopeExtensionsTest() {
feature_list_.InitWithFeatures(
/*enabled_features=*/{features::kWebAppPredictableAppUpdating,
features::kWebAppUsePrimaryIcon},
/*disabled_features=*/{});
}
~WebAppScopeExtensionsTest() override = default;
void SetUp() override {
WebAppTest::SetUp();
auto fake_association_manager =
std::make_unique<FakeWebAppOriginAssociationManager>();
fake_association_manager->set_pass_through(true);
fake_provider().SetOriginAssociationManager(
std::move(fake_association_manager));
test::AwaitStartWebAppProviderAndSubsystems(profile());
}
protected:
webapps::AppId InstallWebAppWithScope(const GURL& scope) {
SetupPageNoScopeExtensions(scope);
return test::InstallForWebContents(
profile(), web_contents(),
webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON);
}
webapps::AppId InstallWebAppWithScopeExtensions(
const GURL& scope,
std::vector<blink::mojom::ManifestScopeExtensionPtr> scope_extensions) {
SetupPageWithScopeExtensions(scope, std::move(scope_extensions));
return test::InstallForWebContents(
profile(), web_contents(),
webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON);
}
FakeWebContentsManager& web_contents_manager() {
return static_cast<FakeWebContentsManager&>(
fake_provider().web_contents_manager());
}
void SetupPageNoScopeExtensions(const GURL& url) {
web_contents_manager().SetUrlLoaded(web_contents(), url);
web_contents_manager().CreateBasicInstallPageState(
/*install_url=*/url,
/*manifest_url=*/GURL("https://www.example.com/manifest.json"),
/*start_url=*/url);
}
void SetupPageWithScopeExtensions(
const GURL& url,
std::vector<blink::mojom::ManifestScopeExtensionPtr> scope_extensions) {
SetupPageNoScopeExtensions(url);
auto& page_state = web_contents_manager().GetOrCreatePageState(url);
page_state.manifest_before_default_processing->scope_extensions =
std::move(scope_extensions);
}
WebAppRegistrar& registrar() { return fake_provider().registrar_unsafe(); }
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(WebAppScopeExtensionsTest, TestScopeNotifiedOnReinstall) {
const GURL scope("https://example.com/");
webapps::AppId app_id = InstallWebAppWithScope(scope);
// Re-install the app with extended scope, and verify that the scope observer
// is notified.
WebAppTestRegistryObserverAdapter adapter(
&fake_provider().registrar_unsafe());
base::test::TestFuture<const webapps::AppId&, const WebAppScope&> future;
adapter.SetWebAppEffectiveScopeChangedDelegate(future.GetRepeatingCallback());
std::vector<blink::mojom::ManifestScopeExtensionPtr> scope_extensions;
scope_extensions.push_back(blink::mojom::ManifestScopeExtension::New(
url::Origin::Create(GURL("https://example.org")),
/*has_origin_wildcard=*/false));
scope_extensions.push_back(blink::mojom::ManifestScopeExtension::New(
url::Origin::Create(GURL("https://example.net")),
/*has_origin_wildcard=*/true));
InstallWebAppWithScopeExtensions(scope, std::move(scope_extensions));
ASSERT_TRUE(future.Wait());
EXPECT_EQ(app_id, future.Get<webapps::AppId>());
// Now re-install without the scope extensions, and verify that the scope
// observer is notified.
future.Clear();
InstallWebAppWithScope(scope);
ASSERT_TRUE(future.Wait());
EXPECT_EQ(app_id, future.Get<webapps::AppId>());
}
TEST_F(WebAppScopeExtensionsTest, TestScopeNotifiedOnAddedUpdate) {
const GURL scope("https://example.com/");
webapps::AppId app_id = InstallWebAppWithScope(scope);
// Update the app with extended scope, and verify that the scope observer
// is notified.
WebAppTestRegistryObserverAdapter adapter(
&fake_provider().registrar_unsafe());
base::test::TestFuture<const webapps::AppId&, const WebAppScope&> future;
adapter.SetWebAppEffectiveScopeChangedDelegate(future.GetRepeatingCallback());
// Do the update.
std::vector<blink::mojom::ManifestScopeExtensionPtr> scope_extensions;
scope_extensions.push_back(blink::mojom::ManifestScopeExtension::New(
url::Origin::Create(GURL("https://example.org")),
/*has_origin_wildcard=*/false));
scope_extensions.push_back(blink::mojom::ManifestScopeExtension::New(
url::Origin::Create(GURL("https://example.net")),
/*has_origin_wildcard=*/true));
SetupPageWithScopeExtensions(scope, std::move(scope_extensions));
base::test::TestFuture<ManifestSilentUpdateCompletionInfo>
manifest_silent_update_future;
fake_provider().scheduler().ScheduleManifestSilentUpdate(
*web_contents(), /*previous_time_for_silent_icon_update=*/std::nullopt,
manifest_silent_update_future.GetCallback());
ASSERT_TRUE(manifest_silent_update_future.Wait());
EXPECT_EQ(manifest_silent_update_future.Take().result,
ManifestSilentUpdateCheckResult::kAppSilentlyUpdated);
// Wait for the scope change to be observed.
ASSERT_TRUE(future.Wait());
EXPECT_EQ(app_id, future.Get<webapps::AppId>());
// Double check the scope extensions are there.
WebAppScope effective_scope = future.Get<WebAppScope>();
EXPECT_TRUE(effective_scope.IsInScope(GURL("https://example.org")));
EXPECT_TRUE(effective_scope.IsInScope(GURL("https://example.net")));
EXPECT_TRUE(effective_scope.IsInScope(GURL("https://sub.example.net")));
}
TEST_F(WebAppScopeExtensionsTest, TestScopeNotifiedOnRemovedUpdate) {
const GURL scope("https://example.com/");
std::vector<blink::mojom::ManifestScopeExtensionPtr> scope_extensions;
scope_extensions.push_back(blink::mojom::ManifestScopeExtension::New(
url::Origin::Create(GURL("https://example.org")),
/*has_origin_wildcard=*/false));
scope_extensions.push_back(blink::mojom::ManifestScopeExtension::New(
url::Origin::Create(GURL("https://example.net")),
/*has_origin_wildcard=*/true));
const webapps::AppId app_id =
InstallWebAppWithScopeExtensions(scope, std::move(scope_extensions));
// Update the app with no extensions, and verify that the scope observer
// is notified.
WebAppTestRegistryObserverAdapter adapter(
&fake_provider().registrar_unsafe());
base::test::TestFuture<const webapps::AppId&, const WebAppScope&> future;
adapter.SetWebAppEffectiveScopeChangedDelegate(future.GetRepeatingCallback());
// Do the update.
SetupPageNoScopeExtensions(scope);
base::test::TestFuture<ManifestSilentUpdateCompletionInfo>
manifest_silent_update_future;
fake_provider().scheduler().ScheduleManifestSilentUpdate(
*web_contents(), /*previous_time_for_silent_icon_update=*/std::nullopt,
manifest_silent_update_future.GetCallback());
ASSERT_TRUE(manifest_silent_update_future.Wait());
EXPECT_EQ(manifest_silent_update_future.Take().result,
ManifestSilentUpdateCheckResult::kAppSilentlyUpdated);
// Wait for the scope change to be observed.
ASSERT_TRUE(future.Wait());
EXPECT_EQ(app_id, future.Get<webapps::AppId>());
// Double check the scope extensions are gone.
WebAppScope effective_scope = future.Get<WebAppScope>();
EXPECT_FALSE(effective_scope.IsInScope(GURL("https://example.org")));
EXPECT_FALSE(effective_scope.IsInScope(GURL("https://example.net")));
EXPECT_FALSE(effective_scope.IsInScope(GURL("https://sub.example.net")));
}
} // namespace
} // namespace web_app