0

Propagating web bundle events to the CDP.

Bug: 1182537
Change-Id: I2dccec56d9c73542e903e5d728ddd78910fca6f1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2904210
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Reviewed-by: Tsuyoshi Horo <horo@chromium.org>
Reviewed-by: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Commit-Queue: Danil Somsikov <dsv@chromium.org>
Cr-Commit-Position: refs/heads/master@{#885650}
This commit is contained in:
Danil Somsikov
2021-05-21 20:58:42 +00:00
committed by Chromium LUCI CQ
parent 6f2bf7b34d
commit 592b4b9468
18 changed files with 615 additions and 26 deletions

@ -212,6 +212,54 @@ void NetworkServiceDevToolsObserver::OnCorsError(
ftn->current_frame_host(), issue.get());
}
void NetworkServiceDevToolsObserver::OnSubresourceWebBundleMetadata(
const std::string& devtools_request_id,
const std::vector<GURL>& urls) {
auto* host = GetDevToolsAgentHost();
if (!host)
return;
DispatchToAgents(host,
&protocol::NetworkHandler::OnSubresourceWebBundleMetadata,
devtools_request_id, urls);
}
void NetworkServiceDevToolsObserver::OnSubresourceWebBundleMetadataError(
const std::string& devtools_request_id,
const std::string& error_message) {
auto* host = GetDevToolsAgentHost();
if (!host)
return;
DispatchToAgents(
host, &protocol::NetworkHandler::OnSubresourceWebBundleMetadataError,
devtools_request_id, error_message);
}
void NetworkServiceDevToolsObserver::OnSubresourceWebBundleInnerResponse(
const std::string& inner_request_devtools_id,
const GURL& url,
const absl::optional<std::string>& bundle_request_devtools_id) {
auto* host = GetDevToolsAgentHost();
if (!host)
return;
DispatchToAgents(
host, &protocol::NetworkHandler::OnSubresourceWebBundleInnerResponse,
inner_request_devtools_id, url, bundle_request_devtools_id);
}
void NetworkServiceDevToolsObserver::OnSubresourceWebBundleInnerResponseError(
const std::string& inner_request_devtools_id,
const GURL& url,
const std::string& error_message,
const absl::optional<std::string>& bundle_request_devtools_id) {
auto* host = GetDevToolsAgentHost();
if (!host)
return;
DispatchToAgents(
host, &protocol::NetworkHandler::OnSubresourceWebBundleInnerResponseError,
inner_request_devtools_id, url, error_message,
bundle_request_devtools_id);
}
void NetworkServiceDevToolsObserver::Clone(
mojo::PendingReceiver<network::mojom::DevToolsObserver> observer) {
mojo::MakeSelfOwnedReceiver(

@ -71,6 +71,20 @@ class CONTENT_EXPORT NetworkServiceDevToolsObserver
const absl::optional<::url::Origin>& initiator_origin,
const GURL& url,
const network::CorsErrorStatus& status) override;
void OnSubresourceWebBundleMetadata(const std::string& devtools_request_id,
const std::vector<GURL>& urls) override;
void OnSubresourceWebBundleMetadataError(
const std::string& devtools_request_id,
const std::string& error_message) override;
void OnSubresourceWebBundleInnerResponse(
const std::string& inner_request_devtools_id,
const GURL& url,
const absl::optional<std::string>& bundle_request_devtools_id) override;
void OnSubresourceWebBundleInnerResponseError(
const std::string& inner_request_devtools_id,
const GURL& url,
const std::string& error_message,
const absl::optional<std::string>& bundle_request_devtools_id) override;
void Clone(mojo::PendingReceiver<network::mojom::DevToolsObserver> listener)
override;

@ -2867,6 +2867,59 @@ void NetworkHandler::OnTrustTokenOperationDone(
result.issued_token_count);
}
void NetworkHandler::OnSubresourceWebBundleMetadata(
const std::string& devtools_request_id,
const std::vector<GURL>& urls) {
if (!enabled_)
return;
auto new_urls = std::make_unique<protocol::Array<protocol::String>>();
for (const auto& url : urls) {
new_urls->push_back(url.spec());
}
frontend()->SubresourceWebBundleMetadataReceived(devtools_request_id,
std::move(new_urls));
}
void NetworkHandler::OnSubresourceWebBundleMetadataError(
const std::string& devtools_request_id,
const std::string& error_message) {
if (!enabled_)
return;
frontend()->SubresourceWebBundleMetadataError(devtools_request_id,
error_message);
}
void NetworkHandler::OnSubresourceWebBundleInnerResponse(
const std::string& inner_request_devtools_id,
const GURL& url,
const absl::optional<std::string>& bundle_request_devtools_id) {
if (!enabled_)
return;
frontend()->SubresourceWebBundleInnerResponseParsed(
inner_request_devtools_id, url.spec(),
bundle_request_devtools_id.has_value()
? Maybe<std::string>(*bundle_request_devtools_id)
: Maybe<std::string>());
}
void NetworkHandler::OnSubresourceWebBundleInnerResponseError(
const std::string& inner_request_devtools_id,
const GURL& url,
const std::string& error_message,
const absl::optional<std::string>& bundle_request_devtools_id) {
if (!enabled_)
return;
frontend()->SubresourceWebBundleInnerResponseError(
inner_request_devtools_id, url.spec(), error_message,
bundle_request_devtools_id.has_value()
? Maybe<std::string>(*bundle_request_devtools_id)
: Maybe<std::string>());
}
String NetworkHandler::BuildPrivateNetworkRequestPolicy(
network::mojom::PrivateNetworkRequestPolicy policy) {
switch (policy) {

@ -229,6 +229,20 @@ class NetworkHandler : public DevToolsDomainHandler,
void OnTrustTokenOperationDone(
const std::string& devtools_request_id,
const network::mojom::TrustTokenOperationResult& result);
void OnSubresourceWebBundleMetadata(const std::string& devtools_request_id,
const std::vector<GURL>& urls);
void OnSubresourceWebBundleMetadataError(
const std::string& devtools_request_id,
const std::string& error_message);
void OnSubresourceWebBundleInnerResponse(
const std::string& inner_request_devtools_id,
const GURL& url,
const absl::optional<std::string>& bundle_request_devtools_id);
void OnSubresourceWebBundleInnerResponseError(
const std::string& inner_request_devtools_id,
const GURL& url,
const std::string& error_message,
const absl::optional<std::string>& bundle_request_devtools_id);
bool enabled() const { return enabled_; }

@ -54,7 +54,7 @@
{
"domain": "Network",
"include": ["enable", "disable", "clearBrowserCache", "clearBrowserCookies", "getCookies", "getAllCookies", "deleteCookies", "setCookie", "setCookies", "setExtraHTTPHeaders", "canEmulateNetworkConditions", "emulateNetworkConditions", "setBypassServiceWorker", "setRequestInterception", "continueInterceptedRequest", "getResponseBodyForInterception", "setCacheDisabled", "takeResponseBodyForInterceptionAsStream", "getSecurityIsolationStatus", "loadNetworkResource", "setAcceptedEncodings", "clearAcceptedEncodingsOverride"],
"include_events": ["requestWillBeSent", "responseReceived", "loadingFinished", "loadingFailed", "requestIntercepted", "signedExchangeReceived", "requestWillBeSentExtraInfo", "responseReceivedExtraInfo", "trustTokenOperationDone"],
"include_events": ["requestWillBeSent", "responseReceived", "loadingFinished", "loadingFailed", "requestIntercepted", "signedExchangeReceived", "requestWillBeSentExtraInfo", "responseReceivedExtraInfo", "trustTokenOperationDone", "subresourceWebBundleMetadataReceived", "subresourceWebBundleMetadataError", "subresourceWebBundleInnerResponseParsed", "subresourceWebBundleInnerResponseError"],
"async": ["clearBrowserCookies", "clearBrowserCache", "getCookies", "getAllCookies", "deleteCookies", "setCookie", "setCookies", "continueInterceptedRequest", "getResponseBodyForInterception", "takeResponseBodyForInterceptionAsStream", "loadNetworkResource"]
},
{

@ -322,6 +322,25 @@ class MockDevToolsObserver : public mojom::DevToolsObserver {
if (wait_for_completed_)
std::move(wait_for_completed_).Run();
}
void OnSubresourceWebBundleMetadata(const std::string& devtools_request_id,
const std::vector<GURL>& urls) override {}
void OnSubresourceWebBundleMetadataError(
const std::string& devtools_request_id,
const std::string& error_message) override {}
void OnSubresourceWebBundleInnerResponse(
const std::string& inner_request_devtools_id,
const ::GURL& url,
const absl::optional<std::string>& bundle_request_devtools_id) override {}
void OnSubresourceWebBundleInnerResponseError(
const std::string& inner_request_devtools_id,
const ::GURL& url,
const std::string& error_message,
const absl::optional<std::string>& bundle_request_devtools_id) override {}
void OnCorsError(const absl::optional<std::string>& devtool_request_id,
const absl::optional<::url::Origin>& initiator_origin,
const GURL& url,

@ -579,6 +579,32 @@ interface DevToolsObserver {
url.mojom.Url url,
CorsErrorStatus status);
// Called when parsing the .wbn file has succeeded. The event
// contains the information about the web bundle contents.
OnSubresourceWebBundleMetadata(
string devtool_request_id,
array<url.mojom.Url> urls);
// Called when parsing the .wbn file has failed.
OnSubresourceWebBundleMetadataError(
string devtool_request_id,
string error_message);
// Called when handling requests for resources within a .wbn file.
// Note: this will only be fired for resources that are requested by the
// webpage.
OnSubresourceWebBundleInnerResponse(
string inner_request_devtools_id,
url.mojom.Url url,
string? bundle_request_devtools_id);
// Called when an error occurs while handling a request within a .wbn file.
OnSubresourceWebBundleInnerResponseError(
string inner_request_devtools_id,
url.mojom.Url url,
string error_message,
string? bundle_request_devtools_id);
// Used by the NetworkService to create a copy of this observer.
// (e.g. when creating an observer for URLLoader from URLLoaderFactory's
// observer).

@ -14,6 +14,7 @@
#include "services/network/public/mojom/http_raw_headers.mojom-forward.h"
#include "services/network/public/mojom/ip_address_space.mojom-forward.h"
#include "services/network/public/mojom/url_request.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace network {
@ -66,6 +67,33 @@ class MockDevToolsObserver : public mojom::DevToolsObserver {
const std::string& devtool_request_id,
network::mojom::TrustTokenOperationResultPtr result) override;
MOCK_METHOD(void,
OnSubresourceWebBundleMetadata,
(const std::string& devtools_request_id,
const std::vector<GURL>& urls),
(override));
MOCK_METHOD(void,
OnSubresourceWebBundleMetadataError,
(const std::string& devtools_request_id,
const std::string& error_message),
(override));
MOCK_METHOD(void,
OnSubresourceWebBundleInnerResponse,
(const std::string& inner_request_devtools_id,
const GURL& url,
const absl::optional<std::string>& bundle_request_devtools_id),
(override));
MOCK_METHOD(void,
OnSubresourceWebBundleInnerResponseError,
(const std::string& inner_request_devtools_id,
const GURL& url,
const std::string& error_message,
const absl::optional<std::string>& bundle_request_devtools_id),
(override));
void OnCorsError(const absl::optional<std::string>& devtool_request_id,
const absl::optional<::url::Origin>& initiator_origin,
const GURL& url,

@ -163,6 +163,7 @@ class WebBundleURLLoaderFactory::URLLoader : public mojom::URLLoader {
request_mode_(request.mode),
request_initiator_(request.request_initiator),
request_initiator_origin_lock_(request_initiator_origin_lock),
devtools_request_id_(request.devtools_request_id),
receiver_(this, std::move(loader)),
client_(std::move(client)),
trusted_header_client_(std::move(trusted_header_client)) {
@ -178,6 +179,9 @@ class WebBundleURLLoaderFactory::URLLoader : public mojom::URLLoader {
const GURL& url() const { return url_; }
const mojom::RequestMode& request_mode() const { return request_mode_; }
const absl::optional<std::string>& devtools_request_id() const {
return devtools_request_id_;
}
const absl::optional<url::Origin>& request_initiator() const {
return request_initiator_;
@ -278,6 +282,7 @@ class WebBundleURLLoaderFactory::URLLoader : public mojom::URLLoader {
// (via URLLoaderFactory -> WebBundleManager -> WebBundleURLLoaderFactory
// -> WebBundleURLLoader).
const absl::optional<url::Origin> request_initiator_origin_lock_;
absl::optional<std::string> devtools_request_id_;
mojo::Receiver<mojom::URLLoader> receiver_;
mojo::Remote<mojom::URLLoaderClient> client_;
mojo::Remote<mojom::TrustedHeaderClient> trusted_header_client_;
@ -616,10 +621,24 @@ void WebBundleURLLoaderFactory::OnMetadataParsed(
ReportErrorAndCancelPendingLoaders(
SubresourceWebBundleLoadResult::kMetadataParseError,
mojom::WebBundleErrorType::kMetadataParseError, error->message);
if (devtools_request_id_) {
devtools_observer_->OnSubresourceWebBundleMetadataError(
*devtools_request_id_, error->message);
}
return;
}
metadata_ = std::move(metadata);
if (devtools_observer_ && devtools_request_id_) {
std::vector<GURL> urls;
urls.reserve(metadata_->requests.size());
for (const auto& item : metadata_->requests) {
urls.push_back(item.first);
}
devtools_observer_->OnSubresourceWebBundleMetadata(*devtools_request_id_,
std::move(urls));
}
if (data_completed_)
MaybeReportLoadResult(SubresourceWebBundleLoadResult::kSuccess);
for (auto loader : pending_loaders_)
@ -635,11 +654,28 @@ void WebBundleURLLoaderFactory::OnResponseParsed(
if (!loader)
return;
if (error) {
if (devtools_observer_ && loader->devtools_request_id()) {
devtools_observer_->OnSubresourceWebBundleInnerResponseError(
*loader->devtools_request_id(), loader->url(), error->message,
devtools_request_id_);
}
web_bundle_handle_->OnWebBundleError(
mojom::WebBundleErrorType::kResponseParseError, error->message);
loader->OnFail(net::ERR_INVALID_WEB_BUNDLE);
return;
}
if (devtools_observer_) {
std::vector<network::mojom::HttpRawHeaderPairPtr> headers;
headers.reserve(response->response_headers.size());
for (const auto& it : response->response_headers) {
headers.push_back(
network::mojom::HttpRawHeaderPair::New(it.first, it.second));
}
if (loader->devtools_request_id()) {
devtools_observer_->OnSubresourceWebBundleInnerResponse(
*loader->devtools_request_id(), loader->url(), devtools_request_id_);
}
}
// Add an artificial "X-Content-Type-Options: "nosniff" header, which is
// explained at
// https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html#name-responses.

@ -13,6 +13,7 @@
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/test/mock_devtools_observer.h"
#include "services/network/test/test_url_loader_client.h"
#include "services/network/web_bundle_memory_quota_consumer.h"
#include "testing/gmock/include/gmock/gmock.h"
@ -24,14 +25,24 @@ namespace {
const char kInitiatorUrl[] = "https://example.com/";
const char kBundleUrl[] = "https://example.com/bundle.wbn";
const char kBundleRequestId[] = "bundle-devtools-request-id";
const char kResourceUrl[] = "https://example.com/";
const char kResourceUrl2[] = "https://example.com/another";
const char kResourceUrl3[] = "https://example.com/yetanother";
const char kResourceRequestId[] = "resource-1-devtools-request-id";
const char kResourceRequestId2[] = "resource-2-devtools-request-id";
const char kResourceRequestId3[] = "resource-3-devtools-request-id";
// Cross origin resources
const char kCrossOriginJsonUrl[] = "https://other.com/resource.json";
const char kCrossOriginJsUrl[] = "https://other.com/resource.js";
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Optional;
using ::testing::Pointee;
std::vector<uint8_t> CreateSmallBundle() {
web_package::test::WebBundleBuilder builder(kResourceUrl,
"" /* manifest_url */);
@ -157,12 +168,12 @@ class WebBundleURLLoaderFactoryTest : public ::testing::Test {
mojo::Remote<mojom::WebBundleHandle> handle;
handle_ = std::make_unique<TestWebBundleHandle>(
handle.BindNewPipeAndPassReceiver());
devtools_observer_ = std::make_unique<MockDevToolsObserver>();
factory_ = std::make_unique<WebBundleURLLoaderFactory>(
GURL(kBundleUrl), std::move(handle),
/*request_initiator_origin_lock=*/absl::nullopt,
std::make_unique<MockMemoryQuotaConsumer>(),
/*devtools_observer=*/mojo::PendingRemote<mojom::DevToolsObserver>(),
/*devtools_request_id=*/absl::nullopt);
std::make_unique<MockMemoryQuotaConsumer>(), devtools_observer_->Bind(),
kBundleRequestId);
factory_->SetBundleStream(std::move(consumer));
}
@ -179,13 +190,16 @@ class WebBundleURLLoaderFactoryTest : public ::testing::Test {
std::unique_ptr<network::TestURLLoaderClient> client;
};
network::ResourceRequest CreateRequest(const GURL& url) {
network::ResourceRequest CreateRequest(
const GURL& url,
const std::string& devtools_request_id) {
network::ResourceRequest request;
request.url = url;
request.method = "GET";
request.request_initiator = url::Origin::Create(GURL(kInitiatorUrl));
request.web_bundle_token_params = ResourceRequest::WebBundleTokenParams();
request.web_bundle_token_params->bundle_url = GURL(kBundleUrl);
request.devtools_request_id = devtools_request_id;
return request;
}
@ -199,8 +213,9 @@ class WebBundleURLLoaderFactoryTest : public ::testing::Test {
return result;
}
StartRequestResult StartRequest(const GURL& url) {
return StartRequest(CreateRequest(url));
StartRequestResult StartRequest(const GURL& url,
const std::string& devtools_request_id) {
return StartRequest(CreateRequest(url, devtools_request_id));
}
void RunUntilBundleError() { handle_->RunUntilBundleError(); }
@ -211,6 +226,7 @@ class WebBundleURLLoaderFactoryTest : public ::testing::Test {
}
protected:
std::unique_ptr<MockDevToolsObserver> devtools_observer_;
std::unique_ptr<WebBundleURLLoaderFactory> factory_;
private:
@ -224,7 +240,15 @@ TEST_F(WebBundleURLLoaderFactoryTest, Basic) {
WriteBundle(CreateSmallBundle());
FinishWritingBundle();
auto request = StartRequest(GURL(kResourceUrl));
EXPECT_CALL(*devtools_observer_,
OnSubresourceWebBundleMetadata(kBundleRequestId,
ElementsAre(GURL(kResourceUrl))));
EXPECT_CALL(*devtools_observer_,
OnSubresourceWebBundleInnerResponse(
kResourceRequestId, GURL(kResourceUrl),
Optional(std::string(kBundleRequestId))));
auto request = StartRequest(GURL(kResourceUrl), kResourceRequestId);
request.client->RunUntilComplete();
EXPECT_EQ(net::OK, request.client->completion_status().error_code);
@ -241,13 +265,18 @@ TEST_F(WebBundleURLLoaderFactoryTest, Basic) {
TEST_F(WebBundleURLLoaderFactoryTest, MetadataParseError) {
base::HistogramTester histogram_tester;
auto request = StartRequest(GURL(kResourceUrl));
auto request = StartRequest(GURL(kResourceUrl), kResourceRequestId);
std::vector<uint8_t> bundle = CreateSmallBundle();
bundle[4] ^= 1; // Mutate magic bytes.
WriteBundle(bundle);
FinishWritingBundle();
EXPECT_CALL(*devtools_observer_,
OnSubresourceWebBundleMetadataError(kBundleRequestId,
Eq("Wrong magic bytes.")));
EXPECT_CALL(*devtools_observer_, OnSubresourceWebBundleInnerResponse(_, _, _))
.Times(0);
request.client->RunUntilComplete();
RunUntilBundleError();
@ -258,7 +287,7 @@ TEST_F(WebBundleURLLoaderFactoryTest, MetadataParseError) {
EXPECT_EQ(last_bundle_error()->second, "Wrong magic bytes.");
// Requests made after metadata parse error should also fail.
auto request2 = StartRequest(GURL(kResourceUrl));
auto request2 = StartRequest(GURL(kResourceUrl), kResourceRequestId);
request2.client->RunUntilComplete();
EXPECT_EQ(net::ERR_INVALID_WEB_BUNDLE,
@ -278,7 +307,16 @@ TEST_F(WebBundleURLLoaderFactoryTest, ResponseParseError) {
WriteBundle(builder.CreateBundle());
FinishWritingBundle();
auto request = StartRequest(GURL(kResourceUrl));
EXPECT_CALL(*devtools_observer_,
OnSubresourceWebBundleMetadata(kBundleRequestId,
ElementsAre(GURL(kResourceUrl))));
EXPECT_CALL(*devtools_observer_,
OnSubresourceWebBundleInnerResponseError(
kResourceRequestId, GURL(kResourceUrl),
Eq(":status must be 3 ASCII decimal digits."),
Optional(std::string(kBundleRequestId))));
auto request = StartRequest(GURL(kResourceUrl), kResourceRequestId);
request.client->RunUntilComplete();
RunUntilBundleError();
@ -294,7 +332,14 @@ TEST_F(WebBundleURLLoaderFactoryTest, ResourceNotFoundInBundle) {
WriteBundle(CreateSmallBundle());
FinishWritingBundle();
auto request = StartRequest(GURL("https://example.com/no-such-resource"));
EXPECT_CALL(*devtools_observer_,
OnSubresourceWebBundleMetadata(kBundleRequestId,
ElementsAre(GURL(kResourceUrl))));
EXPECT_CALL(*devtools_observer_, OnSubresourceWebBundleInnerResponse(_, _, _))
.Times(0);
auto request = StartRequest(GURL("https://example.com/no-such-resource"),
kResourceRequestId);
request.client->RunUntilComplete();
RunUntilBundleError();
@ -318,7 +363,16 @@ TEST_F(WebBundleURLLoaderFactoryTest, RedirectResponseIsNotAllowed) {
WriteBundle(builder.CreateBundle());
FinishWritingBundle();
auto request = StartRequest(GURL(kResourceUrl));
EXPECT_CALL(*devtools_observer_,
OnSubresourceWebBundleMetadata(
kBundleRequestId,
ElementsAre(GURL(kResourceUrl), GURL(kResourceUrl2))));
EXPECT_CALL(*devtools_observer_,
OnSubresourceWebBundleInnerResponse(
kResourceRequestId, GURL(kResourceUrl),
Optional(std::string(kBundleRequestId))));
auto request = StartRequest(GURL(kResourceUrl), kResourceRequestId);
request.client->RunUntilComplete();
RunUntilBundleError();
@ -330,7 +384,7 @@ TEST_F(WebBundleURLLoaderFactoryTest, RedirectResponseIsNotAllowed) {
}
TEST_F(WebBundleURLLoaderFactoryTest, StartRequestBeforeReadingBundle) {
auto request = StartRequest(GURL(kResourceUrl));
auto request = StartRequest(GURL(kResourceUrl), kResourceRequestId);
WriteBundle(CreateSmallBundle());
FinishWritingBundle();
@ -340,8 +394,8 @@ TEST_F(WebBundleURLLoaderFactoryTest, StartRequestBeforeReadingBundle) {
}
TEST_F(WebBundleURLLoaderFactoryTest, MultipleRequests) {
auto request1 = StartRequest(GURL(kResourceUrl));
auto request2 = StartRequest(GURL(kResourceUrl2));
auto request1 = StartRequest(GURL(kResourceUrl), kResourceRequestId);
auto request2 = StartRequest(GURL(kResourceUrl2), kResourceRequestId2);
std::vector<uint8_t> bundle = CreateLargeBundle();
// Write the first 10kB of the bundle in which the bundle's metadata and the
@ -362,11 +416,16 @@ TEST_F(WebBundleURLLoaderFactoryTest, MultipleRequests) {
}
TEST_F(WebBundleURLLoaderFactoryTest, CancelRequest) {
auto request_to_complete1 = StartRequest(GURL(kResourceUrl));
auto request_to_complete2 = StartRequest(GURL(kResourceUrl2));
auto request_to_cancel1 = StartRequest(GURL(kResourceUrl));
auto request_to_cancel2 = StartRequest(GURL(kResourceUrl2));
auto request_to_cancel3 = StartRequest(GURL(kResourceUrl3));
auto request_to_complete1 =
StartRequest(GURL(kResourceUrl), kResourceRequestId);
auto request_to_complete2 =
StartRequest(GURL(kResourceUrl2), kResourceRequestId2);
auto request_to_cancel1 =
StartRequest(GURL(kResourceUrl), kResourceRequestId);
auto request_to_cancel2 =
StartRequest(GURL(kResourceUrl2), kResourceRequestId2);
auto request_to_cancel3 =
StartRequest(GURL(kResourceUrl3), kResourceRequestId3);
// Cancel request before getting metadata.
request_to_cancel1.loader.reset();
@ -396,7 +455,7 @@ TEST_F(WebBundleURLLoaderFactoryTest, CancelRequest) {
TEST_F(WebBundleURLLoaderFactoryTest,
FactoryDestructionCancelsInflightRequests) {
auto request = StartRequest(GURL(kResourceUrl));
auto request = StartRequest(GURL(kResourceUrl), kResourceRequestId);
factory_ = nullptr;
@ -414,7 +473,7 @@ TEST_F(WebBundleURLLoaderFactoryTest, TruncatedBundle) {
WriteBundle(std::move(bundle));
FinishWritingBundle();
auto request = StartRequest(GURL(kResourceUrl));
auto request = StartRequest(GURL(kResourceUrl), kResourceRequestId);
request.client->RunUntilComplete();
RunUntilBundleError();
@ -429,7 +488,7 @@ TEST_F(WebBundleURLLoaderFactoryTest, CrossOiginJson) {
WriteBundle(CreateCrossOriginBundle());
FinishWritingBundle();
auto request = StartRequest(GURL(kCrossOriginJsonUrl));
auto request = StartRequest(GURL(kCrossOriginJsonUrl), kResourceRequestId);
request.client->RunUntilComplete();
EXPECT_EQ(net::OK, request.client->completion_status().error_code);
@ -445,7 +504,7 @@ TEST_F(WebBundleURLLoaderFactoryTest, CrossOriginJs) {
WriteBundle(CreateCrossOriginBundle());
FinishWritingBundle();
auto request = StartRequest(GURL(kCrossOriginJsUrl));
auto request = StartRequest(GURL(kCrossOriginJsUrl), kResourceRequestId);
request.client->RunUntilComplete();
EXPECT_EQ(net::OK, request.client->completion_status().error_code);
@ -463,7 +522,8 @@ TEST_F(WebBundleURLLoaderFactoryTest, WrongBundleURL) {
WriteBundle(CreateSmallBundle());
FinishWritingBundle();
network::ResourceRequest url_request = CreateRequest(GURL(kResourceUrl));
network::ResourceRequest url_request =
CreateRequest(GURL(kResourceUrl), kResourceRequestId);
url_request.web_bundle_token_params->bundle_url =
GURL("https://modified-bundle-url.example.com/");
auto request = StartRequest(url_request);

@ -5898,6 +5898,50 @@ domain Network
# The number of obtained Trust Tokens on a successful "Issuance" operation.
optional integer issuedTokenCount
# Fired once when parsing the .wbn file has succeeded.
# The event contains the information about the web bundle contents.
experimental event subresourceWebBundleMetadataReceived
parameters
# Request identifier. Used to match this information to another event.
RequestId requestId
# A list of URLs of resources in the subresource Web Bundle.
array of string urls
# Fired once when parsing the .wbn file has failed.
experimental event subresourceWebBundleMetadataError
parameters
# Request identifier. Used to match this information to another event.
RequestId requestId
# Error message
string errorMessage
# Fired when handling requests for resources within a .wbn file.
# Note: this will only be fired for resources that are requested by the webpage.
experimental event subresourceWebBundleInnerResponseParsed
parameters
# Request identifier of the subresource request
RequestId innerRequestId
# URL of the subresource resource.
string innerRequestURL
# Bundle request identifier. Used to match this information to another event.
# This made be absent in case when the instrumentation was enabled only
# after webbundle was parsed.
optional RequestId bundleRequestId
# Fired when request for resources within a .wbn file failed.
experimental event subresourceWebBundleInnerResponseError
parameters
# Request identifier of the subresource request
RequestId innerRequestId
# URL of the subresource resource.
string innerRequestURL
# Error message
string errorMessage
# Bundle request identifier. Used to match this information to another event.
# This made be absent in case when the instrumentation was enabled only
# after webbundle was parsed.
optional RequestId bundleRequestId
experimental type CrossOriginOpenerPolicyValue extends string
enum
SameOrigin

@ -0,0 +1,17 @@
#!/bin/sh
set -e
if ! command -v gen-bundle > /dev/null 2>&1; then
echo "gen-bundle is not installed. Please run:"
echo " go get -u github.com/WICG/webpackage/go/bundle/cmd/..."
echo ' export PATH=$PATH:$(go env GOPATH)/bin'
exit 1
fi
gen-bundle \
-version b1 \
-har webbundle.har \
-primaryURL urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720 \
-o webbundle.wbn

@ -0,0 +1,15 @@
<!DOCTYPE html>
<title>WebBundle subresource loading for static elements with a base element</title>
<link
rel="help"
href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md"
/>
<body>
<link rel="webbundle"
href="webbundle.php"
resources="urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720
urn:uuid:429fcc4e-0696-4bad-b099-ee9175f023ae" />
<script src="urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720"></script>
</body>

@ -0,0 +1,44 @@
{
"log": {
"entries": [
{
"request": {
"method": "GET",
"url": "urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720",
"headers": []
},
"response": {
"status": 200,
"headers": [
{
"name": "Content-type",
"value": "application/javascript"
}
],
"content": {
"text": "window.report_result('OK');"
}
}
},
{
"request": {
"method": "GET",
"url": "urn:uuid:429fcc4e-0696-4bad-b099-ee9175f023ae",
"headers": []
},
"response": {
"status": 200,
"headers": [
{
"name": "Content-type",
"value": "text/html"
}
],
"content": {
"text": "<script>\nwindow.addEventListener('message', (e) =>{e.source.postMessage(eval(e.data), e.origin);});\n</script>"
}
}
}
]
}
}

@ -0,0 +1,6 @@
<?php
header('Content-Type: application/webbundle');
header('X-Content-Type-Options: nosniff');
echo file_get_contents('webbundle.wbn');
?>

@ -0,0 +1,107 @@
Verifies that webbundle events are triggered
requestWillBeSent[
[0] : {
documentURL : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
frameId : <string>
hasUserGesture : false
initiator : {
type : other
}
loaderId : <string>
request : {
headers : {
Upgrade-Insecure-Requests : 1
User-Agent : <string>
sec-ch-ua : "content_shell";v="999"
sec-ch-ua-mobile : ?0
}
initialPriority : VeryHigh
method : GET
mixedContentType : none
referrerPolicy : strict-origin-when-cross-origin
url : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
}
requestId : <string>
timestamp : <number>
type : Document
wallTime : <number>
}
[1] : {
documentURL : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
frameId : <string>
hasUserGesture : false
initiator : {
columnNumber : 68
lineNumber : 10
type : parser
url : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
}
loaderId : <string>
request : {
headers : {
Referer : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
User-Agent : <string>
sec-ch-ua : "content_shell";v="999"
sec-ch-ua-mobile : ?0
}
initialPriority : High
method : GET
mixedContentType : none
referrerPolicy : strict-origin-when-cross-origin
url : http://127.0.0.1:8000/inspector-protocol/network/resources/webbundle.php
}
requestId : <string>
timestamp : <number>
type : Other
wallTime : <number>
}
[2] : {
documentURL : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
frameId : <string>
hasUserGesture : false
initiator : {
columnNumber : 71
lineNumber : 12
type : parser
url : http://127.0.0.1:8000/inspector-protocol/network/resources/page-with-webbundle.html
}
loaderId : <string>
request : {
headers : {
Referer : http://127.0.0.1:8000/
User-Agent : <string>
}
initialPriority : High
method : GET
mixedContentType : none
referrerPolicy : strict-origin-when-cross-origin
url : urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720
}
requestId : <string>
timestamp : <number>
type : Script
wallTime : <number>
}
]
webBundleMetadataReceived[
[0] : {
requestId : <string>
urls : [
[0] : urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720
[1] : urn:uuid:429fcc4e-0696-4bad-b099-ee9175f023ae
]
}
]
webBundleInnerResponse[
[0] : {
bundleRequestId : <string>
innerRequestId : <string>
innerRequestURL : urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720
}
]
webBundleMetadataReceived[0].urls: urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720,urn:uuid:429fcc4e-0696-4bad-b099-ee9175f023ae
webBundleInnerResponse[0].innerRequestURL: urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720
bundle request ID from webBundleMetadataReceived matches ID from requestWillBeSent
inner request ID from webBundleInnerResponse matches ID from requestWillBeSent
inner request ID from webBundleInnerResponse matches ID from webBundleMetadataReceived

@ -0,0 +1,58 @@
(async function(testRunner) {
const {page, session, dp} = await testRunner.startBlank(
`Verifies that webbundle events are triggered`);
await dp.Network.enable();
let requestWillBeSent = [];
let webBundleMetadataReceived = [];
let webBundleInnerResponse = [];
const recordEvent = (dest, event) => dest.push(event.params);
dp.Network.onRequestWillBeSent(recordEvent.bind(null, requestWillBeSent));
dp.Network.onSubresourceWebBundleMetadataReceived(
recordEvent.bind(null, webBundleMetadataReceived));
dp.Network.onSubresourceWebBundleInnerResponseParsed(
recordEvent.bind(null, webBundleInnerResponse));
const reportError = event => testRunner.log(event, 'Error: ');
dp.Network.onSubresourceWebBundleMetadataError(reportError);
dp.Network.onSubresourceWebBundleInnerResponseError(reportError);
session.navigate(testRunner.url('./resources/page-with-webbundle.html'));
await dp.Network.onceSubresourceWebBundleInnerResponseParsed();
testRunner.log(requestWillBeSent, 'requestWillBeSent', [
'timestamp', 'wallTime', 'loaderId', 'frameId', 'requestId', 'User-Agent'
]);
testRunner.log(webBundleMetadataReceived, 'webBundleMetadataReceived');
testRunner.log(
webBundleInnerResponse, 'webBundleInnerResponse',
['bundleRequestId', 'innerRequestId']);
testRunner.log(`webBundleMetadataReceived[0].urls: ${
webBundleMetadataReceived[0].urls}`);
testRunner.log(`webBundleInnerResponse[0].innerRequestURL: ${
webBundleInnerResponse[0].innerRequestURL}`);
if (requestWillBeSent[1].requestId ===
webBundleMetadataReceived[0].requestId) {
testRunner.log(
'bundle request ID from webBundleMetadataReceived ' +
'matches ID from requestWillBeSent');
}
if (requestWillBeSent[2].requestId ===
webBundleInnerResponse[0].innerRequestId) {
testRunner.log(
'inner request ID from webBundleInnerResponse ' +
'matches ID from requestWillBeSent');
}
if (webBundleInnerResponse[0].bundleRequestId ===
webBundleMetadataReceived[0].requestId) {
testRunner.log(
'inner request ID from webBundleInnerResponse ' +
'matches ID from webBundleMetadataReceived');
}
testRunner.completeTest();
})