Commit d65cc595 authored by Tsuyoshi Horo's avatar Tsuyoshi Horo Committed by Chromium LUCI CQ

Introduce Link.scopes for SubresourceWebBundles

This CL introduces "scopes" attribute in <link> element for
SubresourceWebBundles feature.

https://github.com/WICG/webpackage/blob/master/explainers/subresource-loading.md#defining-the-scopes

For example, if the following link tag exists in the page,
  <link
    rel="webbundle"
    href="https://example.com/dir/subresources.wbn"
    scopes="https://example.com/dir/js/
            https://example.com/dir/img/
            https://example.com/dir/css/">
subresources under "/dir/js/", "/dir/img/" and "/dir/css/" will be
loaded from the web bundle "subresources.wbn".


Bug: 1082020
Change-Id: I36ab832c301f33b0d0f1b6f4fea7e871f7ce2b83
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2615732Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarKunihiko Sakamoto <ksakamoto@chromium.org>
Reviewed-by: default avatarHayato Ito <hayato@chromium.org>
Commit-Queue: Tsuyoshi Horo <horo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#843995}
parent 93028d46
......@@ -292,6 +292,7 @@
"sandbox",
"scheme",
"scope",
"scopes",
"scrollamount",
"scrolldelay",
"scrolling",
......
......@@ -53,6 +53,23 @@
namespace blink {
namespace {
void ParseUrlsListValue(const AtomicString& value, HashSet<KURL>& url_hash) {
// Parse the attribute value as a space-separated list of urls
SpaceSplitString urls(value);
url_hash.clear();
url_hash.ReserveCapacityForSize(SafeCast<wtf_size_t>(urls.size()));
for (wtf_size_t i = 0; i < urls.size(); ++i) {
KURL url = LinkWebBundle::ParseResourceUrl(urls[i]);
if (url.IsValid()) {
url_hash.insert(std::move(url));
}
}
}
} // namespace
HTMLLinkElement::HTMLLinkElement(Document& document,
const CreateElementFlags flags)
: HTMLElement(html_names::kLinkTag, document),
......@@ -64,6 +81,8 @@ HTMLLinkElement::HTMLLinkElement(Document& document,
resources_(
MakeGarbageCollected<DOMTokenList>(*this,
html_names::kResourcesAttr)),
scopes_(
MakeGarbageCollected<DOMTokenList>(*this, html_names::kScopesAttr)),
created_by_parser_(flags.IsCreatedByParser()) {}
HTMLLinkElement::~HTMLLinkElement() = default;
......@@ -145,18 +164,13 @@ void HTMLLinkElement::ParseAttribute(
RuntimeEnabledFeatures::SubresourceWebBundlesEnabled(
GetExecutionContext())) {
resources_->DidUpdateAttributeValue(params.old_value, value);
// Parse the attribute value as a space-separated list of urls
SpaceSplitString urls(value);
valid_resource_urls_.clear();
valid_resource_urls_.ReserveCapacityForSize(
SafeCast<wtf_size_t>(urls.size()));
for (wtf_size_t i = 0; i < urls.size(); ++i) {
KURL url = LinkWebBundle::ParseResourceUrl(urls[i]);
if (url.IsValid()) {
valid_resource_urls_.insert(std::move(url));
}
}
ParseUrlsListValue(value, valid_resource_urls_);
Process();
} else if (name == html_names::kScopesAttr &&
RuntimeEnabledFeatures::SubresourceWebBundlesEnabled(
GetExecutionContext())) {
scopes_->DidUpdateAttributeValue(params.old_value, value);
ParseUrlsListValue(value, valid_scope_urls_);
Process();
} else if (name == html_names::kDisabledAttr) {
UseCounter::Count(GetDocument(), WebFeature::kHTMLLinkElementDisabled);
......@@ -461,12 +475,17 @@ DOMTokenList* HTMLLinkElement::resources() const {
return resources_.Get();
}
DOMTokenList* HTMLLinkElement::scopes() const {
return scopes_.Get();
}
void HTMLLinkElement::Trace(Visitor* visitor) const {
visitor->Trace(link_);
visitor->Trace(sizes_);
visitor->Trace(link_loader_);
visitor->Trace(rel_list_);
visitor->Trace(resources_);
visitor->Trace(scopes_);
HTMLElement::Trace(visitor);
LinkLoaderClient::Trace(visitor);
}
......
......@@ -98,10 +98,12 @@ class CORE_EXPORT HTMLLinkElement final : public HTMLElement,
// IDL method.
DOMTokenList* resources() const;
DOMTokenList* scopes() const;
const HashSet<KURL>& ValidResourceUrls() const {
return valid_resource_urls_;
}
const HashSet<KURL>& ValidScopeUrls() const { return valid_scope_urls_; }
void ScheduleEvent();
......@@ -178,6 +180,8 @@ class CORE_EXPORT HTMLLinkElement final : public HTMLElement,
LinkRelAttribute rel_attribute_;
Member<DOMTokenList> resources_;
HashSet<KURL> valid_resource_urls_;
Member<DOMTokenList> scopes_;
HashSet<KURL> valid_scope_urls_;
bool created_by_parser_;
};
......
......@@ -59,4 +59,5 @@
// https://github.com/WICG/webpackage/blob/master/explainers/subresource-loading.md
// crbug.com/1082020
[RuntimeEnabled=SubresourceWebBundles, PutForwards=value] readonly attribute DOMTokenList resources;
[RuntimeEnabled=SubresourceWebBundles, PutForwards=value] readonly attribute DOMTokenList scopes;
};
......@@ -209,7 +209,7 @@ void LinkWebBundle::OwnerRemoved() {
bool LinkWebBundle::CanHandleRequest(const KURL& url) const {
if (!url.IsValid())
return false;
if (!owner_ || !owner_->ValidResourceUrls().Contains(url))
if (!ResourcesOrScopesMatch(url))
return false;
if (url.Protocol() == "urn")
return true;
......@@ -232,6 +232,18 @@ bool LinkWebBundle::CanHandleRequest(const KURL& url) const {
return true;
}
bool LinkWebBundle::ResourcesOrScopesMatch(const KURL& url) const {
if (!owner_)
return false;
if (owner_->ValidResourceUrls().Contains(url))
return true;
for (const auto& scope : owner_->ValidScopeUrls()) {
if (url.GetString().StartsWith(scope.GetString()))
return true;
}
return false;
}
String LinkWebBundle::GetCacheIdentifier() const {
DCHECK(bundle_loader_);
return bundle_loader_->url().GetString();
......
......@@ -56,6 +56,8 @@ class CORE_EXPORT LinkWebBundle final : public LinkResource,
static KURL ParseResourceUrl(const AtomicString& str);
private:
bool ResourcesOrScopesMatch(const KURL& url) const;
Member<WebBundleLoader> bundle_loader_;
};
......
......@@ -10,6 +10,7 @@
<link id="link_empty" />
<link id="link_web_bundle_1" rel="webbundle" />
<link id="link_web_bundle_2" rel="webbundle" resources="foo" />
<link id="link_web_bundle_3" rel="webbundle" scopes="bar" />
<script>
test(() => {
assert_false(
......@@ -22,6 +23,17 @@
);
}, "resources must be defined on HTMLLinkElement prototype");
test(() => {
assert_false(
"scopes" in Element.prototype,
"scopes must not be defined on Element prototype"
);
assert_true(
"scopes" in HTMLLinkElement.prototype,
"scopes must be defined on HTMLLinkElement prototype"
);
}, "scopes must be defined on HTMLLinkElement prototype");
test(() => {
const link = document.createElement("link");
assert_true(link.relList.supports("webbundle"));
......@@ -55,6 +67,16 @@
"foo",
"resources attribute must return the specified value"
);
assert_equals(
document.querySelector("#link_web_bundle_2").getAttribute("scopes"),
null,
"scopes attribute must return null when the attribute is not given"
);
assert_equals(
document.querySelector("#link_web_bundle_3").getAttribute("scopes"),
"bar",
"scopes attribute must return the specified value"
);
// TODO: Test more variant of resoruces attribute values.
}, "resoruces attribute must return null or specified value");
......@@ -75,5 +97,23 @@
"https://test2.example.com"
]);
}, "resources must be DOMTokenList");
test(() => {
const link = document.createElement("link");
assert_class_string(link.scopes, "DOMTokenList");
assert_equals(
String(link.scopes.value),
"",
"scopes.value should return the empty list for an undefined scopes attribute"
);
link.setAttribute(
"scopes",
"https://test1.example.com https://test2.example.com "
);
assert_array_equals(link.scopes, [
"https://test1.example.com",
"https://test2.example.com"
]);
}, "scopes must be DOMTokenList");
</script>
</body>
......@@ -80,7 +80,42 @@
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
'classic script from network');
}, 'Dynamically loading classic script from web bundle');
}, 'Dynamically loading classic script from web bundle with link.resources');
promise_test(async () => {
const classic_script_url = 'http://web-platform.test:8001/web-bundle/resources/wbn/dynamic/classic_script.js';
const scope = 'http://web-platform.test:8001/web-bundle/resources/wbn/dynamic/';
const link = document.createElement("link");
link.rel = "webbundle";
link.href = "../resources/wbn/dynamic1.wbn";
link.scopes.add(scope);
document.body.appendChild(link);
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
'classic script from dynamic1.wbn');
link.href = "../resources/wbn/dynamic2.wbn";
// Loading the classic script should not reuse the previously loaded
// script. So in this case, the script must be loaded from dynamic2.wbn.
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
'classic script from dynamic2.wbn');
// Changes the scope not to hit the classic_script.js.
link.scopes = scope + 'dummy';
// And in this case, the script must be loaded from network.
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
'classic script from network');
// Adds the scope to hit the classic_script.js.
link.scopes.add(scope + 'classic_');
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
'classic script from dynamic2.wbn');
document.body.removeChild(link);
// And in this case, the script must be loaded from network.
assert_equals(
await loadScriptAndWaitReport(classic_script_url),
'classic script from network');
}, 'Dynamically loading classic script from web bundle with link.scopes');
promise_test(() => {
return addLinkAndWaitForLoad("../resources/wbn/dynamic1.wbn?test-event");
......@@ -108,7 +143,19 @@
link.resources = url;
document.body.appendChild(link);
assert_equals(await loadScriptAndWaitReport(url), 'OK');
}, 'Subresource loading with urn:uuid: URL');
document.body.removeChild(link);
}, 'Subresource loading with urn:uuid: URL with link.resources');
promise_test(async () => {
const url = 'urn:uuid:020111b3-437a-4c5c-ae07-adb6bbffb720';
const link = document.createElement('link');
link.rel = 'webbundle';
link.href = '../resources/wbn/urn-uuid.wbn';
link.scopes = 'urn:uuid:';
document.body.appendChild(link);
assert_equals(await loadScriptAndWaitReport(url), 'OK');
document.body.removeChild(link);
}, 'Subresource loading with urn:uuid: URL with link.scopes');
promise_test(async () => {
const wbn_url = 'http://web-platform.test:8001/web-bundle/resources/wbn/subresource.wbn?test-resources-update';
......
......@@ -783,6 +783,7 @@ html element link
property relList
property resources
property rev
property scopes
property sheet
property sizes
property target
......
......@@ -3608,6 +3608,7 @@ interface HTMLLinkElement : HTMLElement
getter relList
getter resources
getter rev
getter scopes
getter sheet
getter sizes
getter target
......@@ -3629,6 +3630,7 @@ interface HTMLLinkElement : HTMLElement
setter relList
setter resources
setter rev
setter scopes
setter sizes
setter target
setter type
......
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