Commit 9f4b2312 authored by Jiewei Qian's avatar Jiewei Qian Committed by Commit Bot

system-web-apps: capture links to SWA

A generalized OS Settings approach for capturing navigations to SWA.

This CL adds support to capture the following navigations, and navigate
in a standalone SWA window.
- Omnibox: Navigate; Open in new window
- Anchor link in a tab, click and context menu
- Anchor link in a different SWA, click and context menu
- location.href = swa_url
- window.open from a different SWA
- window.open from the current SWA

A followup CL will support capturing navigations in incognito browsers.
Currently, these navigations will open SWA in the incognito profile.

Bug: 1096345
Change-Id: I4223b8a7dd406229bca793127218d52b625ac0c6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2321912
Commit-Queue: Jiewei Qian  <qjw@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarTrent Apted <tapted@chromium.org>
Reviewed-by: default avatarGiovanni Ortuño Urquidi <ortuno@chromium.org>
Cr-Commit-Position: refs/heads/master@{#797588}
parent b69d24c8
......@@ -33,6 +33,7 @@
#include "chrome/browser/ui/status_bubble.h"
#include "chrome/browser/ui/tab_helpers.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h"
#include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/url_constants.h"
......@@ -465,6 +466,37 @@ void Navigate(NavigateParams* params) {
params->initiating_profile = source_browser->profile();
DCHECK(params->initiating_profile);
if (source_browser &&
platform_util::IsBrowserLockedFullscreen(source_browser)) {
// Block any navigation requests in locked fullscreen mode.
return;
}
// Open System Apps in their standalone window if necessary.
// TODO(crbug.com/1096345): Remove this code after we integrate with intent
// handling.
const base::Optional<web_app::SystemAppType> capturing_system_app_type =
web_app::GetCapturingSystemAppForURL(params->initiating_profile,
params->url);
if (capturing_system_app_type &&
(!params->browser ||
!web_app::IsBrowserForSystemWebApp(params->browser,
capturing_system_app_type.value()))) {
params->browser = web_app::LaunchSystemWebApp(
params->initiating_profile, capturing_system_app_type.value(),
params->url);
// It's okay to early return here, because LaunchSystemWebApp uses a
// different logic to choose (and create if necessary) a browser window for
// system apps.
//
// It's okay to skip the checks and cleanups below. The link captured system
// app will either open in its own browser window, or navigate an existing
// browser window exclusively used by this app. For the initiating browser,
// the navigation should appear to be cancelled.
return;
}
if (!AdjustNavigateParamsForURL(params))
return;
......@@ -478,12 +510,6 @@ void Navigate(NavigateParams* params) {
params->url = GURL(chrome::kExtensionInvalidRequestURL);
#endif
if (source_browser &&
platform_util::IsBrowserLockedFullscreen(source_browser)) {
// Block any navigation requests in locked fullscreen mode.
return;
}
// Trying to open a background tab when in an app browser results in
// focusing a regular browser window an opening a tab in the background
// of that window. Change the disposition to NEW_FOREGROUND_TAB so that
......
......@@ -140,6 +140,11 @@ class AppBrowserController : public TabStripModelObserver,
// Returns true if this controller is for a System Web App.
bool is_for_system_web_app() const { return system_app_type_.has_value(); }
// Returns the SystemAppType for this controller.
const base::Optional<SystemAppType>& system_app_type() const {
return system_app_type_;
}
// Returns true if AppId is non-null
bool HasAppId() const { return app_id_.has_value(); }
......
......@@ -200,6 +200,8 @@ Browser* LaunchSystemWebApp(Profile* profile,
}
}
// TODO(crbug.com/1114939): Need to make sure the browser is shown on the
// correct desktop, when used in multi-profile mode.
browser->window()->Show();
return browser;
}
......@@ -236,6 +238,22 @@ bool IsSystemWebApp(Browser* browser) {
browser->app_controller()->is_for_system_web_app();
}
bool IsBrowserForSystemWebApp(Browser* browser, SystemAppType type) {
DCHECK(browser);
return browser->app_controller() &&
browser->app_controller()->system_app_type() == type;
}
base::Optional<SystemAppType> GetCapturingSystemAppForURL(Profile* profile,
const GURL& url) {
auto* provider = WebAppProvider::Get(profile);
if (!provider)
return base::nullopt;
return provider->system_web_app_manager().GetCapturingSystemAppForURL(url);
}
gfx::Size GetSystemWebAppMinimumWindowSize(Browser* browser) {
DCHECK(browser);
if (!browser->app_controller())
......
......@@ -52,6 +52,13 @@ Browser* FindSystemWebAppBrowser(
// Returns true if the |browser| is a system web app.
bool IsSystemWebApp(Browser* browser);
// Returns the SystemAppType that should capture the |url|.
base::Optional<SystemAppType> GetCapturingSystemAppForURL(Profile* profile,
const GURL& url);
// Returns whether the |browser| hosts the system app |type|.
bool IsBrowserForSystemWebApp(Browser* browser, SystemAppType type);
// Returns the minimum window size for a system web app, or an empty size if
// the app does not specify a minimum size.
gfx::Size GetSystemWebAppMinimumWindowSize(Browser* browser);
......
......@@ -184,6 +184,7 @@ base::flat_map<SystemAppType, SystemAppInfo> CreateSystemWebApps() {
infos.at(SystemAppType::SAMPLE).enabled_origin_trials = OriginTrialsMap(
{{GetOrigin("chrome://sample-system-web-app"), {"Frobulate"}},
{GetOrigin("chrome-untrusted://sample-system-web-app"), {"Frobulate"}}});
infos.at(SystemAppType::SAMPLE).capture_navigations = true;
#endif // !defined(OFFICIAL_BUILD)
#endif // OS_CHROMEOS
......@@ -191,6 +192,11 @@ base::flat_map<SystemAppType, SystemAppInfo> CreateSystemWebApps() {
return infos;
}
bool HasSystemWebAppScheme(const GURL& url) {
return url.SchemeIs(content::kChromeUIScheme) ||
url.SchemeIs(content::kChromeUIUntrustedScheme);
}
ExternalInstallOptions CreateInstallOptionsForSystemApp(
const SystemAppInfo& info,
bool force_update,
......@@ -550,6 +556,29 @@ bool SystemWebAppManager::ShouldShowInSearch(SystemAppType type) const {
return it->second.show_in_search;
}
base::Optional<SystemAppType> SystemWebAppManager::GetCapturingSystemAppForURL(
const GURL& url) const {
if (!HasSystemWebAppScheme(url))
return base::nullopt;
base::Optional<AppId> app_id = registrar_->FindAppWithUrlInScope(url);
if (!app_id.has_value())
return base::nullopt;
base::Optional<SystemAppType> type = GetSystemAppTypeForAppId(app_id.value());
if (!type.has_value())
return base::nullopt;
const auto it = system_app_infos_.find(type);
if (it == system_app_infos_.end())
return base::nullopt;
if (!it->second.capture_navigations)
return base::nullopt;
return type;
}
gfx::Size SystemWebAppManager::GetMinimumWindowSize(const AppId& app_id) const {
auto app_type_it = app_id_to_app_type_.find(app_id);
if (app_type_it == app_id_to_app_type_.end())
......
......@@ -113,6 +113,11 @@ struct SystemAppInfo {
// If set to false, this app will be hidden from the Chrome OS search.
bool show_in_search = true;
// If set to true, navigations (e.g. Omnibox URL, anchor link) to this app
// will open in the app's window instead of the navigation's context (e.g.
// browser tab).
bool capture_navigations = false;
WebApplicationInfoFactory app_info_factory;
};
......@@ -199,6 +204,10 @@ class SystemWebAppManager {
// Returns whether the app should be shown in search.
bool ShouldShowInSearch(SystemAppType type) const;
// Returns the SystemAppType that should capture the navigation to |url|.
base::Optional<SystemAppType> GetCapturingSystemAppForURL(
const GURL& url) const;
// Returns the minimum window size for |app_id| or an empty size if the app
// doesn't specify a minimum.
gfx::Size GetMinimumWindowSize(const AppId& app_id) const;
......
......@@ -83,11 +83,10 @@ TestSystemWebAppInstallation::TestSystemWebAppInstallation(SystemAppType type,
SystemAppInfo info)
: type_(type) {
if (GetWebUIType(info.install_url) == WebUIType::kChrome) {
web_ui_controller_factory_ =
std::make_unique<TestSystemWebAppWebUIControllerFactory>(
auto factory = std::make_unique<TestSystemWebAppWebUIControllerFactory>(
GetDataSourceNameFromSystemAppInstallUrl(info.install_url));
content::WebUIControllerFactory::RegisterFactory(
web_ui_controller_factory_.get());
content::WebUIControllerFactory::RegisterFactory(factory.get());
web_ui_controller_factories_.push_back(std::move(factory));
}
test_web_app_provider_creator_ = std::make_unique<TestWebAppProviderCreator>(
......@@ -113,10 +112,8 @@ TestSystemWebAppInstallation::TestSystemWebAppInstallation() {
}
TestSystemWebAppInstallation::~TestSystemWebAppInstallation() {
if (web_ui_controller_factory_.get()) {
content::WebUIControllerFactory::UnregisterFactoryForTesting(
web_ui_controller_factory_.get());
}
for (auto& factory : web_ui_controller_factories_)
content::WebUIControllerFactory::UnregisterFactoryForTesting(factory.get());
}
// static
......@@ -224,6 +221,28 @@ TestSystemWebAppInstallation::SetUpAppWithAdditionalSearchTerms() {
SystemAppType::SETTINGS, std::move(app_info)));
}
// static
std::unique_ptr<TestSystemWebAppInstallation>
TestSystemWebAppInstallation::SetUpAppThatCapturesNavigation() {
SystemAppInfo app_info("Test", GURL("chrome://test-system-web-app/pwa.html"));
app_info.capture_navigations = true;
auto* installation = new TestSystemWebAppInstallation(SystemAppType::HELP,
std::move(app_info));
// Add a helper system app to test capturing links from it.
const GURL kInitiatingAppUrl = GURL("chrome://initiating-app/pwa.html");
installation->extra_apps_.insert_or_assign(
SystemAppType::SETTINGS,
SystemAppInfo("Initiating App", kInitiatingAppUrl));
auto factory = std::make_unique<TestSystemWebAppWebUIControllerFactory>(
kInitiatingAppUrl.host());
content::WebUIControllerFactory::RegisterFactory(factory.get());
installation->web_ui_controller_factories_.push_back(std::move(factory));
return base::WrapUnique(installation);
}
// static
std::unique_ptr<TestSystemWebAppInstallation>
TestSystemWebAppInstallation::SetUpChromeUntrustedApp() {
......@@ -236,6 +255,11 @@ TestSystemWebAppInstallation::SetUpChromeUntrustedApp() {
std::unique_ptr<KeyedService>
TestSystemWebAppInstallation::CreateWebAppProvider(SystemAppInfo info,
Profile* profile) {
DCHECK(!extra_apps_.contains(type_.value()));
base::flat_map<SystemAppType, SystemAppInfo> apps(extra_apps_);
apps.insert_or_assign(type_.value(), info);
profile_ = profile;
if (GetWebUIType(info.install_url) == WebUIType::kChromeUntrusted) {
web_app::AddTestURLDataSource(
......@@ -245,7 +269,7 @@ TestSystemWebAppInstallation::CreateWebAppProvider(SystemAppInfo info,
auto provider = std::make_unique<TestWebAppProvider>(profile);
auto system_web_app_manager = std::make_unique<SystemWebAppManager>(profile);
system_web_app_manager->SetSystemAppsForTesting({{type_.value(), info}});
system_web_app_manager->SetSystemAppsForTesting(apps);
system_web_app_manager->SetUpdatePolicyForTesting(update_policy_);
provider->SetSystemWebAppManager(std::move(system_web_app_manager));
provider->Start();
......@@ -301,7 +325,7 @@ SystemAppType TestSystemWebAppInstallation::GetType() {
}
void TestSystemWebAppInstallation::SetManifest(std::string manifest) {
web_ui_controller_factory_->set_manifest(std::move(manifest));
web_ui_controller_factories_[0]->set_manifest(std::move(manifest));
}
void TestSystemWebAppInstallation::RegisterAutoGrantedPermissions(
......
......@@ -54,6 +54,11 @@ class TestSystemWebAppInstallation {
static std::unique_ptr<TestSystemWebAppInstallation>
SetUpAppWithAdditionalSearchTerms();
// This method additionally sets up a helper SystemAppType::SETTING system app
// for testing capturing links from a different SWA.
static std::unique_ptr<TestSystemWebAppInstallation>
SetUpAppThatCapturesNavigation();
static std::unique_ptr<TestSystemWebAppInstallation>
SetUpChromeUntrustedApp();
......@@ -90,9 +95,10 @@ class TestSystemWebAppInstallation {
std::unique_ptr<TestWebAppProviderCreator> test_web_app_provider_creator_;
// nullopt if SetUpWithoutApps() was used.
const base::Optional<SystemAppType> type_;
std::unique_ptr<TestSystemWebAppWebUIControllerFactory>
web_ui_controller_factory_;
std::vector<std::unique_ptr<TestSystemWebAppWebUIControllerFactory>>
web_ui_controller_factories_;
std::set<ContentSettingsType> auto_granted_permissions_;
base::flat_map<SystemAppType, SystemAppInfo> extra_apps_;
};
} // namespace web_app
......
......@@ -28,6 +28,11 @@ constexpr char kPwaHtml[] =
</html>
)";
constexpr char kPage2Html[] =
R"(
<!DOCTYPE html><title>Page 2</title>
)";
constexpr char kSwJs[] = "globalThis.addEventListener('fetch', event => {});";
} // namespace
......@@ -47,7 +52,8 @@ void AddTestURLDataSource(const std::string& source_name,
data_source->AddResourcePath("icon-256.png", IDR_PRODUCT_LOGO_256);
data_source->SetRequestFilter(
base::BindLambdaForTesting([](const std::string& path) {
return path == "manifest.json" || path == "pwa.html";
return path == "manifest.json" || path == "pwa.html" ||
path == "page2.html";
}),
base::BindLambdaForTesting(
[manifest_text](const std::string& id,
......@@ -60,6 +66,8 @@ void AddTestURLDataSource(const std::string& source_name,
ref_contents->data() = kPwaHtml;
else if (id == "sw.js")
ref_contents->data() = kSwJs;
else if (id == "page2.html")
ref_contents->data() = kPage2Html;
else
NOTREACHED();
......
......@@ -40,6 +40,11 @@ std::unique_ptr<content::WebUIController>
TestSystemWebAppWebUIControllerFactory::CreateWebUIControllerForURL(
content::WebUI* web_ui,
const GURL& url) {
if (!url.SchemeIs(content::kChromeUIScheme) ||
url.host_piece() != source_name_) {
return nullptr;
}
return std::make_unique<TestSystemWebAppWebUIController>(source_name_,
&manifest_, web_ui);
}
......@@ -47,7 +52,7 @@ TestSystemWebAppWebUIControllerFactory::CreateWebUIControllerForURL(
content::WebUI::TypeID TestSystemWebAppWebUIControllerFactory::GetWebUIType(
content::BrowserContext* browser_context,
const GURL& url) {
if (url.SchemeIs(content::kChromeUIScheme))
if (UseWebUIForURL(browser_context, url))
return reinterpret_cast<content::WebUI::TypeID>(1);
return content::WebUI::kNoWebUI;
......@@ -56,11 +61,12 @@ content::WebUI::TypeID TestSystemWebAppWebUIControllerFactory::GetWebUIType(
bool TestSystemWebAppWebUIControllerFactory::UseWebUIForURL(
content::BrowserContext* browser_context,
const GURL& url) {
return url.SchemeIs(content::kChromeUIScheme);
return url.SchemeIs(content::kChromeUIScheme) &&
url.host_piece() == source_name_;
}
bool TestSystemWebAppWebUIControllerFactory::UseWebUIBindingsForURL(
content::BrowserContext* browser_context,
const GURL& url) {
return url.SchemeIs(content::kChromeUIScheme);
return UseWebUIForURL(browser_context, url);
}
......@@ -1924,6 +1924,7 @@ if (!is_android) {
"../browser/ui/views/extensions/extension_dialog_browsertest.cc",
"../browser/ui/views/web_apps/pwa_confirmation_bubble_view_browsertest.cc",
"../browser/ui/views/web_apps/web_app_confirmation_view_browsertest.cc",
"../browser/ui/web_applications/test/system_web_app_ui_browsertest.cc",
"../browser/ui/web_applications/test/web_app_navigation_browsertest.cc",
"../browser/ui/web_applications/test/web_app_navigation_browsertest.h",
]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment