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 <[email protected]>
Reviewed-by: Tsuyoshi Horo <[email protected]>
Reviewed-by: Kunihiko Sakamoto <[email protected]>
Reviewed-by: Hiroshige Hayashizaki <[email protected]>
Reviewed-by: Kouhei Ueno <[email protected]>
Cr-Commit-Position: refs/heads/main@{#933346}
NOKEYCHECK=True
GitOrigin-RevId: e1adbaeb278ee2ca83bc94a593cdc24dc660cda4
  • Loading branch information
hayatoito authored and copybara-github committed Oct 20, 2021
1 parent c95496f commit cee97f8
Show file tree
Hide file tree
Showing 21 changed files with 772 additions and 12 deletions.
1 change: 1 addition & 0 deletions 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 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 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 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 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
4 changes: 4 additions & 0 deletions blink/renderer/core/html/parser/html_preload_scanner.cc
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 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
185 changes: 185 additions & 0 deletions blink/renderer/core/loader/web_bundle/script_web_bundle.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// 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 "third_party/blink/renderer/core/loader/web_bundle/script_web_bundle.h"

#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/html/cross_origin_attribute.h"
#include "third_party/blink/renderer/core/html/html_script_element.h"
#include "third_party/blink/renderer/core/loader/web_bundle/script_web_bundle_rule.h"
#include "third_party/blink/renderer/core/loader/web_bundle/web_bundle_loader.h"
#include "third_party/blink/renderer/platform/bindings/microtask.h"
#include "third_party/blink/renderer/platform/loader/cors/cors.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/subresource_web_bundle_list.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/casting.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"

namespace blink {

// MicroTask which is used to release a webbundle resource.
class ScriptWebBundle::ReleaseResourceTask {
public:
explicit ReleaseResourceTask(ScriptWebBundle& script_web_bundle)
: script_web_bundle_(&script_web_bundle) {}

void Run() {
if (script_web_bundle_->WillBeReleased()) {
script_web_bundle_->ReleaseBundleLoaderAndUnregister();
}
}

private:
Persistent<ScriptWebBundle> script_web_bundle_;
};

ScriptWebBundle* ScriptWebBundle::CreateOrReuseInline(
Document& element_document,
const String& source_text) {
auto rule =
ScriptWebBundleRule::ParseJson(source_text, element_document.BaseURL());
if (!rule)
return nullptr;

ResourceFetcher* resource_fetcher = element_document.Fetcher();
if (!resource_fetcher)
return nullptr;
SubresourceWebBundleList* active_bundles =
resource_fetcher->GetOrCreateSubresourceWebBundleList();

if (SubresourceWebBundle* found =
active_bundles->FindSubresourceWebBundleWhichWillBeReleased(
rule->source_url())) {
// Re-use the ScriptWebBundle if it has the same bundle URL and is being
// released.
DCHECK(found->IsScriptWebBundle());
ScriptWebBundle* reused_script_web_bundle = To<ScriptWebBundle>(found);
DCHECK_EQ(reused_script_web_bundle->element_document_, element_document);
reused_script_web_bundle->SetRule(std::move(*rule));
reused_script_web_bundle->CancelRelease();
return reused_script_web_bundle;
}
return MakeGarbageCollected<ScriptWebBundle>(element_document, *rule);
}

ScriptWebBundle::ScriptWebBundle(Document& element_document,
const ScriptWebBundleRule& rule)
: element_document_(&element_document), rule_(rule) {
CreateBundleLoaderAndRegister();
}

void ScriptWebBundle::Trace(Visitor* visitor) const {
visitor->Trace(element_document_);
visitor->Trace(bundle_loader_);
SubresourceWebBundle::Trace(visitor);
}

bool ScriptWebBundle::CanHandleRequest(const KURL& url) const {
if (WillBeReleased())
return false;
if (!url.IsValid())
return false;
if (!rule_.ResourcesOrScopesMatch(url))
return false;
if (url.Protocol() == "urn" || url.Protocol() == "uuid-in-package")
return true;
DCHECK(bundle_loader_);
if (!bundle_loader_->GetSecurityOrigin()->IsSameOriginWith(
SecurityOrigin::Create(url).get())) {
OnWebBundleError(url.ElidedString() + " cannot be loaded from WebBundle " +
bundle_loader_->url().ElidedString() +
": bundled resource must be same origin with the bundle.");
return false;
}

if (!url.GetString().StartsWith(bundle_loader_->url().BaseAsString())) {
OnWebBundleError(
url.ElidedString() + " cannot be loaded from WebBundle " +
bundle_loader_->url().ElidedString() +
": bundled resource path must contain the bundle's path as a prefix.");
return false;
}
return true;
}

const KURL& ScriptWebBundle::GetBundleUrl() const {
return rule_.source_url();
}
const base::UnguessableToken& ScriptWebBundle::WebBundleToken() const {
return bundle_loader_->WebBundleToken();
}
String ScriptWebBundle::GetCacheIdentifier() const {
DCHECK(bundle_loader_);
return bundle_loader_->url().GetString();
}

// TODO(crbug.com/1245166): Implement these.
void ScriptWebBundle::OnWebBundleError(const String& message) const {}
void ScriptWebBundle::NotifyLoaded() {}

bool ScriptWebBundle::IsScriptWebBundle() const {
return true;
}

bool ScriptWebBundle::WillBeReleased() const {
return will_be_released_;
}

void ScriptWebBundle::CreateBundleLoaderAndRegister() {
DCHECK(!bundle_loader_);
DCHECK(element_document_);
bundle_loader_ = MakeGarbageCollected<WebBundleLoader>(
*this, *element_document_, rule_.source_url(),
// TODO(crbug.com/1245166): Set a cross origin attribute value from the
// rule.
kCrossOriginAttributeNotSet);
ResourceFetcher* resource_fetcher = element_document_->Fetcher();
if (!resource_fetcher)
return;
SubresourceWebBundleList* active_bundles =
resource_fetcher->GetOrCreateSubresourceWebBundleList();
active_bundles->Add(*this);
}

void ScriptWebBundle::ReleaseBundleLoaderAndUnregister() {
if (bundle_loader_) {
// Clear receivers explicitly here, instead of waiting for Blink GC.
bundle_loader_->ClearReceivers();
bundle_loader_ = nullptr;
}
// element_document_ might not be alive.
if (!element_document_)
return;
ResourceFetcher* resource_fetcher = element_document_->Fetcher();
if (!resource_fetcher)
return;
SubresourceWebBundleList* active_bundles =
resource_fetcher->GetOrCreateSubresourceWebBundleList();
active_bundles->Remove(*this);
}

void ScriptWebBundle::WillReleaseBundleLoaderAndUnregister() {
// We don't release webbundle resources synchronously here. Instead, enqueue a
// microtask which will release webbundle resources later.

// The motivation is that we want to update a mapping rule dynamically without
// releasing webbundle resources.
//
// For example, if we remove <script type=webbundle>, and then add another
// <script type=webbundle> with the same bundle URL, but with a new mapping
// rule, within the same microtask scope, the new one can re-use the webbundle
// resources, instead of releasing them. In other words, we don't fetch the
// same bundle twice.
//
// Tentative spec:
// https://docs.google.com/document/d/1GEJ3wTERGEeTG_4J0QtAwaNXhPTza0tedd00A7vPVsw/edit#heading=h.y88lpjmx2ndn
will_be_released_ = true;
auto task = std::make_unique<ReleaseResourceTask>(*this);
Microtask::EnqueueMicrotask(
WTF::Bind(&ReleaseResourceTask::Run, std::move(task)));
}

} // namespace blink
76 changes: 76 additions & 0 deletions blink/renderer/core/loader/web_bundle/script_web_bundle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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.

#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_WEB_BUNDLE_SCRIPT_WEB_BUNDLE_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_WEB_BUNDLE_SCRIPT_WEB_BUNDLE_H_

#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/loader/web_bundle/script_web_bundle_rule.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/loader/fetch/subresource_web_bundle.h"
#include "third_party/blink/renderer/platform/wtf/casting.h"

namespace WTF {
class String;
}

namespace blink {

class Document;
class KURL;
class WebBundleLoader;

// ScriptLoader creates this for a script whose type is "webbundle".
//
// https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md#script-based-api
class CORE_EXPORT ScriptWebBundle final
: public GarbageCollected<ScriptWebBundle>,
public SubresourceWebBundle {
public:
static ScriptWebBundle* CreateOrReuseInline(Document& element_document,
const String& inline_text);

ScriptWebBundle(Document& element_document, const ScriptWebBundleRule& rule);

void Trace(Visitor* visitor) const override;

// SubresourceWebBundle overrides:
bool CanHandleRequest(const KURL& url) const override;
String GetCacheIdentifier() const override;
const KURL& GetBundleUrl() const override;
const base::UnguessableToken& WebBundleToken() const override;
void NotifyLoaded() override;
void OnWebBundleError(const String& message) const override;
bool IsScriptWebBundle() const override;
bool WillBeReleased() const override;

void CreateBundleLoaderAndRegister();
void ReleaseBundleLoaderAndUnregister();

void WillReleaseBundleLoaderAndUnregister();
void CancelRelease() { will_be_released_ = false; }

void SetRule(ScriptWebBundleRule rule) { rule_ = std::move(rule); }

class ReleaseResourceTask;

private:
bool will_be_released_ = false;
WeakMember<Document> element_document_;
ScriptWebBundleRule rule_;
Member<WebBundleLoader> bundle_loader_;
};

template <>
struct DowncastTraits<ScriptWebBundle> {
static bool AllowFrom(const SubresourceWebBundle& subresource_web_bundle) {
return subresource_web_bundle.IsScriptWebBundle();
}
};

} // namespace blink

#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_WEB_BUNDLE_SCRIPT_WEB_BUNDLE_H_
Loading

0 comments on commit cee97f8

Please sign in to comment.