diff options
Diffstat (limited to 'testing/web-platform/tests/web-bundle')
118 files changed, 3562 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-bundle/META.yml b/testing/web-platform/tests/web-bundle/META.yml new file mode 100644 index 0000000000..0f1912b23a --- /dev/null +++ b/testing/web-platform/tests/web-bundle/META.yml @@ -0,0 +1,5 @@ +spec: https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html +suggested_reviewers: + - horo + - irori + - jyasskin diff --git a/testing/web-platform/tests/web-bundle/README.md b/testing/web-platform/tests/web-bundle/README.md new file mode 100644 index 0000000000..3e300818e2 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/README.md @@ -0,0 +1,10 @@ +This directory contains tests for the +[Web Bundles](https://wicg.github.io/webpackage/draft-yasskin-wpack-bundled-exchanges.html). + +`generate-test-wbns.sh` requires command-line tools in the +[webpackage repository](https://github.com/WICG/webpackage). +To install them, run: +``` +go install github.com/WICG/webpackage/go/bundle/cmd/...@latest +export PATH=$PATH:$(go env GOPATH)/bin +``` diff --git a/testing/web-platform/tests/web-bundle/resources/check-accept-header-and-return-bundle.py b/testing/web-platform/tests/web-bundle/resources/check-accept-header-and-return-bundle.py new file mode 100644 index 0000000000..1fb63a57cb --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/check-accept-header-and-return-bundle.py @@ -0,0 +1,21 @@ +import os + +# Returns the content of ./wbn/subresource.wbn if the request has an "Accept" +# header including "application/webbundle;v=b2" type, otherwise returns an +# empty body with status code 400. + +def main(request, response): + headers = [ + (b"Content-Type", b"application/webbundle"), + (b"X-Content-Type-Options", b"nosniff"), + ] + + accept_values = request.headers.get(b"accept", b"").split(b",") + if b"application/webbundle;v=b2" in accept_values: + with open( + os.path.join(os.path.dirname(__file__), "./wbn/subresource.wbn"), + "rb", + ) as f: + return (200, headers, f.read()) + else: + return (400, [], "") diff --git a/testing/web-platform/tests/web-bundle/resources/check-cookie-and-return-bundle.py b/testing/web-platform/tests/web-bundle/resources/check-cookie-and-return-bundle.py new file mode 100644 index 0000000000..89a22c432e --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/check-cookie-and-return-bundle.py @@ -0,0 +1,29 @@ +import os + + +def main(request, response): + origin = request.headers.get(b"origin") + + if origin is not None: + response.headers.set(b"Access-Control-Allow-Origin", origin) + response.headers.set(b"Access-Control-Allow-Methods", b"GET") + response.headers.set(b"Access-Control-Allow-Credentials", b"true") + + headers = [ + (b"Content-Type", b"application/webbundle"), + (b"X-Content-Type-Options", b"nosniff"), + ] + + cookie = request.cookies.first(b"milk", None) + if (cookie is not None) and cookie.value == b"1": + if request.GET.get(b"bundle", None) == b"cross-origin": + bundle = "./wbn/simple-cross-origin.wbn" + else: + bundle = "./wbn/subresource.wbn" + with open( + os.path.join(os.path.dirname(__file__), bundle), + "rb", + ) as f: + return (200, headers, f.read()) + else: + return (400, [], "") diff --git a/testing/web-platform/tests/web-bundle/resources/check-sec-fetch-dest-header-and-return-bundle.py b/testing/web-platform/tests/web-bundle/resources/check-sec-fetch-dest-header-and-return-bundle.py new file mode 100644 index 0000000000..da36e49320 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/check-sec-fetch-dest-header-and-return-bundle.py @@ -0,0 +1,21 @@ +import os + + +def main(request, response): + origin = request.headers.get(b"origin") + if origin is not None: + response.headers.set(b"Access-Control-Allow-Origin", origin) + + headers = [ + (b"Content-Type", b"application/webbundle"), + (b"X-Content-Type-Options", b"nosniff"), + ] + + if request.headers.get(b"sec-fetch-dest", None) == b"webbundle": + with open( + os.path.join(os.path.dirname(__file__), "./wbn/subresource.wbn"), + "rb", + ) as f: + return (200, headers, f.read()) + else: + return (400, [], "") diff --git a/testing/web-platform/tests/web-bundle/resources/corp.har b/testing/web-platform/tests/web-bundle/resources/corp.har new file mode 100644 index 0000000000..e1b0393957 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/corp.har @@ -0,0 +1,140 @@ +{ + "log": { + "entries": [ + { + "request": { + "method": "GET", + "url": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/no-corp.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + } + ], + "content": { + "text": "scriptLoaded('no-corp.js');" + } + } + }, + { + "request": { + "method": "GET", + "url": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp-same-origin.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + }, + { + "name": "Cross-Origin-Resource-Policy", + "value": "same-origin" + } + ], + "content": { + "text": "scriptLoaded('corp-same-origin.js');" + } + } + }, + { + "request": { + "method": "GET", + "url": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp-cross-origin.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + }, + { + "name": "Cross-Origin-Resource-Policy", + "value": "cross-origin" + } + ], + "content": { + "text": "scriptLoaded('corp-cross-origin.js');" + } + } + }, + { + "request": { + "method": "GET", + "url": "uuid-in-package:5eafff38-e0a0-4661-bde0-434255aa9d93", + "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>" + } + } + }, + { + "request": { + "method": "GET", + "url": "uuid-in-package:7e13b47a-8b91-4a0e-997c-993a5e2f3a34", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/html" + }, + { + "name": "Cross-Origin-Resource-Policy", + "value": "same-origin" + } + ], + "content": { + "text": "<script>\nwindow.addEventListener('message', (e) =>{e.source.postMessage(eval(e.data), e.origin);});\n</script>" + } + } + }, + { + "request": { + "method": "GET", + "url": "uuid-in-package:86d5b696-8867-4454-8b07-51239a0817f7", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/html" + }, + { + "name": "Cross-Origin-Embedder-Policy", + "value": "require-corp" + }, + { + "name": "Cross-Origin-Resource-Policy", + "value": "cross-origin" + } + ], + "content": { + "text": "<script>\nwindow.addEventListener('message', (e) =>{e.source.postMessage(eval(e.data), e.origin);});\n</script>" + } + } + } + ] + } +} diff --git a/testing/web-platform/tests/web-bundle/resources/cross-origin-no-cors.har b/testing/web-platform/tests/web-bundle/resources/cross-origin-no-cors.har new file mode 100644 index 0000000000..c31a0963fd --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/cross-origin-no-cors.har @@ -0,0 +1,48 @@ +{ + "log": { + "entries": [ + { + "request": { + "method": "GET", + "url": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/no-cors/resource.cors.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "*" + } + ], + "content": { + "text": "scriptLoaded('resource.cors.js');" + } + } + }, + { + "request": { + "method": "GET", + "url": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/no-cors/resource.no-cors.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + } + ], + "content": { + "text": "scriptLoaded('resource.no-cors.js');" + } + } + } + ] + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/web-bundle/resources/cross-origin.har b/testing/web-platform/tests/web-bundle/resources/cross-origin.har new file mode 100644 index 0000000000..2c33449cbe --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/cross-origin.har @@ -0,0 +1,48 @@ +{ + "log": { + "entries": [ + { + "request": { + "method": "GET", + "url": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/resource.cors.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "*" + } + ], + "content": { + "text": "scriptLoaded('resource.cors.js');" + } + } + }, + { + "request": { + "method": "GET", + "url": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/resource.no-cors.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + } + ], + "content": { + "text": "scriptLoaded('resource.no-cors.js');" + } + } + } + ] + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/web-bundle/resources/dynamic1/classic_script.js b/testing/web-platform/tests/web-bundle/resources/dynamic1/classic_script.js new file mode 100644 index 0000000000..5fcf045906 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/dynamic1/classic_script.js @@ -0,0 +1 @@ +window.report_result('classic script from dynamic1.wbn'); diff --git a/testing/web-platform/tests/web-bundle/resources/dynamic1/resource1.js b/testing/web-platform/tests/web-bundle/resources/dynamic1/resource1.js new file mode 100644 index 0000000000..6fd1de600e --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/dynamic1/resource1.js @@ -0,0 +1 @@ +export const result = 'resource1 from dynamic1.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/dynamic1/resource2.js b/testing/web-platform/tests/web-bundle/resources/dynamic1/resource2.js new file mode 100644 index 0000000000..3cf012b21a --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/dynamic1/resource2.js @@ -0,0 +1 @@ +export const result = 'resource2 from dynamic1.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/dynamic1/resource3.js b/testing/web-platform/tests/web-bundle/resources/dynamic1/resource3.js new file mode 100644 index 0000000000..c8a4e258b8 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/dynamic1/resource3.js @@ -0,0 +1 @@ +export const result = 'resource3 from dynamic1.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/dynamic1/resource4.js b/testing/web-platform/tests/web-bundle/resources/dynamic1/resource4.js new file mode 100644 index 0000000000..0b2f7920b0 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/dynamic1/resource4.js @@ -0,0 +1 @@ +export const result = 'resource4 from dynamic1.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/dynamic2/classic_script.js b/testing/web-platform/tests/web-bundle/resources/dynamic2/classic_script.js new file mode 100644 index 0000000000..546d8a503d --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/dynamic2/classic_script.js @@ -0,0 +1 @@ +window.report_result('classic script from dynamic2.wbn'); diff --git a/testing/web-platform/tests/web-bundle/resources/dynamic2/resource1.js b/testing/web-platform/tests/web-bundle/resources/dynamic2/resource1.js new file mode 100644 index 0000000000..c6f751cec4 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/dynamic2/resource1.js @@ -0,0 +1 @@ +export const result = 'resource1 from dynamic2.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/dynamic2/resource2.js b/testing/web-platform/tests/web-bundle/resources/dynamic2/resource2.js new file mode 100644 index 0000000000..b4278ee1c1 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/dynamic2/resource2.js @@ -0,0 +1 @@ +export const result = 'resource2 from dynamic2.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/dynamic2/resource3.js b/testing/web-platform/tests/web-bundle/resources/dynamic2/resource3.js new file mode 100644 index 0000000000..0dad7ea7bf --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/dynamic2/resource3.js @@ -0,0 +1 @@ +export const result = 'resource3 from dynamic2.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/dynamic2/resource4.js b/testing/web-platform/tests/web-bundle/resources/dynamic2/resource4.js new file mode 100644 index 0000000000..e51cf7f5dc --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/dynamic2/resource4.js @@ -0,0 +1 @@ +export const result = 'resource4 from dynamic2.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/generate-test-wbns.sh b/testing/web-platform/tests/web-bundle/resources/generate-test-wbns.sh new file mode 100755 index 0000000000..418a56e0e7 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/generate-test-wbns.sh @@ -0,0 +1,105 @@ +#!/bin/sh + +set -e + +if ! command -v gen-bundle > /dev/null 2>&1; then + echo "gen-bundle is not installed. Please run:" + echo " go install github.com/WICG/webpackage/go/bundle/cmd/...@latest" + echo ' export PATH=$PATH:$(go env GOPATH)/bin' + exit 1 +fi + +# TODO: Stop hard-coding "web-platform.test" when generating Web Bundles on the +# fly. +wpt_test_origin=https://web-platform.test:8444 +wpt_test_remote_origin=https://www1.web-platform.test:8444 + +gen-bundle \ + -version b2 \ + -baseURL $wpt_test_origin/web-bundle/resources/wbn/static-element/ \ + -primaryURL $wpt_test_origin/web-bundle/resources/wbn/static-element/resources/style.css \ + -dir static-element/ \ + -o wbn/static-element.wbn + +# Create a bundle, nested-main.wbn, which includes nested-sub.wbn. +cp -a wbn/subresource.wbn nested/nested-sub.wbn +gen-bundle \ + -version b2 \ + -baseURL $wpt_test_origin/web-bundle/resources/wbn/ \ + -primaryURL $wpt_test_origin/web-bundle/resources/wbn/resource.js \ + -dir nested/ \ + -o wbn/nested-main.wbn + +gen-bundle \ + -version b2 \ + -har non-utf8-query-encoding.har \ + -primaryURL $wpt_test_origin/web-bundle/resources/wbn/static-element/resources/script.js?x=%A4%A2 \ + -o wbn/non-utf8-query-encoding.wbn + +gen-bundle \ + -version b2 \ + -har corp.har \ + -primaryURL $wpt_test_remote_origin/web-bundle/resources/wbn/cors/no-corp.js \ + -o wbn/cors/corp.wbn + +gen-bundle \ + -version b2 \ + -baseURL $wpt_test_origin/web-bundle/resources/wbn/ \ + -primaryURL $wpt_test_origin/web-bundle/resources/wbn/location.html \ + -dir location/ \ + -o wbn/location.wbn + +gen-bundle \ + -version b2 \ + -har relative-url.har \ + -o wbn/relative-url.wbn + +gen-bundle \ + -version b2 \ + -baseURL $wpt_test_origin/web-bundle/resources/wbn/ \ + -dir subresource/ \ + -o wbn/subresource.wbn + +gen-bundle \ + -version b2 \ + -baseURL $wpt_test_origin/web-bundle/resources/wbn/dynamic/ \ + -dir dynamic1/ \ + -o wbn/dynamic1.wbn + +gen-bundle \ + -version b2 \ + -baseURL $wpt_test_origin/web-bundle/resources/wbn/dynamic/ \ + -dir dynamic2/ \ + -o wbn/dynamic2.wbn + +gen-bundle \ + -version b2 \ + -baseURL $wpt_test_remote_origin/web-bundle/resources/wbn/dynamic/ \ + -dir dynamic1/ \ + -o wbn/dynamic1-crossorigin.wbn + +gen-bundle \ + -version b2 \ + -baseURL $wpt_test_origin/web-bundle/resources/ \ + -dir path-restriction/ \ + -o wbn/path-restriction.wbn + +gen-bundle \ + -version b2 \ + -har cross-origin.har \ + -o wbn/cors/cross-origin.wbn + +gen-bundle \ + -version b2 \ + -har cross-origin-no-cors.har \ + -o wbn/no-cors/cross-origin.wbn + +gen-bundle \ + -version b2 \ + -har uuid-in-package.har \ + -o wbn/uuid-in-package.wbn + +gen-bundle \ + -version b2 \ + -har simple-cross-origin.har \ + -o wbn/simple-cross-origin.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/location/location.html b/testing/web-platform/tests/web-bundle/resources/location/location.html new file mode 100644 index 0000000000..e272dad23d --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/location/location.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<script src="./location.js"></script> diff --git a/testing/web-platform/tests/web-bundle/resources/location/location.js b/testing/web-platform/tests/web-bundle/resources/location/location.js new file mode 100644 index 0000000000..a1dbba7366 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/location/location.js @@ -0,0 +1,2 @@ + +window.opener.postMessage({location: location.href}, '*'); diff --git a/testing/web-platform/tests/web-bundle/resources/nested/nested-sub.wbn b/testing/web-platform/tests/web-bundle/resources/nested/nested-sub.wbn Binary files differnew file mode 100644 index 0000000000..840773f8db --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/nested/nested-sub.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/nested/resource.js b/testing/web-platform/tests/web-bundle/resources/nested/resource.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/nested/resource.js diff --git a/testing/web-platform/tests/web-bundle/resources/non-utf8-query-encoding.har b/testing/web-platform/tests/web-bundle/resources/non-utf8-query-encoding.har new file mode 100644 index 0000000000..c4acfdb227 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/non-utf8-query-encoding.har @@ -0,0 +1,25 @@ +{ + "log": { + "entries": [ + { + "request": { + "method": "GET", + "url": "https://web-platform.test:8444/web-bundle/resources/wbn/static-element/resources/script.js?x=%A4%A2", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "application/javascript" + } + ], + "content": { + "text": "const resources_script_result = 'loaded from webbundle';" + } + } + } + ] + } +} diff --git a/testing/web-platform/tests/web-bundle/resources/path-restriction/other/resource.js b/testing/web-platform/tests/web-bundle/resources/path-restriction/other/resource.js new file mode 100644 index 0000000000..a4967d2285 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/path-restriction/other/resource.js @@ -0,0 +1 @@ +export const result = 'resource from path-restriction.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/path-restriction/resource.js b/testing/web-platform/tests/web-bundle/resources/path-restriction/resource.js new file mode 100644 index 0000000000..a4967d2285 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/path-restriction/resource.js @@ -0,0 +1 @@ +export const result = 'resource from path-restriction.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/path-restriction/wbn-resource.js b/testing/web-platform/tests/web-bundle/resources/path-restriction/wbn-resource.js new file mode 100644 index 0000000000..a4967d2285 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/path-restriction/wbn-resource.js @@ -0,0 +1 @@ +export const result = 'resource from path-restriction.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/path-restriction/wbn/resource.js b/testing/web-platform/tests/web-bundle/resources/path-restriction/wbn/resource.js new file mode 100644 index 0000000000..a4967d2285 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/path-restriction/wbn/resource.js @@ -0,0 +1 @@ +export const result = 'resource from path-restriction.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/path-restriction/wbn/sub/resource.js b/testing/web-platform/tests/web-bundle/resources/path-restriction/wbn/sub/resource.js new file mode 100644 index 0000000000..a4967d2285 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/path-restriction/wbn/sub/resource.js @@ -0,0 +1 @@ +export const result = 'resource from path-restriction.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/path-restriction/wbn1/resource.js b/testing/web-platform/tests/web-bundle/resources/path-restriction/wbn1/resource.js new file mode 100644 index 0000000000..a4967d2285 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/path-restriction/wbn1/resource.js @@ -0,0 +1 @@ +export const result = 'resource from path-restriction.wbn'; diff --git a/testing/web-platform/tests/web-bundle/resources/relative-url.har b/testing/web-platform/tests/web-bundle/resources/relative-url.har new file mode 100644 index 0000000000..e42e85fd1f --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/relative-url.har @@ -0,0 +1,139 @@ +{ + "log": { + "entries": [ + { + "request": { + "method": "GET", + "url": "relative-url-file.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + } + ], + "content": { + "text": "scriptLoaded('relative-url-file.js');" + } + } + }, + { + "request": { + "method": "GET", + "url": "//web-platform.test:8444/web-bundle/resources/wbn/relative-url/start-with-double-slash.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + } + ], + "content": { + "text": "scriptLoaded('start-with-double-slash.js');" + } + } + }, + { + "request": { + "method": "GET", + "url": "//www1.web-platform.test:8444/web-bundle/resources/wbn/relative-url/start-with-double-slash-cors.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + } + ], + "content": { + "text": "scriptLoaded('start-with-double-slash-cors.js');" + } + } + }, + { + "request": { + "method": "GET", + "url": "/web-bundle/resources/wbn/relative-url/start-with-slash.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + } + ], + "content": { + "text": "scriptLoaded('start-with-slash.js');" + } + } + }, + { + "request": { + "method": "GET", + "url": "relative-url/subdirectory-path.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + } + ], + "content": { + "text": "scriptLoaded('subdirectory-path.js');" + } + } + }, + { + "request": { + "method": "GET", + "url": "../wbn/starts-with-two-dots.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + } + ], + "content": { + "text": "scriptLoaded('starts-with-two-dots.js');" + } + } + }, + { + "request": { + "method": "GET", + "url": "../starts-with-two-dots-out-of-scope.js", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/javascript" + } + ], + "content": { + "text": "scriptLoaded('starts-with-two-dots-out-of-scope.js');" + } + } + } + ] + } +}
\ No newline at end of file diff --git a/testing/web-platform/tests/web-bundle/resources/simple-cross-origin.har b/testing/web-platform/tests/web-bundle/resources/simple-cross-origin.har new file mode 100644 index 0000000000..8566a6c6c2 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/simple-cross-origin.har @@ -0,0 +1,29 @@ +{ + "log": { + "entries": [ + { + "request": { + "method": "GET", + "url": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/simple-cross-origin.txt", + "headers": [] + }, + "response": { + "status": 200, + "headers": [ + { + "name": "Content-type", + "value": "text/plain" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "*" + } + ], + "content": { + "text": "hello from simple-cross-origin.txt" + } + } + } + ] + } +} diff --git a/testing/web-platform/tests/web-bundle/resources/static-element/out-of-scope/script.js b/testing/web-platform/tests/web-bundle/resources/static-element/out-of-scope/script.js new file mode 100644 index 0000000000..0f3e0d27c0 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/static-element/out-of-scope/script.js @@ -0,0 +1 @@ +const out_of_scope_script_result = 'loaded from webbundle'; diff --git a/testing/web-platform/tests/web-bundle/resources/static-element/resources/script.js b/testing/web-platform/tests/web-bundle/resources/static-element/resources/script.js new file mode 100644 index 0000000000..f5327df7ae --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/static-element/resources/script.js @@ -0,0 +1 @@ +const resources_script_result = 'loaded from webbundle'; diff --git a/testing/web-platform/tests/web-bundle/resources/static-element/resources/style-imported-from-file.css b/testing/web-platform/tests/web-bundle/resources/static-element/resources/style-imported-from-file.css new file mode 100644 index 0000000000..6b79f6dd8c --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/static-element/resources/style-imported-from-file.css @@ -0,0 +1,3 @@ +#resources_style_imported_from_file_target { + color: blue; +} diff --git a/testing/web-platform/tests/web-bundle/resources/static-element/resources/style-imported-from-tag.css b/testing/web-platform/tests/web-bundle/resources/static-element/resources/style-imported-from-tag.css new file mode 100644 index 0000000000..38e9fe3133 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/static-element/resources/style-imported-from-tag.css @@ -0,0 +1,3 @@ +#resources_style_imported_from_tag_target { + color: blue; +} diff --git a/testing/web-platform/tests/web-bundle/resources/static-element/resources/style.css b/testing/web-platform/tests/web-bundle/resources/static-element/resources/style.css new file mode 100644 index 0000000000..7f98b85eb6 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/static-element/resources/style.css @@ -0,0 +1,5 @@ +@import 'style-imported-from-file.css'; + +#resources_style_target { + color: blue; +} diff --git a/testing/web-platform/tests/web-bundle/resources/static-element/scopes/script.js b/testing/web-platform/tests/web-bundle/resources/static-element/scopes/script.js new file mode 100644 index 0000000000..140698cb5c --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/static-element/scopes/script.js @@ -0,0 +1 @@ +const scopes_script_result = 'loaded from webbundle'; diff --git a/testing/web-platform/tests/web-bundle/resources/static-element/scopes/style-imported-from-file.css b/testing/web-platform/tests/web-bundle/resources/static-element/scopes/style-imported-from-file.css new file mode 100644 index 0000000000..0162888644 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/static-element/scopes/style-imported-from-file.css @@ -0,0 +1,3 @@ +#scopes_style_imported_from_file_target { + color: blue; +} diff --git a/testing/web-platform/tests/web-bundle/resources/static-element/scopes/style-imported-from-tag.css b/testing/web-platform/tests/web-bundle/resources/static-element/scopes/style-imported-from-tag.css new file mode 100644 index 0000000000..7055c86ee9 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/static-element/scopes/style-imported-from-tag.css @@ -0,0 +1,3 @@ +#scopes_style_imported_from_tag_target { + color: blue; +} diff --git a/testing/web-platform/tests/web-bundle/resources/static-element/scopes/style.css b/testing/web-platform/tests/web-bundle/resources/static-element/scopes/style.css new file mode 100644 index 0000000000..f2af82dfd7 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/static-element/scopes/style.css @@ -0,0 +1,5 @@ +@import 'style-imported-from-file.css'; + +#scopes_style_target { + color: blue; +} diff --git a/testing/web-platform/tests/web-bundle/resources/subresource/fail.png b/testing/web-platform/tests/web-bundle/resources/subresource/fail.png Binary files differnew file mode 100644 index 0000000000..b593380333 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/subresource/fail.png diff --git a/testing/web-platform/tests/web-bundle/resources/subresource/pass.png b/testing/web-platform/tests/web-bundle/resources/subresource/pass.png Binary files differnew file mode 100644 index 0000000000..2fa1e0ac06 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/subresource/pass.png diff --git a/testing/web-platform/tests/web-bundle/resources/subresource/root.js b/testing/web-platform/tests/web-bundle/resources/subresource/root.js new file mode 100644 index 0000000000..2c2a465472 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/subresource/root.js @@ -0,0 +1 @@ +export * from './submodule.js'; diff --git a/testing/web-platform/tests/web-bundle/resources/subresource/submodule.js b/testing/web-platform/tests/web-bundle/resources/subresource/submodule.js new file mode 100644 index 0000000000..4561aaf014 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/subresource/submodule.js @@ -0,0 +1 @@ +export const result = 'OK'; diff --git a/testing/web-platform/tests/web-bundle/resources/test-helpers.js b/testing/web-platform/tests/web-bundle/resources/test-helpers.js new file mode 100644 index 0000000000..5c380c88d8 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/test-helpers.js @@ -0,0 +1,169 @@ +// Helper functions used in web-bundle tests. + +function addElementAndWaitForLoad(element) { + return new Promise((resolve, reject) => { + element.onload = () => resolve(element); + element.onerror = () => reject(element); + document.body.appendChild(element); + }); +} + +function addElementAndWaitForError(element) { + return new Promise((resolve, reject) => { + element.onload = () => reject(element); + element.onerror = () => resolve(element); + document.body.appendChild(element); + }); +} + +// Evaluates |code| in |iframe|. The following message event handler must be +// registered on the iframe page: +// window.addEventListener( +// 'message', +// (e) => { e.source.postMessage(eval(e.data), e.origin); }); +function evalInIframe(iframe, code) { + const message_promise = new Promise((resolve) => { + window.addEventListener( + 'message', + (e) => { resolve(e.data); }, + { once : true }); + }); + iframe.contentWindow.postMessage(code,'*'); + return message_promise; +} + +function fetchAndWaitForReject(url) { + return new Promise((resolve, reject) => { + fetch(url) + .then(() => { + reject(); + }) + .catch(() => { + resolve(); + }); + }); +} + +function isValidCrossOriginAttribute(crossorigin) { + if (crossorigin === undefined) + return true; + if ((typeof crossorigin) != 'string') + return false; + const lower_crossorigin = crossorigin.toLowerCase(); + return (lower_crossorigin === 'anonymous') || + (lower_crossorigin === 'use-credentials'); +} + +function addScriptAndWaitForError(url) { + return new Promise((resolve, reject) => { + const script = document.createElement("script"); + script.src = url; + script.onload = reject; + script.onerror = resolve; + document.body.appendChild(script); + }); +} + +function addScriptAndWaitForExecution(url) { + return new Promise((resolve, reject) => { + window.scriptLoaded = (val) => { + window.scriptLoaded = undefined; + resolve(val); + }; + const script = document.createElement("script"); + script.src = url; + script.onerror = reject; + document.body.appendChild(script); + }); +} + +function createWebBundleElement(url, resources, options) { + const script = document.createElement("script"); + script.type = "webbundle"; + const json_rule = {"source": url, "resources": resources}; + if (options && options.scopes) { + json_rule.scopes = options.scopes; + } + if (options && options.credentials) { + json_rule.credentials = options.credentials; + } + script.textContent = JSON.stringify(json_rule); + return script; +} + +function addWebBundleElementAndWaitForLoad(url, resources, options) { + const element = createWebBundleElement(url, resources, options); + return addElementAndWaitForLoad(element); +} + +function addWebBundleElementAndWaitForError(url, resources, options) { + const element = createWebBundleElement(url, resources, options); + return addElementAndWaitForError(element); +} + +function changeWebBundleUrlInPlace(element, new_url) { + if (window.TEST_WEB_BUNDLE_ELEMENT_TYPE != 'link') { + throw new Error( + 'Changing the URL of web bundle is not supported for : ' + + window.TEST_WEB_BUNDLE_ELEMENT_TYPE); + } + element.href= new_url; +} + +function changeWebBundleScopesInPlace(element, scopes) { + if (window.TEST_WEB_BUNDLE_ELEMENT_TYPE != 'link') { + throw new Error( + 'Changing the scopes of web bundle is not supported for : ' + + window.TEST_WEB_BUNDLE_ELEMENT_TYPE); + } + element.scopes = ''; + for (const scope of scopes) { + element.scopes.add(scope); + } +} + +function changeWebBundleResourcesInPlace(element, resources) { + if (window.TEST_WEB_BUNDLE_ELEMENT_TYPE != 'link') { + throw new Error( + 'Changing the resources of web bundle is not supported for : ' + + window.TEST_WEB_BUNDLE_ELEMENT_TYPE); + } + element.resources = ''; + for (const url of resources) { + element.resources.add(url); + } +} + +// This function creates a new WebBundle element that has a rule +// constructed in accordance with a JSON object |new_rule|: +// 1. Copy over WebBundle rules from an existing element that are +// not present in |new_rule|, in case of <link> API it is all +// relevant attributes: href, resources, scopes and crossOrigin; +// in case of <script> API, it is: source, resources, scopes and +// credentials. +// 2. Then create a new WebBundle element from |new_rule| (that now +// has full information required after 1.) and return it. +function createNewWebBundleElementWithUpdatedRule(element, new_rule) { + if (window.TEST_WEB_BUNDLE_ELEMENT_TYPE == 'link') { + if (element.resources && !new_rule.resources) + new_rule.resources = Array.from(element.resources); + if (element.scopes && !new_rule.scopes) + new_rule.scopes = Array.from(element.scopes); + if (element.crossOrigin && !new_rule.crossOrigin) + new_rule.crossOrigin = element.crossOrigin; + if (!new_rule.url) + new_rule.url = element.href; + } else { + const rule = JSON.parse(element.textContent); + if (rule.resources && !new_rule.resources) + new_rule.resources = rule.resources; + if (rule.scopes && !new_rule.scopes) + new_rule.scopes = rule.scopes; + if (rule.credentials && !new_rule.credentials) + new_rule.credentials = rule.credentials; + if (!new_rule.url) + new_rule.url = rule.source; + } + + return createWebBundleElement(new_rule.url, new_rule.resources, new_rule); +} diff --git a/testing/web-platform/tests/web-bundle/resources/uuid-in-package.har b/testing/web-platform/tests/web-bundle/resources/uuid-in-package.har new file mode 100644 index 0000000000..8dedc951d5 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/uuid-in-package.har @@ -0,0 +1,44 @@ +{ + "log": { + "entries": [ + { + "request": { + "method": "GET", + "url": "uuid-in-package: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": "uuid-in-package: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>" + } + } + } + ] + } +} diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/__dir__.headers b/testing/web-platform/tests/web-bundle/resources/wbn/__dir__.headers new file mode 100644 index 0000000000..21e57b9cac --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/__dir__.headers @@ -0,0 +1,2 @@ +Content-Type: application/webbundle +X-Content-Type-Options: nosniff diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/cors/__dir__.sub.headers b/testing/web-platform/tests/web-bundle/resources/wbn/cors/__dir__.sub.headers new file mode 100644 index 0000000000..767da13cb2 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/cors/__dir__.sub.headers @@ -0,0 +1,4 @@ +Content-Type: application/webbundle +X-Content-Type-Options: nosniff +Access-Control-Allow-Origin: https://{{domains[]}}:{{ports[https][0]}} +Access-Control-Allow-Credentials: true diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/cors/corp.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/cors/corp.wbn Binary files differnew file mode 100644 index 0000000000..08ee4bc80a --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/cors/corp.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/cors/cross-origin-b2.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/cors/cross-origin-b2.wbn Binary files differnew file mode 100644 index 0000000000..03c31a8b5b --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/cors/cross-origin-b2.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/cors/cross-origin.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/cors/cross-origin.wbn Binary files differnew file mode 100644 index 0000000000..03c31a8b5b --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/cors/cross-origin.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/__dir__.headers b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/__dir__.headers new file mode 100644 index 0000000000..cb762eff80 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/__dir__.headers @@ -0,0 +1 @@ +Access-Control-Allow-Origin: * diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/classic_script.js b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/classic_script.js new file mode 100644 index 0000000000..db69b96188 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/classic_script.js @@ -0,0 +1 @@ +window.report_result('classic script from network'); diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/resource1.js b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/resource1.js new file mode 100644 index 0000000000..7bd437364a --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/resource1.js @@ -0,0 +1 @@ +export const result = 'resource1 from network'; diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/resource2.js b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/resource2.js new file mode 100644 index 0000000000..09a4ee7b70 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/resource2.js @@ -0,0 +1 @@ +export const result = 'resource2 from network'; diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/resource3.js b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/resource3.js new file mode 100644 index 0000000000..851168c9da --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/resource3.js @@ -0,0 +1 @@ +export const result = 'resource3 from network'; diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/resource4.js b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/resource4.js new file mode 100644 index 0000000000..ea456b8db0 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic/resource4.js @@ -0,0 +1 @@ +export const result = 'resource4 from network'; diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/dynamic1-crossorigin.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic1-crossorigin.wbn Binary files differnew file mode 100644 index 0000000000..e003f39481 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic1-crossorigin.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/dynamic1.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic1.wbn Binary files differnew file mode 100644 index 0000000000..f9b8e01404 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic1.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/dynamic2.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic2.wbn Binary files differnew file mode 100644 index 0000000000..f23fb4fad4 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/dynamic2.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/location.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/location.wbn Binary files differnew file mode 100644 index 0000000000..54d8f40fce --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/location.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/nested-main.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/nested-main.wbn Binary files differnew file mode 100644 index 0000000000..9c21e73220 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/nested-main.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/no-cors/__dir__.headers b/testing/web-platform/tests/web-bundle/resources/wbn/no-cors/__dir__.headers new file mode 100644 index 0000000000..21e57b9cac --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/no-cors/__dir__.headers @@ -0,0 +1,2 @@ +Content-Type: application/webbundle +X-Content-Type-Options: nosniff diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/no-cors/cross-origin-b2.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/no-cors/cross-origin-b2.wbn Binary files differnew file mode 100644 index 0000000000..f6d565a9c3 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/no-cors/cross-origin-b2.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/no-cors/cross-origin.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/no-cors/cross-origin.wbn Binary files differnew file mode 100644 index 0000000000..f6d565a9c3 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/no-cors/cross-origin.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/non-utf8-query-encoding.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/non-utf8-query-encoding.wbn Binary files differnew file mode 100644 index 0000000000..56f76b8f20 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/non-utf8-query-encoding.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/path-restriction.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/path-restriction.wbn Binary files differnew file mode 100644 index 0000000000..73a02a0829 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/path-restriction.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/relative-url.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/relative-url.wbn Binary files differnew file mode 100644 index 0000000000..13924fdb99 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/relative-url.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/relative-url.wbn.sub.headers b/testing/web-platform/tests/web-bundle/resources/wbn/relative-url.wbn.sub.headers new file mode 100644 index 0000000000..ce4780f454 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/relative-url.wbn.sub.headers @@ -0,0 +1,3 @@ +Content-Type: application/webbundle +X-Content-Type-Options: nosniff +Access-Control-Allow-Origin: https://{{domains[]}}:{{ports[https][0]}} diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/simple-cross-origin.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/simple-cross-origin.wbn Binary files differnew file mode 100644 index 0000000000..a00cce00f0 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/simple-cross-origin.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/static-element.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/static-element.wbn Binary files differnew file mode 100644 index 0000000000..dd50ef320a --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/static-element.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/static-element/out-of-scope/script.js b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/out-of-scope/script.js new file mode 100644 index 0000000000..e761729483 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/out-of-scope/script.js @@ -0,0 +1 @@ +const out_of_scope_script_result = 'loaded from network'; diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/static-element/resources/script.js b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/resources/script.js new file mode 100644 index 0000000000..08c4d9a2ee --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/resources/script.js @@ -0,0 +1 @@ +const resources_script_result = 'loaded from network'; diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/static-element/resources/style-imported-from-file.css b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/resources/style-imported-from-file.css new file mode 100644 index 0000000000..33f0f375da --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/resources/style-imported-from-file.css @@ -0,0 +1,3 @@ +#resources_style_imported_from_file_target { + color: red; +} diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/static-element/resources/style-imported-from-tag.css b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/resources/style-imported-from-tag.css new file mode 100644 index 0000000000..85dbf84db3 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/resources/style-imported-from-tag.css @@ -0,0 +1,3 @@ +#resources_style_imported_from_tag_target { + color: red; +} diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/static-element/resources/style.css b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/resources/style.css new file mode 100644 index 0000000000..41886f41d9 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/resources/style.css @@ -0,0 +1,5 @@ +@import 'style-imported-from-file.css'; + +#resources_style_target { + color: red; +} diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/static-element/scopes/script.js b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/scopes/script.js new file mode 100644 index 0000000000..060d8c50a8 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/scopes/script.js @@ -0,0 +1 @@ +const scopes_script_result = 'loaded from network'; diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/static-element/scopes/style-imported-from-file.css b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/scopes/style-imported-from-file.css new file mode 100644 index 0000000000..d865cebd12 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/scopes/style-imported-from-file.css @@ -0,0 +1,3 @@ +#scopes_style_imported_from_file_target { + color: red; +} diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/static-element/scopes/style-imported-from-tag.css b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/scopes/style-imported-from-tag.css new file mode 100644 index 0000000000..ad70df9b39 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/scopes/style-imported-from-tag.css @@ -0,0 +1,3 @@ +#scopes_style_imported_from_tag_target { + color: red; +} diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/static-element/scopes/style.css b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/scopes/style.css new file mode 100644 index 0000000000..233ecd7113 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/static-element/scopes/style.css @@ -0,0 +1,5 @@ +@import 'style-imported-from-file.css'; + +#scopes_style_target { + color: red; +} diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/subresource.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/subresource.wbn Binary files differnew file mode 100644 index 0000000000..840773f8db --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/subresource.wbn diff --git a/testing/web-platform/tests/web-bundle/resources/wbn/uuid-in-package.wbn b/testing/web-platform/tests/web-bundle/resources/wbn/uuid-in-package.wbn Binary files differnew file mode 100644 index 0000000000..d4d100d121 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/resources/wbn/uuid-in-package.wbn diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/accept-header.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/accept-header.https.tentative.html new file mode 100644 index 0000000000..68b13e53c6 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/accept-header.https.tentative.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<title>Accept: request header in webbundle requests</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + promise_test(async () => { + await addWebBundleElementAndWaitForLoad( + "../resources/check-accept-header-and-return-bundle.py", + /*resources=*/ [] + ); + }, "Accept: header in a request for a bundle should contain application/webbundle MIME type."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html new file mode 100644 index 0000000000..4029fc6f81 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html @@ -0,0 +1,127 @@ +<!DOCTYPE html> +<title>COEP for WebBundle subresource loading</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<link + rel="help" + href="https://html.spec.whatwg.org/multipage/origin.html#coep" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> + +<body> + <!-- + This wpt should run on an origin different from https://www1.web-platform.test:8444/, + from where cross-orign WebBundles are served. + + This test uses a cross-origin WebBundle, + https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp.wbn, + which is served with an Access-Control-Allow-Origin response header. + + `corp.wbn` includes three subresources: + a. `no-corp.js`, which doesn't include a Cross-Origin-Resource-Policy response header. + b. `corp-same-origin.js`, which includes a Cross-Origin-Resource-Policy: same-origin response header. + c. `corp-cross-origin.js`, which includes a Cross-Origin-Resource-Policy: cross-origin response header. + --> + <script type="webbundle"> + { + "source": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp.wbn", + "resources": [ + "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/no-corp.js", + "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp-same-origin.js", + "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp-cross-origin.js", + "uuid-in-package:5eafff38-e0a0-4661-bde0-434255aa9d93", + "uuid-in-package:7e13b47a-8b91-4a0e-997c-993a5e2f3a34", + "uuid-in-package:86d5b696-8867-4454-8b07-51239a0817f7" + ] + } + </script> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + async function expectCOEPReport(func) { + const reportsPromise = new Promise((resolve) => { + const observer = new ReportingObserver((reports) => { + observer.disconnect(); + resolve(reports.map((r) => r.toJSON())); + }); + observer.observe(); + }); + + await func(); + + const reports = await reportsPromise; + assert_equals(reports.length, 1); + assert_equals(reports[0].type, "coep"); + assert_equals(reports[0].url, location.href); + return reports[0]; + } + + const prefix = + "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/"; + const no_corp_url = "uuid-in-package:5eafff38-e0a0-4661-bde0-434255aa9d93"; + const corp_same_origin_url = + "uuid-in-package:7e13b47a-8b91-4a0e-997c-993a5e2f3a34"; + const corp_cross_origin_url = + "uuid-in-package:86d5b696-8867-4454-8b07-51239a0817f7"; + + promise_test(async () => { + const report = await expectCOEPReport(async () => { + await addScriptAndWaitForError(prefix + "no-corp.js"); + }); + assert_equals(report.body.blockedURL, prefix + "no-corp.js"); + assert_equals(report.body.type, "corp"); + assert_equals(report.body.disposition, "enforce"); + assert_equals(report.body.destination, "script"); + }, "Cross-origin subresource without Cross-Origin-Resource-Policy: header should be blocked and generate a report."); + + promise_test(async () => { + await addScriptAndWaitForError(prefix + "corp-same-origin.js"); + }, "Cross-origin subresource with Cross-Origin-Resource-Policy: same-origin should be blocked."); + + promise_test(async () => { + await addScriptAndWaitForExecution(prefix + "corp-cross-origin.js"); + }, "Cross-origin subresource with Cross-Origin-Resource-Policy: cross-origin should be loaded."); + + promise_test(async () => { + const report = await expectCOEPReport(async () => { + const iframe = document.createElement("iframe"); + iframe.src = no_corp_url; + document.body.appendChild(iframe); + }); + + assert_equals(report.body.blockedURL, no_corp_url); + assert_equals(report.body.type, "corp"); + assert_equals(report.body.disposition, "enforce"); + assert_equals(report.body.destination, "iframe"); + }, "uuid-in-package iframe without Cross-Origin-Resource-Policy: header should be blocked and generate a report."); + + promise_test(async () => { + const report = await expectCOEPReport(async () => { + const iframe = document.createElement("iframe"); + iframe.src = corp_same_origin_url; + document.body.appendChild(iframe); + }); + + assert_equals(report.body.blockedURL, corp_same_origin_url); + assert_equals(report.body.type, "corp"); + assert_equals(report.body.disposition, "enforce"); + assert_equals(report.body.destination, "iframe"); + }, "uuid-in-package iframe with Cross-Origin-Resource-Policy: same-origin should be blocked and generate a report."); + + promise_test(async () => { + const iframe = document.createElement("iframe"); + iframe.src = corp_cross_origin_url; + await addElementAndWaitForLoad(iframe); + assert_equals( + await evalInIframe(iframe, "location.href"), + corp_cross_origin_url + ); + }, "uuid-in-package iframe with Cross-Origin-Resource-Policy: cross-origin should not be blocked."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html.headers b/testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html.headers new file mode 100644 index 0000000000..4e798cd9f5 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/coep.https.tentative.html.headers @@ -0,0 +1,2 @@ +Cross-Origin-Embedder-Policy: require-corp +Cross-Origin-Resource-Policy: cross-origin diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/corp.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/corp.https.tentative.html new file mode 100644 index 0000000000..ce18544b0b --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/corp.https.tentative.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<title>CORP for WebBundle subresource loading</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md#cors-and-corp-for-subresource-requests" +/> +<link + rel="help" + href="https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> + +<body> + <!-- + This wpt should run on an origin different from https://www1.web-platform.test:8444/, + from where cross-orign WebBundles are served. + + This test uses a cross-origin WebBundle, + https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp.wbn, + which is served with an Access-Control-Allow-Origin response header. + + `corp.wbn` includes three subresources: + a. `no-corp.js`, which doesn't include a Cross-Origin-Resource-Policy response header. + b. `corp-same-origin.js`, which includes a Cross-Origin-Resource-Policy: same-origin response header. + c. `corp-cross-origin.js`, which includes a Cross-Origin-Resource-Policy: cross-origin response header. + --> + <script type="webbundle"> + { + "source": "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp.wbn", + "resources": [ + "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/no-corp.js", + "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp-same-origin.js", + "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/corp-cross-origin.js", + "uuid-in-package:5eafff38-e0a0-4661-bde0-434255aa9d93", + "uuid-in-package:7e13b47a-8b91-4a0e-997c-993a5e2f3a34", + "uuid-in-package:86d5b696-8867-4454-8b07-51239a0817f7" + ] + } + </script> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + promise_test(async () => { + const prefix = + "https://www1.web-platform.test:8444/web-bundle/resources/wbn/cors/"; + await addScriptAndWaitForExecution(prefix + "no-corp.js"); + await addScriptAndWaitForError(prefix + "corp-same-origin.js"); + await addScriptAndWaitForExecution(prefix + "corp-cross-origin.js"); + }, "Subresource loading from WebBundles should respect Cross-Origin-Resource-Policy header."); + + promise_test(async () => { + const no_corp_url = "uuid-in-package:5eafff38-e0a0-4661-bde0-434255aa9d93"; + const corp_same_origin_url = + "uuid-in-package:7e13b47a-8b91-4a0e-997c-993a5e2f3a34"; + const corp_cross_origin_url = + "uuid-in-package:86d5b696-8867-4454-8b07-51239a0817f7"; + await iframeLocationTest(no_corp_url); + await iframeLocationTest(corp_same_origin_url); + await iframeLocationTest(corp_cross_origin_url); + }, "uuid-in-package iframes should not be blocked regardless of the Cross-Origin-Resource-Policy header, if Cross-Origin-Embedder-Policy is not set."); + + async function iframeLocationTest(url) { + const iframe = document.createElement("iframe"); + iframe.src = url; + await addElementAndWaitForLoad(iframe); + assert_equals(await evalInIframe(iframe, "location.href"), url); + } + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/credentials.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/credentials.https.tentative.sub.html new file mode 100644 index 0000000000..37efc37e6d --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/credentials.https.tentative.sub.html @@ -0,0 +1,173 @@ +<!DOCTYPE html> +<title>Credentials in WebBundle subresource loading</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md#requests-mode-and-credentials-mode" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + // In this wpt, we test a request's credential mode, which controls + // whether UA sends a credential or not to fetch a bundle. + + // If UA sends a credential, check-cookie-and-return-{cross-oriigin}-bundle.py + // returns a valid format webbundle. Then, a subresource fetch should be successful. + // Otherwise, a subresource fetch should be rejected. + + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + document.cookie = "milk=1; path=/"; + + // Make sure to set a cookie for a cross-origin domain from where a cross + // origin bundle is served. + const setCookiePromise = fetch( + "https://{{domains[www1]}}:{{ports[https][0]}}/cookies/resources/set-cookie.py?name=milk&path=/web-bundle/resources/", + { + mode: "no-cors", + credentials: "include", + } + ); + + const same_origin_bundle = "../resources/check-cookie-and-return-bundle.py"; + const cross_origin_bundle = + "https://{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/check-cookie-and-return-bundle.py?bundle=cross-origin"; + + const same_origin_bundle_subresource = "../resources/wbn/root.js"; + const cross_origin_bundle_subresource = + "https://{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/simple-cross-origin.txt"; + + async function assertSubresourceCanBeFetched() { + const response = await fetch(same_origin_bundle_subresource); + const text = await response.text(); + assert_equals(text, "export * from './submodule.js';\n"); + } + + async function assertCrossOriginSubresourceCanBeFetched() { + const response = await fetch(cross_origin_bundle_subresource); + const text = await response.text(); + assert_equals(text, "hello from simple-cross-origin.txt"); + } + + function createScriptWebBundle(credentials) { + const options = {}; + if (credentials) { + options.credentials = credentials; + } + return createWebBundleElement( + same_origin_bundle, + [same_origin_bundle_subresource], + options + ); + } + + function createScriptWebBundleCrossOrigin(credentials) { + const options = {}; + if (credentials) { + options.credentials = credentials; + } + return createWebBundleElement( + cross_origin_bundle, + [cross_origin_bundle_subresource], + options + ); + } + + promise_test(async (t) => { + const script = createScriptWebBundle(); + document.body.append(script); + t.add_cleanup(() => script.remove()); + + await assertSubresourceCanBeFetched(); + }, "The default should send a credential to a same origin bundle"); + + promise_test(async (t) => { + const script = createScriptWebBundle("invalid"); + document.body.append(script); + t.add_cleanup(() => script.remove()); + + await assertSubresourceCanBeFetched(); + }, "An invalid value should send a credential to a same origin bundle"); + + promise_test(async (t) => { + const script = createScriptWebBundle("omit"); + document.body.append(script); + t.add_cleanup(() => script.remove()); + + return promise_rejects_js( + t, + TypeError, + fetch(same_origin_bundle_subresource) + ); + }, "'omit' should not send a credential to a same origin bundle"); + + promise_test(async (t) => { + const script = createScriptWebBundle("same-origin"); + document.body.append(script); + t.add_cleanup(() => script.remove()); + + await assertSubresourceCanBeFetched(); + }, "'same-origin' should send a credential to a same origin bundle"); + + promise_test(async (t) => { + const script = createScriptWebBundle("include"); + document.body.append(script); + t.add_cleanup(() => script.remove()); + + await assertSubresourceCanBeFetched(); + }, "'include' should send a credential to a same origin bundle"); + + promise_test(async (t) => { + await setCookiePromise; + + const script = createScriptWebBundleCrossOrigin("omit"); + document.body.append(script); + t.add_cleanup(() => script.remove()); + + return promise_rejects_js( + t, + TypeError, + fetch(cross_origin_bundle_subresource) + ); + }, "'omit' should not send a credential to a cross origin bundle"); + + promise_test(async (t) => { + await setCookiePromise; + + const script = createScriptWebBundleCrossOrigin("same-origin"); + document.body.append(script); + t.add_cleanup(() => script.remove()); + + return promise_rejects_js( + t, + TypeError, + fetch(cross_origin_bundle_subresource) + ); + }, "'same-origin' should not send a credential to a cross origin bundle"); + + promise_test(async (t) => { + await setCookiePromise; + + const script = createScriptWebBundleCrossOrigin("include"); + document.body.append(script); + t.add_cleanup(() => script.remove()); + + await assertCrossOriginSubresourceCanBeFetched(); + }, "'include' should send a credential to a cross origin bundle"); + + promise_test(async (t) => { + const script = createScriptWebBundleCrossOrigin("invalid"); + document.body.append(script); + t.add_cleanup(() => script.remove()); + + return promise_rejects_js( + t, + TypeError, + fetch(cross_origin_bundle_subresource) + ); + }, "An invalid value should not send a credential to a cross origin bundle"); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/csp-allowed.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/csp-allowed.https.tentative.html new file mode 100644 index 0000000000..55498eaa4e --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/csp-allowed.https.tentative.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<title>CSP for subresource WebBundle (allowed cases)</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<meta + http-equiv="Content-Security-Policy" + content=" + script-src + https://web-platform.test:8444/web-bundle/resources/wbn/uuid-in-package.wbn + https://web-platform.test:8444/resources/testharness.js + https://web-platform.test:8444/resources/testharnessreport.js + 'unsafe-inline'; + img-src + https://web-platform.test:8444/web-bundle/resources/wbn/pass.png; + frame-src + https://web-platform.test:8444/web-bundle/resources/wbn/uuid-in-package.wbn" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + <script type="webbundle"> + { + "source": "../resources/wbn/subresource.wbn", + "resources": ["https://web-platform.test:8444/web-bundle/resources/wbn/pass.png"] + } + </script> + <script type="webbundle"> + { + "source": "../resources/wbn/uuid-in-package.wbn", + "resources": ["uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720", + "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae" + ] + } + </script> + <script> + promise_test(() => { + return new Promise((resolve, reject) => { + const img = document.createElement("img"); + img.src = + "https://web-platform.test:8444/web-bundle/resources/wbn/pass.png"; + img.onload = resolve; + img.onerror = reject; + document.body.appendChild(img); + }); + }, "URL matching of CSP should be done based on the subresource URL " + + "when the subresource URL is HTTPS URL."); + + promise_test(async () => { + const result = await new Promise((resolve) => { + // This function will be called from the script. + window.report_result = resolve; + const script = document.createElement("script"); + script.src = "uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720"; + document.body.appendChild(script); + }); + assert_equals(result, "OK"); + }, "URL matching of script-src CSP should be done based on the bundle URL " + + "when the subresource URL is uuid-in-package: URL."); + + promise_test(async () => { + const frame_url = "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae"; + const iframe = document.createElement("iframe"); + iframe.src = frame_url; + const load_promise = new Promise((resolve) => { + iframe.addEventListener("load", resolve); + }); + document.body.appendChild(iframe); + await load_promise; + assert_equals(await evalInIframe(iframe, "location.href"), frame_url); + }, "URL matching of frame-src CSP should be done based on the bundle URL " + + "when the frame URL is uuid-in-package: URL."); + + async function evalInIframe(iframe, code) { + const message_promise = new Promise((resolve) => { + window.addEventListener( + "message", + (e) => { + resolve(e.data); + }, + { once: true } + ); + }); + iframe.contentWindow.postMessage(code, "*"); + return message_promise; + } + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html new file mode 100644 index 0000000000..6700533b58 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html @@ -0,0 +1,162 @@ +<!DOCTYPE html> +<title>CSP for subresource WebBundle (blocked cases)</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<meta + http-equiv="Content-Security-Policy" + content=" + script-src + urn: + https://web-platform.test:8444/resources/testharness.js + https://web-platform.test:8444/resources/testharnessreport.js + 'unsafe-inline'; + img-src + https://web-platform.test:8444/web-bundle/resources/wbn/subresource.wbn; + frame-src + urn:; + report-to + csp-group" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + <script type="webbundle"> + { + "source": "../resources/wbn/subresource.wbn", + "resources": ["https://web-platform.test:8444/web-bundle/resources/wbn/fail.png"] + } + </script> + <script type="webbundle"> + { + "source": "../resources/wbn/uuid-in-package.wbn", + "resources": ["uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720", + "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae"] + } + </script> + <script> + const uuid_bundle_url = + "https://web-platform.test:8444/web-bundle/resources/wbn/uuid-in-package.wbn"; + + function expect_violation() { + return new Promise((resolve) => { + document.addEventListener( + "securitypolicyviolation", + (e) => { + e.stopPropagation(); + resolve(e); + }, + { once: true } + ); + }); + } + + function getReportID() { + const cookies = document.cookie.split(";"); + for (var i = 0; i < cookies.length; i++) { + const name_value = cookies[i].split("="); + const cookieName = name_value[0].trim(); + if (cookieName === "csp-blocked-report-id") { + return name_value[1].trim(); + } + } + } + + function sortReportsByEffectiveDirective(reports) { + reports.sort( + (report1, report2) => + report1.body.effectiveDirective.localeCompare( + report2.body.effectiveDirective + ) || report1.body.blockedURL.localeCompare(report2.body.blockedURL) + ); + } + + promise_test(async () => { + const p = expect_violation(); + const img = document.createElement("img"); + const error_promise = new Promise((resolve) => { + img.onerror = resolve; + }); + img.src = + "https://web-platform.test:8444/web-bundle/resources/wbn/fail.png"; + document.body.appendChild(img); + const e = await p; + assert_equals(e.blockedURI, img.src); + await error_promise; + }, "URL matching of CSP should be done based on the subresource URL, " + + "not on the bundle URL, when the subresource URL is HTTPS URL."); + + const testCases = [ + { + prefix: "uuid-in-package:", + bundle_url: uuid_bundle_url, + }, + ]; + for (const params of testCases) { + promise_test(async () => { + const urn_uuid = params.prefix + "020111b3-437a-4c5c-ae07-adb6bbffb720"; + const p = expect_violation(); + const script = document.createElement("script"); + script.src = urn_uuid; + document.body.appendChild(script); + const e = await p; + // Currently Chromium is reporting the bundle URL. + // TODO(crbug.com/1208659): Consider deeper integration with CSP for + // providing the both URLs. + assert_equals(e.blockedURI, params.bundle_url); + assert_equals(e.violatedDirective, "script-src-elem"); + }, "URL matching of script-src CSP should be done based on the bundle URL " + + `when the subresource URL is ${params.prefix} URL.`); + + promise_test(async () => { + const urn_uuid = params.prefix + "429fcc4e-0696-4bad-b099-ee9175f023ae"; + const p = expect_violation(); + const iframe = document.createElement("iframe"); + iframe.src = urn_uuid; + const load_promise = new Promise((resolve) => { + iframe.addEventListener("load", resolve); + }); + document.body.appendChild(iframe); + const e = await p; + // Currently Chromium is reporting the bundle URL. + // TODO(crbug.com/1208659): Consider deeper integration with CSP for + // providing the both URLs. + assert_equals(e.blockedURI, params.bundle_url); + assert_equals(e.violatedDirective, "frame-src"); + + // Make sure that the blocked iframe load is finished. + await load_promise; + + // The blocked iframe is cross-origin. So accessing + // iframe.contentWindow.location should throw a SecurityError. + assert_throws_dom("SecurityError", () => { + iframe.contentWindow.location.href; + }); + }, "URL matching of frame-src CSP should be done based on the bundle URL " + + `when the frame URL is ${params.prefix} URL.`); + } + + promise_test(async () => { + const retrieve_report_url = + "/reporting/resources/report.py?op=retrieve_report&timeout=3&reportID=" + + getReportID(); + const reports = await (await fetch(retrieve_report_url)).json(); + sortReportsByEffectiveDirective(reports); + + assert_equals(reports.length, 3, "Report count."); + + assert_equals(reports[0].body.blockedURL, uuid_bundle_url); + assert_equals(reports[0].body.effectiveDirective, "frame-src"); + + assert_equals( + reports[1].body.blockedURL, + "https://web-platform.test:8444/web-bundle/resources/wbn/fail.png" + ); + assert_equals(reports[1].body.effectiveDirective, "img-src"); + + assert_equals(reports[2].body.blockedURL, uuid_bundle_url); + assert_equals(reports[2].body.effectiveDirective, "script-src-elem"); + }, "Check the CSP violation reports."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html.sub.headers b/testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html.sub.headers new file mode 100644 index 0000000000..ac826f8c48 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/csp-blocked.https.tentative.html.sub.headers @@ -0,0 +1,6 @@ +Expires: Mon, 26 Jul 1997 05:00:00 GMT +Cache-Control: no-store, no-cache, must-revalidate +Cache-Control: post-check=0, pre-check=0, false +Pragma: no-cache +Set-Cookie: csp-blocked-report-id={{$id:uuid()}}; Path=/web-bundle/subresource-loading/ +Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{$id}}" }] } diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/csp-blockes-bundle.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/csp-blockes-bundle.https.tentative.html new file mode 100644 index 0000000000..06cef8c118 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/csp-blockes-bundle.https.tentative.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<title>CSP blocks WebBundle</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<meta + http-equiv="Content-Security-Policy" + content=" + default-src + https://web-platform.test:8444/web-bundle/resources/wbn/relative-url-file.js + https://web-platform.test:8444/resources/testharness.js + https://web-platform.test:8444/resources/testharnessreport.js + https://web-platform.test:8444/web-bundle/resources/test-helpers.js + 'unsafe-inline'; + img-src + https://web-platform.test:8444/web-bundle/resources/wbn/pass.png;" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + // This bundle should be blocked because its URL is not listed in CSP directive. + const bundle_url = + "https://web-platform.test:8444/web-bundle/resources/wbn/relative-url.wbn"; + + const subresource_url = + "https://web-platform.test:8444/web-bundle/resources/wbn/relative-url-file.js"; + + promise_test(() => { + // if a WebBundle is blocked by CSP, + // - A request for the WebBundle should fail. + // - A subresource request associated with the bundle should fail. + // - A window.load should be fired. In other words, any request shouldn't remain + // pending forever. + + const window_load = new Promise((resolve) => { + window.addEventListener("load", () => { + resolve(); + }); + }); + + const script_webbundle = createWebBundleElement(bundle_url, [ + subresource_url, + ]); + const webbundle_error = new Promise((resolve) => { + script_webbundle.addEventListener("error", () => { + resolve(); + }); + }); + document.body.appendChild(script_webbundle); + + const script_js = document.createElement("script"); + script_js.src = subresource_url; + const script_js_error = new Promise((resolve) => { + script_js.addEventListener("error", () => { + resolve(); + }); + }); + document.body.appendChild(script_js); + + return Promise.all([window_load, webbundle_error, script_js_error]); + }, "WebBundle and subresource loadings should fail when CSP blocks a WebBundle"); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/element-removal.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/element-removal.https.tentative.html new file mode 100644 index 0000000000..87ab8a9f15 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/element-removal.https.tentative.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title> + On-going subresource loading should fail immediately when the web bundle + element is removed +</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + promise_test(async () => { + const element = createWebBundleElement( + "/xhr/resources/delay.py?ms=100000", + ["/xhr/resources/dummy"] + ); + document.body.appendChild(element); + const waitUntilFail = new Promise((resolve) => { + fetch("/xhr/resources/dummy").then(() => {}, resolve); + }); + document.body.removeChild(element); + await waitUntilFail; + }, "On-going subresource loading should fail immediately when the element is " + "removed."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/invalid-json.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/invalid-json.https.tentative.html new file mode 100644 index 0000000000..f4f9405e2d --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/invalid-json.https.tentative.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<title>WebBundle subresource loading with script API and invalid JSON</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + <script> + setup( + () => { + assert_true(HTMLScriptElement.supports("webbundle")); + }, + { allow_uncaught_exception: true } + ); + promise_test((t) => { + const script = document.createElement("script"); + script.type = "webbundle"; + script.textContent = "invalid json"; + return new Promise((resolve, reject) => { + script.onload = () => reject(script); + script.onerror = () => reject(script); + window.onerror = function (message, url, line, col, error) { + assert_equals(error.name, "SyntaxError"); + resolve(script); + }; + document.body.appendChild(script); + }); + }, "Invalid JSON rule should throw a SyntaxError on the window."); + promise_test((t) => { + const script = document.createElement("script"); + script.type = "webbundle"; + const json_rule = { resources: [] }; + script.textContent = JSON.stringify(json_rule); + return new Promise((resolve, reject) => { + script.onload = () => reject(script); + script.onerror = () => reject(script); + window.onerror = function (message, url, line, col, error) { + assert_equals(error.name, "TypeError"); + resolve(script); + }; + document.body.appendChild(script); + }); + }, "JSON rule with a type error should throw a TypeError on the window."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/nested-bundle.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/nested-bundle.https.tentative.html new file mode 100644 index 0000000000..2e5b102e68 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/nested-bundle.https.tentative.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>A nested bundle is not supported</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script type="webbundle"> + { + "source": "/web-bundle/resources/wbn/nested-main.wbn", + "resources": ["/web-bundle/resources/wbn/nested-sub.wbn"] + } + </script> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + promise_test(async () => { + const response = await fetch("/web-bundle/resources/wbn/nested-sub.wbn"); + assert_true(response.ok); + }, "A nested bundle can be fetched"); + + promise_test(async () => { + await addWebBundleElementAndWaitForError( + "/web-bundle/resources/wbn/nested-sub.wbn", + ["/web-bundle/resources/wbn/root.js"] + ); + const response = await fetch("/web-bundle/resources/wbn/root.js"); + assert_false(response.ok); + }, "Subresources in a nested bundle should not be loaded"); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/network-error.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/network-error.https.tentative.sub.html new file mode 100644 index 0000000000..3ebab3fa90 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/network-error.https.tentative.sub.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Web Bundle fetching failed due to a network error</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + // This test uses a non-existing WebBundle from a non-existent host, which makes + // Web Bundle fetching fail due to a network error. The intent of is to check if + // failing to fetch a WebBundle also makes subresource fetch requests fail. + promise_test(async () => { + const prefix = "https://{{hosts[][nonexistent]}}/"; + const resources = [prefix + "resource.js"]; + await addWebBundleElementAndWaitForError( + prefix + "non-existing.wbn", + resources + ); + + // Can not fetch a subresource because Web Bundle fetch failed. + await fetchAndWaitForReject(prefix + "resource.js"); + }, "Subresource fetch requests for non-existing Web Bundle should fail."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/not-found.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/not-found.https.tentative.html new file mode 100644 index 0000000000..efcb6bc9fb --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/not-found.https.tentative.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Web Bundle fetching failed due to not found error</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + // This test uses a non-existing WebBundle, + // /web-bundle/resources/wbn/cors/non-existing.wbn. + // The intent of this test is to check if failing to fetch a WebBundle due to + // not found error also makes subresource fetch requests fail. + promise_test(async () => { + const prefix = "/web-bundle/resources/wbn/"; + const resources = [prefix + "resource.js"]; + const element = await createWebBundleElement( + prefix + "non-existing.wbn", + resources + ); + document.body.appendChild(element); + + // Can not fetch a subresource because Web Bundle fetch failed. + await fetchAndWaitForReject(prefix + "resource.js"); + }, "Subresource fetch requests for non-existing Web Bundle should fail."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/path-restriction.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/path-restriction.https.tentative.html new file mode 100644 index 0000000000..1d7b6f204b --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/path-restriction.https.tentative.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<title>Path restriction on subresource loading with WebBundles</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + <script type="webbundle"> + { + "source": "../resources/wbn/path-restriction.wbn", + "resources": [ + "/web-bundle/resources/wbn/resource.js", + "/web-bundle/resources/wbn/sub/resource.js", + "/web-bundle/resources/wbn-resource.js", + "/web-bundle/resources/wbn1/resource.js", + "/web-bundle/resources/other/resource.js", + "/web-bundle/resources/resource.js" + ] + } + </script> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + promise_test(async () => { + const resources = [ + "/web-bundle/resources/wbn/resource.js", + "/web-bundle/resources/wbn/sub/resource.js", + ]; + for (const resource of resources) { + const response = await fetch(resource); + assert_true(response.ok, resource + " should be loaded"); + } + }, "Subresources should be loaded."); + + promise_test(async () => { + const resources = [ + "/web-bundle/resources/wbn-resource.js", + "/web-bundle/resources/wbn1/resource.js", + "/web-bundle/resources/other/resource.js", + "/web-bundle/resources/resource.js", + ]; + for (const resource of resources) { + const response = await fetch(resource); + assert_false(response.ok, resource + " should not be loaded"); + } + }, "Subresources should not be loaded due to path restriction."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle-cors.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle-cors.https.tentative.sub.html new file mode 100644 index 0000000000..32f333b67e --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle-cors.https.tentative.sub.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>Relative Url in cross origin web bundle</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + const loaded_scripts = []; + function scriptLoaded(file) { + loaded_scripts.push(file); + } + const failed_scripts = []; + function scriptFailed(file) { + failed_scripts.push(file); + } + </script> + <script type="webbundle"> + { + "source": "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url.wbn", + "resources": [ + "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url-file.js", + "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url/start-with-double-slash-cors.js", + "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url/start-with-slash.js", + "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url/subdirectory-path.js", + "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/starts-with-two-dots.js", + "//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/starts-with-two-dots-out-of-scope.js" + ] + } + </script> + <script src="//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url-file.js"></script> + <script src="//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url/start-with-double-slash-cors.js"></script> + <script src="//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url/start-with-slash.js"></script> + <script src="//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/relative-url/subdirectory-path.js"></script> + <script src="//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/wbn/starts-with-two-dots.js"></script> + <script + src="//{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/starts-with-two-dots-out-of-scope.js" + onerror="scriptFailed('starts-with-two-dots-out-of-scope.js')" + ></script> + <script> + promise_test(async (t) => { + assert_array_equals(loaded_scripts, [ + "relative-url-file.js", + "start-with-double-slash-cors.js", + "start-with-slash.js", + "subdirectory-path.js", + "starts-with-two-dots.js", + ]); + assert_array_equals(failed_scripts, [ + "starts-with-two-dots-out-of-scope.js", + ]); + }, "Relative Url in web bundle."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle.https.tentative.html new file mode 100644 index 0000000000..0b7f63c21c --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-in-web-bundle.https.tentative.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>Relative Url in web bundle</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + const loaded_scripts = []; + function scriptLoaded(file) { + loaded_scripts.push(file); + } + const failed_scripts = []; + function scriptFailed(file) { + failed_scripts.push(file); + } + </script> + <script type="webbundle"> + { + "source": "../resources/wbn/relative-url.wbn", + "resources": [ + "relative-url-file.js", + "relative-url/start-with-double-slash.js", + "relative-url/start-with-slash.js", + "relative-url/subdirectory-path.js", + "starts-with-two-dots.js", + "starts-with-two-dots-out-of-scope.js" + ] + } + </script> + <script src="../resources/wbn/relative-url-file.js"></script> + <script src="../resources/wbn/relative-url/start-with-double-slash.js"></script> + <script src="../resources/wbn/relative-url/start-with-slash.js"></script> + <script src="../resources/wbn/relative-url/subdirectory-path.js"></script> + <script src="../resources/wbn/starts-with-two-dots.js"></script> + <script + src="../resources/starts-with-two-dots-out-of-scope.js" + onerror="scriptFailed('starts-with-two-dots-out-of-scope.js')" + ></script> + <script> + promise_test(async (t) => { + assert_array_equals(loaded_scripts, [ + "relative-url-file.js", + "start-with-double-slash.js", + "start-with-slash.js", + "subdirectory-path.js", + "starts-with-two-dots.js", + ]); + assert_array_equals(failed_scripts, [ + "starts-with-two-dots-out-of-scope.js", + ]); + }, "Relative Url in web bundle."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-resources.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-resources.https.tentative.html new file mode 100644 index 0000000000..1b79b157c4 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-resources.https.tentative.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<title>Subresource loading using relative URLs in the 'resources'</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> + +<script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); +</script> +<body> + <script> + let script; + + function cleanUp() { + if (script) { + script.remove(); + } + } + + function createScriptWebBundle(resource) { + return createWebBundleElement( + "../resources/wbn/subresource.wbn", + /*resources=*/ [resource] + ); + } + + async function assertResourceCanBeFetched() { + const response = await fetch(`../resources/wbn/root.js`); + const text = await response.text(); + assert_equals(text, "export * from './submodule.js';\n"); + } + + async function assertResourceNotFound() { + const response = await fetch(`../resources/wbn/root.js`); + const status = await response.status; + assert_equals(status, 404); + } + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script = createScriptWebBundle("root.js"); + document.body.append(script); + await assertResourceCanBeFetched(); + }, "A relative URL, 'root.js', should be resolved on the bundle's URL"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script = createScriptWebBundle("./root.js"); + document.body.append(script); + await assertResourceCanBeFetched(); + }, "Use './root.js', starting with dot"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script = createScriptWebBundle("../wbn/root.js"); + document.body.append(script); + await assertResourceCanBeFetched(); + }, "Use '../wbn/root.js', starting with two dots"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script = createScriptWebBundle("/web-bundle/resources/wbn/root.js"); + document.body.append(script); + await assertResourceCanBeFetched(); + }, "Use '/web-bundle/resources/wbn/root.js', starting with slash"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script = createScriptWebBundle("unrelated-relative-url.js"); + document.body.append(script); + await assertResourceNotFound(); + }, "A resource should not be found"); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-scopes.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-scopes.https.tentative.html new file mode 100644 index 0000000000..40a49d55d3 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-scopes.https.tentative.html @@ -0,0 +1,85 @@ +<!DOCTYPE html> +<title>Subresource loading using relative URLs in the 'scopes'</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> + +<script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); +</script> +<body> + <script> + let script; + + function cleanUp() { + if (script) { + script.remove(); + } + } + + function createScriptWebBundle(scope) { + return createWebBundleElement( + "../resources/wbn/relative-url.wbn", + /*resources=*/ [], + { scopes: [scope] } + ); + } + + async function assertResourceCanBeFetched() { + const response = await fetch( + "../resources/wbn/relative-url/subdirectory-path.js" + ); + const text = await response.text(); + assert_equals(text, "scriptLoaded('subdirectory-path.js');"); + } + + async function assertResourceNotFound() { + const response = await fetch( + "../resources/wbn/relative-url/subdirectory-path.js" + ); + const status = await response.status; + assert_equals(status, 404); + } + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script = createScriptWebBundle("relative-url"); + document.body.append(script); + await assertResourceCanBeFetched(); + }, "A relative URL, 'relative-url', should be resolved on the bundle's URL"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script = createScriptWebBundle("./relative-url"); + document.body.append(script); + await assertResourceCanBeFetched(); + }, "Use './relative-url', starting with dot"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script = createScriptWebBundle("../wbn/relative-url"); + document.body.append(script); + await assertResourceCanBeFetched(); + }, "Use '../wbn/relative-url', starting with two dots"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script = createScriptWebBundle("/web-bundle/resources/wbn/relative-url"); + document.body.append(script); + await assertResourceCanBeFetched(); + }, "Use '/web-bundle/resources/wbn/relative-url', starting with slash"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script = createScriptWebBundle("unrelated-scope"); + document.body.append(script); + await assertResourceNotFound(); + }, "A resource should not be found"); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-static-element.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-static-element.https.tentative.html new file mode 100644 index 0000000000..55030e234b --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-static-element.https.tentative.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<title>WebBundle subresource loading with relative URLs for static elements</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + <script type="webbundle"> + { + "source": "../resources/wbn/static-element.wbn", + "resources": [ + "static-element/resources/script.js" + ], + "scopes": [ + "static-element/scopes" + ] + } + </script> + <script src="../resources/wbn/static-element/resources/script.js"></script> + <script src="../resources/wbn/static-element/scopes/script.js"></script> + <script src="../resources/wbn/static-element/out-of-scope/script.js"></script> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports('webbundle')); + }); + promise_test(async () => { + assert_equals(resources_script_result, 'loaded from webbundle'); + assert_equals(scopes_script_result, 'loaded from webbundle'); + assert_equals(out_of_scope_script_result, 'loaded from network'); + }, 'Subresources from static elements should be loaded from web bundle.'); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-with-base.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-with-base.https.tentative.html new file mode 100644 index 0000000000..3c7e1a380c --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/relative-url-with-base.https.tentative.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<title> + Subresource loading using relative URLs in the 'resources' attribute with a + base element +</title> +<base href="../resources/wbn/" /> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> + <script type="webbundle"> + { + "source": "static-element.wbn", + "resources": ["static-element/resources/script.js"] + } + </script> + <script id="script" src="static-element/resources/script.js"></script> + + <script type="webbundle"> + { + "source": "dynamic1.wbn", + "scopes": ["dynamic/resource"] + } + </script> + + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + test(() => { + assert_equals(resources_script_result, "loaded from webbundle"); + }, "A subresource script.js should be loaded from WebBundle using the relative " + "URL and a base element."); + + promise_test(async () => { + const module = await import( + "/web-bundle/resources/wbn/dynamic/resource1.js" + ); + assert_equals(module.result, "resource1 from dynamic1.wbn"); + const module2 = await import( + "/web-bundle/resources/wbn/dynamic/resource2.js" + ); + assert_equals(module2.result, "resource2 from dynamic1.wbn"); + const module3 = await import( + "/web-bundle/resources/wbn/dynamic/resource3.js" + ); + assert_equals(module3.result, "resource3 from dynamic1.wbn"); + const module4 = await import( + "/web-bundle/resources/wbn/dynamic/resource4.js" + ); + assert_equals(module4.result, "resource4 from dynamic1.wbn"); + const result_promise = new Promise((resolve) => { + // This function will be called from script.js + window.report_result = resolve; + }); + + const script = document.createElement("script"); + script.src = "/web-bundle/resources/wbn/dynamic/classic_script.js"; + document.body.appendChild(script); + assert_equals(await result_promise, "classic script from network"); + }, "Subresources that start with 'resource' should be loaded from dynamic1.wbn while others from network."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/request-destination.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/request-destination.https.tentative.sub.html new file mode 100644 index 0000000000..da12f3d042 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/request-destination.https.tentative.sub.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<title> + Request's destination must be "webbundle" with the script-based API +</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + // check-sec-fetch-dest-header-and-return-bundle.py returns a valid format + // bundle only if a 'Sec-Fetch-Dest: webbundle' header is present in a request. + // Otherwise, returns an empty body with 400 status code. + // + // In this wpt, we assume that a <script> element fires a load event correctly if + // a valid format webbundle is returned. + + const same_origin_bundle = + "../resources/check-sec-fetch-dest-header-and-return-bundle.py"; + const cross_origin_bundle = + "https://{{domains[www1]}}:{{ports[https][0]}}/web-bundle/resources/check-sec-fetch-dest-header-and-return-bundle.py"; + + promise_test(async () => { + for (const bundle of [same_origin_bundle, cross_origin_bundle]) { + const element = createWebBundleElement(bundle, /*resources=*/ []); + await addElementAndWaitForLoad(element); + element.remove(); + } + }, '"Sec-Fetch-Dest: webbundle" header must be present in a request for a bundle' + + " with <script type=webbundle>."); + + promise_test(async () => { + const res = await fetch(same_origin_bundle); + assert_false(res.ok); + }, '"Sec-Fetch-Dest: webbundle" header must not be present in a fetch request' + " for a same-origin resource."); + + promise_test(async () => { + const res = await fetch(cross_origin_bundle); + assert_false(res.ok); + }, '"Sec-Fetch-Dest: webbundle" header must not be present in a fetch request' + " for a cross-origin resource."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/resource-timing-attributes-consistent.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/resource-timing-attributes-consistent.https.tentative.sub.html new file mode 100644 index 0000000000..3231d69ae9 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/resource-timing-attributes-consistent.https.tentative.sub.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title> + Resource timing attributes are consistent for the same-origin subresources. +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + promise_test(async (t) => { + const bundle_url = + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic1.wbn?pipe=trickle(d0.5)"; + const script_url = + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource1.js"; + const element = createWebBundleElement( + "../resources/wbn/dynamic1.wbn?pipe=trickle(d0.5)", + /*resources=*/ [script_url] + ); + document.body.appendChild(element); + var script_entries = 0; + var web_bundle_entries = 0; + var web_bundle_entry, script_entry; + const promise = new Promise((resolve) => { + new PerformanceObserver( + t.step_func((entryList) => { + var entries = entryList.getEntriesByType("resource"); + for (var i = 0; i < entries.length; ++i) { + if (entries[i].name === script_url) { + script_entry = entries[i]; + script_entries++; + } + + if (entries[i].name === bundle_url) { + web_bundle_entry = entries[i]; + web_bundle_entries++; + } + } + + if (web_bundle_entries > 0 && script_entries > 0) { + // Check timestamps. + assert_greater_than_equal( + script_entry.responseStart, + script_entry.requestStart + 500 + ); + assert_greater_than_equal( + script_entry.responseStart, + web_bundle_entry.responseStart + ); + assert_greater_than_equal( + script_entry.responseEnd, + script_entry.responseStart + ); + assert_greater_than_equal( + script_entry.requestStart, + script_entry.connectEnd + ); + assert_greater_than_equal( + script_entry.responseEnd, + script_entry.responseStart + ); + // Check sizes. + assert_greater_than(script_entry.encodedBodySize, 0); + assert_equals( + script_entry.transferSize, + script_entry.encodedBodySize + 300 + ); + assert_equals( + script_entry.encodedBodySize, + script_entry.decodedBodySize + ); + resolve(); + } + }) + ).observe({ entryTypes: ["resource"] }); + }); + const script = document.createElement("script"); + script.type = "module"; + script.src = script_url; + document.body.appendChild(script); + return promise; + }, "Timestamp attributes filled in resource timing entries should be consistent."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/resource-timing.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/resource-timing.https.tentative.html new file mode 100644 index 0000000000..a2fe38de0f --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/resource-timing.https.tentative.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>Resource timing entries present for uuid-in-package resources</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + promise_test(async (t) => { + const frame_id = "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae"; + const script_id = "uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720"; + const element = createWebBundleElement( + "../resources/wbn/uuid-in-package.wbn", + /*resources=*/ [frame_id, script_id] + ); + document.body.appendChild(element); + let iframe_entries = 0; + let script_entries = 0; + // Declare the report_result function as outputting into stderr + // because it is used in the WebBundle script to report the script load. + window.report_result = console.error; + const promise = new Promise((resolve) => { + new PerformanceObserver( + t.step_func((entryList) => { + let entries = entryList.getEntriesByType("resource"); + for (let i = 0; i < entries.length; ++i) { + // Ignore any entries for the test harness files if present. + if (/testharness(report)?\.js/.test(entries[i].name)) { + continue; + } + + if (entries[i].name === frame_id) ++iframe_entries; + if (entries[i].name === script_id) ++script_entries; + } + if (iframe_entries == 1 && script_entries == 1) { + resolve(); + } + }) + ).observe({ entryTypes: ["resource"] }); + }); + // Add iframe and the script so we get the ResourceTiming + const iframe = document.createElement("iframe"); + iframe.src = frame_id; + document.body.appendChild(iframe); + const script = document.createElement("script"); + script.src = script_id; + document.body.appendChild(script); + return promise; + }, "Each uuid-in-package resource should have exactly 1 ResourceTiming entry."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/resources/check-cookie-and-return-bundle.py b/testing/web-platform/tests/web-bundle/subresource-loading/resources/check-cookie-and-return-bundle.py new file mode 100644 index 0000000000..0d4f14ecb7 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/resources/check-cookie-and-return-bundle.py @@ -0,0 +1,25 @@ +import os + + +def main(request, response): + origin = request.headers.get(b"origin") + + if origin is not None: + response.headers.set(b"Access-Control-Allow-Origin", origin) + response.headers.set(b"Access-Control-Allow-Methods", b"GET") + response.headers.set(b"Access-Control-Allow-Credentials", b"true") + + headers = [ + (b"Content-Type", b"application/webbundle"), + (b"X-Content-Type-Options", b"nosniff"), + ] + + cookie = request.cookies.first(b"milk", None) + if (cookie is not None) and cookie.value == b"1": + with open( + os.path.join(os.path.dirname(__file__), "../../resources/wbn/subresource.wbn"), + "rb", + ) as f: + return (200, headers, f.read()) + else: + return (400, [], "") diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-controlled-iframe.html b/testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-controlled-iframe.html new file mode 100644 index 0000000000..c8b7661f42 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-controlled-iframe.html @@ -0,0 +1 @@ +<body></body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-for-request-monitor.js b/testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-for-request-monitor.js new file mode 100644 index 0000000000..f67ac70686 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/resources/service-worker-for-request-monitor.js @@ -0,0 +1,12 @@ + +let request_urls = []; + +self.addEventListener('fetch', e => { + request_urls.push(e.request.url); + e.respondWith(fetch(e.request)); +}); + +self.addEventListener('message', e => { + e.source.postMessage(request_urls); + request_urls = []; +}); diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/reuse-web-bundle-resource.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/reuse-web-bundle-resource.https.tentative.html new file mode 100644 index 0000000000..81d87bcbf0 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/reuse-web-bundle-resource.https.tentative.html @@ -0,0 +1,329 @@ +<!DOCTYPE html> +<title>script type="webbundle" reuses webbundle resources</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> + +<body> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + const wbn_url = "../resources/wbn/subresource.wbn"; + const wbn_suffix = "subresource.wbn"; + const resource1 = "root.js"; + const resource2 = "submodule.js"; + + const resource1_url = `../resources/wbn/${resource1}`; + const resource2_url = `../resources/wbn/${resource2}`; + + let script1; + let script2; + + function cleanUp() { + if (script1) { + script1.remove(); + } + if (script2) { + script2.remove(); + } + } + + async function assertResource1CanBeFetched() { + const response = await fetch(resource1_url); + const text = await response.text(); + assert_equals(text, "export * from './submodule.js';\n"); + } + + async function assertResource1CanNotBeFetched() { + const response = await fetch(resource1_url); + assert_equals(response.status, 404); + } + + async function assertResource2CanBeFetched() { + const response = await fetch(resource2_url); + const text = await response.text(); + assert_equals(text, "export const result = 'OK';\n"); + } + + function createScriptWebBundle1() { + return createWebBundleElement(wbn_url, /*resources=*/ [resource1]); + } + + function createScriptWebBundle2(options) { + return createWebBundleElement( + wbn_url, + /*resources=*/ [resource2], + /*options=*/ options + ); + } + + async function appendScriptWebBundle1AndFetchResource1() { + clearWebBundleFetchCount(); + script1 = createScriptWebBundle1(); + document.body.append(script1); + await assertResource1CanBeFetched(); + assert_equals(webBundleFetchCount(wbn_suffix), 1); + } + + function clearWebBundleFetchCount() { + performance.clearResourceTimings(); + } + + function webBundleFetchCount(web_bundle_suffix) { + return performance + .getEntriesByType("resource") + .filter((e) => e.name.endsWith(web_bundle_suffix)).length; + } + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + await appendScriptWebBundle1AndFetchResource1(); + clearWebBundleFetchCount(); + + // Append script2 without removing script1. + // script2 should fetch the wbn again. + script2 = createScriptWebBundle2(); + document.body.appendChild(script2); + + await assertResource1CanBeFetched(); + await assertResource2CanBeFetched(); + assert_equals(webBundleFetchCount(wbn_suffix), 1); + }, "A webbundle should be fetched again when new script element is appended."); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + await appendScriptWebBundle1AndFetchResource1(); + clearWebBundleFetchCount(); + + // Remove script1, then append script2 + // script2 should reuse webbundle resources. + script1.remove(); + script2 = createScriptWebBundle2(); + document.body.append(script2); + + await assertResource1CanNotBeFetched(); + await assertResource2CanBeFetched(); + assert_equals(webBundleFetchCount(wbn_suffix), 0); + }, "'remove(), then append()' should reuse webbundle resources"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + clearWebBundleFetchCount(); + script1 = createScriptWebBundle1(); + await addElementAndWaitForLoad(script1); + clearWebBundleFetchCount(); + + // Remove script1, then append script2 + // script2 should reuse webbundle resources. + // And it should also fire a load event. + script1.remove(); + script2 = createScriptWebBundle2(); + await addElementAndWaitForLoad(script2); + + await assertResource1CanNotBeFetched(); + await assertResource2CanBeFetched(); + assert_equals(webBundleFetchCount(wbn_suffix), 0); + }, "'remove(), then append()' should reuse webbundle resources and both scripts should fire load events"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script1 = createWebBundleElement("nonexistent.wbn", []); + await addElementAndWaitForError(script1); + + // Remove script1, then append script2 + // script2 should reuse webbundle resources (but we don't verify that). + // And it should also fire an error event. + script1.remove(); + script2 = createWebBundleElement("nonexistent.wbn", []); + await addElementAndWaitForError(script2); + }, "'remove(), then append()' should reuse webbundle resources and both scripts should fire error events"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + await appendScriptWebBundle1AndFetchResource1(); + clearWebBundleFetchCount(); + + // Remove script1, then append script2 with an explicit 'same-origin' credentials mode. + script1.remove(); + script2 = createScriptWebBundle2({ credentials: "same-origin" }); + document.body.append(script2); + + await assertResource1CanNotBeFetched(); + await assertResource2CanBeFetched(); + assert_equals(webBundleFetchCount(wbn_suffix), 0); + }, "Should reuse webbundle resources if a credential mode is same"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + await appendScriptWebBundle1AndFetchResource1(); + clearWebBundleFetchCount(); + + // Remove script1, then append script2 with a different credentials mode. + script1.remove(); + script2 = createScriptWebBundle2({ credentials: "omit" }); + document.body.append(script2); + + await assertResource1CanNotBeFetched(); + await assertResource2CanBeFetched(); + assert_equals(webBundleFetchCount(wbn_suffix), 1); + }, "Should not reuse webbundle resources if a credentials mode is different (same-origin vs omit)"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + await appendScriptWebBundle1AndFetchResource1(); + clearWebBundleFetchCount(); + + // Remove script1, then append script2 with a different credentials mode. + script1.remove(); + script2 = createScriptWebBundle2({ credentials: "include" }); + document.body.append(script2); + + await assertResource1CanNotBeFetched(); + await assertResource2CanBeFetched(); + assert_equals(webBundleFetchCount(wbn_suffix), 1); + }, "Should not reuse webbundle resources if a credential mode is different (same-origin vs include"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + await appendScriptWebBundle1AndFetchResource1(); + clearWebBundleFetchCount(); + + // Remove script1, then append the removed one. + script1.remove(); + document.body.append(script1); + + await assertResource1CanNotBeFetched(); + assert_equals(webBundleFetchCount(wbn_suffix), 0); + }, "'remove(), then append()' for the same element should reuse webbundle resources"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + await appendScriptWebBundle1AndFetchResource1(); + clearWebBundleFetchCount(); + + // Multiple 'remove(), then append()' for the same element. + script1.remove(); + document.body.append(script1); + + script1.remove(); + document.body.append(script1); + + await assertResource1CanNotBeFetched(); + assert_equals(webBundleFetchCount(wbn_suffix), 0); + }, "Multiple 'remove(), then append()' for the same element should reuse webbundle resources"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + await appendScriptWebBundle1AndFetchResource1(); + clearWebBundleFetchCount(); + + // Remove script1. + script1.remove(); + + // Then append script2 in a separet task. + await new Promise((resolve) => t.step_timeout(resolve, 0)); + script2 = createScriptWebBundle2(); + document.body.append(script2); + + await assertResource1CanNotBeFetched(); + await assertResource2CanBeFetched(); + assert_equals(webBundleFetchCount(wbn_suffix), 1); + }, "'remove(), then append() in a separate task' should not reuse webbundle resources"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + await appendScriptWebBundle1AndFetchResource1(); + clearWebBundleFetchCount(); + + // Use replaceWith() to replace script1 with script2. + // script2 should reuse webbundle resources. + script2 = createScriptWebBundle2(); + script1.replaceWith(script2); + + await assertResource1CanNotBeFetched(); + await assertResource2CanBeFetched(); + + assert_equals(webBundleFetchCount(wbn_suffix), 0); + }, "replaceWith() should reuse webbundle resources."); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + await appendScriptWebBundle1AndFetchResource1(); + clearWebBundleFetchCount(); + + // Move script1 to another document. Then append script2. + // script2 should reuse webbundle resources. + const another_document = new Document(); + another_document.append(script1); + script2 = createScriptWebBundle2(); + document.body.append(script2); + + await assertResource1CanNotBeFetched(); + await assertResource2CanBeFetched(); + + assert_equals(webBundleFetchCount(wbn_suffix), 0); + + // TODO: Test the following cases: + // - The resources are not loaded from the webbundle in the new Document + // (Probably better to use a <iframe>.contentDocument) + // - Even if we move the script element back to the original Document, + // even immediately, the resources are not loaded from the webbundle in the + // original Document. + }, "append() should reuse webbundle resoruces even if the old script was moved to another document."); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + clearWebBundleFetchCount(); + script1 = createWebBundleElement( + wbn_url + "?pipe=trickle(d0.1)", + [resource1] + ); + document.body.appendChild(script1); + + // While script1 is still loading, remove it and make script2 + // reuse the resources. + script1.remove(); + script2 = createWebBundleElement( + wbn_url + "?pipe=trickle(d0.1)", + [resource2] + ); + await addElementAndWaitForLoad(script2); + + assert_equals(webBundleFetchCount(wbn_suffix + "?pipe=trickle(d0.1)"), 1); + }, "Even if the bundle is still loading, we should reuse the resources."); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script1 = createScriptWebBundle1(); + document.body.appendChild(script1); + + // Don't wait for the load event for script1. + script1.remove(); + script2 = createScriptWebBundle2(); + + // Load event should be fired for script2 regardless of + // whether script1 fired a load or not. + await addElementAndWaitForLoad(script2); + }, "When reusing the resources with script2, a load event should be fired regardless of if the script1 fired a load"); + + promise_test(async (t) => { + t.add_cleanup(cleanUp); + script1 = createWebBundleElement("nonexistent.wbn", []); + document.body.appendChild(script1); + + // Don't wait for the error event for script1. + script1.remove(); + script2 = createWebBundleElement("nonexistent.wbn", []); + + // Error event should be fired for script2 regardless of + // whether script1 fired an error event or not. + await addElementAndWaitForError(script2); + }, "When reusing the resources with script2, an error event should be fired regardless of if the script1 fired an error"); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/service-worker-controlled.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/service-worker-controlled.https.tentative.html new file mode 100644 index 0000000000..d5c2a06837 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/service-worker-controlled.https.tentative.html @@ -0,0 +1,138 @@ +<!DOCTYPE html> +<title> + Web Bundle fetching and the inner resouirce fetching should skip service + worker +</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + async function registerServiceWorkerAndReturnActiveWorker( + t, + script, + scope + ) { + const reg = await navigator.serviceWorker.register(script, { + scope: scope, + }); + t.add_cleanup(() => reg.unregister()); + if (reg.active) return reg.active; + const worker = reg.installing || reg.waiting; + await new Promise((resolve) => { + worker.addEventListener("statechange", (event) => { + if (event.target.state == "activated") resolve(); + }); + }); + return worker; + } + + async function getRequestedUrls(worker) { + return new Promise((resolve) => { + navigator.serviceWorker.addEventListener( + "message", + (e) => { + resolve(e.data); + }, + { once: true } + ); + worker.postMessage(null); + }); + } + + promise_test(async (t) => { + const iframe_path = "./resources/service-worker-controlled-iframe.html"; + const iframe_url = new URL(iframe_path, location).href; + + // Register a service worker. + const worker = await registerServiceWorkerAndReturnActiveWorker( + t, + "./resources/service-worker-for-request-monitor.js", + iframe_path + ); + + // Load an iframe which is controlled by the service worker. + const iframe = await new Promise((resolve) => { + const frame = document.createElement("iframe"); + t.add_cleanup(() => frame.remove()); + frame.src = iframe_url; + frame.onload = () => { + resolve(frame); + }; + document.body.appendChild(frame); + }); + // The iframe request should be intercepted by the service worker. + assert_array_equals(await getRequestedUrls(worker), [iframe_url]); + + // Add a web bundle element in the service worker controlled iframe. + const frame_id = "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae"; + const script_id = "uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720"; + + const element = createWebBundleElement( + "../../resources/wbn/uuid-in-package.wbn", + /*resources=*/ [frame_id, script_id] + ); + + const element_load_promise = new Promise((resolve) => { + element.addEventListener("load", () => { + resolve(); + }); + }); + iframe.contentDocument.body.appendChild(element); + await element_load_promise; + // The web bundle request should not be intercepted by the service worker. + assert_array_equals(await getRequestedUrls(worker), []); + + // Add a uuid-in-package URL script element in the service worker + // controlled iframe. + const result_promise = new Promise((resolve) => { + // window.report_result() method will be called by the injected script. + iframe.contentWindow.report_result = resolve; + }); + const script = iframe.contentDocument.createElement("script"); + script.src = script_id; + iframe.contentDocument.body.appendChild(script); + assert_equals(await result_promise, "OK"); + // The urn uuld URL script request should not be intercepted by the + // service worker. + assert_array_equals(await getRequestedUrls(worker), []); + + // Add a uuid-in-package URL iframe element in the service worker controlled + // iframe. + const inner_iframe = iframe.contentDocument.createElement("iframe"); + inner_iframe.src = frame_id; + const load_promise = new Promise((resolve) => { + inner_iframe.addEventListener("load", () => { + resolve(); + }); + }); + iframe.contentDocument.body.appendChild(inner_iframe); + await load_promise; + // The urn uuld URL iframe request should not intercepted by the service + // worker. + assert_array_equals(await getRequestedUrls(worker), []); + + // Check if the uuid-in-package URL iframe element is loaded correctly. + const message_promise = new Promise((resolve) => { + window.addEventListener( + "message", + (e) => { + resolve(e.data); + }, + { once: true } + ); + }); + // location.href is evaluated in the uuid-in-package URL iframe element. + inner_iframe.contentWindow.postMessage("location.href", "*"); + assert_equals(await message_promise, frame_id); + }, "Both Web Bundle request and Subresource fetch requests inside the Web " + "Bundle should skip the service worker."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/static-element-with-base.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/static-element-with-base.https.tentative.sub.html new file mode 100644 index 0000000000..886d9cb783 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/static-element-with-base.https.tentative.sub.html @@ -0,0 +1,65 @@ +<!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" +/> +<base href="../resources/wbn/static-element/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + <script type="webbundle"> + { + "source": "../static-element.wbn", + "resources": [ + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/script.js", + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/style.css", + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/style-imported-from-file.css", + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/style-imported-from-tag.css" + ], + "scopes": [ + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/scopes/" + ] + } + </script> + <style type="text/css"> + @import "resources/style-imported-from-tag.css"; + @import "scopes/style-imported-from-tag.css"; + </style> + <link href="resources/style.css" rel="stylesheet" /> + <link href="scopes/style.css" rel="stylesheet" /> + <script src="resources/script.js"></script> + <script src="scopes/script.js"></script> + <script src="out-of-scope/script.js"></script> + + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + promise_test(async () => { + assert_equals(resources_script_result, "loaded from webbundle"); + assert_equals(scopes_script_result, "loaded from webbundle"); + assert_equals(out_of_scope_script_result, "loaded from network"); + + ["resources_", "scopes_"].forEach((type) => { + [ + "style_target", + "style_imported_from_file_target", + "style_imported_from_tag_target", + ].forEach((target) => { + const element = document.createElement("div"); + element.id = type + target; + document.body.appendChild(element); + assert_equals( + window.getComputedStyle(element).color, + "rgb(0, 0, 255)", + element.id + " color must be blue" + ); + }); + }); + }, "Subresources from static elements should be loaded from web bundle."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/static-element.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/static-element.https.tentative.sub.html new file mode 100644 index 0000000000..198c8f8e5c --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/static-element.https.tentative.sub.html @@ -0,0 +1,68 @@ +<!DOCTYPE html> +<title>WebBundle subresource loading for static elements</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> + <script type="webbundle"> + { + "source": "../resources/wbn/static-element.wbn", + "resources": [ + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/script.js", + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/style.css", + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/style-imported-from-file.css", + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/resources/style-imported-from-tag.css" + ], + "scopes": [ + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/static-element/scopes/" + ] + } + </script> + <style type="text/css"> + @import "../resources/wbn/static-element/resources/style-imported-from-tag.css"; + @import "../resources/wbn/static-element/scopes/style-imported-from-tag.css"; + </style> + <link + href="../resources/wbn/static-element/resources/style.css" + rel="stylesheet" + /> + <link + href="../resources/wbn/static-element/scopes/style.css" + rel="stylesheet" + /> + <script src="../resources/wbn/static-element/resources/script.js"></script> + <script src="../resources/wbn/static-element/scopes/script.js"></script> + <script src="../resources/wbn/static-element/out-of-scope/script.js"></script> + + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + promise_test(async () => { + assert_equals(resources_script_result, "loaded from webbundle"); + assert_equals(scopes_script_result, "loaded from webbundle"); + assert_equals(out_of_scope_script_result, "loaded from network"); + + ["resources_", "scopes_"].forEach((type) => { + [ + "style_target", + "style_imported_from_file_target", + "style_imported_from_tag_target", + ].forEach((target) => { + const element = document.createElement("div"); + element.id = type + target; + document.body.appendChild(element); + assert_equals( + window.getComputedStyle(element).color, + "rgb(0, 0, 255)", + element.id + " color must be blue" + ); + }); + }); + }, "Subresources from static elements should be loaded from web bundle."); + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/subframe-from-web-bundle.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/subframe-from-web-bundle.https.tentative.html new file mode 100644 index 0000000000..9e08ccdd29 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/subframe-from-web-bundle.https.tentative.html @@ -0,0 +1,134 @@ +<!DOCTYPE html> +<title>Subframe loading from Web Bundles</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + promise_test(async (t) => { + const bundle_url = "../resources/wbn/uuid-in-package.wbn"; + const frame_url = "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae"; + const iframe = await createWebBundleElementAndIframe( + t, + bundle_url, + frame_url + ); + // The iframe is cross-origin. So accessing iframe.contentWindow.location + // should throw a SecurityError. + assert_throws_dom("SecurityError", () => { + iframe.contentWindow.location.href; + }); + }, "The uuid-in-package: URL iframe must be cross-origin."); + + uuid_iframe_test( + "location.href", + "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae", + "location.href in opaque-origin iframe." + ); + + uuid_iframe_test( + "(" + + (() => { + try { + let result = window.localStorage; + return "no error"; + } catch (e) { + return e.name; + } + }).toString() + + ")()", + "SecurityError", + "Accesing window.localStorage should throw a SecurityError." + ); + + uuid_iframe_test( + "(" + + (() => { + try { + let result = window.sessionStorage; + return "no error"; + } catch (e) { + return e.name; + } + }).toString() + + ")()", + "SecurityError", + "Accesing window.sessionStorage should throw a SecurityError." + ); + + uuid_iframe_test( + "(" + + (() => { + try { + let result = document.cookie; + return "no error"; + } catch (e) { + return e.name; + } + }).toString() + + ")()", + "SecurityError", + "Accesing document.cookie should throw a SecurityError." + ); + + uuid_iframe_test( + "(" + + (() => { + try { + let request = window.indexedDB.open("db"); + return "no error"; + } catch (e) { + return e.name; + } + }).toString() + + ")()", + "SecurityError", + "Opening an indexedDB should throw a SecurityError." + ); + + uuid_iframe_test( + "window.caches === undefined", + true, + "window.caches should be undefined." + ); + + function uuid_iframe_test(code, expected, name) { + promise_test(async (t) => { + const bundle_url = "../resources/wbn/uuid-in-package.wbn"; + const frame_url = + "uuid-in-package:429fcc4e-0696-4bad-b099-ee9175f023ae"; + const iframe = await createWebBundleElementAndIframe( + t, + bundle_url, + frame_url + ); + assert_equals(await evalInIframe(iframe, code), expected); + }, name + "uuid-in-package"); + } + + async function createWebBundleElementAndIframe(t, bundle_url, frame_url) { + const element = createWebBundleElement(bundle_url, [frame_url]); + document.body.appendChild(element); + const iframe = document.createElement("iframe"); + t.add_cleanup(() => { + document.body.removeChild(element); + document.body.removeChild(iframe); + }); + iframe.src = frame_url; + const load_promise = new Promise((resolve) => { + iframe.addEventListener("load", resolve); + }); + document.body.appendChild(iframe); + await load_promise; + return iframe; + } + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/subresource-load.https.tentative.sub.html b/testing/web-platform/tests/web-bundle/subresource-loading/subresource-load.https.tentative.sub.html new file mode 100644 index 0000000000..10c207020c --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/subresource-load.https.tentative.sub.html @@ -0,0 +1,294 @@ +<!DOCTYPE html> +<title>Subresource loading with script type="webbundle"</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/main/explainers/subresource-loading.md" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script type="webbundle"> + { + "source": "../resources/wbn/subresource.wbn", + "resources": [ + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/root.js", + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/submodule.js" + ] + } + </script> + <script> + setup(() => { + assert_true(HTMLScriptElement.supports("webbundle")); + }); + + promise_test(async () => { + const module = await import( + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/root.js" + ); + assert_equals(module.result, "OK"); + }, "Subresource loading with WebBundle"); + + promise_test(async () => { + const response = await fetch( + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/root.js" + ); + const text = await response.text(); + assert_equals(text, "export * from './submodule.js';\n"); + }, "Subresource loading with WebBundle (Fetch API)"); + + promise_test((t) => { + const url = + "/common/redirect.py?location=https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/root.js"; + return promise_rejects_js(t, TypeError, import(url)); + }, "Subresource loading with WebBundle shouldn't affect redirect"); + + promise_test(async () => { + const element = createWebBundleElement("../resources/wbn/dynamic1.wbn", [ + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource1.js", + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource2.js", + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource4.js", + ]); + document.body.appendChild(element); + + const module = await import( + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource1.js" + ); + assert_equals(module.result, "resource1 from dynamic1.wbn"); + + const new_element = removeAndAppendNewElementWithUpdatedRule(element, { + url: "../resources/wbn/dynamic2.wbn", + }); + const module2 = await import( + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource2.js" + ); + assert_equals(module2.result, "resource2 from dynamic2.wbn"); + + // A resource not specified in the resources attribute, but in the bundle. + const module3 = await import( + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource3.js" + ); + assert_equals(module3.result, "resource3 from network"); + + document.body.removeChild(new_element); + const module4 = await import( + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource4.js" + ); + assert_equals(module4.result, "resource4 from network"); + + // Module scripts are stored to the Document's module map once loaded. + // So import()ing the same module script will reuse the previously loaded + // script. + const module_second = await import( + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource1.js" + ); + assert_equals(module_second.result, "resource1 from dynamic1.wbn"); + }, "Dynamically adding / updating / removing the webbundle element."); + + promise_test(async () => { + const classic_script_url = + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/classic_script.js"; + const element = createWebBundleElement("../resources/wbn/dynamic1.wbn", [ + classic_script_url, + ]); + document.body.appendChild(element); + assert_equals( + await loadScriptAndWaitReport(classic_script_url), + "classic script from dynamic1.wbn" + ); + const new_element = removeAndAppendNewElementWithUpdatedRule(element, { + url: "../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" + ); + document.body.removeChild(new_element); + // 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"); + + promise_test(async (t) => { + // To avoid caching mechanism, this test is using fetch() API with + // { cache: 'no-store' } to load the resource. + const classic_script_url = + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/classic_script.js"; + + assert_equals( + await (await fetch(classic_script_url)).text(), + "window.report_result('classic script from network');\n" + ); + + const element1 = createWebBundleElement("../resources/wbn/dynamic1.wbn", [ + classic_script_url, + ]); + document.body.appendChild(element1); + t.add_cleanup(() => { + if (element1.parentElement) + element1.parentElement.removeChild(element1); + }); + + assert_equals( + await (await fetch(classic_script_url, { cache: "no-store" })).text(), + "window.report_result('classic script from dynamic1.wbn');\n" + ); + + const element2 = createWebBundleElement("../resources/wbn/dynamic2.wbn", [ + classic_script_url, + ]); + document.body.appendChild(element2); + t.add_cleanup(() => { + if (element2.parentElement) + element2.parentElement.removeChild(element2); + }); + + assert_equals( + await (await fetch(classic_script_url, { cache: "no-store" })).text(), + "window.report_result('classic script from dynamic2.wbn');\n" + ); + + document.body.removeChild(element2); + + assert_equals( + await (await fetch(classic_script_url, { cache: "no-store" })).text(), + "window.report_result('classic script from dynamic1.wbn');\n" + ); + + document.body.removeChild(element1); + + assert_equals( + await (await fetch(classic_script_url, { cache: "no-store" })).text(), + "window.report_result('classic script from network');\n" + ); + }, "Multiple web bundle elements. The last added element must be refered."); + + promise_test(async () => { + const classic_script_url = + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/classic_script.js"; + const scope = + "https://{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/"; + const element = createWebBundleElement( + "../resources/wbn/dynamic1.wbn", + [], + { scopes: [scope] } + ); + document.body.appendChild(element); + assert_equals( + await loadScriptAndWaitReport(classic_script_url), + "classic script from dynamic1.wbn" + ); + const new_element = removeAndAppendNewElementWithUpdatedRule(element, { + url: "../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. + const new_element2 = removeAndAppendNewElementWithUpdatedRule( + new_element, + { 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. + const new_element3 = removeAndAppendNewElementWithUpdatedRule( + new_element2, + { scopes: [scope + "dummy", scope + "classic_"] } + ); + assert_equals( + await loadScriptAndWaitReport(classic_script_url), + "classic script from dynamic2.wbn" + ); + document.body.removeChild(new_element3); + // 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 scopes"); + + promise_test(() => { + return addWebBundleElementAndWaitForLoad( + "../resources/wbn/dynamic1.wbn?test-event", + /*resources=*/ [], + { crossOrigin: undefined } + ); + }, "The webbundle element fires a load event on load success"); + + promise_test((t) => { + return addWebBundleElementAndWaitForError( + "../resources/wbn/nonexistent.wbn", + /*resources=*/ [], + { crossOrigin: undefined } + ); + }, "The webbundle element fires an error event on load failure"); + + promise_test(async () => { + const module_script_url = + "https://www1.{{domains[]}}:{{ports[https][0]}}/web-bundle/resources/wbn/dynamic/resource1.js"; + const element = createWebBundleElement( + "../resources/wbn/dynamic1-crossorigin.wbn", + [module_script_url] + ); + document.body.appendChild(element); + const module = await import(module_script_url); + assert_equals(module.result, "resource1 from network"); + }, "Subresource URL must be same-origin with bundle URL"); + + promise_test(async () => { + const url = "uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720"; + const element = createWebBundleElement( + "../resources/wbn/uuid-in-package.wbn", + [url] + ); + document.body.appendChild(element); + assert_equals(await loadScriptAndWaitReport(url), "OK"); + document.body.removeChild(element); + }, "Subresource loading with uuid-in-package: URL with resources attribute"); + + promise_test(async () => { + const url = "uuid-in-package:020111b3-437a-4c5c-ae07-adb6bbffb720"; + const element = createWebBundleElement( + "../resources/wbn/uuid-in-package.wbn", + [], + { scopes: ["uuid-in-package:"] } + ); + document.body.appendChild(element); + assert_equals(await loadScriptAndWaitReport(url), "OK"); + document.body.removeChild(element); + }, "Subresource loading with uuid-in-package: URL with scopes attribute"); + + async function loadScriptAndWaitReport(script_url) { + const result_promise = new Promise((resolve) => { + // This function will be called from script.js + window.report_result = resolve; + }); + + const script = document.createElement("script"); + script.src = script_url; + document.body.appendChild(script); + return result_promise; + } + + function removeAndAppendNewElementWithUpdatedRule(element, new_rule) { + const new_element = createNewWebBundleElementWithUpdatedRule( + element, + new_rule + ); + element.remove(); + document.body.appendChild(new_element); + return new_element; + } + </script> +</body> diff --git a/testing/web-platform/tests/web-bundle/subresource-loading/supports-webbundle.https.tentative.html b/testing/web-platform/tests/web-bundle/subresource-loading/supports-webbundle.https.tentative.html new file mode 100644 index 0000000000..9beae287a0 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/subresource-loading/supports-webbundle.https.tentative.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<title>HTMLScriptElement.supports webbundle</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + assert_true(HTMLScriptElement.supports('webbundle')); +}, 'HTMLScriptElement.supports returns true for \'webbundle\''); + +test(() => { + assert_false(HTMLScriptElement.supports(' webbundle')); + assert_false(HTMLScriptElement.supports('webbundle ')); + assert_false(HTMLScriptElement.supports('WEBBUNDLE')); +}, 'HTMLScriptElement.supports returns false for unsupported types'); +</script> diff --git a/testing/web-platform/tests/web-bundle/wbn-from-network/wbn-location.tentative.html b/testing/web-platform/tests/web-bundle/wbn-from-network/wbn-location.tentative.html new file mode 100644 index 0000000000..20b554b464 --- /dev/null +++ b/testing/web-platform/tests/web-bundle/wbn-from-network/wbn-location.tentative.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>Location of a page in a Web Bundle</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> +<script> +promise_test(async (t) => { + const location_promise = new Promise((resolve, reject) => { + let win = null; + window.addEventListener( + 'message', + (event) => { + win.close(); + resolve(event.data.location); + }, false); + win = window.open( + get_host_info().HTTPS_ORIGIN + '/web-bundle/resources/wbn/location.wbn', + '_blank'); + if (!win) { + reject('Popup could not be opened'); + } + }); + assert_equals( + await location_promise, + get_host_info().HTTPS_ORIGIN + '/web-bundle/resources/wbn/location.html'); +}, 'Location of a page in a Web Bundle'); + +</script> +</body> |