[apps] Pull out user link capturing throttle logic for cross-platform
This change refactors the user link capturing logic inside of both
the AppsNavigationThrottle and the ChromeOsAppsNavigationThrottle into
the LinkCapturingNavigationThrottle with a Delegate class for per-
platform specialization.
This split divorces the core link capturing logic from both
App Service and web apps systems so it can be bound to either as a
provider of apps with URL scopes.
The dependency graph will be:
ChromeContentBrowserClient
/ | \
v | v
WebAppLinkCapturing (next) | AppServiceLinkCapturing
\ | /
v v v
LinkCapturingNavigationThrottle
Design doc: go/user-link-capturing-throttle
The next patch will create the throttle used for non-CrOS platforms.
Bug: b/269773608
Change-Id: I693ab4b78bd4ed61ba4e641b5f167d64c5a4969c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4727423
Reviewed-by: Yuichiro Hanada <yhanada@chromium.org>
Reviewed-by: Yilkal Abe <yilkal@chromium.org>
Reviewed-by: Dibyajyoti Pal <dibyapal@chromium.org>
Reviewed-by: Alan Cutter <alancutter@chromium.org>
Commit-Queue: Daniel Murphy <dmurph@chromium.org>
Reviewed-by: Tim Sergeant <tsergeant@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1188097}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 0fddec2..debf3b3 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3541,8 +3541,6 @@
"accessibility/invert_bubble_prefs.h",
"accessibility/live_translate_controller_factory.cc",
"accessibility/live_translate_controller_factory.h",
- "apps/intent_helper/apps_navigation_throttle.cc",
- "apps/intent_helper/apps_navigation_throttle.h",
"apps/intent_helper/apps_navigation_types.cc",
"apps/intent_helper/apps_navigation_types.h",
"apps/intent_helper/intent_picker_auto_display_prefs.cc",
@@ -3555,8 +3553,6 @@
"apps/intent_helper/intent_picker_helpers.h",
"apps/intent_helper/intent_picker_internal.cc",
"apps/intent_helper/intent_picker_internal.h",
- "apps/intent_helper/page_transition_util.cc",
- "apps/intent_helper/page_transition_util.h",
"autocomplete/tab_matcher_desktop.cc",
"autocomplete/tab_matcher_desktop.h",
"background/background_contents.cc",
@@ -4491,6 +4487,7 @@
"//chrome/app/theme:chrome_unscaled_resources_grit",
"//chrome/app/vector_icons",
"//chrome/browser/apps/app_service",
+ "//chrome/browser/apps/link_capturing",
"//chrome/browser/cart:mojo_bindings",
"//chrome/browser/companion/visual_search",
"//chrome/browser/enterprise/connectors/analysis:features",
@@ -5637,8 +5634,6 @@
if (is_chromeos) {
sources += [
- "apps/intent_helper/chromeos_apps_navigation_throttle.cc",
- "apps/intent_helper/chromeos_apps_navigation_throttle.h",
"apps/intent_helper/chromeos_disabled_apps_throttle.cc",
"apps/intent_helper/chromeos_disabled_apps_throttle.h",
"apps/intent_helper/chromeos_intent_picker_helpers.cc",
diff --git a/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc b/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
deleted file mode 100644
index d756d70..0000000
--- a/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/apps/intent_helper/apps_navigation_throttle.h"
-
-#include <utility>
-
-#include "chrome/browser/apps/app_service/app_launch_params.h"
-#include "chrome/browser/apps/app_service/app_service_proxy.h"
-#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
-#include "chrome/browser/apps/app_service/browser_app_launcher.h"
-#include "chrome/browser/apps/intent_helper/intent_picker_internal.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_finder.h"
-#include "chrome/browser/ui/browser_list.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/web_applications/app_browser_controller.h"
-#include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
-#include "chrome/browser/web_applications/web_app.h"
-#include "chrome/browser/web_applications/web_app_helpers.h"
-#include "chrome/browser/web_applications/web_app_provider.h"
-#include "chrome/browser/web_applications/web_app_registrar.h"
-#include "chrome/browser/web_applications/web_app_tab_helper.h"
-#include "chrome/common/chrome_features.h"
-#include "content/public/browser/browser_context.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/web_contents.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "third_party/blink/public/common/features.h"
-#include "ui/gfx/image/image.h"
-#include "url/origin.h"
-
-namespace {
-
-using ThrottleCheckResult = content::NavigationThrottle::ThrottleCheckResult;
-
-} // namespace
-
-namespace apps {
-
-// static
-std::unique_ptr<content::NavigationThrottle>
-AppsNavigationThrottle::MaybeCreate(content::NavigationHandle* handle) {
- // Don't handle navigations in subframes or main frames that are in a nested
- // frame tree (e.g. portals, fenced-frame). We specifically allow
- // prerendering navigations so that we can destroy the prerender. Opening an
- // app must only happen when the user intentionally navigates; however, for a
- // prerender, the prerender-activating navigation doesn't run throttles so we
- // must cancel it during initial loading to get a standard (non-prerendering)
- // navigation at link-click-time.
- if (!handle->IsInPrimaryMainFrame() && !handle->IsInPrerenderedMainFrame())
- return nullptr;
-
- content::WebContents* web_contents = handle->GetWebContents();
- if (!ShouldCheckAppsForUrl(web_contents))
- return nullptr;
-
- return std::make_unique<AppsNavigationThrottle>(handle);
-}
-
-AppsNavigationThrottle::AppsNavigationThrottle(
- content::NavigationHandle* navigation_handle)
- : content::NavigationThrottle(navigation_handle) {}
-
-AppsNavigationThrottle::~AppsNavigationThrottle() = default;
-
-const char* AppsNavigationThrottle::GetNameForLogging() {
- return "AppsNavigationThrottle";
-}
-
-ThrottleCheckResult AppsNavigationThrottle::WillStartRequest() {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- starting_url_ = GetStartingGURL(navigation_handle());
- return HandleRequest();
-}
-
-ThrottleCheckResult AppsNavigationThrottle::WillRedirectRequest() {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- return HandleRequest();
-}
-
-bool AppsNavigationThrottle::ShouldCancelNavigation(
- content::NavigationHandle* handle) {
- return false;
-}
-
-bool AppsNavigationThrottle::navigate_from_link() const {
- return navigate_from_link_;
-}
-
-ThrottleCheckResult AppsNavigationThrottle::HandleRequest() {
- content::NavigationHandle* handle = navigation_handle();
- // If the navigation won't update the current document, don't check intent for
- // the navigation.
- if (handle->IsSameDocument())
- return content::NavigationThrottle::PROCEED;
-
- content::WebContents* web_contents = handle->GetWebContents();
- const GURL& url = handle->GetURL();
- navigate_from_link_ = IsNavigateFromLink(handle);
-
- // Do not automatically launch the app if we shouldn't override url loading,
- // or if we don't have a browser, or we are already in an app browser.
- if (ShouldOverrideUrlLoading(starting_url_, url) &&
- !InAppBrowser(web_contents)) {
- // Handles apps that are automatically launched and the navigation needs to
- // be cancelled. This only applies on the new intent picker system, because
- // we don't need to defer the navigation to find out preferred app anymore.
- if (ShouldCancelNavigation(handle)) {
- return content::NavigationThrottle::CANCEL_AND_IGNORE;
- }
- }
-
- return content::NavigationThrottle::PROCEED;
-}
-
-} // namespace apps
diff --git a/chrome/browser/apps/intent_helper/apps_navigation_throttle.h b/chrome/browser/apps/intent_helper/apps_navigation_throttle.h
deleted file mode 100644
index 0971220..0000000
--- a/chrome/browser/apps/intent_helper/apps_navigation_throttle.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_APPS_INTENT_HELPER_APPS_NAVIGATION_THROTTLE_H_
-#define CHROME_BROWSER_APPS_INTENT_HELPER_APPS_NAVIGATION_THROTTLE_H_
-
-#include <memory>
-#include <vector>
-
-#include "base/gtest_prod_util.h"
-#include "content/public/browser/navigation_throttle.h"
-#include "url/gurl.h"
-
-namespace content {
-class NavigationHandle;
-} // namespace content
-
-namespace apps {
-
-// Allows canceling a navigation to instead be routed to an installed app.
-class AppsNavigationThrottle : public content::NavigationThrottle {
- public:
- using ThrottleCheckResult = content::NavigationThrottle::ThrottleCheckResult;
-
- // Possibly creates a navigation throttle that checks if any installed apps
- // can handle the URL being navigated to.
- static std::unique_ptr<content::NavigationThrottle> MaybeCreate(
- content::NavigationHandle* handle);
-
- explicit AppsNavigationThrottle(content::NavigationHandle* navigation_handle);
- AppsNavigationThrottle(const AppsNavigationThrottle&) = delete;
- AppsNavigationThrottle& operator=(const AppsNavigationThrottle&) = delete;
- ~AppsNavigationThrottle() override;
-
- // content::NavigationHandle overrides
- const char* GetNameForLogging() override;
- ThrottleCheckResult WillStartRequest() override;
- ThrottleCheckResult WillRedirectRequest() override;
-
- protected:
- virtual bool ShouldCancelNavigation(content::NavigationHandle* handle);
-
- bool navigate_from_link() const;
-
- GURL starting_url_;
-
- private:
- ThrottleCheckResult HandleRequest();
-
- // Keeps track of whether the navigation is coming from a link or not. If the
- // navigation is not from a link, we will not show the pop up for the intent
- // picker bubble.
- bool navigate_from_link_ = false;
-};
-
-} // namespace apps
-
-#endif // CHROME_BROWSER_APPS_INTENT_HELPER_APPS_NAVIGATION_THROTTLE_H_
diff --git a/chrome/browser/apps/intent_helper/chromeos_apps_navigation_throttle.cc b/chrome/browser/apps/intent_helper/chromeos_apps_navigation_throttle.cc
deleted file mode 100644
index 25d53cc..0000000
--- a/chrome/browser/apps/intent_helper/chromeos_apps_navigation_throttle.cc
+++ /dev/null
@@ -1,305 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/apps/intent_helper/chromeos_apps_navigation_throttle.h"
-
-#include <memory>
-#include <sstream>
-#include <string>
-#include <utility>
-
-#include "ash/webui/projector_app/public/cpp/projector_app_constants.h"
-#include "base/functional/bind.h"
-#include "base/functional/callback_forward.h"
-#include "base/functional/callback_helpers.h"
-#include "base/memory/values_equivalent.h"
-#include "base/memory/weak_ptr.h"
-#include "base/task/sequenced_task_runner_helpers.h"
-#include "base/task/single_thread_task_runner.h"
-#include "base/time/default_tick_clock.h"
-#include "base/time/tick_clock.h"
-#include "chrome/browser/apps/app_service/app_service_proxy.h"
-#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
-#include "chrome/browser/apps/app_service/launch_utils.h"
-#include "chrome/browser/apps/intent_helper/apps_navigation_types.h"
-#include "chrome/browser/apps/intent_helper/chromeos_intent_picker_helpers.h"
-#include "chrome/browser/apps/intent_helper/intent_picker_internal.h"
-#include "chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.h"
-#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
-#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
-#include "chrome/browser/web_applications/web_app_helpers.h"
-#include "chrome/browser/web_applications/web_app_id_constants.h"
-#include "chrome/browser/web_applications/web_app_provider.h"
-#include "chrome/browser/web_applications/web_app_tab_helper.h"
-#include "chrome/common/chrome_features.h"
-#include "components/keep_alive_registry/keep_alive_types.h"
-#include "components/keep_alive_registry/scoped_keep_alive.h"
-#include "components/policy/core/common/policy_pref_names.h"
-#include "components/prefs/pref_service.h"
-#include "components/services/app_service/public/cpp/app_launch_util.h"
-#include "components/services/app_service/public/cpp/app_types.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/web_contents.h"
-#include "ui/display/types/display_constants.h"
-
-namespace apps {
-
-namespace {
-
-using ThrottleCheckResult = content::NavigationThrottle::ThrottleCheckResult;
-
-// Usually we want to only capture navigations from clicking a link. For a
-// subset of apps, we want to capture typing into the omnibox as well.
-bool ShouldOnlyCaptureLinks(const std::vector<std::string>& app_ids) {
- for (const auto& app_id : app_ids) {
- if (app_id == ash::kChromeUIUntrustedProjectorSwaAppId) {
- return false;
- }
- }
- return true;
-}
-
-bool IsSystemWebApp(Profile* profile, const std::string& app_id) {
- bool is_system_web_app = false;
- apps::AppServiceProxyFactory::GetForProfile(profile)
- ->AppRegistryCache()
- .ForOneApp(app_id, [&is_system_web_app](const apps::AppUpdate& update) {
- if (update.InstallReason() == apps::InstallReason::kSystem) {
- is_system_web_app = true;
- }
- });
- return is_system_web_app;
-}
-
-// This function redirects an external untrusted |url| to a privileged trusted
-// one for SWAs, if applicable.
-GURL RedirectUrlIfSwa(Profile* profile,
- const std::string& app_id,
- const GURL& url,
- const base::TickClock* clock) {
- if (!IsSystemWebApp(profile, app_id)) {
- return url;
- }
-
- // Projector:
- if (app_id == ash::kChromeUIUntrustedProjectorSwaAppId &&
- url.GetWithEmptyPath() == GURL(ash::kChromeUIUntrustedProjectorPwaUrl)) {
- std::string override_url = ash::kChromeUIUntrustedProjectorUrl;
- if (url.path().length() > 1) {
- override_url += url.path().substr(1);
- }
- std::stringstream ss;
- // Since ChromeOS doesn't reload an app if the URL doesn't change, the line
- // below appends a unique timestamp to the URL to force a reload.
- // TODO(b/211787536): Remove the timestamp after we update the trusted URL
- // to match the user's navigations through the post message api.
- ss << override_url << "?timestamp=" << clock->NowTicks();
-
- if (url.has_query()) {
- ss << '&' << url.query();
- }
-
- GURL result(ss.str());
- DCHECK(result.is_valid());
- return result;
- }
- // Add redirects for other SWAs above this line.
-
- // No matching SWAs found, returning original url.
- return url;
-}
-
-IntentHandlingMetrics::Platform GetMetricsPlatform(AppType app_type) {
- switch (app_type) {
- case AppType::kArc:
- return IntentHandlingMetrics::Platform::ARC;
- case AppType::kWeb:
- case AppType::kSystemWeb:
- return IntentHandlingMetrics::Platform::PWA;
- case AppType::kUnknown:
- case AppType::kBuiltIn:
- case AppType::kCrostini:
- case AppType::kChromeApp:
- case AppType::kMacOs:
- case AppType::kPluginVm:
- case AppType::kStandaloneBrowser:
- case AppType::kRemote:
- case AppType::kBorealis:
- case AppType::kStandaloneBrowserChromeApp:
- case AppType::kExtension:
- case AppType::kStandaloneBrowserExtension:
- case AppType::kBruschetta:
- NOTREACHED();
- return IntentHandlingMetrics::Platform::ARC;
- }
-}
-
-} // namespace
-
-// static
-std::unique_ptr<apps::AppsNavigationThrottle>
-ChromeOsAppsNavigationThrottle::MaybeCreate(content::NavigationHandle* handle) {
- // Don't handle navigations in subframes or main frames that are in a nested
- // frame tree (e.g. portals, fenced-frame). We specifically allow
- // prerendering navigations so that we can destroy the prerender. Opening an
- // app must only happen when the user intentionally navigates; however, for a
- // prerender, the prerender-activating navigation doesn't run throttles so we
- // must cancel it during initial loading to get a standard (non-prerendering)
- // navigation at link-click-time.
- if (!handle->IsInPrimaryMainFrame() && !handle->IsInPrerenderedMainFrame()) {
- return nullptr;
- }
-
- content::WebContents* web_contents = handle->GetWebContents();
-
- Profile* profile =
- Profile::FromBrowserContext(web_contents->GetBrowserContext());
-
- if (!AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) {
- return nullptr;
- }
-
- if (!ShouldCheckAppsForUrl(web_contents)) {
- return nullptr;
- }
-
- return std::make_unique<ChromeOsAppsNavigationThrottle>(handle);
-}
-
-// static
-const base::TickClock* ChromeOsAppsNavigationThrottle::clock_ =
- base::DefaultTickClock::GetInstance();
-
-// static
-void ChromeOsAppsNavigationThrottle::SetClockForTesting(
- const base::TickClock* tick_clock) {
- clock_ = tick_clock;
-}
-
-base::OnceClosure&
-ChromeOsAppsNavigationThrottle::GetLinkCaptureLaunchCallbackForTesting() {
- static base::NoDestructor<base::OnceClosure> callback;
- return *callback;
-}
-
-ChromeOsAppsNavigationThrottle::ChromeOsAppsNavigationThrottle(
- content::NavigationHandle* navigation_handle)
- : apps::AppsNavigationThrottle(navigation_handle) {}
-
-ChromeOsAppsNavigationThrottle::~ChromeOsAppsNavigationThrottle() = default;
-
-bool ChromeOsAppsNavigationThrottle::ShouldCancelNavigation(
- content::NavigationHandle* handle) {
- content::WebContents* web_contents = handle->GetWebContents();
-
- const GURL& url = handle->GetURL();
-
- Profile* profile =
- Profile::FromBrowserContext(web_contents->GetBrowserContext());
-
- auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile);
-
- AppIdsToLaunchForUrl app_id_to_launch = FindAppIdsToLaunchForUrl(proxy, url);
-
- if (app_id_to_launch.candidates.empty()) {
- return false;
- }
-
- if (ShouldOnlyCaptureLinks(app_id_to_launch.candidates) &&
- !navigate_from_link()) {
- return false;
- }
-
- if (!app_id_to_launch.preferred) {
- return false;
- }
-
- const std::string& preferred_app_id = *app_id_to_launch.preferred;
- // Only automatically launch supported app types.
- auto app_type = proxy->AppRegistryCache().GetAppType(preferred_app_id);
- if (app_type != AppType::kArc && app_type != AppType::kWeb &&
- !IsSystemWebApp(profile, preferred_app_id)) {
- return false;
- }
-
- // Don't capture if already inside the target app scope.
- if (app_type == AppType::kWeb &&
- base::ValuesEquivalent(web_app::WebAppTabHelper::GetAppId(web_contents),
- &preferred_app_id)) {
- return false;
- }
-
- // If this is a prerender navigation that would otherwise launch an app, we
- // must cancel it. We only want to launch an app once the URL is
- // intentionally navigated to by the user. We cancel the navigation here so
- // that when the link is clicked, we'll run NavigationThrottles again. If we
- // leave the prerendering alive, the activating navigation won't run
- // throttles.
- if (handle->IsInPrerenderedMainFrame()) {
- return true;
- }
-
- // Browser & profile keep-alives must be used to keep the browser & profile
- // alive because the old window is required to be closed before the new app is
- // launched, which will destroy the profile & browser if it is the last
- // window.
- // Why close the tab first? The way web contents currently work, closing a tab
- // in a window will re-activate that window if there are more tabs there. So
- // if we wait until after the launch completes to close the tab, then it will
- // cause the old window to come to the front hiding the newly launched app
- // window.
- std::unique_ptr<ScopedKeepAlive> browser_keep_alive;
- std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive;
- const GURL& last_committed_url = web_contents->GetLastCommittedURL();
- if (!last_committed_url.is_valid() || last_committed_url.IsAboutBlank() ||
- // After clicking a link in various apps (eg gchat), a blank redirect page
- // is left behind. Remove it to clean up. WasInitiatedByLinkClick()
- // returns false for links clicked from apps.
- !handle->WasInitiatedByLinkClick()) {
- browser_keep_alive = std::make_unique<ScopedKeepAlive>(
- KeepAliveOrigin::APP_LAUNCH, KeepAliveRestartOption::ENABLED);
- if (!profile->IsOffTheRecord()) {
- profile_keep_alive = std::make_unique<ScopedProfileKeepAlive>(
- profile, ProfileKeepAliveOrigin::kAppWindow);
- }
- web_contents->ClosePage();
- }
-
- auto launch_source = navigate_from_link() ? LaunchSource::kFromLink
- : LaunchSource::kFromOmnibox;
- GURL redirected_url =
- RedirectUrlIfSwa(profile, preferred_app_id, url, clock_);
- // The tab may have been closed, which runs async and causes the browser
- // window to be refocused. Post a task to launch the app to ensure launching
- // happens after the tab closed, otherwise the opened app window might be
- // inactivated.
- base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
- FROM_HERE,
- base::BindOnce(
- &AppServiceProxy::LaunchAppWithUrl, proxy->GetWeakPtr(),
- preferred_app_id,
- GetEventFlags(WindowOpenDisposition::NEW_WINDOW,
- /*prefer_container=*/true),
- redirected_url, launch_source,
- std::make_unique<WindowInfo>(display::kDefaultDisplayId),
- base::BindOnce(
- [](std::unique_ptr<ScopedKeepAlive> browser_keep_alive,
- std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive,
- LaunchResult&&) {
- // Note: This function currently only serves to own the "keep
- // alive" objects until the launch is complete.
- if (GetLinkCaptureLaunchCallbackForTesting()) {
- std::move(GetLinkCaptureLaunchCallbackForTesting()).Run();
- }
- },
- std::move(browser_keep_alive), std::move(profile_keep_alive))));
-
- IntentHandlingMetrics::RecordPreferredAppLinkClickMetrics(
- GetMetricsPlatform(app_type));
- return true;
-}
-
-} // namespace apps
diff --git a/chrome/browser/apps/intent_helper/chromeos_apps_navigation_throttle.h b/chrome/browser/apps/intent_helper/chromeos_apps_navigation_throttle.h
deleted file mode 100644
index 58b2cbb..0000000
--- a/chrome/browser/apps/intent_helper/chromeos_apps_navigation_throttle.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_APPS_INTENT_HELPER_CHROMEOS_APPS_NAVIGATION_THROTTLE_H_
-#define CHROME_BROWSER_APPS_INTENT_HELPER_CHROMEOS_APPS_NAVIGATION_THROTTLE_H_
-
-#include <memory>
-#include <vector>
-
-#include "chrome/browser/apps/intent_helper/apps_navigation_throttle.h"
-#include "content/public/browser/navigation_throttle.h"
-#include "url/gurl.h"
-
-namespace base {
-class TickClock;
-}
-
-namespace content {
-class NavigationHandle;
-} // namespace content
-
-namespace apps {
-
-// Allows navigation to be routed to an installed app. This throttle supports
-// all type of apps in the Chrome OS platform using App Service.
-class ChromeOsAppsNavigationThrottle : public apps::AppsNavigationThrottle {
- public:
- // Possibly creates a navigation throttle that checks if any installed apps
- // can handle the URL being navigated to.
- static std::unique_ptr<apps::AppsNavigationThrottle> MaybeCreate(
- content::NavigationHandle* handle);
-
- // Method intended for testing purposes only.
- // Set clock used for timing to enable manipulation during tests.
- static void SetClockForTesting(const base::TickClock* tick_clock);
-
- static base::OnceClosure& GetLinkCaptureLaunchCallbackForTesting();
-
- explicit ChromeOsAppsNavigationThrottle(
- content::NavigationHandle* navigation_handle);
-
- ChromeOsAppsNavigationThrottle(const ChromeOsAppsNavigationThrottle&) =
- delete;
- ChromeOsAppsNavigationThrottle& operator=(
- const ChromeOsAppsNavigationThrottle&) = delete;
-
- ~ChromeOsAppsNavigationThrottle() override;
-
- private:
- bool ShouldCancelNavigation(content::NavigationHandle* handle) override;
-
- // Used to create a unique timestamped URL to force reload apps.
- // Points to the base::DefaultTickClock by default.
- static const base::TickClock* clock_;
-};
-
-} // namespace apps
-
-#endif // CHROME_BROWSER_APPS_INTENT_HELPER_CHROMEOS_APPS_NAVIGATION_THROTTLE_H_
diff --git a/chrome/browser/apps/intent_helper/chromeos_disabled_apps_throttle.cc b/chrome/browser/apps/intent_helper/chromeos_disabled_apps_throttle.cc
index ed94987..567afac 100644
--- a/chrome/browser/apps/intent_helper/chromeos_disabled_apps_throttle.cc
+++ b/chrome/browser/apps/intent_helper/chromeos_disabled_apps_throttle.cc
@@ -13,7 +13,11 @@
#include "chrome/browser/apps/intent_helper/intent_picker_internal.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/policy/system_features_disable_list_policy_handler.h"
+#include "chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_contents_delegate.h"
#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/grit/browser_resources.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/navigation_handle.h"
@@ -45,6 +49,29 @@
return webui::GetI18nTemplateHtml(html, strings);
}
+bool ShouldCheckAppsForUrl(content::WebContents* web_contents) {
+ // Do not check apps for url if no apps can be installed, e.g. in incognito.
+ // Do not check apps for a no-state prefetcher navigation.
+ if (!web_app::AreWebAppsUserInstallable(
+ Profile::FromBrowserContext(web_contents->GetBrowserContext())) ||
+ prerender::ChromeNoStatePrefetchContentsDelegate::FromWebContents(
+ web_contents) != nullptr) {
+ return false;
+ }
+
+ // Do not check apps for url if we are already in an app browser.
+ // It is possible that the web contents is not inserted to tab strip
+ // model at this stage (e.g. open url in new tab). So if we cannot
+ // find a browser at this moment, skip the check and this will be handled
+ // in later stage.
+ Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
+ if (browser && (browser->is_type_app() || browser->is_type_app_popup())) {
+ return false;
+ }
+
+ return true;
+}
+
} // namespace
// static
diff --git a/chrome/browser/apps/intent_helper/intent_picker_helpers.cc b/chrome/browser/apps/intent_helper/intent_picker_helpers.cc
index 68c406a..ef6319e 100644
--- a/chrome/browser/apps/intent_helper/intent_picker_helpers.cc
+++ b/chrome/browser/apps/intent_helper/intent_picker_helpers.cc
@@ -21,11 +21,13 @@
#include "chrome/browser/apps/intent_helper/intent_picker_features.h"
#include "chrome/browser/apps/intent_helper/intent_picker_internal.h"
#include "chrome/browser/feature_engagement/tracker_factory.h"
+#include "chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_contents_delegate.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/intent_picker_tab_helper.h"
#include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
#include "components/feature_engagement/public/tracker.h"
#include "content/public/browser/web_contents.h"
#include "ui/gfx/favicon_size.h"
@@ -42,6 +44,28 @@
namespace {
+bool ShouldConsiderWebContentsForIntentPicker(
+ content::WebContents* web_contents) {
+ if (!web_app::AreWebAppsUserInstallable(
+ Profile::FromBrowserContext(web_contents->GetBrowserContext())) ||
+ prerender::ChromeNoStatePrefetchContentsDelegate::FromWebContents(
+ web_contents) != nullptr) {
+ return false;
+ }
+
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ if (!AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) {
+ return false;
+ }
+
+ Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
+ if (browser && (browser->is_type_app() || browser->is_type_app_popup())) {
+ return false;
+ }
+ return true;
+}
+
void AppendAppsForUrlSync(
content::WebContents* web_contents,
const GURL& url,
@@ -187,14 +211,7 @@
void GetAppsForIntentPicker(
content::WebContents* web_contents,
base::OnceCallback<void(std::vector<IntentPickerAppInfo>)> callback) {
- if (!ShouldCheckAppsForUrl(web_contents)) {
- std::move(callback).Run({});
- return;
- }
-
- Profile* profile =
- Profile::FromBrowserContext(web_contents->GetBrowserContext());
- if (!AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) {
+ if (!ShouldConsiderWebContentsForIntentPicker(web_contents)) {
std::move(callback).Run({});
return;
}
diff --git a/chrome/browser/apps/intent_helper/intent_picker_internal.cc b/chrome/browser/apps/intent_helper/intent_picker_internal.cc
index 7832a8a2..4a7c978 100644
--- a/chrome/browser/apps/intent_helper/intent_picker_internal.cc
+++ b/chrome/browser/apps/intent_helper/intent_picker_internal.cc
@@ -6,62 +6,15 @@
#include <utility>
-#include "chrome/browser/apps/intent_helper/page_transition_util.h"
-#include "chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_contents_delegate.h"
-#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/intent_picker_tab_helper.h"
-#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
-#include "chrome/browser/web_applications/web_app_helpers.h"
-#include "chrome/browser/web_applications/web_app_icon_manager.h"
-#include "chrome/browser/web_applications/web_app_provider.h"
-#include "chrome/browser/web_applications/web_app_registrar.h"
-#include "chrome/browser/web_applications/web_app_utils.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_contents.h"
-#include "components/page_load_metrics/browser/page_load_metrics_util.h"
+#include "components/services/app_service/public/cpp/app_types.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
-#include "extensions/common/constants.h"
namespace apps {
-namespace {
-
-// Returns true if |url| is a known and valid redirector that will redirect a
-// navigation elsewhere.
-bool IsGoogleRedirectorUrl(const GURL& url) {
- // This currently only check for redirectors on the "google" domain.
- if (!page_load_metrics::IsGoogleSearchHostname(url))
- return false;
-
- return url.path_piece() == "/url" && url.has_query();
-}
-
-} // namespace
-
-bool ShouldCheckAppsForUrl(content::WebContents* web_contents) {
- // Do not check apps for url if no apps can be installed, e.g. in incognito.
- // Do not check apps for a no-state prefetcher navigation.
- if (!web_app::AreWebAppsUserInstallable(
- Profile::FromBrowserContext(web_contents->GetBrowserContext())) ||
- prerender::ChromeNoStatePrefetchContentsDelegate::FromWebContents(
- web_contents) != nullptr) {
- return false;
- }
-
- // Do not check apps for url if we are already in an app browser.
- // It is possible that the web contents is not inserted to tab strip
- // model at this stage (e.g. open url in new tab). So if we cannot
- // find a browser at this moment, skip the check and this will be handled
- // in later stage.
- Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
- if (browser && (browser->is_type_app() || browser->is_type_app_popup()))
- return false;
-
- return true;
-}
-
void ShowIntentPickerBubbleForApps(content::WebContents* web_contents,
std::vector<IntentPickerAppInfo> apps,
bool show_stay_in_chrome,
@@ -83,82 +36,6 @@
std::move(callback));
}
-bool InAppBrowser(content::WebContents* web_contents) {
- Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
- return !browser || browser->is_type_app() || browser->is_type_app_popup();
-}
-
-// Compares the host name of the referrer and target URL to decide whether
-// the navigation needs to be overridden.
-bool ShouldOverrideUrlLoading(const GURL& previous_url,
- const GURL& current_url) {
- // When the navigation is initiated in a web page where sending a referrer
- // is disabled, |previous_url| can be empty. In this case, we should open
- // it in the desktop browser.
- if (!previous_url.is_valid() || previous_url.is_empty())
- return false;
-
- // Also check |current_url| just in case.
- if (!current_url.is_valid() || current_url.is_empty()) {
- DVLOG(1) << "Unexpected URL: " << current_url << ", opening it in Chrome.";
- return false;
- }
-
- // Check the scheme for both |previous_url| and |current_url| since an
- // extension could have referred us (e.g. Google Docs).
- if (!current_url.SchemeIsHTTPOrHTTPS() ||
- previous_url.SchemeIs(extensions::kExtensionScheme)) {
- return false;
- }
-
- // Skip URL redirectors that are intermediate pages redirecting towards a
- // final URL.
- if (IsGoogleRedirectorUrl(current_url))
- return false;
-
- return true;
-}
-
-GURL GetStartingGURL(content::NavigationHandle* navigation_handle) {
- // This helps us determine a reference GURL for the current NavigationHandle.
- // This is the order or preference: Referrer > LastCommittedURL >
- // InitiatorOrigin. InitiatorOrigin *should* only be used on very rare cases,
- // e.g. when the navigation goes from https: to http: on a new tab, thus
- // losing the other potential referrers.
- const GURL referrer_url = navigation_handle->GetReferrer().url;
- if (referrer_url.is_valid() && !referrer_url.is_empty())
- return referrer_url;
-
- const GURL last_committed_url =
- navigation_handle->GetWebContents()->GetLastCommittedURL();
- if (last_committed_url.is_valid() && !last_committed_url.is_empty())
- return last_committed_url;
-
- const auto& initiator_origin = navigation_handle->GetInitiatorOrigin();
- return initiator_origin.has_value() ? initiator_origin->GetURL() : GURL();
-}
-
-bool IsGoogleRedirectorUrlForTesting(const GURL& url) {
- return IsGoogleRedirectorUrl(url);
-}
-
-bool IsNavigateFromLink(content::NavigationHandle* navigation_handle) {
- // Always handle http(s) <form> submissions in Chrome for two reasons: 1) we
- // don't have a way to send POST data to ARC, and 2) intercepting http(s) form
- // submissions is not very important because such submissions are usually
- // done within the same domain. ShouldOverrideUrlLoading() below filters out
- // such submissions anyway.
- constexpr bool kAllowFormSubmit = false;
-
- ui::PageTransition page_transition = navigation_handle->GetPageTransition();
-
- return !ShouldIgnoreNavigation(page_transition, kAllowFormSubmit,
- navigation_handle->IsInFencedFrameTree(),
- navigation_handle->HasUserGesture()) &&
- !navigation_handle->WasStartedFromContextMenu() &&
- !navigation_handle->IsSameDocument();
-}
-
void CloseOrGoBack(content::WebContents* web_contents) {
DCHECK(web_contents);
if (web_contents->GetController().CanGoBack())
diff --git a/chrome/browser/apps/intent_helper/intent_picker_internal.h b/chrome/browser/apps/intent_helper/intent_picker_internal.h
index 248078dc..829b323 100644
--- a/chrome/browser/apps/intent_helper/intent_picker_internal.h
+++ b/chrome/browser/apps/intent_helper/intent_picker_internal.h
@@ -11,35 +11,19 @@
#include "components/services/app_service/public/cpp/app_types.h"
namespace content {
-class NavigationHandle;
class WebContents;
} // namespace content
-class GURL;
-
namespace apps {
-bool ShouldCheckAppsForUrl(content::WebContents* web_contents);
-
void ShowIntentPickerBubbleForApps(content::WebContents* web_contents,
std::vector<IntentPickerAppInfo> apps,
bool show_stay_in_chrome,
bool show_remember_selection,
IntentPickerResponse callback);
-bool InAppBrowser(content::WebContents* web_contents);
-
-bool ShouldOverrideUrlLoading(const GURL& previous_url,
- const GURL& current_url);
-
-GURL GetStartingGURL(content::NavigationHandle* navigation_handle);
-
-bool IsNavigateFromLink(content::NavigationHandle* navigation_handle);
-
void CloseOrGoBack(content::WebContents* web_contents);
-bool IsGoogleRedirectorUrlForTesting(const GURL& url);
-
PickerEntryType GetPickerEntryType(AppType app_type);
} // namespace apps
diff --git a/chrome/browser/apps/intent_helper/intent_picker_internal_unittest.cc b/chrome/browser/apps/intent_helper/intent_picker_internal_unittest.cc
deleted file mode 100644
index 69737b45..0000000
--- a/chrome/browser/apps/intent_helper/intent_picker_internal_unittest.cc
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/apps/intent_helper/intent_picker_internal.h"
-
-#include "base/test/gtest_util.h"
-#include "chrome/browser/apps/intent_helper/apps_navigation_types.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "url/gurl.h"
-
-namespace apps {
-
-TEST(IntentPickersInternalTest, TestIsGoogleRedirectorUrl) {
- // Test that redirect urls with different TLDs are still recognized.
- EXPECT_TRUE(IsGoogleRedirectorUrlForTesting(
- GURL("https://www.google.com.au/url?q=wathever")));
- EXPECT_TRUE(IsGoogleRedirectorUrlForTesting(
- GURL("https://www.google.com.mx/url?q=hotpot")));
- EXPECT_TRUE(IsGoogleRedirectorUrlForTesting(
- GURL("https://www.google.co/url?q=query")));
-
- // Non-google domains shouldn't be used as valid redirect links.
- EXPECT_FALSE(IsGoogleRedirectorUrlForTesting(
- GURL("https://www.not-google.com/url?q=query")));
- EXPECT_FALSE(IsGoogleRedirectorUrlForTesting(
- GURL("https://www.gooogle.com/url?q=legit_query")));
-
- // This method only takes "/url" as a valid path, it needs to contain a query,
- // we don't analyze that query as it will expand later on in the same
- // throttle.
- EXPECT_TRUE(IsGoogleRedirectorUrlForTesting(
- GURL("https://www.google.com/url?q=who_dis")));
- EXPECT_TRUE(IsGoogleRedirectorUrlForTesting(
- GURL("http://www.google.com/url?q=who_dis")));
- EXPECT_FALSE(
- IsGoogleRedirectorUrlForTesting(GURL("https://www.google.com/url")));
- EXPECT_FALSE(IsGoogleRedirectorUrlForTesting(
- GURL("https://www.google.com/link?q=query")));
- EXPECT_FALSE(
- IsGoogleRedirectorUrlForTesting(GURL("https://www.google.com/link")));
-}
-
-TEST(IntentPickersInternalTest, TestShouldOverrideUrlLoading) {
- // If either of two parameters is empty, the function should return false.
- EXPECT_FALSE(ShouldOverrideUrlLoading(GURL(), GURL("http://a.google.com/")));
- EXPECT_FALSE(ShouldOverrideUrlLoading(GURL("http://a.google.com/"), GURL()));
- EXPECT_FALSE(ShouldOverrideUrlLoading(GURL(), GURL()));
-
- // A navigation to an a url that is neither an http nor https scheme cannot be
- // override.
- EXPECT_FALSE(ShouldOverrideUrlLoading(
- GURL("http://www.a.com"), GURL("chrome-extension://fake_document")));
- EXPECT_FALSE(ShouldOverrideUrlLoading(
- GURL("https://www.a.com"), GURL("chrome-extension://fake_document")));
- EXPECT_FALSE(ShouldOverrideUrlLoading(GURL("http://www.a.com"),
- GURL("chrome://fake_document")));
- EXPECT_FALSE(ShouldOverrideUrlLoading(GURL("http://www.a.com"),
- GURL("file://fake_document")));
- EXPECT_FALSE(ShouldOverrideUrlLoading(GURL("https://www.a.com"),
- GURL("chrome://fake_document")));
- EXPECT_FALSE(ShouldOverrideUrlLoading(GURL("https://www.a.com"),
- GURL("file://fake_document")));
-
- // A navigation from chrome-extension scheme cannot be overridden.
- EXPECT_FALSE(ShouldOverrideUrlLoading(
- GURL("chrome-extension://fake_document"), GURL("http://www.a.com")));
- EXPECT_FALSE(ShouldOverrideUrlLoading(
- GURL("chrome-extension://fake_document"), GURL("https://www.a.com")));
- EXPECT_FALSE(ShouldOverrideUrlLoading(GURL("chrome-extension://fake_a"),
- GURL("chrome-extension://fake_b")));
-
- // Other navigations can be overridden.
- EXPECT_TRUE(ShouldOverrideUrlLoading(GURL("http://www.google.com"),
- GURL("http://www.not-google.com/")));
- EXPECT_TRUE(ShouldOverrideUrlLoading(GURL("http://www.not-google.com"),
- GURL("http://www.google.com/")));
- EXPECT_TRUE(ShouldOverrideUrlLoading(GURL("http://www.google.com"),
- GURL("http://www.google.com/")));
- EXPECT_TRUE(ShouldOverrideUrlLoading(GURL("http://a.google.com"),
- GURL("http://b.google.com/")));
- EXPECT_TRUE(ShouldOverrideUrlLoading(GURL("http://a.not-google.com"),
- GURL("http://b.not-google.com")));
- EXPECT_TRUE(ShouldOverrideUrlLoading(GURL("chrome://fake_document"),
- GURL("http://www.a.com")));
- EXPECT_TRUE(ShouldOverrideUrlLoading(GURL("file://fake_document"),
- GURL("http://www.a.com")));
- EXPECT_TRUE(ShouldOverrideUrlLoading(GURL("chrome://fake_document"),
- GURL("https://www.a.com")));
- EXPECT_TRUE(ShouldOverrideUrlLoading(GURL("file://fake_document"),
- GURL("https://www.a.com")));
-
- // A navigation going to a redirect url cannot be overridden, unless there's
- // no query or the path is not valid.
- EXPECT_FALSE(ShouldOverrideUrlLoading(
- GURL("http://www.google.com"), GURL("https://www.google.com/url?q=b")));
- EXPECT_FALSE(ShouldOverrideUrlLoading(
- GURL("https://www.a.com"), GURL("https://www.google.com/url?q=a")));
- EXPECT_TRUE(ShouldOverrideUrlLoading(GURL("https://www.a.com"),
- GURL("https://www.google.com/url")));
- EXPECT_TRUE(ShouldOverrideUrlLoading(
- GURL("https://www.a.com"), GURL("https://www.google.com/link?q=a")));
-}
-
-} // namespace apps
diff --git a/chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.cc b/chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.cc
index 06a2cfb..6a3b1aa 100644
--- a/chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.cc
+++ b/chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.cc
@@ -4,13 +4,13 @@
#include "chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.h"
-#include "ash/components/arc/metrics/arc_metrics_constants.h"
#include "base/containers/contains.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/components/arc/metrics/arc_metrics_constants.h"
#include "ash/components/arc/metrics/arc_metrics_service.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/apps/intent_helper/page_transition_util.cc b/chrome/browser/apps/intent_helper/page_transition_util.cc
deleted file mode 100644
index 810d926..0000000
--- a/chrome/browser/apps/intent_helper/page_transition_util.cc
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/apps/intent_helper/page_transition_util.h"
-
-#include "base/check_op.h"
-#include "base/types/cxx23_to_underlying.h"
-
-namespace apps {
-
-bool ShouldIgnoreNavigation(ui::PageTransition page_transition,
- bool allow_form_submit,
- bool is_in_fenced_frame_tree,
- bool has_user_gesture) {
- // Navigations inside fenced frame trees are marked with
- // PAGE_TRANSITION_AUTO_SUBFRAME in order not to add session history items
- // (see https://crrev.com/c/3265344). So we only check |has_user_gesture|.
- if (is_in_fenced_frame_tree) {
- DCHECK(ui::PageTransitionCoreTypeIs(page_transition,
- ui::PAGE_TRANSITION_AUTO_SUBFRAME));
- return !has_user_gesture;
- }
-
- // Mask out any redirect qualifiers
- page_transition = MaskOutPageTransition(page_transition,
- ui::PAGE_TRANSITION_IS_REDIRECT_MASK);
-
- if (!ui::PageTransitionCoreTypeIs(page_transition,
- ui::PAGE_TRANSITION_LINK) &&
- !(allow_form_submit &&
- ui::PageTransitionCoreTypeIs(page_transition,
- ui::PAGE_TRANSITION_FORM_SUBMIT))) {
- // Do not handle the |url| if this event wasn't spawned by the user clicking
- // on a link.
- return true;
- }
-
- if (base::to_underlying(ui::PageTransitionGetQualifier(page_transition)) !=
- 0) {
- // Qualifiers indicate that this navigation was the result of a click on a
- // forward/back button, or typing in the URL bar. Don't handle any of those
- // types of navigations.
- return true;
- }
-
- return false;
-}
-
-ui::PageTransition MaskOutPageTransition(ui::PageTransition page_transition,
- ui::PageTransition mask) {
- return ui::PageTransitionFromInt(page_transition & ~mask);
-}
-
-} // namespace apps
diff --git a/chrome/browser/apps/intent_helper/page_transition_util.h b/chrome/browser/apps/intent_helper/page_transition_util.h
deleted file mode 100644
index 20608a40..0000000
--- a/chrome/browser/apps/intent_helper/page_transition_util.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_APPS_INTENT_HELPER_PAGE_TRANSITION_UTIL_H_
-#define CHROME_BROWSER_APPS_INTENT_HELPER_PAGE_TRANSITION_UTIL_H_
-
-#include "ui/base/page_transition_types.h"
-
-namespace apps {
-
-// Returns true if ARC should ignore the navigation with the |page_transition|.
-bool ShouldIgnoreNavigation(ui::PageTransition page_transition,
- bool allow_form_submit,
- bool is_in_fenced_frame_tree,
- bool has_user_gesture);
-
-// Removes |mask| bits from |page_transition|.
-ui::PageTransition MaskOutPageTransition(ui::PageTransition page_transition,
- ui::PageTransition mask);
-
-} // namespace apps
-
-#endif // CHROME_BROWSER_APPS_INTENT_HELPER_PAGE_TRANSITION_UTIL_H_
diff --git a/chrome/browser/apps/intent_helper/page_transition_util_unittest.cc b/chrome/browser/apps/intent_helper/page_transition_util_unittest.cc
deleted file mode 100644
index fd7b7b3c..0000000
--- a/chrome/browser/apps/intent_helper/page_transition_util_unittest.cc
+++ /dev/null
@@ -1,191 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/apps/intent_helper/page_transition_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/base/page_transition_types.h"
-
-namespace apps {
-
-// Tests that ShouldIgnoreNavigation returns false only for
-// PAGE_TRANSITION_LINK when |allow_form_submit| is false and
-// |is_in_fenced_frame_tree| is false.
-TEST(PageTransitionUtilTest, TestShouldIgnoreNavigationWithCoreTypes) {
- EXPECT_FALSE(
- ShouldIgnoreNavigation(ui::PAGE_TRANSITION_LINK, false, false, false));
- EXPECT_TRUE(
- ShouldIgnoreNavigation(ui::PAGE_TRANSITION_TYPED, false, false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(ui::PAGE_TRANSITION_AUTO_BOOKMARK, false,
- false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(ui::PAGE_TRANSITION_AUTO_SUBFRAME, false,
- false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(ui::PAGE_TRANSITION_MANUAL_SUBFRAME, false,
- false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(ui::PAGE_TRANSITION_GENERATED, false,
- false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(ui::PAGE_TRANSITION_AUTO_TOPLEVEL, false,
- false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(ui::PAGE_TRANSITION_FORM_SUBMIT, false,
- false, false));
- EXPECT_TRUE(
- ShouldIgnoreNavigation(ui::PAGE_TRANSITION_RELOAD, false, false, false));
- EXPECT_TRUE(
- ShouldIgnoreNavigation(ui::PAGE_TRANSITION_KEYWORD, false, false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(ui::PAGE_TRANSITION_KEYWORD_GENERATED,
- false, false, false));
-
- static_assert(static_cast<int32_t>(ui::PAGE_TRANSITION_KEYWORD_GENERATED) ==
- static_cast<int32_t>(ui::PAGE_TRANSITION_LAST_CORE),
- "Not all core transition types are covered here");
-}
-
-// Test that ShouldIgnoreNavigation accepts FORM_SUBMIT when |allow_form_submit|
-// is true.
-TEST(PageTransitionUtilTest, TestFormSubmit) {
- EXPECT_FALSE(ShouldIgnoreNavigation(ui::PAGE_TRANSITION_FORM_SUBMIT, true,
- false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(ui::PAGE_TRANSITION_FORM_SUBMIT, false,
- false, false));
-}
-
-// Tests that ShouldIgnoreNavigation returns true when no qualifiers except
-// client redirect and server redirect are provided when
-// |is_in_fenced_frame_tree| is false.
-TEST(PageTransitionUtilTest, TestShouldIgnoreNavigationWithLinkWithQualifiers) {
- // The navigation is triggered by Forward or Back button.
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_FORWARD_BACK),
- false, false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_FORM_SUBMIT |
- ui::PAGE_TRANSITION_FORWARD_BACK),
- false, false, false));
- // The user used the address bar to trigger the navigation.
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
- false, false, false));
- // The user pressed the Home button.
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_HOME_PAGE),
- false, false, false));
- // ARC (for example) opened the link in Chrome.
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_FROM_API),
- false, false, false));
- // The navigation is triggered by a client side redirect.
- EXPECT_FALSE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_CLIENT_REDIRECT),
- false, false, false));
- // Also tests the case with 2+ qualifiers.
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_FROM_ADDRESS_BAR |
- ui::PAGE_TRANSITION_CLIENT_REDIRECT),
- false, false, false));
- // The navigation is triggered by a server side redirect.
- EXPECT_FALSE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_SERVER_REDIRECT),
- false, false, false));
- // Also tests the case with 2+ qualifiers.
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_FROM_ADDRESS_BAR |
- ui::PAGE_TRANSITION_SERVER_REDIRECT),
- false, false, false));
-}
-
-// Just in case, does the same with ui::PAGE_TRANSITION_TYPED.
-TEST(PageTransitionUtilTest,
- TestShouldIgnoreNavigationWithTypedWithQualifiers) {
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
- ui::PAGE_TRANSITION_FORWARD_BACK),
- false, false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
- ui::PAGE_TRANSITION_FORWARD_BACK),
- false, false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
- ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
- false, false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
- ui::PAGE_TRANSITION_HOME_PAGE),
- false, false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
- ui::PAGE_TRANSITION_FROM_API),
- false, false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
- ui::PAGE_TRANSITION_FROM_ADDRESS_BAR |
- ui::PAGE_TRANSITION_CLIENT_REDIRECT),
- false, false, false));
-}
-
-// Test that ShouldIgnoreNavigation accepts SERVER_REDIRECT and CLIENT_REDIRECT
-// when |is_in_fenced_frame_tree| is false.
-TEST(PageTransitionUtilTest, TestShouldIgnoreNavigationWithClientRedirect) {
- EXPECT_FALSE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK), false, false,
- false));
- EXPECT_FALSE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_CLIENT_REDIRECT),
- false, false, false));
- EXPECT_FALSE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_SERVER_REDIRECT),
- false, false, false));
- EXPECT_FALSE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_SERVER_REDIRECT |
- ui::PAGE_TRANSITION_SERVER_REDIRECT),
- false, false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_CLIENT_REDIRECT |
- ui::PAGE_TRANSITION_HOME_PAGE),
- false, false, false));
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
- ui::PAGE_TRANSITION_HOME_PAGE),
- false, false, false));
-}
-
-// Test that MaskOutPageTransition correctly remove a qualifier from a given
-// |page_transition|.
-TEST(PageTransitionUtilTest, TestMaskOutPageTransition) {
- ui::PageTransition page_transition = ui::PageTransitionFromInt(
- ui::PAGE_TRANSITION_LINK | ui::PAGE_TRANSITION_CLIENT_REDIRECT);
- EXPECT_EQ(static_cast<int>(ui::PAGE_TRANSITION_LINK),
- static_cast<int>(MaskOutPageTransition(
- page_transition, ui::PAGE_TRANSITION_CLIENT_REDIRECT)));
-
- page_transition = ui::PageTransitionFromInt(
- ui::PAGE_TRANSITION_LINK | ui::PAGE_TRANSITION_SERVER_REDIRECT);
- EXPECT_EQ(static_cast<int>(ui::PAGE_TRANSITION_LINK),
- static_cast<int>(MaskOutPageTransition(
- page_transition, ui::PAGE_TRANSITION_SERVER_REDIRECT)));
-}
-
-// Test that ShouldIgnoreNavigation accepts iff |has_user_gesture| is true
-// when |is_in_fenced_frame_tree| is true.
-TEST(PageTransitionUtilTest, TestInFencedFrameTree) {
- EXPECT_TRUE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_AUTO_SUBFRAME), false, true,
- false));
- EXPECT_FALSE(ShouldIgnoreNavigation(
- ui::PageTransitionFromInt(ui::PAGE_TRANSITION_AUTO_SUBFRAME), false, true,
- true));
-}
-
-} // namespace apps
diff --git a/chrome/browser/apps/link_capturing/BUILD.gn b/chrome/browser/apps/link_capturing/BUILD.gn
new file mode 100644
index 0000000..e536bfd
--- /dev/null
+++ b/chrome/browser/apps/link_capturing/BUILD.gn
@@ -0,0 +1,47 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//chrome/browser/buildflags.gni")
+
+source_set("link_capturing") {
+ sources = [
+ "link_capturing_navigation_throttle.cc",
+ "link_capturing_navigation_throttle.h",
+ ]
+
+ deps = [
+ "//base",
+ "//chrome/browser/profiles:profile",
+ "//chrome/browser/web_applications",
+ "//components/keep_alive_registry",
+ "//components/page_load_metrics/browser",
+ "//content/public/browser",
+ "//extensions/common",
+ "//third_party/abseil-cpp:absl",
+ "//url",
+ ]
+
+ if (is_chromeos) {
+ sources += [
+ "chromeos_link_capturing_delegate.cc",
+ "chromeos_link_capturing_delegate.h",
+ ]
+ deps += [
+ "//ash/webui/projector_app/public/cpp",
+ "//chrome/browser/apps/app_service",
+ ]
+ }
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [ "link_capturing_navigation_throttle_unittest.cc" ]
+
+ deps = [
+ ":link_capturing",
+ "//testing/gtest",
+ "//ui/base:base",
+ "//url",
+ ]
+}
diff --git a/chrome/browser/apps/link_capturing/OWNERS b/chrome/browser/apps/link_capturing/OWNERS
new file mode 100644
index 0000000..306ad10
--- /dev/null
+++ b/chrome/browser/apps/link_capturing/OWNERS
@@ -0,0 +1,2 @@
+tsergeant@chromium.org
+mxcai@chromium.org
diff --git a/chrome/browser/apps/link_capturing/chromeos_link_capturing_delegate.cc b/chrome/browser/apps/link_capturing/chromeos_link_capturing_delegate.cc
new file mode 100644
index 0000000..dd1ca76
--- /dev/null
+++ b/chrome/browser/apps/link_capturing/chromeos_link_capturing_delegate.cc
@@ -0,0 +1,208 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/apps/link_capturing/chromeos_link_capturing_delegate.h"
+
+#include "ash/webui/projector_app/public/cpp/projector_app_constants.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/memory/values_equivalent.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/tick_clock.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/apps/app_service/launch_utils.h"
+#include "chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.h" // nogncheck https://crbug.com/1474116
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/web_app_tab_helper.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace apps {
+namespace {
+// Usually we want to only capture navigations from clicking a link. For a
+// subset of apps, we want to capture typing into the omnibox as well.
+bool ShouldOnlyCaptureLinks(const std::vector<std::string>& app_ids) {
+ for (const auto& app_id : app_ids) {
+ if (app_id == ash::kChromeUIUntrustedProjectorSwaAppId) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool IsSystemWebApp(Profile* profile, const std::string& app_id) {
+ bool is_system_web_app = false;
+ apps::AppServiceProxyFactory::GetForProfile(profile)
+ ->AppRegistryCache()
+ .ForOneApp(app_id, [&is_system_web_app](const apps::AppUpdate& update) {
+ if (update.InstallReason() == apps::InstallReason::kSystem) {
+ is_system_web_app = true;
+ }
+ });
+ return is_system_web_app;
+}
+
+// This function redirects an external untrusted |url| to a privileged trusted
+// one for SWAs, if applicable.
+GURL RedirectUrlIfSwa(Profile* profile,
+ const std::string& app_id,
+ const GURL& url,
+ const base::TickClock* clock) {
+ if (!IsSystemWebApp(profile, app_id)) {
+ return url;
+ }
+
+ // Projector:
+ if (app_id == ash::kChromeUIUntrustedProjectorSwaAppId &&
+ url.GetWithEmptyPath() == GURL(ash::kChromeUIUntrustedProjectorPwaUrl)) {
+ std::string override_url = ash::kChromeUIUntrustedProjectorUrl;
+ if (url.path().length() > 1) {
+ override_url += url.path().substr(1);
+ }
+ std::stringstream ss;
+ // Since ChromeOS doesn't reload an app if the URL doesn't change, the line
+ // below appends a unique timestamp to the URL to force a reload.
+ // TODO(b/211787536): Remove the timestamp after we update the trusted URL
+ // to match the user's navigations through the post message api.
+ ss << override_url << "?timestamp=" << clock->NowTicks();
+
+ if (url.has_query()) {
+ ss << '&' << url.query();
+ }
+
+ GURL result(ss.str());
+ DCHECK(result.is_valid());
+ return result;
+ }
+ // Add redirects for other SWAs above this line.
+
+ // No matching SWAs found, returning original url.
+ return url;
+}
+
+IntentHandlingMetrics::Platform GetMetricsPlatform(AppType app_type) {
+ switch (app_type) {
+ case AppType::kArc:
+ return IntentHandlingMetrics::Platform::ARC;
+ case AppType::kWeb:
+ case AppType::kSystemWeb:
+ return IntentHandlingMetrics::Platform::PWA;
+ case AppType::kUnknown:
+ case AppType::kBuiltIn:
+ case AppType::kCrostini:
+ case AppType::kChromeApp:
+ case AppType::kMacOs:
+ case AppType::kPluginVm:
+ case AppType::kStandaloneBrowser:
+ case AppType::kRemote:
+ case AppType::kBorealis:
+ case AppType::kStandaloneBrowserChromeApp:
+ case AppType::kExtension:
+ case AppType::kStandaloneBrowserExtension:
+ case AppType::kBruschetta:
+ NOTREACHED();
+ return IntentHandlingMetrics::Platform::ARC;
+ }
+}
+
+void LaunchApp(base::WeakPtr<AppServiceProxy> proxy,
+ const std::string& app_id,
+ int32_t event_flags,
+ GURL url,
+ LaunchSource launch_source,
+ WindowInfoPtr window_info,
+ AppType app_type,
+ base::OnceClosure callback) {
+ if (!proxy) {
+ std::move(callback).Run();
+ return;
+ }
+
+ proxy->LaunchAppWithUrl(
+ app_id, event_flags, url, launch_source, std::move(window_info),
+ base::IgnoreArgs<LaunchResult&&>(std::move(callback)));
+
+ IntentHandlingMetrics::RecordPreferredAppLinkClickMetrics(
+ GetMetricsPlatform(app_type));
+}
+} // namespace
+
+// static
+const base::TickClock* ChromeOsLinkCapturingDelegate::clock_ =
+ base::DefaultTickClock::GetInstance();
+
+// static
+void ChromeOsLinkCapturingDelegate::SetClockForTesting(
+ const base::TickClock* tick_clock) {
+ clock_ = tick_clock;
+}
+
+ChromeOsLinkCapturingDelegate::ChromeOsLinkCapturingDelegate() = default;
+ChromeOsLinkCapturingDelegate::~ChromeOsLinkCapturingDelegate() = default;
+
+bool ChromeOsLinkCapturingDelegate::ShouldCancelThrottleCreation(
+ content::NavigationHandle* handle) {
+ content::WebContents* web_contents = handle->GetWebContents();
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ return !AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile);
+}
+
+absl::optional<apps::LinkCapturingNavigationThrottle::LaunchCallback>
+ChromeOsLinkCapturingDelegate::CreateLinkCaptureLaunchClosure(
+ Profile* profile,
+ content::WebContents* web_contents,
+ const GURL& url,
+ bool is_navigation_from_link) {
+ AppServiceProxy* proxy = apps::AppServiceProxyFactory::GetForProfile(profile);
+
+ AppIdsToLaunchForUrl app_id_to_launch = FindAppIdsToLaunchForUrl(proxy, url);
+
+ if (app_id_to_launch.candidates.empty()) {
+ return absl::nullopt;
+ }
+
+ if (ShouldOnlyCaptureLinks(app_id_to_launch.candidates) &&
+ !is_navigation_from_link) {
+ return absl::nullopt;
+ }
+
+ if (!app_id_to_launch.preferred) {
+ return absl::nullopt;
+ }
+
+ const std::string& preferred_app_id = *app_id_to_launch.preferred;
+ // Only automatically launch supported app types.
+ AppType app_type = proxy->AppRegistryCache().GetAppType(preferred_app_id);
+ if (app_type != AppType::kArc && app_type != AppType::kWeb &&
+ !IsSystemWebApp(profile, preferred_app_id)) {
+ return absl::nullopt;
+ }
+
+ // Don't capture if already inside the target app scope.
+ if (app_type == AppType::kWeb &&
+ base::ValuesEquivalent(web_app::WebAppTabHelper::GetAppId(web_contents),
+ &preferred_app_id)) {
+ return absl::nullopt;
+ }
+
+ auto launch_source = is_navigation_from_link ? LaunchSource::kFromLink
+ : LaunchSource::kFromOmnibox;
+ GURL redirected_url =
+ RedirectUrlIfSwa(profile, preferred_app_id, url, clock_);
+
+ // Note: The launch can occur after this object is destroyed, so bind to a
+ // static function.
+ return base::BindOnce(
+ &LaunchApp, proxy->GetWeakPtr(), preferred_app_id,
+ GetEventFlags(WindowOpenDisposition::NEW_WINDOW,
+ /*prefer_container=*/true),
+ redirected_url, launch_source,
+ std::make_unique<WindowInfo>(display::kDefaultDisplayId), app_type);
+}
+
+} // namespace apps
diff --git a/chrome/browser/apps/link_capturing/chromeos_link_capturing_delegate.h b/chrome/browser/apps/link_capturing/chromeos_link_capturing_delegate.h
new file mode 100644
index 0000000..c25c742a
--- /dev/null
+++ b/chrome/browser/apps/link_capturing/chromeos_link_capturing_delegate.h
@@ -0,0 +1,52 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_APPS_LINK_CAPTURING_CHROMEOS_LINK_CAPTURING_DELEGATE_H_
+#define CHROME_BROWSER_APPS_LINK_CAPTURING_CHROMEOS_LINK_CAPTURING_DELEGATE_H_
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.h"
+
+class GURL;
+class Profile;
+
+namespace base {
+class TickClock;
+}
+
+namespace content {
+class NavigationHandle;
+class WebContents;
+} // namespace content
+
+namespace apps {
+
+class ChromeOsLinkCapturingDelegate
+ : public apps::LinkCapturingNavigationThrottle::Delegate {
+ public:
+ ChromeOsLinkCapturingDelegate();
+ ~ChromeOsLinkCapturingDelegate() override;
+
+ // Method intended for testing purposes only.
+ // Set clock used for timing to enable manipulation during tests.
+ static void SetClockForTesting(const base::TickClock* tick_clock);
+
+ // apps::LinkCapturingNavigationThrottle::Delegate:
+ bool ShouldCancelThrottleCreation(content::NavigationHandle* handle) override;
+ absl::optional<apps::LinkCapturingNavigationThrottle::LaunchCallback>
+ CreateLinkCaptureLaunchClosure(Profile* profile,
+ content::WebContents* web_contents,
+ const GURL& url,
+ bool is_navigation_from_link) final;
+
+ private:
+ // Used to create a unique timestamped URL to force reload apps.
+ // Points to the base::DefaultTickClock by default.
+ static const base::TickClock* clock_;
+
+ base::WeakPtrFactory<ChromeOsLinkCapturingDelegate> weak_factory_{this};
+};
+} // namespace apps
+
+#endif // CHROME_BROWSER_APPS_LINK_CAPTURING_CHROMEOS_LINK_CAPTURING_DELEGATE_H_
diff --git a/chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.cc b/chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.cc
new file mode 100644
index 0000000..cf371c7
--- /dev/null
+++ b/chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.cc
@@ -0,0 +1,342 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.h"
+
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/no_destructor.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/types/cxx23_to_underlying.h"
+#include "chrome/browser/preloading/prefetch/no_state_prefetch/chrome_no_state_prefetch_contents_delegate.h" // nogncheck https://crbug.com/1474116
+#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h" // nogncheck https://crbug.com/1474116
+#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h" // nogncheck https://crbug.com/1474116
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/browser/web_applications/web_app_ui_manager.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
+#include "components/keep_alive_registry/keep_alive_types.h"
+#include "components/keep_alive_registry/scoped_keep_alive.h"
+#include "components/page_load_metrics/browser/page_load_metrics_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/common/constants.h"
+#include "url/origin.h"
+
+namespace apps {
+
+namespace {
+
+using ThrottleCheckResult = content::NavigationThrottle::ThrottleCheckResult;
+
+// Retrieves the 'starting' url for the given navigation handle. This considers
+// the referrer url, last committed url, and the initiator origin.
+GURL GetStartingUrl(content::NavigationHandle* navigation_handle) {
+ // This helps us determine a reference GURL for the current NavigationHandle.
+ // This is the order or preference: Referrer > LastCommittedURL >
+ // InitiatorOrigin. InitiatorOrigin *should* only be used on very rare cases,
+ // e.g. when the navigation goes from https: to http: on a new tab, thus
+ // losing the other potential referrers.
+ const GURL referrer_url = navigation_handle->GetReferrer().url;
+ if (referrer_url.is_valid() && !referrer_url.is_empty()) {
+ return referrer_url;
+ }
+
+ const GURL last_committed_url =
+ navigation_handle->GetWebContents()->GetLastCommittedURL();
+ if (last_committed_url.is_valid() && !last_committed_url.is_empty()) {
+ return last_committed_url;
+ }
+
+ const auto& initiator_origin = navigation_handle->GetInitiatorOrigin();
+ return initiator_origin.has_value() ? initiator_origin->GetURL() : GURL();
+}
+
+// Returns if the navigation appears to be a link navigation, but not from an
+// HTML post form.
+bool IsNavigateFromNonFormNonContextMenuLink(
+ content::NavigationHandle* navigation_handle) {
+ // Always handle http(s) <form> submissions in Chrome for two reasons: 1) we
+ // don't have a way to send POST data to ARC, and 2) intercepting http(s) form
+ // submissions is not very important because such submissions are usually
+ // done within the same domain. ShouldOverrideUrlLoading() below filters out
+ // such submissions anyway.
+ constexpr bool kAllowFormSubmit = false;
+
+ ui::PageTransition page_transition = navigation_handle->GetPageTransition();
+
+ return LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ page_transition, kAllowFormSubmit,
+ navigation_handle->IsInFencedFrameTree(),
+ navigation_handle->HasUserGesture()) &&
+ !navigation_handle->WasStartedFromContextMenu();
+}
+
+} // namespace
+
+bool LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransition page_transition,
+ bool allow_form_submit,
+ bool is_in_fenced_frame_tree,
+ bool has_user_gesture) {
+ // Navigations inside fenced frame trees are marked with
+ // PAGE_TRANSITION_AUTO_SUBFRAME in order not to add session history items
+ // (see https://crrev.com/c/3265344). So we only check |has_user_gesture|.
+ if (is_in_fenced_frame_tree) {
+ DCHECK(ui::PageTransitionCoreTypeIs(page_transition,
+ ui::PAGE_TRANSITION_AUTO_SUBFRAME));
+ return has_user_gesture;
+ }
+
+ // Mask out any redirect qualifiers
+ page_transition = MaskOutPageTransition(page_transition,
+ ui::PAGE_TRANSITION_IS_REDIRECT_MASK);
+
+ if (!ui::PageTransitionCoreTypeIs(page_transition,
+ ui::PAGE_TRANSITION_LINK) &&
+ !(allow_form_submit &&
+ ui::PageTransitionCoreTypeIs(page_transition,
+ ui::PAGE_TRANSITION_FORM_SUBMIT))) {
+ // Do not handle the |url| if this event wasn't spawned by the user clicking
+ // on a link.
+ return false;
+ }
+
+ if (base::to_underlying(ui::PageTransitionGetQualifier(page_transition)) !=
+ 0) {
+ // Qualifiers indicate that this navigation was the result of a click on a
+ // forward/back button, or typing in the URL bar. Don't handle any of those
+ // types of navigations.
+ return false;
+ }
+
+ return true;
+}
+
+ui::PageTransition LinkCapturingNavigationThrottle::MaskOutPageTransition(
+ ui::PageTransition page_transition,
+ ui::PageTransition mask) {
+ return ui::PageTransitionFromInt(page_transition & ~mask);
+}
+
+LinkCapturingNavigationThrottle::Delegate::~Delegate() = default;
+
+// static
+std::unique_ptr<content::NavigationThrottle>
+LinkCapturingNavigationThrottle::MaybeCreate(
+ content::NavigationHandle* handle,
+ std::unique_ptr<Delegate> delegate) {
+ // Don't handle navigations in subframes or main frames that are in a nested
+ // frame tree (e.g. portals, fenced-frame). We specifically allow
+ // prerendering navigations so that we can destroy the prerender. Opening an
+ // app must only happen when the user intentionally navigates; however, for a
+ // prerender, the prerender-activating navigation doesn't run throttles so we
+ // must cancel it during initial loading to get a standard (non-prerendering)
+ // navigation at link-click-time.
+ if (!handle->IsInPrimaryMainFrame() && !handle->IsInPrerenderedMainFrame()) {
+ return nullptr;
+ }
+
+ content::WebContents* web_contents = handle->GetWebContents();
+ if (prerender::ChromeNoStatePrefetchContentsDelegate::FromWebContents(
+ web_contents) != nullptr) {
+ return nullptr;
+ }
+
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ if (!web_app::AreWebAppsUserInstallable(profile)) {
+ return nullptr;
+ }
+
+ // Do not check apps for url if we are already in an app browser.
+ // It is possible that the web contents is not inserted to tab strip
+ // model at this stage (e.g. open url in new tab). So if we cannot
+ // find a browser at this moment, skip the check and this will be handled
+ // in `HandleRequest()`.
+ // This also checks if there is no browser attached to this web-contents yet,
+ // which means this was a middle-mouse-click action, which should not be
+ // captured.
+ // TODO(dmurph): Find a better way to detect middle-clicks.
+ // https://crbug.com/1474984
+ if (web_app::WebAppProvider::GetForWebApps(profile)
+ ->ui_manager()
+ .IsAppAffiliatedWindowOrNone(web_contents)) {
+ return nullptr;
+ }
+
+ return base::WrapUnique(
+ new LinkCapturingNavigationThrottle(handle, std::move(delegate)));
+}
+
+base::OnceClosure&
+LinkCapturingNavigationThrottle::GetLinkCaptureLaunchCallbackForTesting() {
+ static base::NoDestructor<base::OnceClosure> callback;
+ return *callback;
+}
+
+LinkCapturingNavigationThrottle::~LinkCapturingNavigationThrottle() = default;
+
+const char* LinkCapturingNavigationThrottle::GetNameForLogging() {
+ return "LinkCapturingNavigationThrottle";
+}
+
+ThrottleCheckResult LinkCapturingNavigationThrottle::WillStartRequest() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ starting_url_ = GetStartingUrl(navigation_handle());
+ return HandleRequest();
+}
+
+ThrottleCheckResult LinkCapturingNavigationThrottle::WillRedirectRequest() {
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ return HandleRequest();
+}
+
+// Returns true if |url| is a known and valid redirector that will redirect a
+// navigation elsewhere.
+// static
+bool LinkCapturingNavigationThrottle::IsGoogleRedirectorUrl(const GURL& url) {
+ // This currently only check for redirectors on the "google" domain.
+ if (!page_load_metrics::IsGoogleSearchHostname(url)) {
+ return false;
+ }
+
+ return url.path_piece() == "/url" && url.has_query();
+}
+
+// If the previous url and current url are not the same (AKA a redirection),
+// determines if the redirection should be considered for an app launch. Returns
+// false for redirections where:
+// * `previous_url` is an extension.
+// * `previous_url` is a google redirector.
+// static
+bool LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ const GURL& previous_url,
+ const GURL& current_url) {
+ // Check the scheme for both |previous_url| and |current_url| since an
+ // extension could have referred us (e.g. Google Docs).
+ if (previous_url.SchemeIs(extensions::kExtensionScheme)) {
+ return false;
+ }
+
+ // Skip URL redirectors that are intermediate pages redirecting towards a
+ // final URL.
+ if (IsGoogleRedirectorUrl(current_url)) {
+ return false;
+ }
+
+ return true;
+}
+
+ThrottleCheckResult LinkCapturingNavigationThrottle::HandleRequest() {
+ content::NavigationHandle* handle = navigation_handle();
+
+ // If the navigation will update the same document, don't consider as a
+ // capturable link.
+ if (handle->IsSameDocument()) {
+ return content::NavigationThrottle::PROCEED;
+ }
+
+ // When the navigation is initiated in a web page where sending a referrer
+ // is disabled, |previous_url| can be empty. In this case, we should open
+ // it in the desktop browser.
+ if (!starting_url_.is_valid()) {
+ return content::NavigationThrottle::PROCEED;
+ }
+
+ const GURL& url = handle->GetURL();
+ if (!url.is_valid()) {
+ DVLOG(1) << "Unexpected URL: " << url << ", opening in Chrome.";
+ return content::NavigationThrottle::PROCEED;
+ }
+
+ // Only http-style schemes are allowed.
+ if (!url.SchemeIsHTTPOrHTTPS()) {
+ return content::NavigationThrottle::PROCEED;
+ }
+
+ content::WebContents* web_contents = handle->GetWebContents();
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+
+ // Check this again, as the tab may have been reparented now.
+ // TODO(dmurph): Find a better way to detect middle clicks.
+ // https://crbug.com/1474984
+ if (web_app::WebAppProvider::GetForWebApps(profile)
+ ->ui_manager()
+ .IsAppAffiliatedWindowOrNone(web_contents)) {
+ return content::NavigationThrottle::PROCEED;
+ }
+
+ if (!ShouldOverrideUrlIfRedirected(starting_url_, url)) {
+ return content::NavigationThrottle::PROCEED;
+ }
+
+ bool is_navigation_from_link =
+ IsNavigateFromNonFormNonContextMenuLink(handle);
+
+ absl::optional<LaunchCallback> launch_link_capture =
+ delegate_->CreateLinkCaptureLaunchClosure(profile, web_contents, url,
+ is_navigation_from_link);
+ if (!launch_link_capture.has_value()) {
+ return content::NavigationThrottle::PROCEED;
+ }
+
+ // Browser & profile keep-alives must be used to keep the browser & profile
+ // alive because the old window is required to be closed before the new app is
+ // launched, which will destroy the profile & browser if it is the last
+ // window.
+ // Why close the tab first? The way web contents currently work, closing a tab
+ // in a window will re-activate that window if there are more tabs there. So
+ // if we wait until after the launch completes to close the tab, then it will
+ // cause the old window to come to the front hiding the newly launched app
+ // window.
+ std::unique_ptr<ScopedKeepAlive> browser_keep_alive;
+ std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive;
+ const GURL& last_committed_url = web_contents->GetLastCommittedURL();
+ if (!last_committed_url.is_valid() || last_committed_url.IsAboutBlank() ||
+ // After clicking a link in various apps (eg gchat), a blank redirect
+ // page is left behind. Remove it to clean up.
+ // WasInitiatedByLinkClick()
+ // returns false for links clicked from apps.
+ !handle->WasInitiatedByLinkClick()) {
+ browser_keep_alive = std::make_unique<ScopedKeepAlive>(
+ KeepAliveOrigin::APP_LAUNCH, KeepAliveRestartOption::ENABLED);
+ if (!profile->IsOffTheRecord()) {
+ profile_keep_alive = std::make_unique<ScopedProfileKeepAlive>(
+ profile, ProfileKeepAliveOrigin::kAppWindow);
+ }
+ web_contents->ClosePage();
+ }
+
+ base::OnceClosure launch_callback = base::BindOnce(
+ [](std::unique_ptr<ScopedKeepAlive> browser_keep_alive,
+ std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive) {
+ // Note: This callback currently serves to own the "keep alive" objects
+ // until the launch is complete.
+ if (GetLinkCaptureLaunchCallbackForTesting()) { // IN-TEST
+ std::move(GetLinkCaptureLaunchCallbackForTesting()).Run(); // IN-TEST
+ }
+ },
+ std::move(browser_keep_alive), std::move(profile_keep_alive));
+
+ // The tab may have been closed, which runs async and causes the browser
+ // window to be refocused. Post a task to launch the app to ensure launching
+ // happens after the tab closed, otherwise the opened app window might be
+ // inactivated.
+ base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(launch_link_capture.value()),
+ std::move(launch_callback)));
+ return content::NavigationThrottle::CANCEL_AND_IGNORE;
+}
+
+LinkCapturingNavigationThrottle::LinkCapturingNavigationThrottle(
+ content::NavigationHandle* navigation_handle,
+ std::unique_ptr<Delegate> delegate)
+ : content::NavigationThrottle(navigation_handle),
+ delegate_(std::move(delegate)) {}
+
+} // namespace apps
diff --git a/chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.h b/chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.h
new file mode 100644
index 0000000..ed86ac9
--- /dev/null
+++ b/chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.h
@@ -0,0 +1,96 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_APPS_LINK_CAPTURING_LINK_CAPTURING_NAVIGATION_THROTTLE_H_
+#define CHROME_BROWSER_APPS_LINK_CAPTURING_LINK_CAPTURING_NAVIGATION_THROTTLE_H_
+
+#include <memory>
+
+#include "content/public/browser/navigation_throttle.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+
+class Profile;
+
+namespace content {
+class NavigationHandle;
+class WebContents;
+} // namespace content
+
+namespace apps {
+
+// Allows canceling a navigation to instead be routed to an installed app.
+class LinkCapturingNavigationThrottle : public content::NavigationThrottle {
+ public:
+ using ThrottleCheckResult = content::NavigationThrottle::ThrottleCheckResult;
+
+ static bool IsCapturableLinkNavigation(ui::PageTransition page_transition,
+ bool allow_form_submit,
+ bool is_in_fenced_frame_tree,
+ bool has_user_gesture);
+
+ // Removes |mask| bits from |page_transition|.
+ static ui::PageTransition MaskOutPageTransition(
+ ui::PageTransition page_transition,
+ ui::PageTransition mask);
+
+ using LaunchCallback =
+ base::OnceCallback<void(base::OnceClosure on_launch_complete)>;
+
+ class Delegate {
+ public:
+ virtual ~Delegate();
+
+ virtual bool ShouldCancelThrottleCreation(
+ content::NavigationHandle* handle) = 0;
+
+ // If the return value is a nullopt, then no capture was possible.
+ // Otherwise, the returned closure will launch the application at the
+ // appropriate URL.
+ virtual absl::optional<LaunchCallback> CreateLinkCaptureLaunchClosure(
+ Profile* profile,
+ content::WebContents* web_contents,
+ const GURL& url,
+ bool is_navigation_from_link) = 0;
+ };
+
+ // Possibly creates a navigation throttle that checks if any installed apps
+ // can handle the URL being navigated to.
+ static std::unique_ptr<content::NavigationThrottle> MaybeCreate(
+ content::NavigationHandle* handle,
+ std::unique_ptr<Delegate> delegate);
+
+ static base::OnceClosure& GetLinkCaptureLaunchCallbackForTesting();
+
+ LinkCapturingNavigationThrottle(const LinkCapturingNavigationThrottle&) =
+ delete;
+ LinkCapturingNavigationThrottle& operator=(
+ const LinkCapturingNavigationThrottle&) = delete;
+ ~LinkCapturingNavigationThrottle() override;
+
+ // content::NavigationHandle overrides
+ const char* GetNameForLogging() override;
+ ThrottleCheckResult WillStartRequest() override;
+ ThrottleCheckResult WillRedirectRequest() override;
+
+ // Visible for testing.
+ static bool IsGoogleRedirectorUrl(const GURL& url);
+ // Visible for testing.
+ static bool ShouldOverrideUrlIfRedirected(const GURL& previous_url,
+ const GURL& current_url);
+
+ private:
+ explicit LinkCapturingNavigationThrottle(
+ content::NavigationHandle* navigation_handle,
+ std::unique_ptr<Delegate> delegate);
+ std::unique_ptr<Delegate> delegate_;
+ GURL starting_url_;
+
+ ThrottleCheckResult HandleRequest();
+};
+
+} // namespace apps
+
+#endif // CHROME_BROWSER_APPS_LINK_CAPTURING_LINK_CAPTURING_NAVIGATION_THROTTLE_H_
diff --git a/chrome/browser/apps/link_capturing/link_capturing_navigation_throttle_unittest.cc b/chrome/browser/apps/link_capturing/link_capturing_navigation_throttle_unittest.cc
new file mode 100644
index 0000000..ae6abe6
--- /dev/null
+++ b/chrome/browser/apps/link_capturing/link_capturing_navigation_throttle_unittest.cc
@@ -0,0 +1,270 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+
+namespace apps {
+namespace {
+
+TEST(LinkCapturingNavigationThrottleTest, TestIsGoogleRedirectorUrl) {
+ // Test that redirect urls with different TLDs are still recognized.
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsGoogleRedirectorUrl(
+ GURL("https://www.google.com.au/url?q=whatever")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsGoogleRedirectorUrl(
+ GURL("https://www.google.com.mx/url?q=hotpot")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsGoogleRedirectorUrl(
+ GURL("https://www.google.co/url?q=query")));
+
+ // Non-google domains shouldn't be used as valid redirect links.
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsGoogleRedirectorUrl(
+ GURL("https://www.not-google.com/url?q=query")));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsGoogleRedirectorUrl(
+ GURL("https://www.gooogle.com/url?q=legit_query")));
+
+ // This method only takes "/url" as a valid path, it needs to contain a query,
+ // we don't analyze that query as it will expand later on in the same
+ // throttle.
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsGoogleRedirectorUrl(
+ GURL("https://www.google.com/url?q=who_dis")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsGoogleRedirectorUrl(
+ GURL("http://www.google.com/url?q=who_dis")));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsGoogleRedirectorUrl(
+ GURL("https://www.google.com/url")));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsGoogleRedirectorUrl(
+ GURL("https://www.google.com/link?q=query")));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsGoogleRedirectorUrl(
+ GURL("https://www.google.com/link")));
+}
+
+TEST(LinkCapturingNavigationThrottleTest, TestShouldOverrideUrlLoading) {
+ // A navigation from chrome-extension scheme cannot be overridden.
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("chrome-extension://fake_document"), GURL("http://www.a.com")));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("chrome-extension://fake_document"), GURL("https://www.a.com")));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("chrome-extension://fake_a"), GURL("chrome-extension://fake_b")));
+
+ // Other navigations can be overridden.
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("http://www.google.com"), GURL("http://www.not-google.com/")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("http://www.not-google.com"), GURL("http://www.google.com/")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("http://www.google.com"), GURL("http://www.google.com/")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("http://a.google.com"), GURL("http://b.google.com/")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("http://a.not-google.com"), GURL("http://b.not-google.com")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("chrome://fake_document"), GURL("http://www.a.com")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("file://fake_document"), GURL("http://www.a.com")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("chrome://fake_document"), GURL("https://www.a.com")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("file://fake_document"), GURL("https://www.a.com")));
+
+ // A navigation going to a redirect url cannot be overridden, unless there's
+ // no query or the path is not valid.
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("http://www.google.com"), GURL("https://www.google.com/url?q=b")));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("https://www.a.com"), GURL("https://www.google.com/url?q=a")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("https://www.a.com"), GURL("https://www.google.com/url")));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::ShouldOverrideUrlIfRedirected(
+ GURL("https://www.a.com"), GURL("https://www.google.com/link?q=a")));
+}
+
+// Tests that LinkCapturingNavigationThrottle::ShouldIgnoreNavigation returns
+// false only for PAGE_TRANSITION_LINK when |allow_form_submit| is false and
+// |is_in_fenced_frame_tree| is false.
+TEST(LinkCapturingNavigationThrottleTest,
+ TestShouldIgnoreNavigationWithCoreTypes) {
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_LINK, false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_TYPED, false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_AUTO_BOOKMARK, false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_AUTO_SUBFRAME, false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_MANUAL_SUBFRAME, false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_GENERATED, false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_AUTO_TOPLEVEL, false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_FORM_SUBMIT, false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_RELOAD, false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_KEYWORD, false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_KEYWORD_GENERATED, false, false, false));
+
+ static_assert(static_cast<int32_t>(ui::PAGE_TRANSITION_KEYWORD_GENERATED) ==
+ static_cast<int32_t>(ui::PAGE_TRANSITION_LAST_CORE),
+ "Not all core transition types are covered here");
+}
+
+// Test that LinkCapturingNavigationThrottle::ShouldIgnoreNavigation accepts
+// FORM_SUBMIT when |allow_form_submit| is true.
+TEST(LinkCapturingNavigationThrottleTest, TestFormSubmit) {
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_FORM_SUBMIT, true, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PAGE_TRANSITION_FORM_SUBMIT, false, false, false));
+}
+
+// Tests that LinkCapturingNavigationThrottle::ShouldIgnoreNavigation returns
+// true when no qualifiers except client redirect and server redirect are
+// provided when |is_in_fenced_frame_tree| is false.
+TEST(LinkCapturingNavigationThrottleTest,
+ TestShouldIgnoreNavigationWithLinkWithQualifiers) {
+ // The navigation is triggered by Forward or Back button.
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_FORWARD_BACK),
+ false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_FORM_SUBMIT |
+ ui::PAGE_TRANSITION_FORWARD_BACK),
+ false, false, false));
+ // The user used the address bar to trigger the navigation.
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
+ false, false, false));
+ // The user pressed the Home button.
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_HOME_PAGE),
+ false, false, false));
+ // ARC (for example) opened the link in Chrome.
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_FROM_API),
+ false, false, false));
+ // The navigation is triggered by a client side redirect.
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_CLIENT_REDIRECT),
+ false, false, false));
+ // Also tests the case with 2+ qualifiers.
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_FROM_ADDRESS_BAR |
+ ui::PAGE_TRANSITION_CLIENT_REDIRECT),
+ false, false, false));
+ // The navigation is triggered by a server side redirect.
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_SERVER_REDIRECT),
+ false, false, false));
+ // Also tests the case with 2+ qualifiers.
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_FROM_ADDRESS_BAR |
+ ui::PAGE_TRANSITION_SERVER_REDIRECT),
+ false, false, false));
+}
+
+// Just in case, does the same with ui::PAGE_TRANSITION_TYPED.
+TEST(LinkCapturingNavigationThrottleTest,
+ TestShouldIgnoreNavigationWithTypedWithQualifiers) {
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
+ ui::PAGE_TRANSITION_FORWARD_BACK),
+ false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
+ ui::PAGE_TRANSITION_FORWARD_BACK),
+ false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
+ ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
+ false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
+ ui::PAGE_TRANSITION_HOME_PAGE),
+ false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
+ ui::PAGE_TRANSITION_FROM_API),
+ false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED |
+ ui::PAGE_TRANSITION_FROM_ADDRESS_BAR |
+ ui::PAGE_TRANSITION_CLIENT_REDIRECT),
+ false, false, false));
+}
+
+// Test that LinkCapturingNavigationThrottle::ShouldIgnoreNavigation accepts
+// SERVER_REDIRECT and CLIENT_REDIRECT when |is_in_fenced_frame_tree| is false.
+TEST(LinkCapturingNavigationThrottleTest,
+ TestShouldIgnoreNavigationWithClientRedirect) {
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK), false, false,
+ false));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_CLIENT_REDIRECT),
+ false, false, false));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_SERVER_REDIRECT),
+ false, false, false));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_SERVER_REDIRECT |
+ ui::PAGE_TRANSITION_SERVER_REDIRECT),
+ false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_CLIENT_REDIRECT |
+ ui::PAGE_TRANSITION_HOME_PAGE),
+ false, false, false));
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK |
+ ui::PAGE_TRANSITION_HOME_PAGE),
+ false, false, false));
+}
+
+// Test that MaskOutPageTransition correctly remove a qualifier from a given
+// |page_transition|.
+TEST(LinkCapturingNavigationThrottleTest, TestMaskOutPageTransition) {
+ ui::PageTransition page_transition = ui::PageTransitionFromInt(
+ ui::PAGE_TRANSITION_LINK | ui::PAGE_TRANSITION_CLIENT_REDIRECT);
+ EXPECT_EQ(
+ static_cast<int>(ui::PAGE_TRANSITION_LINK),
+ static_cast<int>(LinkCapturingNavigationThrottle::MaskOutPageTransition(
+ page_transition, ui::PAGE_TRANSITION_CLIENT_REDIRECT)));
+
+ page_transition = ui::PageTransitionFromInt(
+ ui::PAGE_TRANSITION_LINK | ui::PAGE_TRANSITION_SERVER_REDIRECT);
+ EXPECT_EQ(
+ static_cast<int>(ui::PAGE_TRANSITION_LINK),
+ static_cast<int>(LinkCapturingNavigationThrottle::MaskOutPageTransition(
+ page_transition, ui::PAGE_TRANSITION_SERVER_REDIRECT)));
+}
+
+// Test that LinkCapturingNavigationThrottle::ShouldIgnoreNavigation accepts iff
+// |has_user_gesture| is true when |is_in_fenced_frame_tree| is true.
+TEST(LinkCapturingNavigationThrottleTest, TestInFencedFrameTree) {
+ EXPECT_FALSE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_AUTO_SUBFRAME), false, true,
+ false));
+ EXPECT_TRUE(LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_AUTO_SUBFRAME), false, true,
+ true));
+}
+
+} // namespace
+} // namespace apps
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index ec9545d..72839015 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -553,12 +553,11 @@
// BUILDFLAG(IS_CHROMEOS_ASH)
#if !BUILDFLAG(IS_ANDROID)
+#include "chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.h"
#if BUILDFLAG(IS_CHROMEOS)
-#include "chrome/browser/apps/intent_helper/chromeos_apps_navigation_throttle.h"
#include "chrome/browser/apps/intent_helper/chromeos_disabled_apps_throttle.h"
+#include "chrome/browser/apps/link_capturing/chromeos_link_capturing_delegate.h"
#include "chrome/browser/policy/system_features_disable_list_policy_handler.h"
-#else
-#include "chrome/browser/apps/intent_helper/apps_navigation_throttle.h"
#endif
#endif
@@ -5048,25 +5047,19 @@
}
#endif
-#if !BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_CHROMEOS)
auto disabled_app_throttle =
apps::ChromeOsDisabledAppsThrottle::MaybeCreate(handle);
if (disabled_app_throttle) {
throttles.push_back(std::move(disabled_app_throttle));
}
-#endif // BUILDFLAG(IS_CHROMEOS)
-
auto url_to_apps_throttle =
-#if BUILDFLAG(IS_CHROMEOS)
- apps::ChromeOsAppsNavigationThrottle::MaybeCreate(handle);
-#else
- apps::AppsNavigationThrottle::MaybeCreate(handle);
-#endif // BUILDFLAG(IS_CHROMEOS)
+ apps::LinkCapturingNavigationThrottle::MaybeCreate(
+ handle, std::make_unique<apps::ChromeOsLinkCapturingDelegate>());
if (url_to_apps_throttle) {
throttles.push_back(std::move(url_to_apps_throttle));
}
-#endif // !BUILDFLAG(IS_ANDROID)
+#endif // BUILDFLAG(IS_CHROMEOS)
Profile* profile = Profile::FromBrowserContext(
handle->GetWebContents()->GetBrowserContext());
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index f42a90f..a02ce799 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -255,6 +255,7 @@
"//chrome/browser:browser_process",
"//chrome/browser:resources",
"//chrome/browser/apps/app_service",
+ "//chrome/browser/apps/link_capturing",
"//chrome/browser/browsing_data:constants",
"//chrome/browser/enterprise/data_controls",
"//chrome/browser/favicon",
diff --git a/chrome/browser/chromeos/arc/arc_external_protocol_dialog.cc b/chrome/browser/chromeos/arc/arc_external_protocol_dialog.cc
index ecee3c6..2dbfc71 100644
--- a/chrome/browser/chromeos/arc/arc_external_protocol_dialog.cc
+++ b/chrome/browser/chromeos/arc/arc_external_protocol_dialog.cc
@@ -12,7 +12,7 @@
#include "base/ranges/algorithm.h"
#include "build/chromeos_buildflags.h"
#include "chrome/app/vector_icons/vector_icons.h"
-#include "chrome/browser/apps/intent_helper/page_transition_util.h"
+#include "chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.h"
#include "chrome/browser/chromeos/arc/arc_web_contents_data.h"
#include "chrome/browser/sharing/click_to_call/click_to_call_metrics.h"
#include "chrome/browser/sharing/click_to_call/click_to_call_ui_controller.h"
@@ -91,8 +91,9 @@
}
// Append the previous list by moving its elements.
- for (auto& entry : picker_entries)
+ for (auto& entry : picker_entries) {
all_entries.emplace_back(std::move(entry));
+ }
return all_entries;
}
@@ -109,8 +110,9 @@
IntentPickerResponseWithDevices callback) {
Browser* browser =
web_contents ? chrome::FindBrowserWithWebContents(web_contents) : nullptr;
- if (!browser)
+ if (!browser) {
return false;
+ }
bool has_apps = !app_info.empty();
bool has_devices = false;
@@ -125,12 +127,14 @@
ClickToCallUiController::GetOrCreateFromWebContents(web_contents);
devices = controller->GetDevices();
has_devices = !devices.empty();
- if (has_devices)
+ if (has_devices) {
app_info = AddDevices(devices, std::move(app_info));
+ }
}
- if (app_info.empty())
+ if (app_info.empty()) {
return false;
+ }
IntentPickerTabHelper::ShowOrHideIcon(
web_contents,
@@ -140,16 +144,18 @@
initiating_origin,
base::BindOnce(std::move(callback), std::move(devices), bubble_type));
- if (controller)
+ if (controller) {
controller->OnIntentPickerShown(has_devices, has_apps);
+ }
return true;
}
void CloseTabIfNeeded(base::WeakPtr<WebContents> web_contents,
bool safe_to_bypass_ui) {
- if (!web_contents)
+ if (!web_contents) {
return;
+ }
if (web_contents->GetController().IsInitialNavigation() ||
safe_to_bypass_ui) {
@@ -162,8 +168,9 @@
const std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo>&
handlers) {
for (const auto& handler : handlers) {
- if (handler.package_name == kArcIntentHelperPackageName)
+ if (handler.package_name == kArcIntentHelperPackageName) {
return true;
+ }
}
return false;
}
@@ -185,8 +192,9 @@
// Shows |url| in the current tab.
void OpenUrlInChrome(base::WeakPtr<WebContents> web_contents, const GURL& url) {
- if (!web_contents)
+ if (!web_contents) {
return;
+ }
const ui::PageTransition page_transition_type =
ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK);
@@ -246,8 +254,9 @@
// Since |package_name| is "Chrome", and |fallback_url| is not null, the
// URL must be either http or https. Check it just in case, and if not,
// fallback to HANDLE_URL_IN_ARC;
- if (out_url_and_activity_name->first.SchemeIsHTTPOrHTTPS())
+ if (out_url_and_activity_name->first.SchemeIsHTTPOrHTTPS()) {
return GetActionResult::OPEN_URL_IN_CHROME;
+ }
LOG(WARNING) << "Failed to handle " << out_url_and_activity_name->first
<< " in Chrome. Falling back to ARC...";
@@ -319,8 +328,9 @@
for (size_t i = 0; i < handlers.size(); ++i) {
const ArcIntentHelperMojoDelegate::IntentHandlerInfo& handler =
handlers[i];
- if (!handler.is_preferred)
+ if (!handler.is_preferred) {
continue;
+ }
// This is another way to bypass the UI, since the user already expressed
// some sort of preference.
*in_out_safe_to_bypass_ui = true;
@@ -352,8 +362,9 @@
arc::ArcWebContentsData::ArcWebContentsData::kArcTransitionFlag;
arc::ArcWebContentsData* arc_data =
static_cast<arc::ArcWebContentsData*>(web_contents->GetUserData(key));
- if (!arc_data)
+ if (!arc_data) {
return false;
+ }
web_contents->RemoveUserData(key);
return true;
@@ -364,8 +375,9 @@
const std::vector<std::unique_ptr<syncer::DeviceInfo>>& devices,
const std::string& device_guid,
const GURL& url) {
- if (!web_contents)
+ if (!web_contents) {
return;
+ }
const auto it =
base::ranges::find(devices, device_guid, &syncer::DeviceInfo::guid);
@@ -394,8 +406,9 @@
const GetActionResult result =
GetAction(url, handlers, selected_app_index, &url_and_activity_name,
&safe_to_bypass_ui);
- if (out_result)
+ if (out_result) {
*out_result = result;
+ }
switch (result) {
case GetActionResult::OPEN_URL_IN_CHROME:
@@ -440,10 +453,11 @@
const std::vector<ArcIntentHelperMojoDelegate::IntentHandlerInfo>&
handlers) {
const GURL url_to_open_in_chrome = GetUrlToNavigateOnDeactivate(handlers);
- if (url_to_open_in_chrome.is_empty())
+ if (url_to_open_in_chrome.is_empty()) {
CloseTabIfNeeded(web_contents, safe_to_bypass_ui);
- else
+ } else {
OpenUrlInChrome(web_contents, url_to_open_in_chrome);
+ }
}
size_t GetAppIndex(
@@ -451,8 +465,9 @@
app_candidates,
const std::string& selected_app_package) {
for (size_t i = 0; i < app_candidates.size(); ++i) {
- if (app_candidates[i].package_name == selected_app_package)
+ if (app_candidates[i].package_name == selected_app_package) {
return i;
+ }
}
return app_candidates.size();
}
@@ -512,8 +527,9 @@
const size_t selected_app_index = GetAppIndex(handlers, selected_app_package);
// Make sure ARC intent helper instance is connected.
- if (!mojo_delegate->IsArcAvailable())
+ if (!mojo_delegate->IsArcAvailable()) {
reason = apps::IntentPickerCloseReason::ERROR_AFTER_PICKER;
+ }
if (reason == apps::IntentPickerCloseReason::OPEN_APP ||
reason == apps::IntentPickerCloseReason::STAY_IN_CHROME) {
@@ -602,8 +618,9 @@
web_contents ? chrome::FindBrowserWithWebContents(web_contents.get())
: nullptr;
- if (!browser)
+ if (!browser) {
return std::move(handled_cb).Run(false);
+ }
bool handled = MaybeAddDevicesAndShowPicker(
url, initiating_origin, web_contents.get(), std::move(app_info),
@@ -715,12 +732,14 @@
DCHECK(!url.SchemeIsHTTPOrHTTPS()) << url;
// For external protocol navigation, always ignore the FROM_API qualifier.
- const ui::PageTransition masked_page_transition = apps::MaskOutPageTransition(
- page_transition, ui::PAGE_TRANSITION_FROM_API);
+ const ui::PageTransition masked_page_transition =
+ apps::LinkCapturingNavigationThrottle::MaskOutPageTransition(
+ page_transition, ui::PAGE_TRANSITION_FROM_API);
- if (apps::ShouldIgnoreNavigation(masked_page_transition,
- /*allow_form_submit=*/true,
- is_in_fenced_frame_tree, has_user_gesture)) {
+ if (!apps::LinkCapturingNavigationThrottle::IsCapturableLinkNavigation(
+ masked_page_transition,
+ /*allow_form_submit=*/true, is_in_fenced_frame_tree,
+ has_user_gesture)) {
LOG(WARNING) << "RunArcExternalProtocolDialog: ignoring " << url
<< " with PageTransition=" << masked_page_transition
<< ", is_in_fenced_frame_tree=" << is_in_fenced_frame_tree
diff --git a/chrome/browser/ui/ash/projector/projector_navigation_throttle_browsertest.cc b/chrome/browser/ui/ash/projector/projector_navigation_throttle_browsertest.cc
index 673ddf8..a98a932 100644
--- a/chrome/browser/ui/ash/projector/projector_navigation_throttle_browsertest.cc
+++ b/chrome/browser/ui/ash/projector/projector_navigation_throttle_browsertest.cc
@@ -13,7 +13,7 @@
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/time.h"
#include "chrome/browser/apps/app_service/metrics/app_service_metrics.h"
-#include "chrome/browser/apps/intent_helper/chromeos_apps_navigation_throttle.h"
+#include "chrome/browser/apps/link_capturing/chromeos_link_capturing_delegate.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
@@ -69,7 +69,7 @@
base::TimeDelta forward_by = start_time - task_runner_->Now();
EXPECT_LT(base::TimeDelta(), forward_by);
task_runner_->AdvanceMockTickClock(forward_by);
- apps::ChromeOsAppsNavigationThrottle::SetClockForTesting(
+ apps::ChromeOsLinkCapturingDelegate::SetClockForTesting(
task_runner_->GetMockTickClock());
}
diff --git a/chrome/browser/ui/web_applications/BUILD.gn b/chrome/browser/ui/web_applications/BUILD.gn
index d272553..f784c4e 100644
--- a/chrome/browser/ui/web_applications/BUILD.gn
+++ b/chrome/browser/ui/web_applications/BUILD.gn
@@ -55,7 +55,7 @@
if (!is_chromeos_lacros) {
sources += [
- # Test not valid on Lacros as web apps are only enabled in the main
+ # This is not valid on Lacros as web apps are only enabled in the main
# profile which can never be deleted.
"web_app_profile_deletion_browsertest.cc",
]
@@ -65,6 +65,7 @@
sources += [
"app_browser_controller_browsertest_chromeos.cc",
"web_app_guest_session_browsertest_chromeos.cc",
+ "web_app_link_capturing_browsertest.cc",
]
}
@@ -76,6 +77,8 @@
"//chrome/browser:theme_properties",
"//chrome/browser/apps/app_service",
"//chrome/browser/apps/app_service:app_registry_cache_waiter",
+ "//chrome/browser/apps/app_service:test_support",
+ "//chrome/browser/apps/link_capturing",
"//chrome/browser/browsing_data:constants",
"//chrome/browser/devtools",
"//chrome/browser/web_applications:web_applications_test_support",
@@ -115,7 +118,6 @@
sources = [
"launch_web_app_browsertest.cc",
"web_app_badging_browsertest.cc",
- "web_app_link_capturing_browsertest.cc",
"web_app_protocol_handling_browsertest.cc",
"web_app_tab_restore_browsertest.cc",
"web_app_url_handling_browsertest.cc",
@@ -133,6 +135,7 @@
sources += [
"lacros_web_app_browsertest.cc",
"lacros_web_app_shelf_browsertest.cc",
+ "web_app_link_capturing_browsertest.cc",
]
}
@@ -142,6 +145,7 @@
"//chrome/app:command_ids",
"//chrome/browser/apps/app_service",
"//chrome/browser/apps/app_service:app_registry_cache_waiter",
+ "//chrome/browser/apps/link_capturing",
"//chrome/browser/browsing_data:constants",
"//chrome/browser/ui/web_applications/diagnostics:app_service_browser_tests",
"//chrome/browser/web_applications",
diff --git a/chrome/browser/ui/web_applications/web_app_link_capturing_browsertest.cc b/chrome/browser/ui/web_applications/web_app_link_capturing_browsertest.cc
index 275ca5a0..3ccb0f6 100644
--- a/chrome/browser/ui/web_applications/web_app_link_capturing_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_link_capturing_browsertest.cc
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/location.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
@@ -10,8 +11,8 @@
#include "chrome/browser/apps/app_service/app_registry_cache_waiter.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
-#include "chrome/browser/apps/intent_helper/chromeos_apps_navigation_throttle.h"
#include "chrome/browser/apps/intent_helper/preferred_apps_test_util.h"
+#include "chrome/browser/apps/link_capturing/link_capturing_navigation_throttle.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_list.h"
@@ -26,11 +27,10 @@
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/ui_test_utils.h"
-#include "components/embedder_support/switches.h"
-#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/prerender_test_util.h"
@@ -48,18 +48,23 @@
using ui_test_utils::BrowserChangeObserver;
namespace web_app {
-
+namespace {
using ClientMode = LaunchHandler::ClientMode;
-#if BUILDFLAG(IS_CHROMEOS)
-
// Tests that links are captured correctly into an installed WebApp using the
// 'tabbed' display mode, which allows the webapp window to have multiple tabs.
class WebAppLinkCapturingBrowserTest : public WebAppNavigationBrowserTest {
public:
WebAppLinkCapturingBrowserTest() {
- feature_list_.InitAndEnableFeature(
- blink::features::kWebAppEnableLaunchHandler);
+ std::vector<base::test::FeatureRef> features = {
+ blink::features::kWebAppEnableLaunchHandler};
+#if !BUILDFLAG(IS_CHROMEOS)
+ features.push_back(features::kDesktopPWAsLinkCapturing);
+#endif
+ feature_list_.InitWithFeatures(
+ /*enabled_features=*/
+ features,
+ /*disabled_features=*/{});
}
~WebAppLinkCapturingBrowserTest() override = default;
@@ -119,7 +124,9 @@
return observer.Wait();
}
- void ExpectTabs(Browser* test_browser, std::vector<GURL> urls) {
+ void ExpectTabs(Browser* test_browser,
+ std::vector<GURL> urls,
+ base::Location location = FROM_HERE) {
std::string debug_info = "\nOpen browsers:\n";
for (Browser* open_browser : *BrowserList::GetInstance()) {
debug_info += " ";
@@ -139,6 +146,7 @@
"\n";
}
}
+ SCOPED_TRACE(location.ToString());
SCOPED_TRACE(debug_info);
TabStripModel& tab_strip = *test_browser->tab_strip_model();
ASSERT_EQ(static_cast<size_t>(tab_strip.count()), urls.size());
@@ -152,7 +160,14 @@
}
void TurnOnLinkCapturing() {
+#if BUILDFLAG(IS_CHROMEOS)
apps_util::SetSupportedLinksPreferenceAndWait(profile(), app_id_);
+#else
+ ScopedRegistryUpdate update = provider().sync_bridge_unsafe().BeginUpdate();
+ WebApp* app = update->UpdateApp(app_id_);
+ CHECK(app);
+ app->SetIsUserSelectedAppForSupportedLinks(true);
+#endif // BUILDFLAG(IS_CHROMEOS)
}
absl::optional<LaunchHandler> GetLaunchHandler(const AppId& app_id) {
@@ -252,7 +267,7 @@
// Must wait for link capturing launch to complete so that its keep alives go
// out of scope.
base::test::TestFuture<void> future;
- apps::ChromeOsAppsNavigationThrottle::
+ apps::LinkCapturingNavigationThrottle::
GetLinkCaptureLaunchCallbackForTesting() = future.GetCallback();
ASSERT_TRUE(future.Wait());
}
@@ -283,9 +298,14 @@
: public WebAppLinkCapturingBrowserTest {
public:
WebAppTabStripLinkCapturingBrowserTest() {
- features_.InitWithFeatures({features::kDesktopPWAsTabStrip,
- features::kDesktopPWAsTabStripSettings},
- {});
+ std::vector<base::test::FeatureRef> features = {
+ features::kDesktopPWAsTabStrip, features::kDesktopPWAsTabStripSettings};
+#if !BUILDFLAG(IS_CHROMEOS)
+ features.push_back(features::kDesktopPWAsLinkCapturing);
+#endif
+ features_.InitWithFeatures(
+ /*enabled_features=*/features,
+ /*disabled_features=*/{});
}
void InstallTestTabbedApp() {
@@ -338,6 +358,5 @@
ExpectTabs(app_browser, {in_scope_1_, in_scope_2_, scope_});
}
-#endif // BUILDFLAG(IS_CHROMEOS)
-
+} // namespace
} // namespace web_app
diff --git a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
index ea53e46..d1ef0c8 100644
--- a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
+++ b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
@@ -158,8 +158,9 @@
started_ = true;
for (Browser* browser : *BrowserList::GetInstance()) {
- if (!IsBrowserForInstalledApp(browser))
+ if (!IsBrowserForInstalledApp(browser)) {
continue;
+ }
++num_windows_for_apps_map_[GetAppIdForBrowser(browser)];
}
@@ -184,8 +185,9 @@
DCHECK(started_);
auto it = num_windows_for_apps_map_.find(app_id);
- if (it == num_windows_for_apps_map_.end())
+ if (it == num_windows_for_apps_map_.end()) {
return 0;
+ }
return it->second;
}
@@ -255,19 +257,27 @@
bool WebAppUiManagerImpl::IsInAppWindow(content::WebContents* web_contents,
const AppId* app_id) const {
Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
- if (app_id)
+ if (app_id) {
return AppBrowserController::IsForWebApp(browser, *app_id);
+ }
return AppBrowserController::IsWebApp(browser);
}
+bool WebAppUiManagerImpl::IsAppAffiliatedWindowOrNone(
+ content::WebContents* web_contents) const {
+ Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
+ return !browser || browser->is_type_app_popup() || browser->is_type_app();
+}
+
void WebAppUiManagerImpl::NotifyOnAssociatedAppChanged(
content::WebContents* web_contents,
const absl::optional<AppId>& previous_app_id,
const absl::optional<AppId>& new_app_id) const {
WebAppMetrics* web_app_metrics = WebAppMetrics::Get(profile_);
// Unavailable in guest sessions.
- if (!web_app_metrics)
+ if (!web_app_metrics) {
return;
+ }
web_app_metrics->NotifyOnAssociatedAppChanged(web_contents, previous_app_id,
new_app_id);
}
@@ -425,16 +435,18 @@
void WebAppUiManagerImpl::OnBrowserAdded(Browser* browser) {
DCHECK(started_);
- if (!IsBrowserForInstalledApp(browser))
+ if (!IsBrowserForInstalledApp(browser)) {
return;
+ }
++num_windows_for_apps_map_[GetAppIdForBrowser(browser)];
}
void WebAppUiManagerImpl::OnBrowserRemoved(Browser* browser) {
DCHECK(started_);
- if (!IsBrowserForInstalledApp(browser))
+ if (!IsBrowserForInstalledApp(browser)) {
return;
+ }
const auto& app_id = GetAppIdForBrowser(browser);
@@ -442,15 +454,18 @@
DCHECK_GT(num_windows_for_app, 0u);
--num_windows_for_app;
- if (num_windows_for_app > 0)
+ if (num_windows_for_app > 0) {
return;
+ }
auto it = windows_closed_requests_map_.find(app_id);
- if (it == windows_closed_requests_map_.end())
+ if (it == windows_closed_requests_map_.end()) {
return;
+ }
- for (auto& callback : it->second)
+ for (auto& callback : it->second) {
std::move(callback).Run();
+ }
windows_closed_requests_map_.erase(app_id);
}
@@ -466,11 +481,13 @@
#endif // BUILDFLAG(IS_WIN)
bool WebAppUiManagerImpl::IsBrowserForInstalledApp(Browser* browser) {
- if (browser->profile() != profile_)
+ if (browser->profile() != profile_) {
return false;
+ }
- if (!browser->app_controller())
+ if (!browser->app_controller()) {
return false;
+ }
return true;
}
diff --git a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h
index e8b8150..2df1bb5 100644
--- a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h
+++ b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h
@@ -62,6 +62,8 @@
bool IsAppInQuickLaunchBar(const AppId& app_id) const override;
bool IsInAppWindow(content::WebContents* web_contents,
const AppId* app_id) const override;
+ bool IsAppAffiliatedWindowOrNone(
+ content::WebContents* web_contents) const override;
void NotifyOnAssociatedAppChanged(
content::WebContents* web_contents,
const absl::optional<AppId>& previous_app_id,
diff --git a/chrome/browser/web_applications/test/fake_web_app_ui_manager.cc b/chrome/browser/web_applications/test/fake_web_app_ui_manager.cc
index 88c605f..363ad57 100644
--- a/chrome/browser/web_applications/test/fake_web_app_ui_manager.cc
+++ b/chrome/browser/web_applications/test/fake_web_app_ui_manager.cc
@@ -96,6 +96,11 @@
return false;
}
+bool FakeWebAppUiManager::IsAppAffiliatedWindowOrNone(
+ content::WebContents* web_contents) const {
+ return false;
+}
+
bool FakeWebAppUiManager::CanReparentAppTabToWindow(
const AppId& app_id,
bool shortcut_created) const {
diff --git a/chrome/browser/web_applications/test/fake_web_app_ui_manager.h b/chrome/browser/web_applications/test/fake_web_app_ui_manager.h
index 5961582..069b9988 100644
--- a/chrome/browser/web_applications/test/fake_web_app_ui_manager.h
+++ b/chrome/browser/web_applications/test/fake_web_app_ui_manager.h
@@ -48,6 +48,8 @@
bool IsAppInQuickLaunchBar(const AppId& app_id) const override;
bool IsInAppWindow(content::WebContents* web_contents,
const AppId* app_id) const override;
+ bool IsAppAffiliatedWindowOrNone(
+ content::WebContents* web_contents) const override;
void NotifyOnAssociatedAppChanged(
content::WebContents* web_contents,
const absl::optional<AppId>& previous_app_id,
diff --git a/chrome/browser/web_applications/web_app_ui_manager.h b/chrome/browser/web_applications/web_app_ui_manager.h
index d4b8961..b6a4e90 100644
--- a/chrome/browser/web_applications/web_app_ui_manager.h
+++ b/chrome/browser/web_applications/web_app_ui_manager.h
@@ -30,7 +30,7 @@
namespace content {
class WebContents;
class NavigationHandle;
-}
+} // namespace content
namespace web_app {
@@ -130,6 +130,11 @@
// |app_id|, or any web app window if |app_id| is nullptr.
virtual bool IsInAppWindow(content::WebContents* web_contents,
const AppId* app_id = nullptr) const = 0;
+ // Returns true if the given web contents is associated with an app window, or
+ // if there is no browser associated with this web contents yet.
+ // TODO(https://crbug.com/1474984): Remove the 'none' condition here.
+ virtual bool IsAppAffiliatedWindowOrNone(
+ content::WebContents* web_contents) const = 0;
virtual void NotifyOnAssociatedAppChanged(
content::WebContents* web_contents,
const absl::optional<AppId>& previous_app_id,
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 3fc26a48..2ce5a3d3 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -4661,6 +4661,7 @@
"//ash/webui/shortcut_customization_ui/backend/search:mojo_bindings",
"//ash/webui/web_applications/test:test_support",
"//chrome/browser/apps/app_preload_service:browser_tests",
+ "//chrome/browser/apps/link_capturing:link_capturing",
"//chrome/browser/ash",
"//chrome/browser/ash:add_remove_user_event_proto",
"//chrome/browser/ash:arc_test_support",
@@ -7221,8 +7222,6 @@
# !is_android
sources += [
"../browser/apps/intent_helper/intent_picker_auto_display_prefs_unittest.cc",
- "../browser/apps/intent_helper/intent_picker_internal_unittest.cc",
- "../browser/apps/intent_helper/page_transition_util_unittest.cc",
"../browser/autocomplete/tab_matcher_desktop_unittest.cc",
"../browser/autofill/autofill_image_fetcher_impl_unittest.cc",
"../browser/browser_commands_unittest.cc",
@@ -7806,6 +7805,7 @@
"//chrome/browser/apps/app_service:app_registry_cache_waiter",
"//chrome/browser/apps/app_service:test_support",
"//chrome/browser/apps/app_service:unit_tests",
+ "//chrome/browser/apps/link_capturing:unit_tests",
"//chrome/browser/companion/core",
"//chrome/browser/companion/core/mojom:mojo_bindings",
"//chrome/browser/companion/core/proto",