Commit 43960693 authored by Matt Menke's avatar Matt Menke Committed by Commit Bot

Add NetworkIsolationKey support to renderer-initiated preconnects.

Bug: 966896
Change-Id: Id119c1ad1f8ca675b77a4525ba6655d978bb980b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1713306Reviewed-by: default avatarPhilip Jägenstedt <foolip@chromium.org>
Reviewed-by: default avatarCharlie Harrison <csharrison@chromium.org>
Reviewed-by: default avatarGreg Kerr <kerrnel@chromium.org>
Reviewed-by: default avatarAlex Ilin <alexilin@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarShivani Sharma <shivanisha@chromium.org>
Commit-Queue: Matt Menke <mmenke@chromium.org>
Cr-Commit-Position: refs/heads/master@{#682675}
parent 036f59e2
......@@ -36,6 +36,7 @@
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/referrer.h"
#include "content/public/test/browser_test_utils.h"
#include "net/base/features.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server_connection_listener.h"
#include "net/test/embedded_test_server/request_handler_util.h"
......@@ -375,12 +376,13 @@ class TestPreconnectManagerObserver : public PreconnectManager::Observer {
class LoadingPredictorBrowserTest : public InProcessBrowserTest {
public:
LoadingPredictorBrowserTest() {}
LoadingPredictorBrowserTest() {
scoped_feature_list_.InitAndEnableFeature(
features::kLoadingOnlyLearnHighPriorityResources);
}
~LoadingPredictorBrowserTest() override {}
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(
features::kLoadingOnlyLearnHighPriorityResources);
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
InProcessBrowserTest::SetUp();
}
......@@ -827,6 +829,209 @@ IN_PROC_BROWSER_TEST_F(LoadingPredictorBrowserTest,
EXPECT_EQ(1u, connection_tracker()->GetReadSocketCount());
}
enum class NetworkIsolationKeyMode {
kNone,
kTopFrameOrigin,
kTopFrameAndFrameOrigins,
};
class LoadingPredictorNetworkIsolationKeyBrowserTest
: public LoadingPredictorBrowserTest,
public testing::WithParamInterface<NetworkIsolationKeyMode> {
public:
LoadingPredictorNetworkIsolationKeyBrowserTest() {
preconnecting_test_server_.AddDefaultHandlers(GetChromeTestDataDir());
EXPECT_TRUE(preconnecting_test_server_.Start());
switch (GetParam()) {
case NetworkIsolationKeyMode::kNone:
break;
case NetworkIsolationKeyMode::kTopFrameOrigin:
scoped_feature_list2_.InitWithFeatures(
// enabled_features
{net::features::kPartitionConnectionsByNetworkIsolationKey},
// disabled_features
{net::features::kAppendFrameOriginToNetworkIsolationKey});
break;
case NetworkIsolationKeyMode::kTopFrameAndFrameOrigins:
scoped_feature_list2_.InitWithFeatures(
// enabled_features
{net::features::kPartitionConnectionsByNetworkIsolationKey,
net::features::kAppendFrameOriginToNetworkIsolationKey},
// disabled_features
{});
break;
}
}
~LoadingPredictorNetworkIsolationKeyBrowserTest() override {}
// One server is used to initiate preconnects, and one is preconnected to.
// This makes tracking preconnected sockets much easier, and removes all
// worried about favicon fetches and other sources of preconnects.
net::EmbeddedTestServer* preconnecting_test_server() {
return &preconnecting_test_server_;
}
private:
base::test::ScopedFeatureList scoped_feature_list2_;
// Test server that initiates preconnect. Separate server from the one being
// preconnected to separate preconnected connection count.
net::EmbeddedTestServer preconnecting_test_server_;
};
INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
LoadingPredictorNetworkIsolationKeyBrowserTest,
::testing::Values(NetworkIsolationKeyMode::kNone,
NetworkIsolationKeyMode::kTopFrameOrigin,
NetworkIsolationKeyMode::kTopFrameAndFrameOrigins));
IN_PROC_BROWSER_TEST_P(LoadingPredictorNetworkIsolationKeyBrowserTest,
LinkRelPreconnectMainFrame) {
const char kHost1[] = "host1.test";
const char kHost2[] = "host2.test";
GURL preconnect_url = embedded_test_server()->GetURL("/echo");
// Navigate two tabs, one to each host.
content::WebContents* tab1 =
browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(
browser(), preconnecting_test_server()->GetURL(kHost1, "/title1.html"));
chrome::NewTab(browser());
content::WebContents* tab2 =
browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(
browser(), preconnecting_test_server()->GetURL(kHost2, "/title1.html"));
std::string start_preconnect = base::StringPrintf(
"var link = document.createElement('link');"
"link.rel = 'preconnect';"
"link.crossOrigin = 'anonymous';"
"link.href = '%s';"
"document.head.appendChild(link);",
preconnect_url.spec().c_str());
ASSERT_TRUE(content::ExecJs(tab1->GetMainFrame(), start_preconnect));
connection_tracker()->WaitUntilFirstConnectionAccepted();
EXPECT_EQ(1u, connection_tracker()->GetAcceptedSocketCount());
EXPECT_EQ(0u, connection_tracker()->GetReadSocketCount());
std::string fetch_resource = base::StringPrintf(
"(async () => {"
" var resp = (await fetch('%s',"
" {credentials: 'omit',"
" mode: 'no-cors'}));"
" return resp.status; })();",
preconnect_url.spec().c_str());
// Fetch a resource from the test server from tab 2, without CORS.
EXPECT_EQ(0, EvalJs(tab2->GetMainFrame(), fetch_resource));
if (GetParam() == NetworkIsolationKeyMode::kNone) {
// When not using NetworkIsolationKeys, the preconnected socket from a tab
// at one site is usable by a request from another site.
EXPECT_EQ(1u, connection_tracker()->GetAcceptedSocketCount());
EXPECT_EQ(1u, connection_tracker()->GetReadSocketCount());
} else {
// When using NetworkIsolationKeys, the preconnected socket cannot be used.
EXPECT_EQ(2u, connection_tracker()->GetAcceptedSocketCount());
EXPECT_EQ(1u, connection_tracker()->GetReadSocketCount());
}
// Now try fetching a resource from tab 1.
EXPECT_EQ(0, EvalJs(tab1->GetMainFrame(), fetch_resource));
// If the preconnected socket was not used before, it should now be used. If
// it was used before, a new socket will be used.
EXPECT_EQ(2u, connection_tracker()->GetAcceptedSocketCount());
EXPECT_EQ(2u, connection_tracker()->GetReadSocketCount());
}
IN_PROC_BROWSER_TEST_P(LoadingPredictorNetworkIsolationKeyBrowserTest,
LinkRelPreconnectSubFrame) {
const char kHost1[] = "host1.test";
const char kHost2[] = "host2.test";
GURL preconnect_url = embedded_test_server()->GetURL("/echo");
// Tab 1 has two iframes, one at kHost1, one at kHost2.
content::WebContents* tab1 =
browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(
browser(), preconnecting_test_server()->GetURL(
kHost1, GetPathWithPortReplacement(
"/predictors/two_iframes.html",
preconnecting_test_server()->port())));
std::vector<content::RenderFrameHost*> frames = tab1->GetAllFrames();
ASSERT_EQ(3u, frames.size());
ASSERT_EQ(kHost1, frames[0]->GetLastCommittedOrigin().host());
ASSERT_EQ(kHost1, frames[1]->GetLastCommittedOrigin().host());
ASSERT_EQ(kHost2, frames[2]->GetLastCommittedOrigin().host());
// Create another tab without an iframe, at kHost2.
chrome::NewTab(browser());
content::WebContents* tab2 =
browser()->tab_strip_model()->GetActiveWebContents();
ui_test_utils::NavigateToURL(
browser(), preconnecting_test_server()->GetURL(kHost2, "/title1.html"));
// Preconnect a socket in the cross-origin iframe.
std::string start_preconnect = base::StringPrintf(
"var link = document.createElement('link');"
"link.rel = 'preconnect';"
"link.crossOrigin = 'anonymous';"
"link.href = '%s';"
"document.head.appendChild(link);",
preconnect_url.spec().c_str());
ASSERT_TRUE(content::ExecJs(frames[2], start_preconnect));
connection_tracker()->WaitUntilFirstConnectionAccepted();
EXPECT_EQ(1u, connection_tracker()->GetAcceptedSocketCount());
EXPECT_EQ(0u, connection_tracker()->GetReadSocketCount());
std::string fetch_resource = base::StringPrintf(
"(async () => {"
" var resp = (await fetch('%s',"
" {credentials: 'omit',"
" mode: 'no-cors'}));"
" return resp.status; })();",
preconnect_url.spec().c_str());
// Fetch a resource from the test server from tab 2 iframe, without CORS.
EXPECT_EQ(0, EvalJs(tab2->GetMainFrame(), fetch_resource));
if (GetParam() == NetworkIsolationKeyMode::kNone) {
// When not using NetworkIsolationKeys, the preconnected socket from the
// iframe from the first tab can be used.
EXPECT_EQ(1u, connection_tracker()->GetAcceptedSocketCount());
EXPECT_EQ(1u, connection_tracker()->GetReadSocketCount());
} else {
// Otherwise, the preconnected socket cannot be used.
EXPECT_EQ(2u, connection_tracker()->GetAcceptedSocketCount());
EXPECT_EQ(1u, connection_tracker()->GetReadSocketCount());
}
// Fetch a resource from the test server from the same-origin iframe, without
// CORS.
EXPECT_EQ(0, EvalJs(frames[1], fetch_resource));
if (GetParam() != NetworkIsolationKeyMode::kTopFrameAndFrameOrigins) {
// When not using NetworkIsolationKeys, a new socket is created and used.
//
// When using the origin of the main frame, the preconnected socket from the
// cross-origin iframe can be used, since only the top frame origin matters.
EXPECT_EQ(2u, connection_tracker()->GetAcceptedSocketCount());
EXPECT_EQ(2u, connection_tracker()->GetReadSocketCount());
} else {
// Otherwise, the preconnected socket cannot be used.
EXPECT_EQ(3u, connection_tracker()->GetAcceptedSocketCount());
EXPECT_EQ(2u, connection_tracker()->GetReadSocketCount());
}
// Now try fetching a resource the cross-site iframe.
EXPECT_EQ(0, EvalJs(frames[2], fetch_resource));
// If the preconnected socket was not used before, it should now be used. If
// it was used before, a new socket will be used.
EXPECT_EQ(3u, connection_tracker()->GetAcceptedSocketCount());
EXPECT_EQ(3u, connection_tracker()->GetReadSocketCount());
}
class LoadingPredictorBrowserTestWithProxy
: public LoadingPredictorBrowserTest {
public:
......
......@@ -30,7 +30,9 @@
#include "components/web_cache/browser/web_cache_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "extensions/buildflags/buildflags.h"
#include "net/base/network_isolation_key.h"
#include "ppapi/buildflags/buildflags.h"
......@@ -49,6 +51,34 @@ const uint32_t kRenderFilteredMessageClasses[] = {
ChromeMsgStart, NetworkHintsMsgStart,
};
void StartPreconnect(
base::WeakPtr<predictors::PreconnectManager> preconnect_manager,
int render_process_id,
int render_frame_id,
const GURL& url,
bool allow_credentials) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!preconnect_manager)
return;
content::RenderFrameHost* render_frame_host =
content::RenderFrameHost::FromID(render_process_id, render_frame_id);
if (!render_frame_host)
return;
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
if (!web_contents)
return;
net::NetworkIsolationKey network_isolation_key(
web_contents->GetMainFrame()->GetLastCommittedOrigin(),
render_frame_host->GetLastCommittedOrigin());
preconnect_manager->StartPreconnectUrl(url, allow_credentials,
network_isolation_key);
}
} // namespace
ChromeRenderMessageFilter::ChromeRenderMessageFilter(int render_process_id,
......@@ -117,7 +147,8 @@ void ChromeRenderMessageFilter::OnDnsPrefetch(
}
}
void ChromeRenderMessageFilter::OnPreconnect(const GURL& url,
void ChromeRenderMessageFilter::OnPreconnect(int render_frame_id,
const GURL& url,
bool allow_credentials,
int count) {
if (count < 1) {
......@@ -134,14 +165,12 @@ void ChromeRenderMessageFilter::OnPreconnect(const GURL& url,
if (!preconnect_manager_initialized_)
return;
// TODO(mmenke): Use process and frame ids to populate NetworkIsolationKey.
// May also need to think about enabling cross-site preconnects, though that
// TODO(mmenke): Think about enabling cross-site preconnects, though that
// will result in at least some cross-site information leakage.
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&predictors::PreconnectManager::StartPreconnectUrl,
preconnect_manager_, url, allow_credentials,
net::NetworkIsolationKey()));
base::BindOnce(&StartPreconnect, preconnect_manager_, render_process_id_,
render_frame_id, url, allow_credentials));
}
void ChromeRenderMessageFilter::OnAllowDatabase(
......
......@@ -49,7 +49,10 @@ class ChromeRenderMessageFilter : public content::BrowserMessageFilter {
~ChromeRenderMessageFilter() override;
void OnDnsPrefetch(const network_hints::LookupRequest& request);
void OnPreconnect(const GURL& url, bool allow_credentials, int count);
void OnPreconnect(int render_frame_id,
const GURL& url,
bool allow_credentials,
int count);
void OnAllowDatabase(int render_frame_id,
const GURL& origin_url,
......
<!DOCTYPE html>
<html>
<head>
<title>Page with two iframes on different origins</title>
</head>
<body>
<iframe src="/echo"></iframe>
<iframe src="http://host2.test:REPLACE_WITH_PORT/"></iframe>
</body>
</html>
per-file *_messages.h=set noparent
per-file *_messages.h=file://ipc/SECURITY_OWNERS
per-file *_messages*.h=set noparent
per-file *_messages*.h=file://ipc/SECURITY_OWNERS
per-file *_messages.cc=set noparent
per-file *_messages.cc=file://ipc/SECURITY_OWNERS
......@@ -2,7 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Multiply-included file, no traditional include guard.
// Silence presubmit and Tricium warnings about include guards
// no-include-guard-because-multiply-included
// NOLINT(build/header_guard)
#include <string>
#include <vector>
......@@ -44,7 +47,8 @@ IPC_MESSAGE_CONTROL1(NetworkHintsMsg_DNSPrefetch,
// Request for preconnect to host providing resource specified by URL
IPC_MESSAGE_CONTROL3(NetworkHintsMsg_Preconnect,
IPC_MESSAGE_CONTROL4(NetworkHintsMsg_Preconnect,
int /* render_frame_id */,
GURL /* preconnect target url */,
bool /* Does connection have its credentials flag set */,
int /* number of connections */)
......@@ -24,10 +24,12 @@ void PrescientNetworkingDispatcher::PrefetchDNS(
dns_prefetch_.Resolve(hostname_utf8.data(), hostname_utf8.length());
}
void PrescientNetworkingDispatcher::Preconnect(const blink::WebURL& url,
bool allow_credentials) {
void PrescientNetworkingDispatcher::Preconnect(
blink::WebLocalFrame* web_local_frame,
const blink::WebURL& url,
bool allow_credentials) {
VLOG(2) << "Preconnect: " << url.GetString().Utf8();
preconnect_.Preconnect(url, allow_credentials);
preconnect_.Preconnect(web_local_frame, url, allow_credentials);
}
} // namespace network_hints
......@@ -10,6 +10,10 @@
#include "components/network_hints/renderer/renderer_preconnect.h"
#include "third_party/blink/public/platform/web_prescient_networking.h"
namespace blink {
class WebLocalFrame;
}
namespace network_hints {
// The main entry point from blink for sending DNS prefetch requests to the
......@@ -20,7 +24,8 @@ class PrescientNetworkingDispatcher : public blink::WebPrescientNetworking {
~PrescientNetworkingDispatcher() override;
void PrefetchDNS(const blink::WebString& hostname) override;
void Preconnect(const blink::WebURL& url,
void Preconnect(blink::WebLocalFrame* web_local_frame,
const blink::WebURL& url,
const bool allow_credentials) override;
private:
......
......@@ -8,6 +8,7 @@
#include "components/network_hints/common/network_hints_common.h"
#include "components/network_hints/common/network_hints_messages.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
using content::RenderThread;
......@@ -20,12 +21,15 @@ RendererPreconnect::RendererPreconnect() {
RendererPreconnect::~RendererPreconnect() {
}
void RendererPreconnect::Preconnect(const GURL& url, bool allow_credentials) {
if (!url.is_valid())
void RendererPreconnect::Preconnect(blink::WebLocalFrame* web_local_frame,
const GURL& url,
bool allow_credentials) {
if (!url.is_valid() || !web_local_frame)
return;
RenderThread::Get()->Send(
new NetworkHintsMsg_Preconnect(url, allow_credentials, 1));
RenderThread::Get()->Send(new NetworkHintsMsg_Preconnect(
content::RenderFrame::FromWebFrame(web_local_frame)->GetRoutingID(), url,
allow_credentials, 1));
}
} // namespace network_hints
......@@ -20,6 +20,10 @@
#include "base/macros.h"
#include "url/gurl.h"
namespace blink {
class WebLocalFrame;
}
namespace network_hints {
// An internal interface to the network_hints component for efficiently sending
......@@ -30,7 +34,9 @@ class RendererPreconnect {
~RendererPreconnect();
// Submit a preconnect request for a single connection.
void Preconnect(const GURL& url, bool allow_credentials);
void Preconnect(blink::WebLocalFrame* web_local_frame,
const GURL& url,
bool allow_credentials);
private:
......
......@@ -31,12 +31,13 @@
#ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_PRESCIENT_NETWORKING_H_
#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_WEB_PRESCIENT_NETWORKING_H_
#include "third_party/blink/public/platform/web_common.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url.h"
namespace blink {
class WebLocalFrame;
class WebPrescientNetworking {
public:
virtual ~WebPrescientNetworking() = default;
......@@ -45,7 +46,9 @@ class WebPrescientNetworking {
// the host resolution latency.
virtual void PrefetchDNS(const WebString& hostname) {}
virtual void Preconnect(const WebURL& url, const bool allow_credentials) {}
virtual void Preconnect(blink::WebLocalFrame* web_local_frame,
const WebURL& url,
const bool allow_credentials) {}
};
} // namespace blink
......
......@@ -18,7 +18,7 @@ class MockPrescientNetworking : public WebPrescientNetworking {
private:
void PrefetchDNS(const WebString&) override { did_dns_prefetch_ = true; }
void Preconnect(const WebURL&, const bool) override {
void Preconnect(WebLocalFrame*, const WebURL&, const bool) override {
did_preconnect_ = true;
}
......
......@@ -35,6 +35,7 @@
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/deprecation.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
......@@ -48,7 +49,7 @@ void HTMLResourcePreloader::Trace(Visitor* visitor) {
visitor->Trace(document_);
}
static void PreconnectHost(PreloadRequest* request) {
static void PreconnectHost(LocalFrame* local_frame, PreloadRequest* request) {
DCHECK(request);
DCHECK(request->IsPreconnect());
KURL host(request->BaseURL(), request->ResourceURL());
......@@ -58,13 +59,14 @@ static void PreconnectHost(PreloadRequest* request) {
Platform::Current()->PrescientNetworking();
if (web_prescient_networking) {
web_prescient_networking->Preconnect(
host, request->CrossOrigin() != kCrossOriginAttributeAnonymous);
WebLocalFrameImpl::FromFrame(local_frame), host,
request->CrossOrigin() != kCrossOriginAttributeAnonymous);
}
}
void HTMLResourcePreloader::Preload(std::unique_ptr<PreloadRequest> preload) {
if (preload->IsPreconnect()) {
PreconnectHost(preload.get());
PreconnectHost(document_->GetFrame(), preload.get());
return;
}
......
......@@ -25,7 +25,9 @@ class PreloaderNetworkHintsMock : public WebPrescientNetworking {
PreloaderNetworkHintsMock() : did_preconnect_(false) {}
void PrefetchDNS(const WebString& hostname) override {}
void Preconnect(const WebURL& url, const bool allow_credentials) override {
void Preconnect(WebLocalFrame* web_local_frame,
const WebURL& url,
const bool allow_credentials) override {
did_preconnect_ = true;
is_https_ = url.ProtocolIs("https");
allow_credentials_ = allow_credentials;
......
......@@ -74,7 +74,9 @@ class NetworkHintsMock : public WebPrescientNetworking {
void PrefetchDNS(const WebString& hostname) override {
did_dns_prefetch_ = true;
}
void Preconnect(const WebURL& url, const bool allow_credentials) override {
void Preconnect(WebLocalFrame* web_local_frame,
const WebURL& url,
const bool allow_credentials) override {
did_preconnect_ = true;
is_https_ = url.ProtocolIs("https");
allow_credentials_ = allow_credentials;
......
......@@ -17,6 +17,7 @@
#include "third_party/blink/renderer/core/frame/navigator.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/viewport_data.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/html/parser/html_preload_scanner.h"
#include "third_party/blink/renderer/core/html/parser/html_srcset_parser.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
......@@ -188,7 +189,8 @@ void PreloadHelper::PreconnectIfNeeded(
Platform::Current()->PrescientNetworking();
if (web_prescient_networking) {
web_prescient_networking->Preconnect(
params.href, params.cross_origin != kCrossOriginAttributeAnonymous);
WebLocalFrameImpl::FromFrame(frame), params.href,
params.cross_origin != kCrossOriginAttributeAnonymous);
}
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment