Commit 29950ebb authored by Matt Falkenhagen's avatar Matt Falkenhagen Committed by Commit Bot

Allow extensions to intercept service worker navigation preload requests.

Heavily based on an original CL by Charles Vazac <cvazac@akamai.com>
at https://chromium-review.googlesource.com/c/chromium/src/+/1396938

Service worker navigation preload[1] is a web platform feature that
involves sending a request to network at the same time as starting up
the service worker for a navigation. This network request was not
visible to extensions.

This CL:
- Modifies the network service path for service worker navigation
preload requests so that the embedder is aware of them.
- In web_request_permissions.cc, if a request is not considered a browser
side navigation, it becomes opaque to extensions. This CL makes it so
that service worker navigation preload requests[1] are exempted from
that.

This CL deletes the unit test for navigation preload in
ServiceWorkerNavigationLoaderTest. It was too much hassle getting that
to work with a TestWebContents because the infrastructure for enabling
NetworkService in content_unittests is not set up yet so
unittest_test_suite.cc typically disables NetworkService (issue 901092).
We have sufficient navigation preload test coverage in browser tests and
web tests.

[1] https://w3c.github.io/ServiceWorker/#navigationpreloadmanager

Bug: 914062
Change-Id: I4c91521e55c1103a1a67d973e595094b78357c34
Reviewed-on: https://chromium-review.googlesource.com/c/1491168Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarVarun Khaneja <vakh@chromium.org>
Reviewed-by: default avatarKaran Bhatia <karandeepb@chromium.org>
Commit-Queue: Matt Falkenhagen <falken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#636598}
parent cb0fbc70
...@@ -288,6 +288,29 @@ class ExtensionWebRequestApiTest : public ExtensionApiTest { ...@@ -288,6 +288,29 @@ class ExtensionWebRequestApiTest : public ExtensionApiTest {
const char* expected_content_regular_window, const char* expected_content_regular_window,
const char* exptected_content_incognito_window); const char* exptected_content_incognito_window);
void InstallRequestHeaderModifyingExtension() {
TestExtensionDir test_dir;
test_dir.WriteManifest(R"({
"name": "Web Request Header Modifying Extension",
"manifest_version": 2,
"version": "0.1",
"background": { "scripts": ["background.js"] },
"permissions": ["<all_urls>", "webRequest", "webRequestBlocking"]
})");
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"(
chrome.webRequest.onBeforeSendHeaders.addListener(function(details) {
details.requestHeaders.push({name: 'foo', value: 'bar'});
return {requestHeaders: details.requestHeaders};
}, {urls: ['*://*/echoheader*']}, ['blocking', 'requestHeaders']);
chrome.test.sendMessage('ready');
)");
ExtensionTestMessageListener listener("ready", false);
ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath()));
EXPECT_TRUE(listener.WaitUntilSatisfied());
}
// Ensures requests made by the |worker_script_name| service worker can be // Ensures requests made by the |worker_script_name| service worker can be
// intercepted by extensions. // intercepted by extensions.
void RunServiceWorkerFetchTest(const std::string& worker_script_name); void RunServiceWorkerFetchTest(const std::string& worker_script_name);
...@@ -320,6 +343,18 @@ class ExtensionWebRequestApiTest : public ExtensionApiTest { ...@@ -320,6 +343,18 @@ class ExtensionWebRequestApiTest : public ExtensionApiTest {
test_dirs_.push_back(std::move(dir)); test_dirs_.push_back(std::move(dir));
} }
void RegisterServiceWorker(const std::string& worker_path,
const base::Optional<std::string>& scope) {
GURL url = embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html");
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
std::string script = content::JsReplace("register($1, $2);", worker_path,
scope ? *scope : std::string());
EXPECT_EQ(
"DONE",
EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), script));
}
private: private:
std::vector<std::unique_ptr<TestExtensionDir>> test_dirs_; std::vector<std::unique_ptr<TestExtensionDir>> test_dirs_;
}; };
...@@ -752,39 +787,21 @@ void ExtensionWebRequestApiTest::RunServiceWorkerFetchTest( ...@@ -752,39 +787,21 @@ void ExtensionWebRequestApiTest::RunServiceWorkerFetchTest(
embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data"); embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
TestExtensionDir test_dir; // Install the test extension.
test_dir.WriteManifest(R"({ InstallRequestHeaderModifyingExtension();
"name": "Web Request Service Worker Test",
"manifest_version": 2,
"version": "0.1",
"background": { "scripts": ["background.js"] },
"permissions": ["<all_urls>", "webRequest", "webRequestBlocking"]
})");
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"(
chrome.webRequest.onBeforeSendHeaders.addListener(function(details) {
details.requestHeaders.push({name: 'foo', value: 'bar'});
return {requestHeaders: details.requestHeaders};
}, {urls: ['*://*/echoheader*']}, ['blocking', 'requestHeaders']);
chrome.test.sendMessage('ready');
)");
ExtensionTestMessageListener listener("ready", false);
ASSERT_TRUE(LoadExtension(test_dir.UnpackedPath()));
EXPECT_TRUE(listener.WaitUntilSatisfied());
// Register the |worker_script_name| as a service worker.
EXPECT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(
"/service_worker/create_service_worker.html")));
EXPECT_EQ("DONE", EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
"register('" + worker_script_name + "');"));
// Register a service worker and navigate to a page it controls.
RegisterServiceWorker(worker_script_name, base::nullopt);
EXPECT_TRUE(ui_test_utils::NavigateToURL( EXPECT_TRUE(ui_test_utils::NavigateToURL(
browser(), browser(),
embedded_test_server()->GetURL("/service_worker/fetch_from_page.html"))); embedded_test_server()->GetURL("/service_worker/fetch_from_page.html")));
// Ensure the extension was able to intercept the service worker request and
// modify the request headers. // Make a fetch from the controlled page. Depending on the worker script, the
// fetch might go to the service worker and be re-issued, or might fallback to
// network, or skip the worker, etc. In any case, this function expects a
// network request to happen, and that the extension modify the headers of the
// request before it goes to network. Verify that it was able to inject a
// header of "foo=bar".
EXPECT_EQ("bar", EvalJs(browser()->tab_strip_model()->GetActiveWebContents(), EXPECT_EQ("bar", EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
"fetch_from_page('/echoheader?foo');")); "fetch_from_page('/echoheader?foo');"));
} }
...@@ -2527,6 +2544,43 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest, ...@@ -2527,6 +2544,43 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
RunServiceWorkerFetchTest("empty.js"); RunServiceWorkerFetchTest("empty.js");
} }
// Ensure that extensions can intercept service worker navigation preload
// requests.
IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
ServiceWorkerNavigationPreload) {
ASSERT_TRUE(embedded_test_server()->Start());
// Install the test extension.
InstallRequestHeaderModifyingExtension();
// Register a service worker that uses navigation preload.
RegisterServiceWorker("/service_worker/navigation_preload_worker.js",
"/echoheader");
// Navigate to "/echoheader". The browser will detect that the service worker
// above is registered with this scope and has navigation preload enabled.
// So it will send the navigation preload request to network while at the same
// time starting up the service worker. The service worker will get the
// response for the navigation preload request, and respond with it to create
// the page.
GURL url = embedded_test_server()->GetURL(
"/echoheader?foo&service-worker-navigation-preload");
ui_test_utils::NavigateToURL(browser(), url);
// Since the request was to "/echoheader", the response describes the request
// headers.
//
// The extension is expected to add a "foo: bar" header to the request
// before it goes to network. Verify that it did.
//
// The browser adds a "service-worker-navigation-preload: true" header for
// navigation preload requests, so also sanity check that header to prove
// that this test is really testing the navigation preload request.
EXPECT_EQ("bar\ntrue",
EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
"document.body.textContent;"));
}
// Ensure we don't strip off initiator incorrectly in web request events when // Ensure we don't strip off initiator incorrectly in web request events when
// both the normal and incognito contexts are active. Regression test for // both the normal and incognito contexts are active. Regression test for
// crbug.com/934398. // crbug.com/934398.
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
#include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h" #include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h" #include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "net/test/spawned_test_server/spawned_test_server.h" #include "net/test/spawned_test_server/spawned_test_server.h"
namespace extensions { namespace extensions {
...@@ -76,19 +77,22 @@ std::unique_ptr<net::test_server::HttpResponse> HandleEchoHeaderRequest( ...@@ -76,19 +77,22 @@ std::unique_ptr<net::test_server::HttpResponse> HandleEchoHeaderRequest(
base::CompareCase::SENSITIVE)) base::CompareCase::SENSITIVE))
return nullptr; return nullptr;
size_t query_string_pos = request.relative_url.find('?'); std::string content;
std::string header_name = net::test_server::RequestQuery headers =
request.relative_url.substr(query_string_pos + 1); net::test_server::ParseQuery(request.GetURL());
for (const auto& header : headers) {
std::string header_value; std::string header_name = header.first;
auto it = request.headers.find(header_name); std::string header_value;
if (it != request.headers.end()) if (request.headers.find(header_name) != request.headers.end())
header_value = it->second; header_value = request.headers.at(header_name);
if (!content.empty())
content += "\n";
content += header_value;
}
std::unique_ptr<net::test_server::BasicHttpResponse> http_response( auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
new net::test_server::BasicHttpResponse);
http_response->set_code(net::HTTP_OK); http_response->set_code(net::HTTP_OK);
http_response->set_content(header_value); http_response->set_content(content);
return std::move(http_response); return std::move(http_response);
} }
...@@ -146,6 +150,7 @@ std::unique_ptr<net::test_server::HttpResponse> HandleSetHeaderRequest( ...@@ -146,6 +150,7 @@ std::unique_ptr<net::test_server::HttpResponse> HandleSetHeaderRequest(
} // namespace } // namespace
// TODO(karandeepb): See if this custom handling can be removed.
ExtensionApiTest::ExtensionApiTest() { ExtensionApiTest::ExtensionApiTest() {
embedded_test_server()->RegisterRequestHandler( embedded_test_server()->RegisterRequestHandler(
base::Bind(&HandleServerRedirectRequest)); base::Bind(&HandleServerRedirectRequest));
......
...@@ -119,7 +119,7 @@ RemoteSafeBrowsingDatabaseManager::RemoteSafeBrowsingDatabaseManager() { ...@@ -119,7 +119,7 @@ RemoteSafeBrowsingDatabaseManager::RemoteSafeBrowsingDatabaseManager() {
if (ints_str.empty()) { if (ints_str.empty()) {
// By default, we check all types except a few. // By default, we check all types except a few.
static_assert(content::RESOURCE_TYPE_LAST_TYPE == static_assert(content::RESOURCE_TYPE_LAST_TYPE ==
content::RESOURCE_TYPE_PLUGIN_RESOURCE + 1, content::RESOURCE_TYPE_NAVIGATION_PRELOAD + 1,
"Decide if new resource type should be skipped on mobile."); "Decide if new resource type should be skipped on mobile.");
for (int t_int = 0; t_int < content::RESOURCE_TYPE_LAST_TYPE; t_int++) { for (int t_int = 0; t_int < content::RESOURCE_TYPE_LAST_TYPE; t_int++) {
content::ResourceType t = static_cast<content::ResourceType>(t_int); content::ResourceType t = static_cast<content::ResourceType>(t_int);
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "content/browser/loader/navigation_url_loader_impl.h" #include "content/browser/loader/navigation_url_loader_impl.h"
#include <memory> #include <memory>
#include <utility>
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h" #include "base/bind_helpers.h"
...@@ -136,13 +137,6 @@ bool IsLoaderInterceptionEnabled() { ...@@ -136,13 +137,6 @@ bool IsLoaderInterceptionEnabled() {
signed_exchange_utils::IsSignedExchangeHandlingEnabled(); signed_exchange_utils::IsSignedExchangeHandlingEnabled();
} }
// Request ID for browser initiated requests. We start at -2 on the same lines
// as ResourceDispatcherHostImpl.
int g_next_request_id = -2;
GlobalRequestID MakeGlobalRequestID() {
return GlobalRequestID(-1, g_next_request_id--);
}
size_t GetCertificateChainsSizeInKB(const net::SSLInfo& ssl_info) { size_t GetCertificateChainsSizeInKB(const net::SSLInfo& ssl_info) {
base::Pickle cert_pickle; base::Pickle cert_pickle;
ssl_info.cert->Persist(&cert_pickle); ssl_info.cert->Persist(&cert_pickle);
...@@ -381,29 +375,6 @@ class AboutURLLoaderFactory : public network::mojom::URLLoaderFactory { ...@@ -381,29 +375,6 @@ class AboutURLLoaderFactory : public network::mojom::URLLoaderFactory {
mojo::BindingSet<network::mojom::URLLoaderFactory> bindings_; mojo::BindingSet<network::mojom::URLLoaderFactory> bindings_;
}; };
// Creates a URLLoaderFactory that uses |header_client|. This should have the
// same settings as the factory from the URLLoaderFactoryGetter.
std::unique_ptr<network::SharedURLLoaderFactoryInfo>
CreateNetworkFactoryInfoWithHeaderClient(
network::mojom::TrustedURLLoaderHeaderClientPtrInfo header_client,
StoragePartitionImpl* partition) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
network::mojom::URLLoaderFactoryPtrInfo factory_info;
network::mojom::URLLoaderFactoryParamsPtr params =
network::mojom::URLLoaderFactoryParams::New();
params->header_client = std::move(header_client);
params->process_id = network::mojom::kBrowserProcessId;
params->is_corb_enabled = false;
params->disable_web_security =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableWebSecurity);
partition->GetNetworkContext()->CreateURLLoaderFactory(
mojo::MakeRequest(&factory_info), std::move(params));
return std::make_unique<network::WrapperSharedURLLoaderFactoryInfo>(
std::move(factory_info));
}
} // namespace } // namespace
// Kept around during the lifetime of the navigation request, and is // Kept around during the lifetime of the navigation request, and is
...@@ -1741,8 +1712,12 @@ NavigationURLLoaderImpl::NavigationURLLoaderImpl( ...@@ -1741,8 +1712,12 @@ NavigationURLLoaderImpl::NavigationURLLoaderImpl(
partition->url_loader_factory_getter()->GetNetworkFactoryInfo(); partition->url_loader_factory_getter()->GetNetworkFactoryInfo();
if (header_client) { if (header_client) {
needs_loader_factory_interceptor = true; needs_loader_factory_interceptor = true;
network_factory_info = CreateNetworkFactoryInfoWithHeaderClient( network::mojom::URLLoaderFactoryPtrInfo factory_info;
std::move(header_client), partition); CreateURLLoaderFactoryWithHeaderClient(
std::move(header_client), mojo::MakeRequest(&factory_info), partition);
network_factory_info =
std::make_unique<network::WrapperSharedURLLoaderFactoryInfo>(
std::move(factory_info));
} }
DCHECK(!request_controller_); DCHECK(!request_controller_);
...@@ -1838,6 +1813,7 @@ void NavigationURLLoaderImpl::OnComplete( ...@@ -1838,6 +1813,7 @@ void NavigationURLLoaderImpl::OnComplete(
delegate_->OnRequestFailed(status); delegate_->OnRequestFailed(status);
} }
// static
void NavigationURLLoaderImpl::SetBeginNavigationInterceptorForTesting( void NavigationURLLoaderImpl::SetBeginNavigationInterceptorForTesting(
const BeginNavigationInterceptor& interceptor) { const BeginNavigationInterceptor& interceptor) {
DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::IO) || DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::IO) ||
...@@ -1846,6 +1822,7 @@ void NavigationURLLoaderImpl::SetBeginNavigationInterceptorForTesting( ...@@ -1846,6 +1822,7 @@ void NavigationURLLoaderImpl::SetBeginNavigationInterceptorForTesting(
g_interceptor.Get() = interceptor; g_interceptor.Get() = interceptor;
} }
// static
void NavigationURLLoaderImpl::SetURLLoaderFactoryInterceptorForTesting( void NavigationURLLoaderImpl::SetURLLoaderFactoryInterceptorForTesting(
const URLLoaderFactoryInterceptor& interceptor) { const URLLoaderFactoryInterceptor& interceptor) {
DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::IO) || DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::IO) ||
...@@ -1854,6 +1831,33 @@ void NavigationURLLoaderImpl::SetURLLoaderFactoryInterceptorForTesting( ...@@ -1854,6 +1831,33 @@ void NavigationURLLoaderImpl::SetURLLoaderFactoryInterceptorForTesting(
g_loader_factory_interceptor.Get() = interceptor; g_loader_factory_interceptor.Get() = interceptor;
} }
// static
void NavigationURLLoaderImpl::CreateURLLoaderFactoryWithHeaderClient(
network::mojom::TrustedURLLoaderHeaderClientPtrInfo header_client,
network::mojom::URLLoaderFactoryRequest factory_request,
StoragePartitionImpl* partition) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
network::mojom::URLLoaderFactoryParamsPtr params =
network::mojom::URLLoaderFactoryParams::New();
params->header_client = std::move(header_client);
params->process_id = network::mojom::kBrowserProcessId;
params->is_corb_enabled = false;
params->disable_web_security =
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableWebSecurity);
partition->GetNetworkContext()->CreateURLLoaderFactory(
std::move(factory_request), std::move(params));
}
// static
GlobalRequestID NavigationURLLoaderImpl::MakeGlobalRequestID() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Start at -2 on the same lines as ResourceDispatcherHostImpl.
static int s_next_request_id = -2;
return GlobalRequestID(-1, s_next_request_id--);
}
void NavigationURLLoaderImpl::OnRequestStarted(base::TimeTicks timestamp) { void NavigationURLLoaderImpl::OnRequestStarted(base::TimeTicks timestamp) {
DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_CURRENTLY_ON(BrowserThread::UI);
delegate_->OnRequestStarted(timestamp); delegate_->OnRequestStarted(timestamp);
......
...@@ -27,6 +27,7 @@ class NavigationData; ...@@ -27,6 +27,7 @@ class NavigationData;
class NavigationLoaderInterceptor; class NavigationLoaderInterceptor;
class ResourceContext; class ResourceContext;
class StoragePartition; class StoragePartition;
class StoragePartitionImpl;
struct GlobalRequestID; struct GlobalRequestID;
class CONTENT_EXPORT NavigationURLLoaderImpl : public NavigationURLLoader { class CONTENT_EXPORT NavigationURLLoaderImpl : public NavigationURLLoader {
...@@ -90,6 +91,18 @@ class CONTENT_EXPORT NavigationURLLoaderImpl : public NavigationURLLoader { ...@@ -90,6 +91,18 @@ class CONTENT_EXPORT NavigationURLLoaderImpl : public NavigationURLLoader {
static void SetURLLoaderFactoryInterceptorForTesting( static void SetURLLoaderFactoryInterceptorForTesting(
const URLLoaderFactoryInterceptor& interceptor); const URLLoaderFactoryInterceptor& interceptor);
// Creates a URLLoaderFactory for a navigation. The factory uses
// |header_client|. This should have the same settings as the factory from the
// URLLoaderFactoryGetter. Called on the UI thread.
static void CreateURLLoaderFactoryWithHeaderClient(
network::mojom::TrustedURLLoaderHeaderClientPtrInfo header_client,
network::mojom::URLLoaderFactoryRequest factory_request,
StoragePartitionImpl* partition);
// Returns a Request ID for browser-initiated navigation requests. Called on
// the IO thread.
static GlobalRequestID MakeGlobalRequestID();
private: private:
class URLLoaderRequestController; class URLLoaderRequestController;
void OnRequestStarted(base::TimeTicks timestamp); void OnRequestStarted(base::TimeTicks timestamp);
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_FETCH_DISPATCHER_H_ #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_FETCH_DISPATCHER_H_
#include <memory> #include <memory>
#include <string>
#include "base/callback.h" #include "base/callback.h"
#include "base/macros.h" #include "base/macros.h"
...@@ -52,6 +53,7 @@ class CONTENT_EXPORT ServiceWorkerFetchDispatcher { ...@@ -52,6 +53,7 @@ class CONTENT_EXPORT ServiceWorkerFetchDispatcher {
blink::mojom::ServiceWorkerStreamHandlePtr, blink::mojom::ServiceWorkerStreamHandlePtr,
blink::mojom::ServiceWorkerFetchEventTimingPtr, blink::mojom::ServiceWorkerFetchEventTimingPtr,
scoped_refptr<ServiceWorkerVersion>)>; scoped_refptr<ServiceWorkerVersion>)>;
using WebContentsGetter = base::RepeatingCallback<WebContents*()>;
ServiceWorkerFetchDispatcher(blink::mojom::FetchAPIRequestPtr request, ServiceWorkerFetchDispatcher(blink::mojom::FetchAPIRequestPtr request,
ResourceType resource_type, ResourceType resource_type,
...@@ -72,6 +74,8 @@ class CONTENT_EXPORT ServiceWorkerFetchDispatcher { ...@@ -72,6 +74,8 @@ class CONTENT_EXPORT ServiceWorkerFetchDispatcher {
bool MaybeStartNavigationPreloadWithURLLoader( bool MaybeStartNavigationPreloadWithURLLoader(
const network::ResourceRequest& original_request, const network::ResourceRequest& original_request,
URLLoaderFactoryGetter* url_loader_factory_getter, URLLoaderFactoryGetter* url_loader_factory_getter,
scoped_refptr<ServiceWorkerContextWrapper> context_wrapper,
const WebContentsGetter& web_contents_getter,
base::OnceClosure on_response); base::OnceClosure on_response);
// Dispatches a fetch event to the |version| given in ctor, and fires // Dispatches a fetch event to the |version| given in ctor, and fires
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "content/browser/service_worker/service_worker_navigation_loader.h" #include "content/browser/service_worker/service_worker_navigation_loader.h"
#include <sstream> #include <sstream>
#include <string>
#include <utility> #include <utility>
#include "base/bind.h" #include "base/bind.h"
...@@ -14,6 +15,8 @@ ...@@ -14,6 +15,8 @@
#include "base/optional.h" #include "base/optional.h"
#include "base/strings/strcat.h" #include "base/strings/strcat.h"
#include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_provider_host.h" #include "content/browser/service_worker/service_worker_provider_host.h"
#include "content/browser/service_worker/service_worker_version.h" #include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/url_loader_factory_getter.h" #include "content/browser/url_loader_factory_getter.h"
...@@ -227,6 +230,14 @@ void ServiceWorkerNavigationLoader::StartRequest( ...@@ -227,6 +230,14 @@ void ServiceWorkerNavigationLoader::StartRequest(
return; return;
} }
base::WeakPtr<ServiceWorkerContextCore> core = active_worker->context();
if (!core) {
CommitCompleted(net::ERR_ABORTED, "No service worker context");
return;
}
scoped_refptr<ServiceWorkerContextWrapper> context = core->wrapper();
DCHECK(context);
// Dispatch the fetch event. // Dispatch the fetch event.
fetch_dispatcher_ = std::make_unique<ServiceWorkerFetchDispatcher>( fetch_dispatcher_ = std::make_unique<ServiceWorkerFetchDispatcher>(
blink::mojom::FetchAPIRequest::From(resource_request_), blink::mojom::FetchAPIRequest::From(resource_request_),
...@@ -242,6 +253,7 @@ void ServiceWorkerNavigationLoader::StartRequest( ...@@ -242,6 +253,7 @@ void ServiceWorkerNavigationLoader::StartRequest(
did_navigation_preload_ = did_navigation_preload_ =
fetch_dispatcher_->MaybeStartNavigationPreloadWithURLLoader( fetch_dispatcher_->MaybeStartNavigationPreloadWithURLLoader(
resource_request_, url_loader_factory_getter_.get(), resource_request_, url_loader_factory_getter_.get(),
std::move(context), provider_host_->web_contents_getter(),
base::DoNothing(/* TODO(crbug/762357): metrics? */)); base::DoNothing(/* TODO(crbug/762357): metrics? */));
// Record worker start time here as |fetch_dispatcher_| will start a service // Record worker start time here as |fetch_dispatcher_| will start a service
......
...@@ -83,103 +83,6 @@ blink::mojom::FetchAPIResponsePtr RedirectResponse( ...@@ -83,103 +83,6 @@ blink::mojom::FetchAPIResponsePtr RedirectResponse(
return response; return response;
} }
// NavigationPreloadLoaderClient mocks the renderer-side URLLoaderClient for the
// navigation preload network request performed by the browser. In production
// code, this is ServiceWorkerContextClient::NavigationPreloadRequest,
// which it forwards the response to FetchEvent#preloadResponse. Here, it
// simulates passing the response to FetchEvent#respondWith.
//
// The navigation preload test is quite involved. The flow of data is:
// 1. ServiceWorkerNavigationLoader asks ServiceWorkerFetchDispatcher to start
// navigation preload.
// 2. ServiceWorkerFetchDispatcher starts the network request which is mocked
// by EmbeddedWorkerTestHelper's default network loader factory. The
// response is sent to
// ServiceWorkerFetchDispatcher::DelegatingURLLoaderClient.
// 3. DelegatingURLLoaderClient sends the response to the |preload_handle|
// that was passed to Helper::OnFetchEvent().
// 4. Helper::OnFetchEvent() creates NavigationPreloadLoaderClient, which
// receives the response.
// 5. NavigationPreloadLoaderClient calls OnFetchEvent()'s callbacks
// with the response.
// 6. Like all FetchEvent responses, the response is sent to
// ServiceWorkerNavigationLoader::DidDispatchFetchEvent, and the
// RequestHandler is returned.
class NavigationPreloadLoaderClient final
: public network::mojom::URLLoaderClient {
public:
NavigationPreloadLoaderClient(
blink::mojom::FetchEventPreloadHandlePtr preload_handle,
blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback,
blink::mojom::ServiceWorker::DispatchFetchEventCallback finish_callback)
: url_loader_(std::move(preload_handle->url_loader)),
binding_(this, std::move(preload_handle->url_loader_client_request)),
response_callback_(std::move(response_callback)),
finish_callback_(std::move(finish_callback)) {
binding_.set_connection_error_handler(
base::BindOnce(&NavigationPreloadLoaderClient::OnConnectionError,
base::Unretained(this)));
}
~NavigationPreloadLoaderClient() override = default;
// network::mojom::URLLoaderClient implementation
void OnReceiveResponse(
const network::ResourceResponseHead& response_head) override {
response_head_ = response_head;
}
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) override {
body_ = std::move(body);
// We could call OnResponseStream() here, but for simplicity, don't do
// anything until OnComplete().
}
void OnComplete(const network::URLLoaderCompletionStatus& status) override {
blink::mojom::ServiceWorkerStreamCallbackPtr stream_callback;
auto stream_handle = blink::mojom::ServiceWorkerStreamHandle::New();
stream_handle->callback_request = mojo::MakeRequest(&stream_callback);
stream_handle->stream = std::move(body_);
// Simulate passing the navigation preload response to
// FetchEvent#respondWith.
auto response = blink::mojom::FetchAPIResponse::New();
response->url_list =
std::vector<GURL>(response_head_.url_list_via_service_worker);
response->status_code = response_head_.headers->response_code();
response->status_text = response_head_.headers->GetStatusText();
response->response_type = response_head_.response_type;
response_callback_->OnResponseStream(
std::move(response), std::move(stream_handle),
blink::mojom::ServiceWorkerFetchEventTiming::New());
std::move(finish_callback_)
.Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
stream_callback->OnCompleted();
delete this;
}
void OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const network::ResourceResponseHead& response_head) override {}
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) override {}
void OnReceiveCachedMetadata(const std::vector<uint8_t>& data) override {}
void OnTransferSizeUpdated(int32_t transfer_size_diff) override {}
void OnConnectionError() { delete this; }
private:
network::mojom::URLLoaderPtr url_loader_;
mojo::Binding<network::mojom::URLLoaderClient> binding_;
network::ResourceResponseHead response_head_;
mojo::ScopedDataPipeConsumerHandle body_;
// Callbacks that complete Helper::OnFetchEvent().
blink::mojom::ServiceWorkerFetchResponseCallbackPtr response_callback_;
blink::mojom::ServiceWorker::DispatchFetchEventCallback finish_callback_;
DISALLOW_COPY_AND_ASSIGN(NavigationPreloadLoaderClient);
};
// Simulates a service worker handling fetch events. The response can be // Simulates a service worker handling fetch events. The response can be
// customized via RespondWith* functions. // customized via RespondWith* functions.
class FetchEventServiceWorker : public FakeServiceWorker { class FetchEventServiceWorker : public FakeServiceWorker {
...@@ -216,13 +119,6 @@ class FetchEventServiceWorker : public FakeServiceWorker { ...@@ -216,13 +119,6 @@ class FetchEventServiceWorker : public FakeServiceWorker {
// Tells this worker to respond to fetch events with an error response. // Tells this worker to respond to fetch events with an error response.
void RespondWithError() { response_mode_ = ResponseMode::kErrorResponse; } void RespondWithError() { response_mode_ = ResponseMode::kErrorResponse; }
// Tells this worker to respond to fetch events with
// FetchEvent#preloadResponse. See NavigationPreloadLoaderClient's
// documentation for details.
void RespondWithNavigationPreloadResponse() {
response_mode_ = ResponseMode::kNavigationPreloadResponse;
}
// Tells this worker to respond to fetch events with the redirect response. // Tells this worker to respond to fetch events with the redirect response.
void RespondWithRedirectResponse(const GURL& new_url) { void RespondWithRedirectResponse(const GURL& new_url) {
response_mode_ = ResponseMode::kRedirect; response_mode_ = ResponseMode::kRedirect;
...@@ -326,12 +222,6 @@ class FetchEventServiceWorker : public FakeServiceWorker { ...@@ -326,12 +222,6 @@ class FetchEventServiceWorker : public FakeServiceWorker {
std::move(finish_callback) std::move(finish_callback)
.Run(blink::mojom::ServiceWorkerEventStatus::REJECTED); .Run(blink::mojom::ServiceWorkerEventStatus::REJECTED);
break; break;
case ResponseMode::kNavigationPreloadResponse:
// Deletes itself when done.
new NavigationPreloadLoaderClient(std::move(params->preload_handle),
std::move(response_callback),
std::move(finish_callback));
break;
case ResponseMode::kFailFetchEventDispatch: case ResponseMode::kFailFetchEventDispatch:
// Simulate failure by stopping the worker before the event finishes. // Simulate failure by stopping the worker before the event finishes.
// This causes ServiceWorkerVersion::StartRequest() to call its error // This causes ServiceWorkerVersion::StartRequest() to call its error
...@@ -380,7 +270,6 @@ class FetchEventServiceWorker : public FakeServiceWorker { ...@@ -380,7 +270,6 @@ class FetchEventServiceWorker : public FakeServiceWorker {
kStream, kStream,
kFallbackResponse, kFallbackResponse,
kErrorResponse, kErrorResponse,
kNavigationPreloadResponse,
kFailFetchEventDispatch, kFailFetchEventDispatch,
kDeferredResponse, kDeferredResponse,
kEarlyResponse, kEarlyResponse,
...@@ -1063,32 +952,6 @@ TEST_F(ServiceWorkerNavigationLoaderTest, FallbackToNetwork) { ...@@ -1063,32 +952,6 @@ TEST_F(ServiceWorkerNavigationLoaderTest, FallbackToNetwork) {
0); 0);
} }
// Test responding to the fetch event with the navigation preload response.
TEST_F(ServiceWorkerNavigationLoaderTest, NavigationPreload) {
registration_->EnableNavigationPreload(true);
service_worker_->RespondWithNavigationPreloadResponse();
// Perform the request
LoaderResult result = StartRequest(CreateRequest());
ASSERT_EQ(LoaderResult::kHandledRequest, result);
client_.RunUntilComplete();
EXPECT_EQ(net::OK, client_.completion_status().error_code);
const network::ResourceResponseHead& info = client_.response_head();
EXPECT_EQ(200, info.headers->response_code());
std::unique_ptr<network::ResourceResponseHead> expected_info =
CreateResponseInfoFromServiceWorker();
expected_info->did_service_worker_navigation_preload = true;
ExpectResponseInfo(info, *expected_info);
std::string response;
EXPECT_TRUE(client_.response_body().is_valid());
EXPECT_TRUE(
mojo::BlockingCopyToString(client_.response_body_release(), &response));
EXPECT_EQ("this body came from the network", response);
}
// Test responding to the fetch event with a redirect response. // Test responding to the fetch event with a redirect response.
TEST_F(ServiceWorkerNavigationLoaderTest, Redirect) { TEST_F(ServiceWorkerNavigationLoaderTest, Redirect) {
base::HistogramTester histogram_tester; base::HistogramTester histogram_tester;
......
...@@ -30,7 +30,9 @@ enum ResourceType { ...@@ -30,7 +30,9 @@ enum ResourceType {
RESOURCE_TYPE_SERVICE_WORKER = 15, // the main resource of a service worker. RESOURCE_TYPE_SERVICE_WORKER = 15, // the main resource of a service worker.
RESOURCE_TYPE_CSP_REPORT = 16, // a report of Content Security Policy RESOURCE_TYPE_CSP_REPORT = 16, // a report of Content Security Policy
// violations. // violations.
RESOURCE_TYPE_PLUGIN_RESOURCE = 17, // a resource that a plugin requested. RESOURCE_TYPE_PLUGIN_RESOURCE = 17, // a resource that a plugin requested.
RESOURCE_TYPE_NAVIGATION_PRELOAD =
18, // a service worker navigation preload request.
RESOURCE_TYPE_LAST_TYPE RESOURCE_TYPE_LAST_TYPE
}; };
......
...@@ -26,5 +26,7 @@ enum ResourceType { ...@@ -26,5 +26,7 @@ enum ResourceType {
RESOURCE_TYPE_CSP_REPORT = 16, // a report of Content Security Policy RESOURCE_TYPE_CSP_REPORT = 16, // a report of Content Security Policy
// violations. // violations.
RESOURCE_TYPE_PLUGIN_RESOURCE = 17, // a resource that a plugin requested. RESOURCE_TYPE_PLUGIN_RESOURCE = 17, // a resource that a plugin requested.
RESOURCE_TYPE_NAVIGATION_PRELOAD =
18, // a service worker navigation preload request.
RESOURCE_TYPE_LAST_TYPE RESOURCE_TYPE_LAST_TYPE
}; };
...@@ -47,6 +47,8 @@ EnumTraits<content::mojom::ResourceType, content::ResourceType>::ToMojom( ...@@ -47,6 +47,8 @@ EnumTraits<content::mojom::ResourceType, content::ResourceType>::ToMojom(
return content::mojom::ResourceType::RESOURCE_TYPE_CSP_REPORT; return content::mojom::ResourceType::RESOURCE_TYPE_CSP_REPORT;
case content::RESOURCE_TYPE_PLUGIN_RESOURCE: case content::RESOURCE_TYPE_PLUGIN_RESOURCE:
return content::mojom::ResourceType::RESOURCE_TYPE_PLUGIN_RESOURCE; return content::mojom::ResourceType::RESOURCE_TYPE_PLUGIN_RESOURCE;
case content::RESOURCE_TYPE_NAVIGATION_PRELOAD:
return content::mojom::ResourceType::RESOURCE_TYPE_NAVIGATION_PRELOAD;
case content::RESOURCE_TYPE_LAST_TYPE: case content::RESOURCE_TYPE_LAST_TYPE:
return content::mojom::ResourceType::RESOURCE_TYPE_LAST_TYPE; return content::mojom::ResourceType::RESOURCE_TYPE_LAST_TYPE;
} }
...@@ -114,6 +116,9 @@ bool EnumTraits<content::mojom::ResourceType, content::ResourceType>::FromMojom( ...@@ -114,6 +116,9 @@ bool EnumTraits<content::mojom::ResourceType, content::ResourceType>::FromMojom(
case content::mojom::ResourceType::RESOURCE_TYPE_PLUGIN_RESOURCE: case content::mojom::ResourceType::RESOURCE_TYPE_PLUGIN_RESOURCE:
*output = content::RESOURCE_TYPE_PLUGIN_RESOURCE; *output = content::RESOURCE_TYPE_PLUGIN_RESOURCE;
return true; return true;
case content::mojom::ResourceType::RESOURCE_TYPE_NAVIGATION_PRELOAD:
*output = content::RESOURCE_TYPE_NAVIGATION_PRELOAD;
return true;
case content::mojom::ResourceType::RESOURCE_TYPE_LAST_TYPE: case content::mojom::ResourceType::RESOURCE_TYPE_LAST_TYPE:
*output = content::RESOURCE_TYPE_LAST_TYPE; *output = content::RESOURCE_TYPE_LAST_TYPE;
return true; return true;
......
...@@ -54,6 +54,7 @@ flat_rule::ElementType GetElementType(content::ResourceType type) { ...@@ -54,6 +54,7 @@ flat_rule::ElementType GetElementType(content::ResourceType type) {
case content::RESOURCE_TYPE_LAST_TYPE: case content::RESOURCE_TYPE_LAST_TYPE:
case content::RESOURCE_TYPE_PREFETCH: case content::RESOURCE_TYPE_PREFETCH:
case content::RESOURCE_TYPE_SUB_RESOURCE: case content::RESOURCE_TYPE_SUB_RESOURCE:
case content::RESOURCE_TYPE_NAVIGATION_PRELOAD:
return flat_rule::ElementType_OTHER; return flat_rule::ElementType_OTHER;
case content::RESOURCE_TYPE_MAIN_FRAME: case content::RESOURCE_TYPE_MAIN_FRAME:
return flat_rule::ElementType_MAIN_FRAME; return flat_rule::ElementType_MAIN_FRAME;
......
...@@ -276,6 +276,7 @@ WebRequestInfo::WebRequestInfo(net::URLRequest* url_request) ...@@ -276,6 +276,7 @@ WebRequestInfo::WebRequestInfo(net::URLRequest* url_request)
render_process_id = url_loader->GetProcessId(); render_process_id = url_loader->GetProcessId();
frame_id = url_loader->GetRenderFrameId(); frame_id = url_loader->GetRenderFrameId();
} }
type = static_cast<content::ResourceType>(url_loader->GetResourceType());
} else { } else {
// There may be basic process and frame info associated with the request // There may be basic process and frame info associated with the request
// even when |info| is null. Attempt to grab it as a last ditch effort. If // even when |info| is null. Attempt to grab it as a last ditch effort. If
......
...@@ -285,11 +285,14 @@ bool WebRequestPermissions::HideRequest( ...@@ -285,11 +285,14 @@ bool WebRequestPermissions::HideRequest(
if (is_request_from_browser) { if (is_request_from_browser) {
// Hide all non-navigation requests made by the browser. crbug.com/884932. // Hide all non-navigation requests made by the browser. crbug.com/884932.
if (!request.is_browser_side_navigation) if (!request.is_browser_side_navigation &&
request.type != content::RESOURCE_TYPE_NAVIGATION_PRELOAD) {
return true; return true;
}
DCHECK(request.type == content::RESOURCE_TYPE_MAIN_FRAME || DCHECK(request.type == content::RESOURCE_TYPE_MAIN_FRAME ||
request.type == content::RESOURCE_TYPE_SUB_FRAME); request.type == content::RESOURCE_TYPE_SUB_FRAME ||
request.type == content::RESOURCE_TYPE_NAVIGATION_PRELOAD);
// Hide sub-frame requests to clientsX.google.com. // Hide sub-frame requests to clientsX.google.com.
// TODO(crbug.com/890006): Determine if the code here can be cleaned up // TODO(crbug.com/890006): Determine if the code here can be cleaned up
......
...@@ -78,6 +78,8 @@ WebRequestResourceType ToWebRequestResourceType(content::ResourceType type) { ...@@ -78,6 +78,8 @@ WebRequestResourceType ToWebRequestResourceType(content::ResourceType type) {
return WebRequestResourceType::CSP_REPORT; return WebRequestResourceType::CSP_REPORT;
case content::RESOURCE_TYPE_PLUGIN_RESOURCE: case content::RESOURCE_TYPE_PLUGIN_RESOURCE:
return WebRequestResourceType::OBJECT; return WebRequestResourceType::OBJECT;
case content::RESOURCE_TYPE_NAVIGATION_PRELOAD:
return WebRequestResourceType::OTHER;
case content::RESOURCE_TYPE_LAST_TYPE: case content::RESOURCE_TYPE_LAST_TYPE:
return WebRequestResourceType::OTHER; return WebRequestResourceType::OTHER;
} }
......
...@@ -1052,6 +1052,10 @@ void URLLoader::SetAllowReportingRawHeaders(bool allow) { ...@@ -1052,6 +1052,10 @@ void URLLoader::SetAllowReportingRawHeaders(bool allow) {
report_raw_headers_ = want_raw_headers_ && allow; report_raw_headers_ = want_raw_headers_ && allow;
} }
uint32_t URLLoader::GetResourceType() const {
return resource_type_;
}
// static // static
URLLoader* URLLoader::ForRequest(const net::URLRequest& request) { URLLoader* URLLoader::ForRequest(const net::URLRequest& request) {
auto* pointer = auto* pointer =
......
...@@ -114,6 +114,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) URLLoader ...@@ -114,6 +114,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) URLLoader
uint32_t GetRenderFrameId() const; uint32_t GetRenderFrameId() const;
uint32_t GetProcessId() const; uint32_t GetProcessId() const;
uint32_t GetResourceType() const;
const net::HttpRequestHeaders& custom_proxy_pre_cache_headers() const { const net::HttpRequestHeaders& custom_proxy_pre_cache_headers() const {
return custom_proxy_pre_cache_headers_; return custom_proxy_pre_cache_headers_;
......
...@@ -8159,7 +8159,7 @@ Called by update_net_error_codes.py.--> ...@@ -8159,7 +8159,7 @@ Called by update_net_error_codes.py.-->
<enum name="ContentResourceType"> <enum name="ContentResourceType">
<obsolete> <obsolete>
Superseded by ContentResourceType in December 2015 when SUB_RESOURCE was Superseded by ContentResourceType2 in December 2015 when SUB_RESOURCE was
split into RESOURCE_TYPE_SUB_RESOURCE and RESOURCE_TYPE_PLUGIN_RESOURCE, and split into RESOURCE_TYPE_SUB_RESOURCE and RESOURCE_TYPE_PLUGIN_RESOURCE, and
PING was split into RESOURCE_TYPE_PING and RESOURCE_TYPE_CSP_REPORT. PING was split into RESOURCE_TYPE_PING and RESOURCE_TYPE_CSP_REPORT.
</obsolete> </obsolete>
...@@ -8200,6 +8200,7 @@ Called by update_net_error_codes.py.--> ...@@ -8200,6 +8200,7 @@ Called by update_net_error_codes.py.-->
<int value="15" label="RESOURCE_TYPE_SERVICE_WORKER"/> <int value="15" label="RESOURCE_TYPE_SERVICE_WORKER"/>
<int value="16" label="RESOURCE_TYPE_CSP_REPORT"/> <int value="16" label="RESOURCE_TYPE_CSP_REPORT"/>
<int value="17" label="RESOURCE_TYPE_PLUGIN_RESOURCE"/> <int value="17" label="RESOURCE_TYPE_PLUGIN_RESOURCE"/>
<int value="18" label="RESOURCE_TYPE_NAVIGATION_PRELOAD"/>
</enum> </enum>
<enum name="ContentSetting"> <enum name="ContentSetting">
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