Skip to content

Commit

Permalink
Introduce <script type=webbundle>
Browse files Browse the repository at this point in the history
Introduce <script>-based API for subresource loading with Web Bundles.

See the design doc [1] for the motivation of switching from
<link>-based API to <script>-based API. The explainer [2] was already
updated to use <script>-based API.

This feature is guarded by `SubresourceWebBundles` flag.

We eventually drop the <link rel=webbundle> support and remove the
<link>-based API code once we can confirm <script>-based API can be
used as a replacement of <link>-based API.

This CL should be considered as the first step to switch to
<script>-based API. There are still gaps between <link>-based API and
<script>-based API, which will be addressed later [3].

This CL intentionally adds a very minimum test for <script>-based API
because there are already WPT tests for <script type=webbundle>. They
all have been marked as [ SKIP ] in TestExpectations until now. Now
some of them are passing after this CL.

We'll make the remaining tests pass in follow-up CLs, and also add
tests which are specific to <script>-based API as necessary. These
efforts should be tracked by crbug.com/1245166.

[1]: https://docs.google.com/document/d/1q_SodTcLuwya4cXt1gIRaVrkiaBfwWyPvkY1fqRKkgM/edit?usp=sharing&resourcekey=0-dqrFOGVCYsg8WRZ4RFgwuw
[2]: https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md
[3]: WICG/webpackage#670

Bug: 1245166
Change-Id: I5109b6e692baf10fd1d8a31a31d93176d4dc4ad2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3128843
Commit-Queue: Hayato Ito <hayato@chromium.org>
Reviewed-by: Tsuyoshi Horo <horo@chromium.org>
Reviewed-by: Kunihiko Sakamoto <ksakamoto@chromium.org>
Reviewed-by: Hiroshige Hayashizaki <hiroshige@chromium.org>
Reviewed-by: Kouhei Ueno <kouhei@chromium.org>
Cr-Commit-Position: refs/heads/main@{#933346}
  • Loading branch information
hayatoito authored and Chromium LUCI CQ committed Oct 20, 2021
1 parent c903a5a commit e1adbae
Show file tree
Hide file tree
Showing 23 changed files with 965 additions and 12 deletions.
192 changes: 192 additions & 0 deletions content/browser/web_package/script_web_bundle_browsertest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright 2021 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 "base/path_service.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "components/web_package/web_bundle_builder.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/shell/browser/shell.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

// Tests for <script type=webbundle>.
class ScriptWebBundleBrowserTest : public ContentBrowserTest {
protected:
ScriptWebBundleBrowserTest() {
feature_list_.InitAndEnableFeature(features::kSubresourceWebBundles);
}
~ScriptWebBundleBrowserTest() override = default;

void SetUpCommandLine(base::CommandLine* command_line) override {
ContentBrowserTest::SetUpCommandLine(command_line);
mock_cert_verifier_.SetUpCommandLine(command_line);
}

void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
original_client_ = SetBrowserClientForTesting(&browser_client_);
host_resolver()->AddRule("*", "127.0.0.1");
https_server_.RegisterRequestHandler(base::BindRepeating(
&ScriptWebBundleBrowserTest::HandleTestWebBundleRequest,
base::Unretained(this)));
https_server_.RegisterRequestMonitor(
base::BindRepeating(&ScriptWebBundleBrowserTest::MonitorResourceRequest,
base::Unretained(this)));
https_server_.AddDefaultHandlers(GetTestDataFilePath());
ASSERT_TRUE(https_server_.Start());
}

void TearDownOnMainThread() override {
ContentBrowserTest::TearDownOnMainThread();
SetBrowserClientForTesting(original_client_);
}

void SetUpInProcessBrowserTestFixture() override {
ContentBrowserTest::SetUpInProcessBrowserTestFixture();
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}

void TearDownInProcessBrowserTestFixture() override {
ContentBrowserTest::TearDownInProcessBrowserTestFixture();
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
}

std::unique_ptr<net::test_server::HttpResponse> HandleTestWebBundleRequest(
const net::test_server::HttpRequest& request) {
if (request.relative_url != "/web_bundle/test.wbn")
return nullptr;
GURL test1_url(https_server_.GetURL("/web_bundle/test1.txt"));
GURL test2_url(https_server_.GetURL("/web_bundle/test2.txt"));
web_package::WebBundleBuilder builder("" /* fallback_url */,
"" /* manifest_url */);
builder.AddExchange(test1_url.spec(),
{{":status", "200"}, {"content-type", "text/plain"}},
"test1");
builder.AddExchange(test2_url.spec(),
{{":status", "200"}, {"content-type", "text/plain"}},
"test2");
auto bundle = builder.CreateBundle();
std::string body(reinterpret_cast<const char*>(bundle.data()),
bundle.size());
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->set_content(body);
http_response->set_content_type("application/webbundle");
http_response->AddCustomHeader("X-Content-Type-Options", "nosniff");
return http_response;
}

net::EmbeddedTestServer* https_server() { return &https_server_; }

void MonitorResourceRequest(const net::test_server::HttpRequest& request) {
// This should be called on `EmbeddedTestServer::io_thread_`.
EXPECT_FALSE(
content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
base::AutoLock auto_lock(lock_);
request_count_by_path_[request.GetURL().PathForRequest()]++;
}

int GetRequestCount(const GURL& url) {
EXPECT_TRUE(content::BrowserThread::CurrentlyOn(BrowserThread::UI));
base::AutoLock auto_lock(lock_);
return request_count_by_path_[url.PathForRequest()];
}

private:
content::ContentMockCertVerifier mock_cert_verifier_;
ContentBrowserClient* original_client_ = nullptr;
ContentBrowserClient browser_client_;
base::test::ScopedFeatureList feature_list_;
net::EmbeddedTestServer https_server_{
net::EmbeddedTestServer::Type::TYPE_HTTPS};
// Counts of requests sent to the server. Keyed by path (not by full URL)
std::map<std::string, int> request_count_by_path_ GUARDED_BY(lock_);
base::Lock lock_;
};

IN_PROC_BROWSER_TEST_F(ScriptWebBundleBrowserTest,
WebBundleResourceShouldBeReused) {
// The tentative spec:
// https://docs.google.com/document/d/1GEJ3wTERGEeTG_4J0QtAwaNXhPTza0tedd00A7vPVsw/edit

// Tests that webbundle resources are surely re-used when we remove a <script
// type=webbunble> and add a new <script type=webbundle> with the same bundle
// URL to the removed one, in the same microtask scope.

GURL url(https_server()->GetURL("/web_bundle/empty.html"));
EXPECT_TRUE(NavigateToURL(shell(), url));

{
// Add a <script type=webbundle>.
DOMMessageQueue dom_message_queue(shell()->web_contents());
ExecuteScriptAsync(shell(),
R"HTML(
const script = document.createElement("script");
script.type = "webbundle";
script.textContent =
JSON.stringify({"source": "/web_bundle/test.wbn",
"resources": ["/web_bundle/test1.txt"]});
document.body.appendChild(script);
(async () => {
const response = await fetch("/web_bundle/test1.txt");
const text = await response.text();
window.domAutomationController.send(`fetch: ${text}`);
})();
)HTML");
std::string message;
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
EXPECT_EQ(message, "\"fetch: test1\"");
EXPECT_EQ(GetRequestCount(https_server()->GetURL("/web_bundle/test.wbn")),
1);
}
{
// Remove the <script type=webbundle> from the document, and then add a new
// <script type=webbundle> whose bundle URL is same to the removed one in
// the same microtask scope, The added element should re-use the webbundle
// resource which the old <script type=webbundle> has been using. Thus, the
// bundle shouldn't be fetched twice.
DOMMessageQueue dom_message_queue(shell()->web_contents());
ExecuteScriptAsync(shell(),
R"HTML(
script.remove();
const script2 = document.createElement("script");
script2.type = "webbundle";
script2.textContent =
JSON.stringify({"source": "/web_bundle/test.wbn",
"resources": ["/web_bundle/test2.txt"]});
document.body.appendChild(script2);
(async () => {
const response = await fetch("/web_bundle/test2.txt");
const text = await response.text();
window.domAutomationController.send(`fetch: ${text}`);
})();
)HTML");
std::string message;
EXPECT_TRUE(dom_message_queue.WaitForMessage(&message));
EXPECT_EQ(message, "\"fetch: test2\"")
<< "A new script element's rule should be effective.";
EXPECT_EQ(GetRequestCount(https_server()->GetURL("/web_bundle/test.wbn")),
1)
<< "A bundle should not be fetched twice.";
}
}

} // namespace content
1 change: 1 addition & 0 deletions content/test/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1297,6 +1297,7 @@ test("content_browsertests") {
"../browser/web_contents/web_contents_view_aura_browsertest.cc",
"../browser/web_package/link_web_bundle_browsertest.cc",
"../browser/web_package/save_page_as_web_bundle_browsertest.cc",
"../browser/web_package/script_web_bundle_browsertest.cc",
"../browser/web_package/signed_exchange_request_handler_browsertest.cc",
"../browser/web_package/signed_exchange_subresource_prefetch_browsertest.cc",
"../browser/web_package/web_bundle_browsertest_base.cc",
Expand Down
1 change: 1 addition & 0 deletions third_party/blink/renderer/core/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,7 @@ source_set("unit_tests") {
"loader/threadable_loader_test.cc",
"loader/threaded_icon_loader_test.cc",
"loader/web_associated_url_loader_impl_test.cc",
"loader/web_bundle/script_web_bundle_rule_test.cc",
"messaging/blink_transferable_message_mojom_traits_test.cc",
"messaging/message_port_descriptor_mojom_traits_test.cc",
"mobile_metrics/mobile_friendliness_checker_test.cc",
Expand Down
9 changes: 9 additions & 0 deletions third_party/blink/renderer/core/html/html_script_element.cc
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ Node::InsertionNotificationRequest HTMLScriptElement::InsertedInto(
return kInsertionShouldCallDidNotifySubtreeInsertions;
}

void HTMLScriptElement::RemovedFrom(ContainerNode& insertion_point) {
HTMLElement::RemovedFrom(insertion_point);
loader_->ReleaseWebBundleResource();
}

void HTMLScriptElement::DidNotifySubtreeInsertionsToDocument() {
loader_->DidNotifySubtreeInsertionsToDocument();
}
Expand Down Expand Up @@ -332,6 +337,10 @@ bool HTMLScriptElement::supports(ScriptState* script_state,
RuntimeEnabledFeatures::SpeculationRulesEnabled(execution_context)) {
return true;
}
if ((type == script_type_names::kWebbundle) &&
RuntimeEnabledFeatures::SubresourceWebBundlesEnabled(execution_context)) {
return true;
}

return false;
}
Expand Down
2 changes: 2 additions & 0 deletions third_party/blink/renderer/core/html/html_script_element.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ class CORE_EXPORT HTMLScriptElement final : public HTMLElement,
private:
void ParseAttribute(const AttributeModificationParams&) override;
InsertionNotificationRequest InsertedInto(ContainerNode&) override;
void RemovedFrom(ContainerNode& insertion_point) override;

void DidNotifySubtreeInsertionsToDocument() override;
void ChildrenChanged(const ChildrenChange&) override;
void DidMoveToNewDocument(Document& old_document) override;
Expand Down
10 changes: 10 additions & 0 deletions third_party/blink/renderer/core/html/link_web_bundle.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ void LinkWebBundle::OnWebBundleError(const String& message) const {
mojom::blink::ConsoleMessageLevel::kWarning, message));
}

bool LinkWebBundle::IsScriptWebBundle() const {
NOTREACHED() << "Should never happen since IsScriptWebBundle() is called "
"only for ScriptWebBundle in the current implementation.";
return false;
}

bool LinkWebBundle::WillBeReleased() const {
return false;
}

void LinkWebBundle::Process() {
if (!owner_ || !owner_->GetDocument().GetFrame())
return;
Expand Down
2 changes: 2 additions & 0 deletions third_party/blink/renderer/core/html/link_web_bundle.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class CORE_EXPORT LinkWebBundle final : public LinkResource,
const base::UnguessableToken& WebBundleToken() const override;
void NotifyLoaded() override;
void OnWebBundleError(const String& message) const override;
bool IsScriptWebBundle() const override;
bool WillBeReleased() const override;

// Returns a valid absolute URL if |str| can be parsed as a valid
// absolute URL, or a relative URL with a given |base_url|.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,10 @@ class TokenPreloadScanner::StartTagScanner {
// supported.
return false;

case ScriptLoader::ScriptTypeAtPrepare::kWebBundle:
// External webbundle is not yet supported.
return false;

case ScriptLoader::ScriptTypeAtPrepare::kClassic:
case ScriptLoader::ScriptTypeAtPrepare::kModule:
if (ScriptLoader::BlockForNoModule(script_type,
Expand Down
4 changes: 4 additions & 0 deletions third_party/blink/renderer/core/loader/build.gni
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ blink_core_sources_loader = [
"threaded_icon_loader.h",
"web_associated_url_loader_impl.cc",
"web_associated_url_loader_impl.h",
"web_bundle/script_web_bundle.cc",
"web_bundle/script_web_bundle.h",
"web_bundle/script_web_bundle_rule.cc",
"web_bundle/script_web_bundle_rule.h",
"web_bundle/web_bundle_loader.cc",
"web_bundle/web_bundle_loader.h",
"worker_fetch_context.cc",
Expand Down
Loading

0 comments on commit e1adbae

Please sign in to comment.