blob: 851524d847fe960f559cf492176415245e3b7c20 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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 "base/optional.h"
#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/web_app_launch_utils.h"
#include "chrome/browser/web_applications/components/app_registrar.h"
#include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "chrome/browser/web_applications/components/web_app_provider_base.h"
#include "chrome/browser/web_applications/components/web_app_tab_helper_base.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/common/chrome_features.h"
#include "components/services/app_service/public/mojom/types.mojom.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/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) {
if (!handle->IsInMainFrame())
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);
// TODO(crbug.com/824598): This is no longer needed after removing
// ChromeOsAppsNavigationThrottle.
if (ui_displayed_)
return content::NavigationThrottle::PROCEED;
return HandleRequest();
}
bool AppsNavigationThrottle::ShouldCancelNavigation(
content::NavigationHandle* handle) {
return false;
}
bool AppsNavigationThrottle::ShouldDeferNavigation(
content::NavigationHandle* handle) {
return false;
}
bool AppsNavigationThrottle::ShouldShowDisablePage(
content::NavigationHandle* handle) {
return false;
}
ThrottleCheckResult AppsNavigationThrottle::MaybeShowCustomResult() {
return content::NavigationThrottle::CANCEL_AND_IGNORE;
}
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;
DCHECK(!ui_displayed_);
content::WebContents* web_contents = handle->GetWebContents();
const GURL& url = handle->GetURL();
navigate_from_link_ = IsNavigateFromLink(handle);
MaybeRemoveComingFromArcFlag(web_contents, starting_url_, url);
// Do not pop up the intent picker bubble or 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;
}
// Handles web app link capturing that has not yet integrated with the
// intent handling system.
// TODO(crbug.com/1163398): Remove this code path.
base::Optional<ThrottleCheckResult> web_app_capture =
CaptureWebAppScopeNavigations(web_contents, handle);
if (web_app_capture.has_value())
return web_app_capture.value();
if (ShouldDeferNavigation(handle)) {
// Handling is now deferred to ArcIntentPickerAppFetcher, which
// asynchronously queries ARC for apps, and runs
// OnDeferredNavigationProcessed() with an action based on whether an
// acceptable app was found and user consent to open received. We assume
// the UI is shown or a preferred app was found; reset to false if we
// resume the navigation.
ui_displayed_ = true;
return content::NavigationThrottle::DEFER;
}
if (ShouldShowDisablePage(handle))
return MaybeShowCustomResult();
}
return content::NavigationThrottle::PROCEED;
}
base::Optional<ThrottleCheckResult>
AppsNavigationThrottle::CaptureWebAppScopeNavigations(
content::WebContents* web_contents,
content::NavigationHandle* handle) const {
if (!navigate_from_link())
return base::nullopt;
Profile* const profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
web_app::WebAppProviderBase* provider =
web_app::WebAppProviderBase::GetProviderBase(profile);
if (!provider)
return base::nullopt;
base::Optional<web_app::AppId> app_id =
provider->registrar().FindInstalledAppWithUrlInScope(
handle->GetURL(), /*window_only=*/true);
if (!app_id)
return base::nullopt;
// Experimental tabbed web app link capturing behaves like new-client.
// This will be removed once we phase out kDesktopPWAsTabStripLinkCapturing in
// favor of kWebAppEnableLinkCapturing.
bool app_in_tabbed_mode =
provider->registrar().IsInExperimentalTabbedWindowMode(*app_id);
bool tabbed_link_capturing =
base::FeatureList::IsEnabled(features::kDesktopPWAsTabStripLinkCapturing);
bool web_apps_integrated_into_intent_handling =
base::FeatureList::IsEnabled(features::kIntentPickerPWAPersistence);
// This particular link capturing code path only applies to tabbed web app
// link capturing and the version of declarative link capturing that has not
// yet integrated with app service's intent handling system.
if ((!app_in_tabbed_mode || !tabbed_link_capturing) &&
web_apps_integrated_into_intent_handling) {
return base::nullopt;
}
auto* tab_helper =
web_app::WebAppTabHelperBase::FromWebContents(web_contents);
if (tab_helper && tab_helper->GetAppId() == *app_id) {
// Already in app scope, do not alter window state while using the app.
return base::nullopt;
}
blink::mojom::CaptureLinks capture_links = provider->registrar()
.AsWebAppRegistrar()
->GetAppById(*app_id)
->capture_links();
if (capture_links == blink::mojom::CaptureLinks::kUndefined &&
app_in_tabbed_mode && tabbed_link_capturing) {
capture_links = blink::mojom::CaptureLinks::kNewClient;
}
switch (capture_links) {
case blink::mojom::CaptureLinks::kUndefined:
case blink::mojom::CaptureLinks::kNone:
return base::nullopt;
case blink::mojom::CaptureLinks::kExistingClientNavigate:
case blink::mojom::CaptureLinks::kNewClient: {
Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
if (!browser) {
// This is a middle click open in new tab action; do not capture.
return base::nullopt;
}
if (web_app::AppBrowserController::IsForWebApp(browser, *app_id)) {
// Already in the app window; navigation already captured.
return base::nullopt;
}
if (capture_links ==
blink::mojom::CaptureLinks::kExistingClientNavigate) {
for (Browser* open_browser : *BrowserList::GetInstance()) {
if (web_app::AppBrowserController::IsForWebApp(open_browser,
*app_id)) {
open_browser->OpenURL(
content::OpenURLParams::FromNavigationHandle(handle));
// If |web_contents| hasn't loaded yet or has only loaded
// about:blank we should remove it to avoid leaving behind a blank
// tab.
if (tab_helper && !tab_helper->HasLoadedNonAboutBlankPage())
web_contents->ClosePage();
return content::NavigationThrottle::CANCEL_AND_IGNORE;
}
}
// No browser found; fallthrough to new-client behaviour.
}
// If |web_contents| hasn't loaded yet or has only loaded about:blank we
// should reparent it into the app window to avoid leaving behind a blank
// tab.
if (tab_helper && !tab_helper->HasLoadedNonAboutBlankPage()) {
web_app::ReparentWebContentsIntoAppBrowser(web_contents, *app_id);
return content::NavigationThrottle::PROCEED;
}
apps::AppLaunchParams launch_params(
*app_id, apps::mojom::LaunchContainer::kLaunchContainerWindow,
WindowOpenDisposition::CURRENT_TAB,
apps::mojom::AppLaunchSource::kSourceUrlHandler);
launch_params.override_url = handle->GetURL();
apps::AppServiceProxyFactory::GetForProfile(profile)
->BrowserAppLauncher()
->LaunchAppWithParams(std::move(launch_params));
return content::NavigationThrottle::CANCEL_AND_IGNORE;
}
}
}
} // namespace apps