Skip to content

Commit

Permalink
Implement the registerProtocolHandler method for ContentShell
Browse files Browse the repository at this point in the history
This CL implements in the ContentShell the registerProtocolHandler()
method as defined in the HTML spec [1]

The change is basically making the Shell class implementing the
RegisterProtocolHandler method form the WebContentsDelegate public API.

Thanks to the change landed in r982488 we have now all the privacy and
security checks performed by the browser process in the WebContentsImpl
class, so that chrome or any embedder can rely on this logic to ensure
the registerProtocolHandler params normalization defined in the spec is
complete. Hence, this CL would only have to worry about the registration
logic.

The ContentShell, as any other embedder, should use its own registry
factory. The Chrome's implementation has one, using its own delegate
to deal with the OS integration and the user preferences storage. In
this CL a new TestProtocolHandlerRegistryFactory class is added in the
Custom Handlers component, using the TestProtocolHandlerRegistryDelegate
for now.

Since the ContentShell's implementation is intended to be used to run
Web Platform Tests, I haven't included the permission related logic that
is present in the Chrome's implementation. We just call to the
OnAcceptRegisterProtocolHandler to perform the handler registration, or
OnIgnoreRegisterProtocolHandler if we want to cancel the request (eg,
request coming from a Fenced frame)

Additionally, it shouldn't make sense to rely on a user-preference
storage to provide a persistent handler registration. Given that the
goal is to run WPTs, we would just need to use the in-memory data
structures to verify the tests. In order to avoid the pref storage, the
mentioned testing registry factory pass nullptr as the PrefService
pointer.

This CL also implements the spec regarding the use of the registered
handler. This issue is described in the bug 1292925 and it's needed to
pass all the already defined manual tests in the WPT repo [2]. This
changes implies the ShellContentBrowserClient class implementing the
ContentBrowserClient::CreateURLLoaderThrottles public API, as described
in the mentioned bug.

Finally, this CL also provides ContentBrowserTest to ensure the method
is implemented correctly using the content-shell. These browser tests
are a subset of the ones already defined for Chrome. In order to avoid
duplication, I've moved some of the test/data support files to the
Custom Handlers component.


[1] https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers
[2] https://wpt.fyi/results/html/webappapis/system-state-and-capabilities/the-navigator-object

Bug: 1292925, 1293295
Change-Id: I20619a75124cf7f5fd1a589a80c013fa27ea70d4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3557297
Reviewed-by: Dominick Ng <dominickn@chromium.org>
Reviewed-by: Mike West <mkwst@chromium.org>
Reviewed-by: David Benjamin <davidben@chromium.org>
Commit-Queue: Javier Fernandez <jfernandez@igalia.com>
Cr-Commit-Position: refs/heads/main@{#988671}
  • Loading branch information
javifernandez authored and Chromium LUCI CQ committed Apr 4, 2022
1 parent c32fefc commit 13150a0
Show file tree
Hide file tree
Showing 20 changed files with 483 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,19 @@ class ProtocolHandlerChangeWaiter : public ProtocolHandlerRegistry::Observer {

} // namespace

class RegisterProtocolHandlerBrowserTest : public InProcessBrowserTest {
class ChromeRegisterProtocolHandlerBrowserTest : public InProcessBrowserTest {
public:
RegisterProtocolHandlerBrowserTest() { }
ChromeRegisterProtocolHandlerBrowserTest() = default;

void SetUpOnMainThread() override {
#if BUILDFLAG(IS_MAC)
ASSERT_TRUE(test::RegisterAppWithLaunchServices());
#endif

// We might define browser tests for other embedders, so the test's data
// files will be shared via //componennts
embedded_test_server()->ServeFilesFromSourceDirectory(
"components/test/data/");
}

TestRenderViewContextMenu* CreateContextMenu(GURL url) {
Expand Down Expand Up @@ -127,8 +132,8 @@ class RegisterProtocolHandlerBrowserTest : public InProcessBrowserTest {
content::test::FencedFrameTestHelper fenced_frame_helper_;
};

IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerBrowserTest,
ContextMenuEntryAppearsForHandledUrls) {
IN_PROC_BROWSER_TEST_F(ChromeRegisterProtocolHandlerBrowserTest,
ContextMenuEntryAppearsForHandledUrls) {
std::unique_ptr<TestRenderViewContextMenu> menu(
CreateContextMenu(GURL("https://www.google.com/")));
ASSERT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKWITH));
Expand All @@ -144,8 +149,8 @@ IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerBrowserTest,
ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKWITH));
}

IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerBrowserTest,
UnregisterProtocolHandler) {
IN_PROC_BROWSER_TEST_F(ChromeRegisterProtocolHandlerBrowserTest,
UnregisterProtocolHandler) {
std::unique_ptr<TestRenderViewContextMenu> menu(
CreateContextMenu(GURL("https://www.google.com/")));
ASSERT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKWITH));
Expand All @@ -166,7 +171,8 @@ IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerBrowserTest,
ASSERT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKWITH));
}

IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerBrowserTest, CustomHandler) {
IN_PROC_BROWSER_TEST_F(ChromeRegisterProtocolHandlerBrowserTest,
CustomHandler) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL handler_url = embedded_test_server()->GetURL("/custom_handler.html");
AddProtocolHandler("news", handler_url);
Expand All @@ -190,7 +196,7 @@ IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerBrowserTest, CustomHandler) {
}

// FencedFrames can not register to handle any protocols.
IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerBrowserTest, FencedFrame) {
IN_PROC_BROWSER_TEST_F(ChromeRegisterProtocolHandlerBrowserTest, FencedFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/title1.html")));
Expand Down Expand Up @@ -260,10 +266,15 @@ IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerExtensionBrowserTest, Basic) {
->GetLastCommittedURL());
}

class RegisterProtocolHandlerAndServiceWorkerInterceptor
class ChromeRegisterProtocolHandlerAndServiceWorkerInterceptor
: public InProcessBrowserTest {
public:
void SetUpOnMainThread() override {
// We might define browser tests for other embedders, so the test's data
// files will be shared via //componennts
embedded_test_server()->ServeFilesFromSourceDirectory(
"components/test/data/");

ASSERT_TRUE(embedded_test_server()->Start());

// Navigate to the test page.
Expand All @@ -281,7 +292,7 @@ class RegisterProtocolHandlerAndServiceWorkerInterceptor
};

// TODO(crbug.com/1204127): Fix flakiness.
IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerAndServiceWorkerInterceptor,
IN_PROC_BROWSER_TEST_F(ChromeRegisterProtocolHandlerAndServiceWorkerInterceptor,
DISABLED_RegisterFetchListenerForHTMLHandler) {
WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
Expand Down
1 change: 1 addition & 0 deletions chrome/test/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1499,6 +1499,7 @@ if (!is_android) {
"//ash/components/arc/test/data/icons",
"//chrome/browser/page_load_metrics/integration_tests/data/",
"//chrome/test/data/cart/",
"//components/test/data/",
"//components/test/data/ad_tagging/",
"//components/test/data/ads_observer/",
"//components/test/data/autofill/",
Expand Down
1 change: 1 addition & 0 deletions components/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,7 @@ if (!is_ios) {
"//components/content_settings/renderer",
"//components/continuous_search/common/public/mojom",
"//components/continuous_search/renderer",
"//components/custom_handlers:browser_tests",
"//components/dom_distiller/content/browser",
"//components/dom_distiller/core",
"//components/dom_distiller/core:test_support",
Expand Down
22 changes: 22 additions & 0 deletions components/custom_handlers/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ static_library("custom_handlers") {

deps = [
"//build:chromeos_buildflags",
"//components/keyed_service/content",
"//components/keyed_service/core",
"//components/pref_registry",
"//components/prefs",
Expand Down Expand Up @@ -44,12 +45,15 @@ static_library("custom_handlers") {
source_set("test_support") {
testonly = true
sources = [
"simple_protocol_handler_registry_factory.cc",
"simple_protocol_handler_registry_factory.h",
"test_protocol_handler_registry_delegate.cc",
"test_protocol_handler_registry_delegate.h",
]
deps = [
":custom_handlers",
"//base",
"//components/keyed_service/content",
]
}

Expand All @@ -74,3 +78,21 @@ source_set("unit_tests") {
"//third_party/blink/public/common:headers",
]
}

source_set("browser_tests") {
testonly = true
defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
sources = [ "protocol_handler_registry_browsertest.cc" ]
deps = [
":custom_handlers",
":test_support",
"//base/test:test_support",
"//components/keyed_service/content",
"//content/shell:content_shell_lib",
"//content/test:browsertest_support",
"//content/test:test_support",
"//net:test_support",
"//testing/gmock",
"//testing/gtest",
]
}
2 changes: 2 additions & 0 deletions components/custom_handlers/DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ include_rules = [
"+content/public/common",
"+content/public/test",
"+net/base",
"+content/shell",
"+net/test/embedded_test_server",
"+third_party/blink/public/common",
"+services/network/public/cpp",
"+ui/base/l10n",
Expand Down
200 changes: 200 additions & 0 deletions components/custom_handlers/protocol_handler_registry_browsertest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Copyright (c) 2022 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 <memory>
#include <string>

#include "base/scoped_observation.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "components/custom_handlers/protocol_handler.h"
#include "components/custom_handlers/protocol_handler_registry.h"
#include "components/custom_handlers/simple_protocol_handler_registry_factory.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/shell/browser/shell.h"
#include "net/test/embedded_test_server/embedded_test_server.h"

using content::WebContents;

namespace {

using custom_handlers::ProtocolHandlerRegistry;

class ProtocolHandlerChangeWaiter : public ProtocolHandlerRegistry::Observer {
public:
explicit ProtocolHandlerChangeWaiter(ProtocolHandlerRegistry* registry) {
registry_observation_.Observe(registry);
}
ProtocolHandlerChangeWaiter(const ProtocolHandlerChangeWaiter&) = delete;
ProtocolHandlerChangeWaiter& operator=(const ProtocolHandlerChangeWaiter&) =
delete;
~ProtocolHandlerChangeWaiter() override = default;

void Wait() { run_loop_.Run(); }
// ProtocolHandlerRegistry::Observer:
void OnProtocolHandlerRegistryChanged() override { run_loop_.Quit(); }

private:
base::ScopedObservation<custom_handlers::ProtocolHandlerRegistry,
custom_handlers::ProtocolHandlerRegistry::Observer>
registry_observation_{this};
base::RunLoop run_loop_;
};

} // namespace

namespace custom_handlers {

class RegisterProtocolHandlerBrowserTest : public content::ContentBrowserTest {
public:
RegisterProtocolHandlerBrowserTest() = default;

void SetUpOnMainThread() override {
embedded_test_server()->ServeFilesFromSourceDirectory(
"components/test/data/");
}

void AddProtocolHandler(const std::string& protocol, const GURL& url) {
ProtocolHandler handler =
ProtocolHandler::CreateProtocolHandler(protocol, url);
ProtocolHandlerRegistry* registry =
SimpleProtocolHandlerRegistryFactory::GetForBrowserContext(
browser_context(), true);
// Fake that this registration is happening on profile startup. Otherwise
// it'll try to register with the OS, which causes DCHECKs on Windows when
// running as admin on Windows 7.
registry->SetIsLoading(true);
registry->OnAcceptRegisterProtocolHandler(handler);
registry->SetIsLoading(true);
ASSERT_TRUE(registry->IsHandledProtocol(protocol));
}

void RemoveProtocolHandler(const std::string& protocol, const GURL& url) {
ProtocolHandler handler =
ProtocolHandler::CreateProtocolHandler(protocol, url);
ProtocolHandlerRegistry* registry =
SimpleProtocolHandlerRegistryFactory::GetForBrowserContext(
browser_context(), true);
registry->RemoveHandler(handler);
ASSERT_FALSE(registry->IsHandledProtocol(protocol));
}

content::WebContents* web_contents() { return shell()->web_contents(); }
content::BrowserContext* browser_context() {
return web_contents()->GetBrowserContext();
}

protected:
content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
return fenced_frame_helper_;
}

private:
content::test::FencedFrameTestHelper fenced_frame_helper_;
};

IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerBrowserTest, CustomHandler) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL handler_url = embedded_test_server()->GetURL("/custom_handler.html");
AddProtocolHandler("news", handler_url);

ASSERT_TRUE(NavigateToURL(shell(), GURL("news:test"), handler_url));

ASSERT_EQ(handler_url, web_contents()->GetLastCommittedURL());

// Also check redirects.
GURL redirect_url =
embedded_test_server()->GetURL("/server-redirect?news:test");
ASSERT_TRUE(NavigateToURL(shell(), redirect_url, handler_url));

ASSERT_EQ(handler_url, web_contents()->GetLastCommittedURL());
}

// https://crbug.com/178097: Implement registerProtocolHandler on Android
#if !BUILDFLAG(IS_ANDROID)
// FencedFrames can not register to handle any protocols.
IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerBrowserTest, FencedFrame) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html")));

// Create a FencedFrame.
content::RenderFrameHost* fenced_frame_host =
fenced_frame_test_helper().CreateFencedFrame(
web_contents()->GetMainFrame(),
embedded_test_server()->GetURL("/fenced_frames/title1.html"));
ASSERT_TRUE(fenced_frame_host);

// Ensure the registry is currently empty.
GURL url("web+search:testing");
ProtocolHandlerRegistry* registry =
SimpleProtocolHandlerRegistryFactory::GetForBrowserContext(
browser_context(), true);
ASSERT_EQ(0u, registry->GetHandlersFor(url.scheme()).size());

// Attempt to add an entry.
ProtocolHandlerChangeWaiter waiter(registry);
ASSERT_TRUE(content::ExecuteScript(fenced_frame_host,
"navigator.registerProtocolHandler('web+"
"search', 'test.html?%s', 'test');"));
waiter.Wait();

// Ensure the registry is still empty.
ASSERT_EQ(0u, registry->GetHandlersFor(url.scheme()).size());
}
#endif

// https://crbug.com/178097: Implement registerProtocolHandler on Android
#if !BUILDFLAG(IS_ANDROID)
class RegisterProtocolHandlerAndServiceWorkerInterceptor
: public RegisterProtocolHandlerBrowserTest {
public:
void SetUpOnMainThread() override {
RegisterProtocolHandlerBrowserTest::SetUpOnMainThread();

ASSERT_TRUE(embedded_test_server()->Start());

// Navigate to the test page.
ASSERT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL(
"/protocol_handler/service_workers/"
"test_protocol_handler_and_service_workers.html")));
}
};

// TODO(crbug.com/1204127): Fix flakiness.
IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerAndServiceWorkerInterceptor,
DISABLED_RegisterFetchListenerForHTMLHandler) {
// Register a service worker intercepting requests to the HTML handler.
EXPECT_EQ(true,
content::EvalJs(shell(), "registerFetchListenerForHTMLHandler();"));

{
// Register a HTML handler with a user gesture.
ProtocolHandlerRegistry* registry =
SimpleProtocolHandlerRegistryFactory::GetForBrowserContext(
browser_context(), true);
ProtocolHandlerChangeWaiter waiter(registry);
ASSERT_TRUE(content::ExecJs(shell(), "registerHTMLHandler();"));
waiter.Wait();
}

// Verify that a page with the registered scheme is managed by the service
// worker, not the HTML handler.
EXPECT_EQ(true,
content::EvalJs(shell(),
"pageWithCustomSchemeHandledByServiceWorker();"));
}
#endif

} // namespace custom_handlers

0 comments on commit 13150a0

Please sign in to comment.