Commit 529f43bc authored by Hayato Ito's avatar Hayato Ito Committed by Chromium LUCI CQ

Support WebBundle Subresource loading via Network Service

This is a follow-up to https://crrev.com/c/2560526.

This CL changes the implementation of WebBundle subresource loading
from "renderer oriented" to "network service oriented".

Before this CL, a renderer parses a WebBundle and serves a subresource
from there directly.

After this CL, they are done in the Network Service, instead of a renderer.

The motivation we're changing our design is "we should honor CORB
[1]". A renderer shouldn't have cross-origin data in their memory.
See the design doc [2] for details.

Note this CL removes
blink/renderer/platform/loader/fetch/url_loader/web_bundle_subresource_loader_test.cc,
which was already moved to
services/network/web_bundle_url_loader_factory_unittest.cc in a previous CL.

- [1]: https://source.chromium.org/chromium/chromium/src/+/master:services/network/cross_origin_read_blocking_explainer.md
- [2]: https://docs.google.com/document/d/1_AqUBS4Gr45MPPtXGTUl7Q0DMIEw2zUeWO0zo6Sj0z4/edit

Bug: 1082020
Change-Id: I78b0e81b573f735c84334a4cd2271c248609c2e8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2564924
Commit-Queue: Hayato Ito <hayato@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarKunihiko Sakamoto <ksakamoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#834195}
parent 67870a5b
......@@ -185,6 +185,8 @@ component("network_service") {
"url_request_context_builder_mojo.h",
"url_request_context_owner.cc",
"url_request_context_owner.h",
"web_bundle_manager.cc",
"web_bundle_manager.h",
"web_bundle_url_loader_factory.cc",
"web_bundle_url_loader_factory.h",
]
......@@ -351,6 +353,7 @@ source_set("tests") {
"udp_socket_unittest.cc",
"upload_progress_tracker_unittest.cc",
"url_loader_unittest.cc",
"web_bundle_manager_unittest.cc",
"web_bundle_url_loader_factory_unittest.cc",
]
......
......@@ -58,6 +58,7 @@
#include "services/network/public/mojom/websocket.mojom.h"
#include "services/network/socket_factory.h"
#include "services/network/url_request_context_owner.h"
#include "services/network/web_bundle_manager.h"
#if BUILDFLAG(IS_ASH)
#include "crypto/scoped_nss_types.h"
......@@ -513,6 +514,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext
return trust_token_store_.get();
}
WebBundleManager& GetWebBundleManager() { return web_bundle_manager_; }
#if BUILDFLAG(IS_CT_SUPPORTED)
void SetIsSCTAuditingEnabledForTesting(bool enabled) {
is_sct_auditing_enabled_ = enabled;
......@@ -751,6 +754,10 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext
// HttpAuthHandle via |NetworkContext::CreateHttpAuthHandlerFactory|.
net::HttpAuthPreferences http_auth_merged_preferences_;
// Each network context holds its own WebBundleManager, which
// manages the lifetiem of a WebBundleURLLoaderFactory object.
WebBundleManager web_bundle_manager_;
base::WeakPtrFactory<NetworkContext> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(NetworkContext);
......
......@@ -29,6 +29,7 @@
#include "services/network/trust_tokens/local_trust_token_operation_delegate_impl.h"
#include "services/network/trust_tokens/trust_token_request_helper_factory.h"
#include "services/network/url_loader.h"
#include "services/network/web_bundle_url_loader_factory.h"
#include "url/gurl.h"
#include "url/origin.h"
......@@ -129,6 +130,31 @@ void URLLoaderFactory::CreateLoaderAndStart(
origin_head_same_as_request_origin);
}
if (url_request.web_bundle_token_params.has_value() &&
url_request.destination !=
network::mojom::RequestDestination::kWebBundle) {
// Load a subresource from a WebBundle.
base::WeakPtr<WebBundleURLLoaderFactory> web_bundle_url_loader_factory =
context_->GetWebBundleManager().GetWebBundleURLLoaderFactory(
url_request.web_bundle_token_params->token);
if (web_bundle_url_loader_factory) {
web_bundle_url_loader_factory->CreateLoaderAndStart(
std::move(receiver), routing_id, request_id, options, url_request,
std::move(client), traffic_annotation);
return;
}
// Fails if the token is missing in the WebBundleManager. This can happen
// when the network process crashes. In normal cases, our assumption is this
// shouldn't happen as long as requests from a renderer are ordered.
//
// TODO(crbug.com/1082020): Re-visit this case to be more robust.
URLLoaderCompletionStatus status;
status.error_code = net::ERR_INVALID_WEB_BUNDLE; // Tentative.
status.completion_time = base::TimeTicks::Now();
mojo::Remote<mojom::URLLoaderClient>(std::move(client))->OnComplete(status);
return;
}
mojom::NetworkServiceClient* network_service_client = nullptr;
base::WeakPtr<KeepaliveStatisticsRecorder> keepalive_statistics_recorder;
base::WeakPtr<NetworkUsageAccumulator> network_usage_accumulator;
......@@ -247,6 +273,16 @@ void URLLoaderFactory::CreateLoaderAndStart(
cookie_observer_->Clone(cookie_observer.InitWithNewPipeAndPassReceiver());
}
if (url_request.destination ==
network::mojom::RequestDestination::kWebBundle) {
DCHECK(url_request.web_bundle_token_params.has_value());
base::WeakPtr<WebBundleURLLoaderFactory> web_bundle_url_loader_factory =
context_->GetWebBundleManager().CreateWebBundleURLLoaderFactory(
*url_request.web_bundle_token_params);
client =
web_bundle_url_loader_factory->WrapURLLoaderClient(std::move(client));
}
auto loader = std::make_unique<URLLoader>(
context_->url_request_context(), network_service_client,
context_->client(),
......
// Copyright 2020 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 "services/network/web_bundle_manager.h"
#include "base/bind.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/mojom/web_bundle_handle.mojom.h"
#include "services/network/web_bundle_url_loader_factory.h"
namespace network {
WebBundleManager::WebBundleManager() = default;
WebBundleManager::~WebBundleManager() = default;
base::WeakPtr<WebBundleURLLoaderFactory>
WebBundleManager::CreateWebBundleURLLoaderFactory(
const ResourceRequest::WebBundleTokenParams& web_bundle_token_params) {
DCHECK(factories_.find(web_bundle_token_params.token) == factories_.end());
mojo::Remote<mojom::WebBundleHandle> remote(
web_bundle_token_params.CloneHandle());
// Set a disconnect handler to remove a WebBundleURLLoaderFactory from this
// WebBundleManager when the corresponding endpoint in the renderer is
// removed.
remote.set_disconnect_handler(
base::BindOnce(&WebBundleManager::DisconnectHandler,
// |this| outlives |remote|.
base::Unretained(this), web_bundle_token_params.token));
auto factory = std::make_unique<WebBundleURLLoaderFactory>(std::move(remote));
auto weak_factory = factory->GetWeakPtr();
factories_.insert({web_bundle_token_params.token, std::move(factory)});
return weak_factory;
}
base::WeakPtr<WebBundleURLLoaderFactory>
WebBundleManager::GetWebBundleURLLoaderFactory(
const base::UnguessableToken& web_bundle_token) {
auto it = factories_.find(web_bundle_token);
if (it == factories_.end()) {
return nullptr;
}
return it->second->GetWeakPtr();
}
void WebBundleManager::DisconnectHandler(
base::UnguessableToken web_bundle_token) {
factories_.erase(web_bundle_token);
}
} // namespace network
// Copyright 2020 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.
#ifndef SERVICES_NETWORK_WEB_BUNDLE_MANAGER_H_
#define SERVICES_NETWORK_WEB_BUNDLE_MANAGER_H_
#include <map>
#include "base/component_export.h"
#include "base/memory/weak_ptr.h"
#include "base/unguessable_token.h"
#include "services/network/public/cpp/resource_request.h"
namespace network {
class WebBundleURLLoaderFactory;
// WebBundleManager manages the lifetime of a WebBundleURLLoaderFactory object,
// which is created for each WebBundle.
class COMPONENT_EXPORT(NETWORK_SERVICE) WebBundleManager {
public:
WebBundleManager();
~WebBundleManager();
WebBundleManager(const WebBundleManager&) = delete;
WebBundleManager& operator=(const WebBundleManager&) = delete;
base::WeakPtr<WebBundleURLLoaderFactory> CreateWebBundleURLLoaderFactory(
const ResourceRequest::WebBundleTokenParams& params);
base::WeakPtr<WebBundleURLLoaderFactory> GetWebBundleURLLoaderFactory(
const base::UnguessableToken& token);
private:
void DisconnectHandler(base::UnguessableToken token);
// Maps a WebBundle token to a WebBundleURLLoaderFactory.
// TODO(crbug.com/1149255): Use a tuple of (PID, token) as a key.
std::map<base::UnguessableToken, std::unique_ptr<WebBundleURLLoaderFactory>>
factories_;
};
} // namespace network
#endif // SERVICES_NETWORK_WEB_BUNDLE_MANAGER_H_
// Copyright 2020 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 "services/network/web_bundle_manager.h"
#include "base/test/task_environment.h"
#include "base/unguessable_token.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/web_bundle_handle.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
class WebBundleManagerTest : public testing::Test {
public:
WebBundleManagerTest() = default;
~WebBundleManagerTest() override = default;
private:
base::test::TaskEnvironment task_environment_;
};
TEST_F(WebBundleManagerTest, RemoveFactoryWhenDisconnected) {
WebBundleManager manager;
base::UnguessableToken token = base::UnguessableToken::Create();
{
mojo::PendingRemote<network::mojom::WebBundleHandle> handle;
mojo::PendingReceiver<network::mojom::WebBundleHandle> receiver =
handle.InitWithNewPipeAndPassReceiver();
auto token_params =
ResourceRequest::WebBundleTokenParams(token, std::move(handle));
auto factory = manager.CreateWebBundleURLLoaderFactory(token_params);
ASSERT_TRUE(factory);
ASSERT_TRUE(manager.GetWebBundleURLLoaderFactory(token));
// Getting out of scope to delete |receiver|.
}
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(manager.GetWebBundleURLLoaderFactory(token))
<< "The manager should remove a factory when the handle is disconnected.";
}
} // namespace network
......@@ -6,6 +6,7 @@
#include "components/web_package/web_bundle_parser.h"
#include "components/web_package/web_bundle_utils.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "mojo/public/cpp/system/data_pipe_drainer.h"
#include "mojo/public/cpp/system/data_pipe_producer.h"
......@@ -15,6 +16,8 @@ namespace network {
namespace {
constexpr size_t kBlockedBodyAllocationSize = 1;
class PipeDataSource : public mojo::DataPipeProducer::DataSource {
public:
explicit PipeDataSource(std::vector<uint8_t> data) : data_(std::move(data)) {}
......@@ -49,6 +52,76 @@ void DeleteProducerAndRunCallback(
std::move(callback).Run(result);
}
// URLLoaderClient which wraps the real URLLoaderClient.
class WebBundleURLLoaderClient : public network::mojom::URLLoaderClient {
public:
WebBundleURLLoaderClient(
base::WeakPtr<WebBundleURLLoaderFactory> factory,
mojo::PendingRemote<network::mojom::URLLoaderClient> wrapped)
: factory_(factory), wrapped_(std::move(wrapped)) {}
private:
// network::mojom::URLLoaderClient implementation:
void OnReceiveResponse(
network::mojom::URLResponseHeadPtr response_head) override {
wrapped_->OnReceiveResponse(std::move(response_head));
}
void OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr response_head) override {
wrapped_->OnReceiveRedirect(redirect_info, std::move(response_head));
}
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback ack_callback) override {
wrapped_->OnUploadProgress(current_position, total_size,
std::move(ack_callback));
}
void OnReceiveCachedMetadata(mojo_base::BigBuffer data) override {
wrapped_->OnReceiveCachedMetadata(std::move(data));
}
void OnTransferSizeUpdated(int32_t transfer_size_diff) override {
wrapped_->OnTransferSizeUpdated(transfer_size_diff);
}
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) override {
if (factory_)
factory_->SetBundleStream(std::move(body));
// Send empty body to the wrapped URLLoaderClient.
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes = kBlockedBodyAllocationSize;
mojo::ScopedDataPipeProducerHandle producer;
mojo::ScopedDataPipeConsumerHandle consumer;
MojoResult result = mojo::CreateDataPipe(&options, &producer, &consumer);
if (result != MOJO_RESULT_OK) {
wrapped_->OnComplete(
URLLoaderCompletionStatus(net::ERR_INSUFFICIENT_RESOURCES));
completed_ = true;
return;
}
wrapped_->OnStartLoadingResponseBody(std::move(consumer));
}
void OnComplete(const network::URLLoaderCompletionStatus& status) override {
if (completed_)
return;
wrapped_->OnComplete(status);
}
base::WeakPtr<WebBundleURLLoaderFactory> factory_;
mojo::Remote<network::mojom::URLLoaderClient> wrapped_;
bool completed_ = false;
};
} // namespace
class WebBundleURLLoaderFactory::URLLoader : public mojom::URLLoader {
......@@ -272,6 +345,17 @@ void WebBundleURLLoaderFactory::SetBundleStream(
weak_ptr_factory_.GetWeakPtr()));
}
mojo::PendingRemote<mojom::URLLoaderClient>
WebBundleURLLoaderFactory::WrapURLLoaderClient(
mojo::PendingRemote<mojom::URLLoaderClient> wrapped) {
mojo::PendingRemote<mojom::URLLoaderClient> client;
auto client_impl = std::make_unique<WebBundleURLLoaderClient>(
weak_ptr_factory_.GetWeakPtr(), std::move(wrapped));
mojo::MakeSelfOwnedReceiver(std::move(client_impl),
client.InitWithNewPipeAndPassReceiver());
return client;
}
void WebBundleURLLoaderFactory::CreateLoaderAndStart(
mojo::PendingReceiver<mojom::URLLoader> receiver,
int32_t routing_id,
......
......@@ -28,6 +28,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) WebBundleURLLoaderFactory
base::WeakPtr<WebBundleURLLoaderFactory> GetWeakPtr() const;
void SetBundleStream(mojo::ScopedDataPipeConsumerHandle body);
mojo::PendingRemote<mojom::URLLoaderClient> WrapURLLoaderClient(
mojo::PendingRemote<mojom::URLLoaderClient> wrapped);
// mojom::URLLoaderFactory implementation.
void CreateLoaderAndStart(mojo::PendingReceiver<mojom::URLLoader> receiver,
......
......@@ -4,6 +4,8 @@
#include "third_party/blink/renderer/core/html/link_web_bundle.h"
#include "base/unguessable_token.h"
#include "services/network/public/mojom/web_bundle_handle.mojom-blink.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h"
#include "third_party/blink/public/web/web_local_frame.h"
......@@ -18,31 +20,22 @@
#include "third_party/blink/renderer/platform/loader/cors/cors.h"
#include "third_party/blink/renderer/platform/loader/fetch/bytes_consumer.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/url_loader/web_bundle_subresource_loader.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
namespace blink {
// WebBundleLoader is responsible for loading a WebBundle resource.
class WebBundleLoader : public GarbageCollected<WebBundleLoader>,
public ThreadableLoaderClient {
public ThreadableLoaderClient,
public network::mojom::WebBundleHandle {
public:
WebBundleLoader(LinkWebBundle& link_web_bundle,
Document& document,
const KURL& url)
: link_web_bundle_(&link_web_bundle),
url_(url),
security_origin_(SecurityOrigin::Create(url)) {
blink::CrossVariantMojoReceiver<
network::mojom::URLLoaderFactoryInterfaceBase>
receiver(loader_factory_.BindNewPipeAndPassReceiver());
document.GetFrame()
->Client()
->GetWebFrame()
->Client()
->MaybeProxyURLLoaderFactory(&receiver);
pending_factory_receiver_ = std::move(receiver);
security_origin_(SecurityOrigin::Create(url)),
web_bundle_token_(base::UnguessableToken::Create()) {
ResourceRequest request(url);
request.SetUseStreamOnResponse(true);
// TODO(crbug.com/1082020): Revisit these once the fetch and process the
......@@ -50,8 +43,19 @@ class WebBundleLoader : public GarbageCollected<WebBundleLoader>,
// [1]
// https://html.spec.whatwg.org/multipage/semantics.html#fetch-and-process-the-linked-resource
request.SetRequestContext(mojom::blink::RequestContextType::SUBRESOURCE);
// TODO(crbug.com/1149816): Set CORS mode respecting the crossorigin=
// attribute of the <link> element.
request.SetMode(network::mojom::blink::RequestMode::kCors);
request.SetCredentialsMode(network::mojom::blink::CredentialsMode::kOmit);
request.SetRequestDestination(
network::mojom::RequestDestination::kWebBundle);
request.SetPriority(ResourceLoadPriority::kHigh);
mojo::PendingRemote<network::mojom::WebBundleHandle> web_bundle_handle;
web_bundle_handles_.Add(this,
web_bundle_handle.InitWithNewPipeAndPassReceiver());
request.SetWebBundleTokenParams(ResourceRequestHead::WebBundleTokenParams(
web_bundle_token_, std::move(web_bundle_handle)));
ExecutionContext* execution_context = document.GetExecutionContext();
ResourceLoaderOptions resource_loader_options(
......@@ -70,68 +74,55 @@ class WebBundleLoader : public GarbageCollected<WebBundleLoader>,
bool HasLoaded() const { return !failed_; }
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
GetURLLoaderFactory() {
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory> factory_clone;
loader_factory_->Clone(factory_clone.InitWithNewPipeAndPassReceiver());
return factory_clone;
}
// ThreadableLoaderClient
void DidReceiveResponse(uint64_t, const ResourceResponse& response) override {
if (!cors::IsOkStatus(response.HttpStatusCode()))
failed_ = true;
// TODO(crbug.com/1082020): Check response headers, as spec'ed in
// https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html#name-serving-constraints.
}
void DidStartLoadingResponseBody(BytesConsumer& consumer) override {
DCHECK(pending_factory_receiver_);
CreateWebBundleSubresourceLoaderFactory(
std::move(pending_factory_receiver_), consumer.DrainAsDataPipe(),
ConvertToBaseRepeatingCallback(
CrossThreadBindRepeating(&WebBundleLoader::OnWebBundleError,
WrapCrossThreadWeakPersistent(this))));
// Drain |consumer| so that DidFinishLoading is surely called later.
consumer.DrainAsDataPipe();
}
void DidFinishLoading(uint64_t) override { link_web_bundle_->NotifyLoaded(); }
void DidFail(const ResourceError&) override { DidFailInternal(); }
void DidFailRedirectCheck() override { DidFailInternal(); }
// network::mojom::WebBundleHandle
void Clone(mojo::PendingReceiver<network::mojom::WebBundleHandle> receiver)
override {
web_bundle_handles_.Add(this, std::move(receiver));
}
void OnWebBundleError(network::mojom::WebBundleErrorType type,
const std::string& message) override {
link_web_bundle_->OnWebBundleError(url_.ElidedString() + ": " +
message.c_str());
}
const KURL& url() const { return url_; }
scoped_refptr<SecurityOrigin> GetSecurityOrigin() const {
return security_origin_;
}
const base::UnguessableToken& WebBundleToken() const {
return web_bundle_token_;
}
private:
void DidFailInternal() {
if (pending_factory_receiver_) {
// If we haven't create a WebBundleSubresourceLoaderFactory, create it
// with an empty bundle body so that requests to
// |pending_factory_receiver_| are processed (and fail).
CreateWebBundleSubresourceLoaderFactory(
std::move(pending_factory_receiver_),
mojo::ScopedDataPipeConsumerHandle(), base::DoNothing());
}
failed_ = true;
link_web_bundle_->NotifyLoaded();
}
void OnWebBundleError(WebBundleErrorType type, const String& message) {
// TODO(crbug.com/1082020): Dispatch "error" event on metadata parse error.
// Simply setting |failed_| here does not work because DidFinishLoading()
// may already be called.
link_web_bundle_->OnWebBundleError(url_.ElidedString() + ": " + message);
}
Member<LinkWebBundle> link_web_bundle_;
Member<ThreadableLoader> loader_;
mojo::Remote<network::mojom::blink::URLLoaderFactory> loader_factory_;
mojo::PendingReceiver<network::mojom::blink::URLLoaderFactory>
pending_factory_receiver_;
bool failed_ = false;
KURL url_;
scoped_refptr<SecurityOrigin> security_origin_;
base::UnguessableToken web_bundle_token_;
// we need ReceiverSet here because WebBundleHandle is cloned when
// ResourceRequest is copied.
mojo::ReceiverSet<network::mojom::WebBundleHandle> web_bundle_handles_;
};
LinkWebBundle::LinkWebBundle(HTMLLinkElement* owner) : LinkResource(owner) {
......@@ -173,6 +164,23 @@ void LinkWebBundle::Process() {
return;
if (!bundle_loader_ || bundle_loader_->url() != owner_->Href()) {
if (resource_fetcher->ShouldBeLoadedFromWebBundle(owner_->Href())) {
// This can happen when a requested bundle is a nested bundle.
//
// clang-format off
// Example:
// <link rel="webbundle" href=".../nested-main.wbn" resources=".../nested-sub.wbn">
// <link rel="webbundle" href=".../nested-sub.wbn" resources="...">
// clang-format on
if (bundle_loader_) {
resource_fetcher->RemoveSubresourceWebBundle(*this);
bundle_loader_ = nullptr;
}
NotifyLoaded();
OnWebBundleError("A nested bundle is not supported: " +
owner_->Href().ElidedString());
return;
}
bundle_loader_ = MakeGarbageCollected<WebBundleLoader>(
*this, owner_->GetDocument(), owner_->Href());
}
......@@ -199,6 +207,8 @@ void LinkWebBundle::OwnerRemoved() {
}
bool LinkWebBundle::CanHandleRequest(const KURL& url) const {
if (!url.IsValid())
return false;
if (!owner_ || !owner_->ValidResourceUrls().Contains(url))
return false;
DCHECK(bundle_loader_);
......@@ -220,15 +230,14 @@ bool LinkWebBundle::CanHandleRequest(const KURL& url) const {
return true;
}
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
LinkWebBundle::GetURLLoaderFactory() {
String LinkWebBundle::GetCacheIdentifier() const {
DCHECK(bundle_loader_);
return bundle_loader_->GetURLLoaderFactory();
return bundle_loader_->url().GetString();
}
String LinkWebBundle::GetCacheIdentifier() const {
const base::UnguessableToken& LinkWebBundle::WebBundleToken() const {
DCHECK(bundle_loader_);
return bundle_loader_->url().GetString();
return bundle_loader_->WebBundleToken();
}
// static
......
......@@ -12,6 +12,10 @@
#include "third_party/blink/renderer/platform/loader/fetch/subresource_web_bundle.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
namespace base {
class UnguessableToken;
}
namespace blink {
class WebBundleLoader;
......@@ -41,9 +45,8 @@ class CORE_EXPORT LinkWebBundle final : public LinkResource,
// SubresourceWebBundle overrides:
bool CanHandleRequest(const KURL& url) const override;
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
GetURLLoaderFactory() override;
String GetCacheIdentifier() const override;
const base::UnguessableToken& WebBundleToken() const override;
// Parse the given |str| as a url. If |str| doesn't meet the criteria which
// WebBundles specification requires, this returns invalid empty KURL as an
......
......@@ -118,8 +118,6 @@ blink_platform_sources("loader") {
"fetch/unique_identifier.h",
"fetch/url_loader/request_conversion.cc",
"fetch/url_loader/request_conversion.h",
"fetch/url_loader/web_bundle_subresource_loader.cc",
"fetch/url_loader/web_bundle_subresource_loader.h",
"fetch/url_loader/worker_main_script_loader.cc",
"fetch/url_loader/worker_main_script_loader.h",
"fetch/url_loader/worker_main_script_loader_client.h",
......@@ -147,7 +145,6 @@ blink_platform_sources("loader") {
deps = [
":make_platform_loader_generated_fetch_initiator_type_names",
"//components/link_header_util",
"//components/web_package",
"//net",
"//services/metrics/public/cpp:ukm_builders",
"//services/network/public/cpp",
......@@ -193,7 +190,6 @@ source_set("unit_tests") {
"fetch/response_body_loader_test.cc",
"fetch/shared_buffer_bytes_consumer_test.cc",
"fetch/source_keyed_cached_metadata_handler_test.cc",
"fetch/url_loader/web_bundle_subresource_loader_test.cc",
"fetch/url_loader/worker_main_script_loader_unittest.cc",
"ftp_directory_listing_test.cc",
"link_header_test.cc",
......@@ -205,7 +201,6 @@ source_set("unit_tests") {
deps = [
"//base/test:test_support",
"//components/web_package:test_support",
"//mojo/public/cpp/test_support:test_utils",
"//net/traffic_annotation:test_support",
"//services/network:test_support",
......
......@@ -975,9 +975,36 @@ base::Optional<ResourceRequestBlockedReason> ResourceFetcher::PrepareRequest(
resource_request.SetAllowStoredCredentials(false);
}
if (resource_request.GetWebBundleTokenParams()) {
DCHECK_EQ(resource_request.GetRequestDestination(),
network::mojom::RequestDestination::kWebBundle);
} else {
AttachWebBundleTokenIfNeeded(resource_request);
}
return base::nullopt;
}
bool ResourceFetcher::ShouldBeLoadedFromWebBundle(const KURL& url) const {
for (auto& bundle : subresource_web_bundles_) {
if (bundle->CanHandleRequest(url))
return true;
}
return false;
}
void ResourceFetcher::AttachWebBundleTokenIfNeeded(
ResourceRequest& resource_request) const {
for (auto& bundle : subresource_web_bundles_) {
if (!bundle->CanHandleRequest(resource_request.Url()))
continue;
resource_request.SetWebBundleTokenParams(
ResourceRequestHead::WebBundleTokenParams(bundle->WebBundleToken(),
mojo::NullRemote()));
return;
}
}
Resource* ResourceFetcher::RequestResource(FetchParameters& params,
const ResourceFactory& factory,
ResourceClient* client) {
......@@ -1248,19 +1275,6 @@ std::unique_ptr<WebURLLoader> ResourceFetcher::CreateURLLoader(
const ResourceLoaderOptions& options) {
DCHECK(!GetProperties().IsDetached());
DCHECK(loader_factory_);
for (auto& bundle : subresource_web_bundles_) {
if (!bundle->CanHandleRequest(request.Url()))
continue;
ResourceLoaderOptions new_options(options);
new_options.url_loader_factory = base::MakeRefCounted<base::RefCountedData<
mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>>>(
bundle->GetURLLoaderFactory());
// TODO(yoichio): CreateURLLoader take a ResourceRequestHead instead of
// ResourceRequest.
return loader_factory_->CreateURLLoader(ResourceRequest(request),
new_options, freezable_task_runner_,
unfreezable_task_runner_);
}
return loader_factory_->CreateURLLoader(ResourceRequest(request), options,
freezable_task_runner_,
unfreezable_task_runner_);
......
......@@ -305,6 +305,8 @@ class PLATFORM_EXPORT ResourceFetcher
void AddSubresourceWebBundle(SubresourceWebBundle& subresource_web_bundle);
void RemoveSubresourceWebBundle(SubresourceWebBundle& subresource_web_bundle);
void AttachWebBundleTokenIfNeeded(ResourceRequest&) const;
bool ShouldBeLoadedFromWebBundle(const KURL&) const;
void EvictFromBackForwardCache();
......
......@@ -10,20 +10,23 @@
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/platform_export.h"
namespace base {
class UnguessableToken;
}
namespace blink {
class KURL;
// SubresourceWebBundle is attached to ResourceFetcher and used to intercept
// subresource requests for a certain set of URLs and serve responses from a
// SubresourceWebBundle is attached to ResourceFetcher and used to set
// WebBundleToken to subresource requests which should be served from a
// WebBundle. This is used for Subresource loading with Web Bundles
// (https://github.com/WICG/webpackage/blob/master/explainers/subresource-loading.md).
class PLATFORM_EXPORT SubresourceWebBundle : public GarbageCollectedMixin {
public:
void Trace(Visitor* visitor) const override {}
virtual bool CanHandleRequest(const KURL& url) const = 0;
virtual mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
GetURLLoaderFactory() = 0;
virtual const base::UnguessableToken& WebBundleToken() const = 0;
virtual String GetCacheIdentifier() const = 0;
};
......
......@@ -2,13 +2,4 @@ specific_include_rules = {
"request_conversion.cc" : [
"+media/media_buildflags.h"
],
"web_bundle_subresource_loader.cc" : [
"+components/web_package",
"+net/http/http_status_code.h",
],
"web_bundle_subresource_loader_test.cc" : [
"+components/web_package",
"+net/traffic_annotation/network_traffic_annotation_test_helper.h",
"+services/network/test/test_url_loader_client.h",
],
}
// Copyright 2020 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_URL_LOADER_WEB_BUNDLE_SUBRESOURCE_LOADER_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_URL_LOADER_WEB_BUNDLE_SUBRESOURCE_LOADER_H_
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "services/network/public/mojom/url_loader_factory.mojom-shared.h"
#include "third_party/blink/public/platform/cross_variant_mojo_util.h"
#include "third_party/blink/renderer/platform/platform_export.h"
namespace WTF {
class String;
} // namespace WTF
namespace blink {
enum class WebBundleErrorType {
kMetadataParseError,
kResponseParseError,
kResourceNotFound,
};
using WebBundleErrorCallback =
base::RepeatingCallback<void(WebBundleErrorType,
const WTF::String& message)>;
// Creates a network::mojom::URLLoaderFactory that can load resources from a
// WebBundle, and binds it to |factory_receiver|.
PLATFORM_EXPORT void CreateWebBundleSubresourceLoaderFactory(
CrossVariantMojoReceiver<network::mojom::URLLoaderFactoryInterfaceBase>
factory_receiver,
mojo::ScopedDataPipeConsumerHandle bundle_body,
WebBundleErrorCallback error_callback);
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FETCH_URL_LOADER_WEB_BUNDLE_SUBRESOURCE_LOADER_H_
......@@ -708,9 +708,9 @@ WEB-PLATFORM.TEST:signed-exchange/resources/generate-test-sxgs.sh
# Web Bundle files have hard-coded URLs
WEB-PLATFORM.TEST:web-bundle/resources/generate-test-wbns.sh
WEB-PLATFORM.TEST:web-bundle/resources/nested/*.wbn
WEB-PLATFORM.TEST:web-bundle/resources/wbn/*.wbn
WEB-PLATFORM.TEST:web-bundle/subresource-loading/subresource-loading-from-web-bundle.tentative.html
WEB-PLATFORM.TEST:web-bundle/subresource-loading/subresource-loading-path-restriction.tentative.html
WEB-PLATFORM.TEST:web-bundle/subresource-loading/*.html
# Tests that depend on resources in /gen/ in Chromium:
# https://github.com/web-platform-tests/wpt/issues/16455
......
......@@ -55,3 +55,12 @@ gen-bundle \
-primaryURL $wpt_test_http_origin/web-bundle/resources/wbn/resource.js \
-dir path-restriction/ \
-o wbn/path-restriction.wbn
# Create a bundle, nested-main.wbn, which includes nested-sub.wbn.
cp -a wbn/subresource.wbn nested/nested-sub.wbn
gen-bundle \
-version b1 \
-baseURL $wpt_test_http_origin/web-bundle/resources/wbn/ \
-primaryURL $wpt_test_http_origin/web-bundle/resources/wbn/resource.js \
-dir nested/ \
-o wbn/nested-main.wbn
<!DOCTYPE html>
<title>A nested bundle is not supported</title>
<link
rel="help"
href="https://github.com/WICG/webpackage/blob/master/explainers/subresource-loading.md"
/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<link
rel="webbundle"
href="http://web-platform.test:8001/web-bundle/resources/wbn/nested-main.wbn"
resources="http://web-platform.test:8001/web-bundle/resources/wbn/nested-sub.wbn"
/>
<script>
promise_test(async () => {
const response = await fetch(
"http://web-platform.test:8001/web-bundle/resources/wbn/nested-sub.wbn"
);
assert_true(response.ok);
}, "A nested bundle can be fetched");
promise_test(async () => {
await addLinkAndWaitForError(
"http://web-platform.test:8001/web-bundle/resources/wbn/nested-sub.wbn",
"http://web-platform.test:8001/web-bundle/resources/wbn/root.js"
);
const response = await fetch(
"http://web-platform.test:8001/web-bundle/resources/wbn/root.js"
);
assert_false(response.ok);
}, "Subresources in a nested bundle should not be loaded");
function addLinkAndWaitForError(url, resources) {
return new Promise((resolve, reject) => {
const link = document.createElement("link");
link.rel = "webbundle";
link.href = url;
link.resources = resources;
link.onload = reject;
link.onerror = () => resolve();
document.body.appendChild(link);
});
}
</script>
</body>
......@@ -38,7 +38,10 @@ await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'http://127.0.0.1:8000/loading/wbn/resources/wbn/server/hello/notfound.js';
script.onload = reject;
script.onerror = resolve;
script.onerror = () => {
// Wait for the console message to be logged.
setTimeout(resolve, 100);
}
document.body.appendChild(script);
});
......
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