Merge pull request #6936 from frivoal/text-overflow-line-edge
[css-ui] Update test to match spec change
diff --git a/2dcontext/imagebitmap/createImageBitmap-drawImage.html b/2dcontext/imagebitmap/createImageBitmap-drawImage.html
index 5c69041..39c5ac1 100644
--- a/2dcontext/imagebitmap/createImageBitmap-drawImage.html
+++ b/2dcontext/imagebitmap/createImageBitmap-drawImage.html
@@ -37,7 +37,7 @@
promise_test(function() {
var testCanvas = document.createElement("canvas");
initializeTestCanvas(testCanvas);
- testDrawImageBitmap(testCanvas);
+ return testDrawImageBitmap(testCanvas);
}, "createImageBitmap from a HTMLCanvasElement, and drawImage on the created ImageBitmap");
promise_test(function() {
diff --git a/2dcontext/imagebitmap/createImageBitmap-invalid-args.html b/2dcontext/imagebitmap/createImageBitmap-invalid-args.html
new file mode 100644
index 0000000..a0bcf48
--- /dev/null
+++ b/2dcontext/imagebitmap/createImageBitmap-invalid-args.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+
+function makeCanvas() {
+ return new Promise(resolve => {
+ let canvas = document.createElement('canvas');
+ canvas.setAttribute('width', '10');
+ canvas.setAttribute('height', '10');
+ resolve(canvas);
+ });
+}
+
+function makeOffscreenCanvas() {
+ return new Promise(resolve => {
+ let canvas = new OffscreenCanvas(10, 10);
+ resolve(canvas);
+ });
+}
+
+function makeOversizedCanvas() {
+
+ return new Promise(resolve => {
+ let canvas = document.createElement('canvas');
+ canvas.setAttribute('width', '100000000');
+ canvas.setAttribute('height', '100000000');
+ resolve(canvas);
+ });
+}
+
+function makeOversizedOffscreenCanvas() {
+ return new Promise(resolve =>{
+ let canvas = new OffscreenCanvas(100000000, 100000000);
+ resolve(canvas);
+ });
+}
+
+function makeVideo() {
+ return new Promise(resolve => {
+ let video = document.createElement('video');
+ video.addEventListener('canplaythrough', resolve.bind(undefined, video), false);
+ video.src = '/media/A4.ogv';
+ });
+}
+
+function makeImage() {
+ return makeCanvas().then(canvas => {
+ let image = new Image();
+ image.src = canvas.toDataURL();
+ return new Promise(resolve => {
+ image.onload = resolve.bind(undefined, image);
+ });
+ });
+}
+
+function makeImageData() {
+ return makeCanvas().then(canvas => {
+ return new Promise(function(resolve, reject) {
+ resolve(canvas.getContext('2d').getImageData(0, 0, 10, 10));
+ });
+ });
+}
+
+function makeImageBitmap() {
+ return makeCanvas().then(canvas => {
+ return createImageBitmap(canvas);
+ });
+}
+
+function makeBlob() {
+ return makeCanvas().then(canvas => {
+ return new Promise(resolve => {
+ canvas.toBlob(resolve);
+ });
+ });
+}
+
+function makeInvalidBlob() {
+ return new Promise(resolve => {
+ resolve(new Blob()); // Blob with no data cannot be decoded.
+ });
+}
+
+imageSourceTypes = [
+ { name: 'HTMLImageElement', factory: makeImage },
+ { name: 'HTMLVideoElement', factory: makeVideo },
+ { name: 'HTMLCanvasElement', factory: makeCanvas },
+ { name: 'OffscreenCanvas', factory: makeOffscreenCanvas },
+ { name: 'ImageData', factory: makeImageData },
+ { name: 'ImageBitmap', factory: makeImageBitmap },
+ { name: 'Blob', factory: makeBlob },
+];
+
+testCases = [
+ {
+ description: 'createImageBitmap with a <sourceType> source and sw set to ' +
+ '0 rejects with a RangeError.',
+ promiseTestFunction:
+ (source, t) => {
+ return promise_rejects(t, new RangeError(),
+ createImageBitmap(source, 0, 0, 0, 10));
+ }
+ },
+ {
+ description: 'createImageBitmap with a <sourceType> source and sh set to ' +
+ '0 rejects with a RangeError.',
+ promiseTestFunction:
+ (source, t) => {
+ return promise_rejects(t, new RangeError(),
+ createImageBitmap(source, 0, 0, 10, 0));
+ }
+ },
+ {
+ // This case is not explicitly documented in the specification for
+ // createImageBitmap, but it is expected that internal failures cause
+ //
+ description: 'createImageBitmap with a <sourceType> source and oversized ' +
+ '(unallocatable) crop region rejects with an InvalidStateError ' +
+ 'DOMException.',
+ promiseTestFunction:
+ (source, t) => {
+ return promise_rejects(t, new DOMException('', 'InvalidStateError'),
+ createImageBitmap(source, 0, 0, 100000000, 100000000));
+ }
+ },
+];
+
+// Generate the test matrix for each sourceType + testCase combo.
+imageSourceTypes.forEach(imageSourceType => {
+ testCases.forEach(testCase => {
+ let description = testCase.description.replace('<sourceType>',
+ imageSourceType.name);
+ promise_test( t => {
+ return imageSourceType.factory().then(source => {
+ return testCase.promiseTestFunction(source, t);
+ });
+ }, description);
+ });
+});
+
+promise_test( t => {
+ return promise_rejects(t, new TypeError(), createImageBitmap(undefined));
+}, "createImageBitmap with undefined image source rejects with a TypeError.");
+
+promise_test( t => {
+ return promise_rejects(t, new TypeError(), createImageBitmap(null));
+}, "createImageBitmap with null image source rejects with a TypeError.");
+
+promise_test( t => {
+ return promise_rejects(t, new DOMException('', 'InvalidStateError'),
+ createImageBitmap(new Image()));
+}, "createImageBitmap with empty image source rejects with a InvalidStateError.");
+
+promise_test( t => {
+ return promise_rejects(t, new DOMException('', 'InvalidStateError'),
+ createImageBitmap(document.createElement('video')));
+}, "createImageBitmap with empty video source rejects with a InvalidStateError.");
+
+promise_test( t => {
+ return makeOversizedCanvas().then(canvas => {
+ return promise_rejects(t, new DOMException('', 'InvalidStateError'),
+ createImageBitmap(canvas));
+ });
+}, "createImageBitmap with an oversized canvas source rejects with a RangeError.");
+
+promise_test( t => {
+ return makeOversizedOffscreenCanvas().then(offscreenCanvas => {
+ return promise_rejects(t, new DOMException('', 'InvalidStateError'),
+ createImageBitmap(offscreenCanvas));
+ });
+}, "createImageBitmap with an invalid OffscreenCanvas source rejects with a RangeError.");
+
+promise_test( t => {
+ return makeInvalidBlob().then(blob => {
+ return promise_rejects(t, new DOMException('', 'InvalidStateError'),
+ createImageBitmap(blob));
+ });
+}, "createImageBitmap with an undecodable blob source rejects with an InvalidStateError.");
+
+</script>
+</body>
+</html>
diff --git a/README.md b/README.md
index 9d0d8f3..a3c41a0 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,6 @@
The tests are designed to be run from your local computer. The test
environment requires [Python 2.7+](http://www.python.org/downloads) (but not Python 3.x).
-You will also need a copy of OpenSSL.
On Windows, be sure to add the Python directory (`c:\python2x`, by default) to
your `%Path%` [Environment Variable](http://www.computerhope.com/issues/ch000549.htm),
@@ -70,21 +69,12 @@
"http": [1234, "auto"]
```
-If you installed OpenSSL in such a way that running `openssl` at a
-command line doesn't work, you also need to adjust the path to the
-OpenSSL binary. This can be done by adding a section to `config.json`
-like:
-
-```
-"ssl": {"openssl": {"binary": "/path/to/openssl"}}
-```
-
Running Tests Automatically
---------------------------
Tests can be run automatically in a browser using the `run` command of
the `wpt` script in the root of the checkout. This requires the hosts
-file and OpenSSL setup documented above, but you must *not* have the
+file setup documented above, but you must *not* have the
test server already running when calling `wpt run`. The basic command
line syntax is:
@@ -187,9 +177,31 @@
On Windows `wpt` commands mut bre prefixed with `python` or the path
to the python binary (if `python` is not in your `%PATH%`).
-Running `wpt serve` or `wpt run` with SSL enabled on Windows typically
-requires installing an OpenSSL
-distribution.
+Alternatively, you may also use
+[Bash on Ubuntu on Windows](https://msdn.microsoft.com/en-us/commandline/wsl/about)
+in the Windows 10 Anniversary Update build, then access your windows
+partition from there to launch `wpt` commands.
+
+Certificates
+============
+
+By default pregenerated certificates for the web-platform.test domain
+are provided in the repository. If you wish to generate new
+certificates for any reason it's possible to use OpenSSL when starting
+the server, or starting a test run, by providing the
+`--ssl-type=openssl` argument to the `wpt serve` or `wpt run`
+commands.
+
+If you installed OpenSSL in such a way that running `openssl` at a
+command line doesn't work, you also need to adjust the path to the
+OpenSSL binary. This can be done by adding a section to `config.json`
+like:
+
+```
+"ssl": {"openssl": {"binary": "/path/to/openssl"}}
+```
+
+On Windows using OpenSSL typically requires installing an OpenSSL distribution.
[Shining Light](https://slproweb.com/products/Win32OpenSSL.html)
provide a convenient installer that is known to work, but requires a
little extra setup, i.e.:
@@ -211,10 +223,6 @@
value that is the path to the OpenSSL config file (typically this
will be `C:\\OpenSSL-Win32\\bin\\openssl.cfg`).
-Alternatively, you may also use
-[Bash on Ubuntu on Windows](https://msdn.microsoft.com/en-us/commandline/wsl/about)
-in the Windows 10 Anniversary Update build, then access your windows
-partition from there to launch `wpt` commands.
Publication
===========
diff --git a/WebIDL/interfaces.html b/WebIDL/interfaces.html
new file mode 100644
index 0000000..c3e6695
--- /dev/null
+++ b/WebIDL/interfaces.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>WebIDL IDL tests</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/WebIDLParser.js></script>
+<script src=/resources/idlharness.js></script>
+
+<div id=log></div>
+<script>
+"use strict";
+var idlArray = new IdlArray();
+
+function doTest(idl) {
+ idlArray.add_idls(idl);
+ idlArray.add_objects({
+ DOMException: ['new DOMException()',
+ 'new DOMException("my message")',
+ 'new DOMException("my message", "myName")'],
+ });
+ idlArray.test();
+}
+
+promise_test(function() {
+ return fetch("/interfaces/webidl.idl").then(response => response.text())
+ .then(doTest);
+}, "Test driver");
+</script>
diff --git a/XMLHttpRequest/abort-after-send.htm b/XMLHttpRequest/abort-after-send.htm
index aa4632f..523a0d6 100644
--- a/XMLHttpRequest/abort-after-send.htm
+++ b/XMLHttpRequest/abort-after-send.htm
@@ -38,7 +38,7 @@
client.abort()
assert_true(control_flag)
assert_equals(client.readyState, 0)
- assert_xhr_event_order_matches([1, "loadstart(0,0,false)", 4, "upload.abort(0,0,false)", "upload.loadend(0,0,false)", "abort(0,0,false)", "loadend(0,0,false)"])
+ assert_xhr_event_order_matches([1, "loadstart(0,0,false)", 4, "abort(0,0,false)", "loadend(0,0,false)"])
test.done()
})
</script>
diff --git a/XMLHttpRequest/access-control-basic-allow-non-cors-safelisted-method-async.htm b/XMLHttpRequest/access-control-basic-allow-non-cors-safelisted-method-async.htm
new file mode 100644
index 0000000..57721aa
--- /dev/null
+++ b/XMLHttpRequest/access-control-basic-allow-non-cors-safelisted-method-async.htm
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Tests cross-origin async request with non-CORS-safelisted method</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ </head>
+ <body>
+ <script type="text/javascript">
+ async_test((test) => {
+ const xhr = new XMLHttpRequest;
+
+ xhr.onload = test.step_func_done(() => {
+ assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.\nPASS: PUT data received");
+ });
+
+ xhr.onerror = test.unreached_func("Unexpected error.");
+
+ xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN +
+ "/XMLHttpRequest/resources/access-control-basic-put-allow.py");
+ xhr.setRequestHeader("Content-Type", "text/plain; charset=UTF-8");
+ xhr.send("PASS: PUT data received");
+ }, "Allow async PUT request");
+ </script>
+ </body>
+</html>
diff --git a/XMLHttpRequest/access-control-basic-non-cors-safelisted-content-type.htm b/XMLHttpRequest/access-control-basic-non-cors-safelisted-content-type.htm
new file mode 100644
index 0000000..bff0cf5
--- /dev/null
+++ b/XMLHttpRequest/access-control-basic-non-cors-safelisted-content-type.htm
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Tests cross-origin request with non-CORS-safelisted content type</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ </head>
+ <body>
+ <script type="text/javascript">
+ test(() => {
+ const xhr = new XMLHttpRequest;
+
+ xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN +
+ "/XMLHttpRequest/resources/access-control-basic-put-allow.py", false);
+ xhr.setRequestHeader("Content-Type", "text/plain");
+ xhr.send("PASS: PUT data received");
+
+ assert_equals(xhr.responseText, "PASS: Cross-domain access allowed.\nPASS: PUT data received");
+
+ xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN +
+ "/XMLHttpRequest/resources/access-control-basic-put-allow.py", false);
+ xhr.setRequestHeader("Content-Type", "application/xml");
+
+ try {
+ xhr.send("FAIL: PUT data received");
+ } catch(e) {
+ assert_equals(xhr.status, 0, "Cross-domain access was denied in 'send'.");
+ return;
+ }
+ assert_unreached("Cross-domain access was not denied in 'send'.");
+ }, "Deny cross-origin request with non-CORS-safelisted content type");
+ </script>
+ </body>
+</html>
diff --git a/XMLHttpRequest/access-control-basic-post-success-no-content-type.htm b/XMLHttpRequest/access-control-basic-post-success-no-content-type.htm
new file mode 100644
index 0000000..8785a44
--- /dev/null
+++ b/XMLHttpRequest/access-control-basic-post-success-no-content-type.htm
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Tests that POST requests with text content and no content-type set explicitly don't generate a preflight request.</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ </head>
+ <body>
+ <script type="text/javascript">
+ async_test(function(test) {
+ const xhr = new XMLHttpRequest;
+
+ xhr.open("POST", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-basic-options-not-supported.py");
+
+ xhr.onerror = test.unreached_func("Network error.");
+
+ xhr.onload = test.step_func_done(function() {
+ assert_equals(xhr.status, 200);
+ });
+
+ xhr.send("Test");
+ }, "POST request with text content and no Content-Type header");
+ </script>
+ </body>
+</html>
diff --git a/XMLHttpRequest/access-control-basic-preflight-denied.htm b/XMLHttpRequest/access-control-basic-preflight-denied.htm
new file mode 100644
index 0000000..d02bdf0
--- /dev/null
+++ b/XMLHttpRequest/access-control-basic-preflight-denied.htm
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Tests async XHR preflight denial due to lack of CORS headers</title>
+ <!--The original test addressed a more specific issue involving caching,
+ but that issue has since been resolved.
+ We maintain this test as a basic test of invalid preflight denial.
+ Please refer to the comment in the following link for more information:
+ https://chromium-review.googlesource.com/c/chromium/src/+/630338#message-0280542b95c9b0f82b121dc373320c04fcaece31
+ -->
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ </head>
+ <body>
+ <script type="text/javascript">
+ async_test((test) => {
+ const xhr = new XMLHttpRequest;
+ xhr.onerror = test.step_func_done(() => {
+ assert_equals(xhr.status, 0);
+ });
+
+ xhr.onload = test.unreached_func("Request succeeded unexpectedly");
+
+ xhr.open("FOO", get_host_info().HTTP_REMOTE_ORIGIN +
+ "/XMLHttpRequest/resources/access-control-basic-denied.py");
+ xhr.send();
+ });
+ </script>
+ </body>
+</html>
diff --git a/XMLHttpRequest/access-control-preflight-async-header-denied.htm b/XMLHttpRequest/access-control-preflight-async-header-denied.htm
new file mode 100644
index 0000000..84a60eb
--- /dev/null
+++ b/XMLHttpRequest/access-control-preflight-async-header-denied.htm
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Async request denied at preflight because of non-CORS-safelisted header</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ <script src="/common/utils.js"></script>
+ </head>
+ <body>
+ <script type="text/javascript">
+ const uuid = token();
+ const url = get_host_info().HTTP_REMOTE_ORIGIN +
+ "/XMLHttpRequest/resources/access-control-preflight-denied.py?token=" + uuid;
+
+ async_test((test) => {
+ let xhr = new XMLHttpRequest;
+ xhr.open("GET", url + "&command=reset", false);
+ xhr.send();
+
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", url + "&command=header", true);
+ xhr.setRequestHeader("x-test", "foo");
+
+ xhr.onload = test.unreached_func(
+ "Cross-domain access with custom header allowed without throwing exception");
+
+ xhr.onerror = test.step_func_done(() => {
+ xhr = new XMLHttpRequest;
+ xhr.open("GET", url + "&command=complete", false);
+ xhr.send();
+ assert_equals(xhr.responseText, "Request successfully blocked.");
+ });
+
+ xhr.send();
+ }, "Async request denied at preflight");
+ </script>
+ </body>
+</html>
diff --git a/XMLHttpRequest/access-control-preflight-credential-async.htm b/XMLHttpRequest/access-control-preflight-credential-async.htm
new file mode 100644
index 0000000..d9ccc10
--- /dev/null
+++ b/XMLHttpRequest/access-control-preflight-credential-async.htm
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Tests proper handling of cross-origin async request with credentials</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ </head>
+ <body>
+ <script type="text/javascript">
+ async_test((test) => {
+ const xhr = new XMLHttpRequest;
+
+ xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN +
+ "/XMLHttpRequest/resources/access-control-auth-basic.py?uid=fooUser",
+ true, "fooUser", "barPass");
+ xhr.withCredentials = true;
+
+ xhr.onerror = test.unreached_func("Unexpected error.");
+
+ xhr.onload = test.step_func_done(() => {
+ assert_equals(xhr.status, 401, "Request raises HTTP 401: Unauthorized error.");
+ });
+
+ xhr.send();
+ }, "CORS async request with URL credentials");
+ </script>
+ </body>
+</html>
diff --git a/XMLHttpRequest/access-control-preflight-credential-sync.htm b/XMLHttpRequest/access-control-preflight-credential-sync.htm
new file mode 100644
index 0000000..d0b9901
--- /dev/null
+++ b/XMLHttpRequest/access-control-preflight-credential-sync.htm
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Tests proper handling of cross-origin sync request with credentials</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ </head>
+ <body>
+ <script type="text/javascript">
+ test(() => {
+ const xhr = new XMLHttpRequest;
+
+ xhr.open("PUT", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-auth-basic.py?uid=fooUser", false, "fooUser", "barPass");
+
+ xhr.withCredentials = true;
+
+ xhr.send();
+
+ assert_equals(xhr.status, 401, "Request raises HTTP 401: Unauthorized error.");
+ }, "CORS sync request with URL credentials");
+ </script>
+ </body>
+</html>
diff --git a/XMLHttpRequest/access-control-preflight-request-header-lowercase.htm b/XMLHttpRequest/access-control-preflight-request-header-lowercase.htm
new file mode 100644
index 0000000..d88cac8
--- /dev/null
+++ b/XMLHttpRequest/access-control-preflight-request-header-lowercase.htm
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Access-Control-Request-Headers values should be lowercase</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ </head>
+ <body>
+ <script type="text/javascript">
+ async_test(function(test) {
+ const xhr = new XMLHttpRequest;
+
+ xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-preflight-request-header-lowercase.py");
+
+ xhr.setRequestHeader("X-Test", "foobar");
+
+ xhr.onerror = test.unreached_func("Error occurred.");
+
+ xhr.onload = test.step_func_done(function() {
+ assert_equals(xhr.status, 200);
+ assert_equals(xhr.responseText, "PASS");
+ });
+
+ xhr.send();
+ }, "Request with uppercase header set");
+ </script>
+ </body>
+</html>
diff --git a/XMLHttpRequest/access-control-preflight-request-invalid-status-301.htm b/XMLHttpRequest/access-control-preflight-request-invalid-status-301.htm
new file mode 100644
index 0000000..669f7db
--- /dev/null
+++ b/XMLHttpRequest/access-control-preflight-request-invalid-status-301.htm
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Tests that preflight requests returning invalid 301 status code result in error.</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ </head>
+ <body>
+ <script type="text/javascript">
+ async_test((test) => {
+ const xhr = new XMLHttpRequest;
+
+ xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-preflight-request-invalid-status.py?code=301");
+
+ xhr.setRequestHeader("x-pass", "pass");
+
+ xhr.onerror = test.step_func_done(function() {
+ assert_equals(xhr.status, 0);
+ });
+
+ xhr.onload = test.unreached_func("Invalid 301 response to preflight should result in error.");
+
+ xhr.send();
+ }, "Request with 301 preflight response");
+ </script>
+ </body>
+</html>
diff --git a/XMLHttpRequest/access-control-preflight-request-invalid-status-501.htm b/XMLHttpRequest/access-control-preflight-request-invalid-status-501.htm
new file mode 100644
index 0000000..5f3c5e6
--- /dev/null
+++ b/XMLHttpRequest/access-control-preflight-request-invalid-status-501.htm
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Tests that preflight requests returning invalid 501 status code result in error.</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/common/get-host-info.sub.js"></script>
+ </head>
+ <body>
+ <script type="text/javascript">
+ async_test((test) => {
+ const xhr = new XMLHttpRequest;
+
+ xhr.open("GET", get_host_info().HTTP_REMOTE_ORIGIN + "/XMLHttpRequest/resources/access-control-preflight-request-invalid-status.py?code=501");
+
+ xhr.setRequestHeader("x-pass", "pass");
+
+ xhr.onerror = test.step_func_done(function() {
+ assert_equals(xhr.status, 0);
+ });
+
+ xhr.onload = test.unreached_func("Invalid 501 response to preflight should result in error.");
+
+ xhr.send();
+ }, "Request with 501 preflight response");
+ </script>
+ </body>
+</html>
diff --git a/XMLHttpRequest/data-uri.htm b/XMLHttpRequest/data-uri.htm
index 5123789..88a7d78 100644
--- a/XMLHttpRequest/data-uri.htm
+++ b/XMLHttpRequest/data-uri.htm
@@ -10,13 +10,13 @@
if (typeof mimeType === 'undefined' || mimeType === null) mimeType = 'text/plain';
var test = async_test("XHR method " + method + " with MIME type " + mimeType + (testNamePostfix||''));
test.step(function() {
- var client = new XMLHttpRequest();
+ var client = new XMLHttpRequest(),
+ body = method === "HEAD" ? "" : "Hello, World!";
client.onreadystatechange = test.step_func(function () {
if (client.readyState !== 4) {
return;
}
-
- assert_equals(client.responseText, "Hello, World!");
+ assert_equals(client.responseText, body);
assert_equals(client.status, 200);
assert_equals(client.getResponseHeader('Content-Type'), mimeType);
var allHeaders = client.getAllResponseHeaders();
diff --git a/XMLHttpRequest/resources/access-control-auth-basic.py b/XMLHttpRequest/resources/access-control-auth-basic.py
new file mode 100644
index 0000000..af32aab
--- /dev/null
+++ b/XMLHttpRequest/resources/access-control-auth-basic.py
@@ -0,0 +1,17 @@
+def main(request, response):
+ response.headers.set("Cache-Control", "no-store")
+ response.headers.set("Access-Control-Allow-Origin", request.headers.get("origin"))
+ response.headers.set("Access-Control-Allow-Credentials", "true")
+ uid = request.GET.first("uid", None)
+
+ if request.method == "OPTIONS":
+ response.headers.set("Access-Control-Allow-Methods", "PUT")
+ else:
+ username = request.auth.username
+ password = request.auth.password
+ if (not username) or (username != uid):
+ response.headers.set("WWW-Authenticate", "Basic realm='Test Realm/Cross Origin'")
+ response.status = 401
+ response.content = "Authentication cancelled"
+ else:
+ response.content = "User: " + username + ", Password: " + password
diff --git a/XMLHttpRequest/resources/access-control-basic-denied.py b/XMLHttpRequest/resources/access-control-basic-denied.py
index 5bd5dae..9f86878 100644
--- a/XMLHttpRequest/resources/access-control-basic-denied.py
+++ b/XMLHttpRequest/resources/access-control-basic-denied.py
@@ -1,4 +1,5 @@
def main(request, response):
- response.headers.set("Content-Type", "text/plain");
+ response.headers.set("Cache-Control", "no-store")
+ response.headers.set("Content-Type", "text/plain")
response.text = "FAIL: Cross-domain access allowed."
diff --git a/XMLHttpRequest/resources/access-control-preflight-denied.py b/XMLHttpRequest/resources/access-control-preflight-denied.py
new file mode 100644
index 0000000..fd221a6
--- /dev/null
+++ b/XMLHttpRequest/resources/access-control-preflight-denied.py
@@ -0,0 +1,50 @@
+def main(request, response):
+ def fail(message):
+ response.content = "FAIL: " + str(message)
+ response.status = 400
+
+ def getState(token):
+ server_state = request.server.stash.take(token)
+ if not server_state:
+ return "Uninitialized"
+ return server_state
+
+ def setState(token, state):
+ request.server.stash.put(token, state)
+
+ def resetState(token):
+ setState(token, "")
+
+ response.headers.set("Cache-Control", "no-store")
+ response.headers.set("Access-Control-Allow-Origin", request.headers.get("origin"))
+ response.headers.set("Access-Control-Max-Age", 1)
+ token = request.GET.first("token", None)
+ state = getState(token)
+ command = request.GET.first("command", None)
+
+ if command == "reset":
+ if request.method == "GET":
+ resetState(token)
+ response.content = "Server state reset"
+ else:
+ fail("Invalid Method.")
+ elif state == "Uninitialized":
+ if request.method == "OPTIONS":
+ response.content = "This request should not be displayed."
+ setState(token, "Denied")
+ else:
+ fail(state)
+ elif state == "Denied":
+ if request.method == "GET" and command == "complete":
+ resetState(token)
+ response.content = "Request successfully blocked."
+ else:
+ setState("Deny Ignored")
+ fail("The request was not denied.")
+ elif state == "Deny Ignored":
+ resetState(token)
+ fail(state)
+ else:
+ resetState(token)
+ fail("Unknown Error.")
+
diff --git a/XMLHttpRequest/resources/access-control-preflight-request-header-lowercase.py b/XMLHttpRequest/resources/access-control-preflight-request-header-lowercase.py
new file mode 100644
index 0000000..d35b89b
--- /dev/null
+++ b/XMLHttpRequest/resources/access-control-preflight-request-header-lowercase.py
@@ -0,0 +1,16 @@
+def main(request, response):
+ response.headers.set("Cache-Control", "no-store")
+ response.headers.set("Access-Control-Allow-Origin", "*")
+ response.headers.set("Access-Control-Max-Age", 0)
+
+ if request.method == "OPTIONS":
+ if "x-test" in [header.strip(" ") for header in
+ request.headers.get("Access-Control-Request-Headers").split(",")]:
+ response.headers.set("Access-Control-Allow-Headers", "X-Test")
+ else:
+ response.status = 400
+ elif request.method == "GET":
+ if request.headers.get("X-Test"):
+ response.content = "PASS"
+ else:
+ response.status = 400
diff --git a/XMLHttpRequest/resources/access-control-preflight-request-invalid-status.py b/XMLHttpRequest/resources/access-control-preflight-request-invalid-status.py
new file mode 100644
index 0000000..e613229
--- /dev/null
+++ b/XMLHttpRequest/resources/access-control-preflight-request-invalid-status.py
@@ -0,0 +1,16 @@
+def main(request, response):
+ try:
+ code = int(request.GET.first("code", None))
+ except:
+ code = None
+
+ if request.method == "OPTIONS":
+ if code:
+ response.status = code
+ response.headers.set("Access-Control-Max-Age", 1)
+ response.headers.set("Access-Control-Allow-Headers", "x-pass")
+ else:
+ response.status = 200
+
+ response.headers.set("Cache-Control", "no-store")
+ response.headers.set("Access-Control-Allow-Origin", request.headers.get("origin"))
diff --git a/app-uri/OWNERS b/app-uri/OWNERS
deleted file mode 100644
index 261dea7..0000000
--- a/app-uri/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-@happyoungj
diff --git a/app-uri/README.md b/app-uri/README.md
deleted file mode 100644
index a33f0b0..0000000
--- a/app-uri/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-app-URI test suite
-==========
-
-Test suites for [app-URI scheme](http://app-uri.sysapps.org/) specification
-
-You can find here:
-
-[app-URI test suite html file](appURI_test.html)
-
-
-If you want to use System application packages there is manifest file on Sysapps github repo:
-
-[Manifest file - can be used for building application](https://github.com/sysapps/testsuites/blob/gh-pages/app-URI/manifest.webapp)
\ No newline at end of file
diff --git a/app-uri/appURI_test.html b/app-uri/appURI_test.html
deleted file mode 100644
index 722c210..0000000
--- a/app-uri/appURI_test.html
+++ /dev/null
@@ -1,393 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <title>app:URI compliance tests</title>
- <script src='/resources/testharness.js'></script>
- <script src='/resources/testharnessreport.js'></script>
- <link rel="help" href="http://app-uri.sysapps.org/" data-tested-assertations="following::OL[1]/LI[2]" />
- <style>
- div {padding:5px 5px 10px 5px;}
- </style>
- </head>
- <body>
- <div id="logs"></div>
- <div id="log"></div>
-
-
- <script>
- /**********************************************************************************************
- * This test suite checks your implementation for compliance with the app-URI specification *
- **********************************************************************************************/
-
- /* Check if protocol is "app" */
- if (window.location.protocol === "app:") {
-
- /* Logging current domain */
- var domain = window.location.protocol + "//" + window.location.host + "/";
- document.getElementById("logs").innerHTML = "Current domain: " + domain;
-
- /* Function that will change HOST value if it is equal to currect application host */
- function ChangeHostIfEqCurrent(URI){
- /* Check if URI host is NOT the same as current application host */
- if (URI.substring(6, 35) === window.location.host) {
- if (URI.substring(6, 7) !== 4)
- URI = URI.replace(URI.substring(6, 7), "4");
- else
- URI = URI.replace(URI.substring(6, 7), "5");
- }
- return URI;
- }
-
- /********************************************************
- * 6.1 Synthesizing an app: URI
- *
- * link: http://app-uri.sysapps.org/#synthesizing
- *
- ********************************************************/
-
- /**
- * Syntax-Based Normalization
- *
- * rules for dereferencing an app: URI => 3. Let URI be the value of the [HTTP] Request URI.
- */
-
- var TC_name = "Test: Synthesizing an app:URI. Syntax-Based Normalization";
-
- // variable needed to test scheme-based normalization
- var tmp_domain = window.location.protocol + "//" + window.location.host;
-
- var CaseNormalizationXHRtests = [ /* scheme: [ TC name, URI] */
- [TC_name + " XHR full path: {} => %7b%7d",
- domain + 'resources/ExamPLE/%7bmY%7d/z...z/index.html'],
- [TC_name + " XHR full path: } => %7D",
- domain + 'resources/ExamPLE/%7bmY%7D/z...z/index.html'],
- [TC_name + " XHR full path: {} => %7B%7D",
- domain + 'resources/ExamPLE/%7BmY%7D/z...z/index.html'],
- [TC_name + " XHR full path: Capital letters in patch",
- domain + 'resources/ExamPLE/mmY/index.html'],
- [TC_name + " XHR relative path: {} => %7b%7d",
- 'resources/ExamPLE/%7bmY%7d/z...z/index.html'],
- [TC_name + " XHR relative path: } => %7D",
- 'resources/ExamPLE/%7bmY%7D/z...z/index.html'],
- [TC_name + " XHR relative path: P. => %50%2e",
- 'resources/Exam%50LE/%7bmY%7d/z%2e..z/index.html'],
- [TC_name + " XHR relative path: Capital letters in patch",
- 'resources/ExamPLE/mmY/index.html'],
- [TC_name + " XHR relative path: ~ => ~",
- 'resources/ImaGes/~t/{!a}/~sth.png'],
- [TC_name + " XHR relative path: ~{} => ~%7b%7d",
- 'resources/ImaGes/~t/%7b!a%7d/~sth.png'],
-
- /* Percent-Encoding Normalization*/
- [TC_name + " Percent-Encoding Normalization - XHR full path: c. => %63%2e",
- domain + 'resources/ExamPLE/%7bmY%7d/z%2e..z/index.html'],
- [TC_name + " Percent-Encoding Normalization - XHR full path: P. => %50%2e",
- domain + 'resources/Exam%50LE/%7bmY%7d/z%2e..z/index.html'],
- [TC_name + " Percent-Encoding Normalization - XHR relative path: {} => %7B%7D",
- 'resources/ExamPLE/%7BmY%7D/z...z/index.html'],
- [TC_name + " Percent-Encoding Normalization - XHR relative path: c. => %63%2e",
- 'resources/ExamPLE/%7bmY%7d/z%2e..z/index.html'],
- [TC_name + " XHR relative path: ~{} => ~%7b%7d",
- 'resources/ImaGes/~t/%7b!a%7d/~sth.png'],
-
- /* Path Segment Normalization */
- [TC_name + " Path Segment Normalization: using '../..' in path ",
- 'resources/ExamPLE/mmY/../../ImaGes/~t/%7b!a%7d/~sth.png'],
-
- /* Scheme-Based Normalization */
- [TC_name + " Scheme-Based Normalization: domain:/",
- tmp_domain + ':/resources/ImaGes/~t/%7b!a%7d/~sth.png']
- ];
-
- CaseNormalizationXHRtests.forEach(function (data, i) {
- var name = data[0];
- var URI = data[1];
- var xhrTest = async_test(name + " [case " + i + "]");
- var xhr;
- xhrTest.step(function () {
- xhr = new XMLHttpRequest();
- xhr.open("GET", URI, true);
- xhr.onreadystatechange = xhrTest.step_func(function (ev) {
- if (xhr.readyState === 4 && xhr.status === 200) {
- assert_true(true);
- xhrTest.done();
- }
- if (xhr.readyState !== 4 && xhr.status !== 200 && xhr.status !== 0) {
- assert_true(false,
- "[ Error: readyState=" + xhr.readyState + " satus=" + xhr.status + "] ");
- }
- });
- xhr.send();
- });
- delete name, URI, xhr, xhrTest;
- });
- delete CaseNormalizationXHRtests;
-
-
- /**
- * ContentType response
- *
- * rules for dereferencing an app: URI =>
- 8. Let potential-file be the result of attempting locate the file at path
- */
-
- TC_name = "Test: [HTTP] 200 response status (OK),value of content-type as the [HTTP] ";
- TC_name += "Content-Type header, and the contents of potential-file as the response body";
-
- var ContentTypeResponsetests = [ /* scheme:[ TC name, URI, content-type] */
- [TC_name + ' [text/html]', 'resources/ExamPLE/mmY/index.html', 'text/html'],
- [TC_name + " [image/png]", 'resources/ImaGes/~t/%7b!a%7d/~sth.png', "image/png"],
- [TC_name + " [text/plain]", 'resources/ExamPLE/mmY/sth.txt', "text/plain"]
- ];
-
- ContentTypeResponsetests.forEach(function (data, i) {
- var name = data[0];
- var URI = data[1];
- var content_type = data[2];
- var xhrTest = async_test(name + " [case " + i + "]");
- var xhr;
- xhrTest.step(function () {
- xhr = new XMLHttpRequest();
- xhr.open("GET", URI, true);
- xhr.onreadystatechange = xhrTest.step_func(function (ev) {
- if (xhr.readyState === 4 && xhr.status === 200) {
- assert_true(xhr.getResponseHeader("Content-Type") === content_type,
- "[Content-Type does not mach with expected, it is: "
- + xhr.getResponseHeader("Content-Type") + "]");
-
- xhrTest.done();
- }
- if (xhr.readyState !== 4 && xhr.status !== 200 && xhr.status !== 0) {
- assert_true(false,
- "[ Error: readyState=" + xhr.readyState + " satus=" + xhr.status + "] ");
- }
- });
- xhr.send();
- });
- delete name, URI, xhr, xhrTest, content_type;
- });
- delete ContentTypeResponsetests;
-
-
- /**
- * Case Normalization in Path
- *
- * rules for dereferencing an app: URI =>
- 4. Resolve URI into an absolute URL using the document's origin as the base.
- * rules for dereferencing an app: URI => 7. Let path be the path component of URI.
- *
- * Character Normalization in domain name
- */
-
-
-
- TC_name = "Test: Synthesizing an app:URI. Syntax-Based Normalization: Case Normalization";
- var PathCaseNormalizationtests = [ /* scheme: [ TC name, URI] */
- [TC_name, "resources/ImaGes/{{a}}/Test_1/$a/sth34!.png",
- domain + "resources/ImaGes/%7B%7Ba%7D%7D/Test_1/$a/sth34!.png", true],
- [TC_name, "resources/ImaGes/{{a}}/Test_1/$a/sth34!.png",
- domain + "resources/ImaGes/%7b%7Ba%7D%7D/Test_1/$a/sth34!.png", false],
-
- /* Character Normalization in Domain */
- [TC_name, window.location.protocol + "//" + window.location.host.toUpperCase()
- + "/resources/ImaGes/{{a}}/Test_1/$a/sth34!.png",
- domain + "resources/ImaGes/%7B%7Ba%7D%7D/Test_1/$a/sth34!.png", true]
- ];
-
- PathCaseNormalizationtests.forEach(function (data, i) {
- var name = data[0];
- var elem_path = data[1];
- var path_expected = data[2];
- var expected = data[3];
- test(function () {
- var img = document.createElement("img");
- img.src = elem_path;
- if (expected)
- assert_true(img.src === path_expected,
- "[Error, path=" + img.src + " Expected=" + domain + path_expected + "]");
- else
- assert_false(img.src === path_expected,
- "[Error, path=" + img.src + " Expected=" + domain + path_expected + "]");
- delete img;
-
- }, name + " [case " + i + "]");
- delete elem_path, path_expected, expected, name;
- });
- delete PathCaseNormalizationtests;
-
-
-
-
- /********************************************************************************************
- * 6.4 Dereferencing and retrieval of files from a container
- *
- * link: http://app-uri.sysapps.org/#dereferencing-and-retrieval-of-files-from-a-container
- *
- ********************************************************************************************/
-
-
-
- /**
- * 501 Method Not Implemented error - response body MUST be empty
- *
- * rules for dereferencing an app: URI =>
- 1. If the request is not a [HTTP] GET request,
- return a [HTTP] 501 Not Implemented response and terminate this algorithm.
- */
-
-
- function Get_501_reponse(name, URI, expected_response) {
- var xhrTest = async_test(name);
- xhrTest.step(function () {
- var xhr = new XMLHttpRequest();
- /* on purpose wrong method "gett" instead of "get" was used */
- xhr.open("gett", URI, true);
- xhr.onreadystatechange = xhrTest.step_func(function (ev) {
- if (xhr.readyState !== 4 && xhr.status === 501) {
- assert_true(xhr.response === expected_response,
- "[" + xhr.status + " error, response:[" + xhr.response + "] " + "]");
-
- xhrTest.done();
- }
- if (xhr.readyState === 4 && xhr.status === 200) {
- assert_true(false,
- "[Should get 501 but got 200 OK, "
- +"file found while it should not find it, "
- +" non existing 'gett' method used");
- }
- });
- xhr.send();
- });
- delete xhrTest;
- }
- Get_501_reponse(TC_name + " 501 Method Not Implemented error expected",
- 'resources/ExamPLE/mmY/index.html', "");
-
-
- /**
- * 400 Bad Request error - response body MUST be empty
- *
- * rules for dereferencing an app: URI =>
- 5. If the URI does not conform to the appuri ABNF, return a
- [HTTP] 400 Bad Request response and terminate this algorithm.
- */
-
- function Get_400_reponse(name, URI, expected_response) {
- var xhrTest = async_test(name);
- xhrTest.step(function () {
- var xhr = new XMLHttpRequest();
- //alert(URI);
- xhr.open("GET", URI, true);
- xhr.onreadystatechange = xhrTest.step_func(function (ev) {
- if (xhr.readyState !== 4 && xhr.status === 400) {
- assert_true(xhr.response === expected_response,
- "[" + xhr.status + " error, response:[" + xhr.response + "] " + "]");
-
- xhrTest.done();
- }
- if (xhr.readyState === 4 && xhr.status === 200) {
- assert_true(false,
- "[Should get 400 but got 200 OK, "
- +"file found while it should not find it, "
- +"since no specific file requested]");
- }
- });
- xhr.send();
- });
- delete xhrTest;
- }
- Get_400_reponse(TC_name + " 400 Method Not Implemented error expected, no path",
- tmp_domain, "");
- Get_400_reponse(TC_name + " 400 Method Not Implemented error expected, different domain with no path",
- ChangeHostIfEqCurrent("app://f15a6d20-cefa-13e5-1972-800e20d19a76"), "");
-
-
- /**
- * 403 Forbidden error - response body MUST be empty
- *
- * rules for dereferencing an app: URI =>
- 6. If the URI uses the scheme 'app', but the authority does not match
- the one assigned to this document, return a [HTTP] 403 Forbidden
- response and terminate this algorithm
- (i.e., prevent inter-application content access).
- */
-
- function Get_403_reponse(name, URI, expected_response) {
- var xhrTest = async_test(name);
- xhrTest.step(function () {
- /* Change if URI host is the same as current application host */
- URI = ChangeHostIfEqCurrent(URI);
- var xhr = new XMLHttpRequest();
- xhr.open("GET", URI, true);
- xhr.onreadystatechange = xhrTest.step_func(function (ev) {
- if (xhr.readyState === 4 && xhr.status === 200) {
- assert_true(false, "[403 error expected, got: 200 OK instead]");
- }
- if (xhr.readyState !== 4 && xhr.status === 403) {
- assert_true(xhr.response === expected_response, "["
- + xhr.status + " error, response:[" + xhr.response + "] "
- + "]");
- xhrTest.done();
- }
- });
- xhr.send();
- });
- }
- Get_403_reponse(TC_name + " Access to restricted URI - 403 Forbidden error expected",
- window.location.protocol + "//f15a6d20-cefa-13e5-1972-800e20d19a76/" + 'resources/ExamPLE/mmY/index.html', "");
-
-
- /**
- * 404 Not Found error - response body MUST be empty
- *
- * rules for dereferencing an app: URI =>
- 9. If potential-file is not found at the given path inside the container,
- return a [HTTP] 404 Not Found response.
- */
-
- TC_name = "Test: 6.4 Dereferencing and retrieval of files from a container";
-
- var CompareResponseBodytests = [ /* scheme: [TC name, URI, expected_response]*/
- [TC_name + " 404 Not Found error expected",
- 'resources/ImaGes/~t/%7b!a%7d/~sth11.png', ""]
- ];
-
- CompareResponseBodytests.forEach(function (data, i) {
- var name = data[0];
- var URI = data[1];
- var expected_response = data[2];
- var xhrTest = async_test(name);
- var xhr;
- xhrTest.step(function () {
- xhr = new XMLHttpRequest();
- xhr.open("GET", URI, true);
- xhr.onreadystatechange = xhrTest.step_func(function (ev) {
- if (xhr.readyState !== 4 && xhr.status === 404) {
- assert_true(xhr.response === expected_response,
- "[" + xhr.status + " error, response:[" + xhr.response + "] " + "]");
- xhrTest.done();
- }
- if (xhr.readyState === 4 && xhr.status === 200) {
- assert_true(false, "[404 error expected, got: 200 OK instead]");
- }
- });
- xhr.send();
- });
- delete xhrTest, xhr;
- });
- delete CompareResponseBodytests;
-
-
-
- } else {
- document.getElementById("logs").innerHTML =
- "This is a test suite for app protocol only. Test aborted due to current protocol "
- + window.location.protocol;
- }
-
- </script>
- </body>
-</html>
-
-
-
diff --git a/app-uri/resources/ExamPLE/mmY/index.html b/app-uri/resources/ExamPLE/mmY/index.html
deleted file mode 100644
index 7ef22e9..0000000
--- a/app-uri/resources/ExamPLE/mmY/index.html
+++ /dev/null
@@ -1 +0,0 @@
-PASS
diff --git a/app-uri/resources/ExamPLE/mmY/sth.txt b/app-uri/resources/ExamPLE/mmY/sth.txt
deleted file mode 100644
index 5e3cbc9..0000000
--- a/app-uri/resources/ExamPLE/mmY/sth.txt
+++ /dev/null
@@ -1 +0,0 @@
-This is simple text file
diff --git "a/app-uri/resources/ExamPLE/\173mY\175/z...z/index.html" "b/app-uri/resources/ExamPLE/\173mY\175/z...z/index.html"
deleted file mode 100644
index 7ef22e9..0000000
--- "a/app-uri/resources/ExamPLE/\173mY\175/z...z/index.html"
+++ /dev/null
@@ -1 +0,0 @@
-PASS
diff --git "a/app-uri/resources/ImaGes/\173\173a\175\175/Test_1/$a/sth34\041.png" "b/app-uri/resources/ImaGes/\173\173a\175\175/Test_1/$a/sth34\041.png"
deleted file mode 100644
index 4a775de..0000000
--- "a/app-uri/resources/ImaGes/\173\173a\175\175/Test_1/$a/sth34\041.png"
+++ /dev/null
Binary files differ
diff --git "a/app-uri/resources/ImaGes/~t/\173\041a\175/corrupted_file.png" "b/app-uri/resources/ImaGes/~t/\173\041a\175/corrupted_file.png"
deleted file mode 100644
index d05eb6e..0000000
--- "a/app-uri/resources/ImaGes/~t/\173\041a\175/corrupted_file.png"
+++ /dev/null
Binary files differ
diff --git "a/app-uri/resources/ImaGes/~t/\173\041a\175/~sth.png" "b/app-uri/resources/ImaGes/~t/\173\041a\175/~sth.png"
deleted file mode 100644
index 4a775de..0000000
--- "a/app-uri/resources/ImaGes/~t/\173\041a\175/~sth.png"
+++ /dev/null
Binary files differ
diff --git a/app-uri/resources/icons/w3c-128.png b/app-uri/resources/icons/w3c-128.png
deleted file mode 100644
index c010156..0000000
--- a/app-uri/resources/icons/w3c-128.png
+++ /dev/null
Binary files differ
diff --git a/app-uri/resources/icons/w3c-16.png b/app-uri/resources/icons/w3c-16.png
deleted file mode 100644
index 105194b..0000000
--- a/app-uri/resources/icons/w3c-16.png
+++ /dev/null
Binary files differ
diff --git a/app-uri/resources/icons/w3c-48.png b/app-uri/resources/icons/w3c-48.png
deleted file mode 100644
index a19c55d..0000000
--- a/app-uri/resources/icons/w3c-48.png
+++ /dev/null
Binary files differ
diff --git a/audio-output/HTMLMediaElement-sinkId-idl.html b/audio-output/HTMLMediaElement-sinkId-idl.html
new file mode 100644
index 0000000..4260672
--- /dev/null
+++ b/audio-output/HTMLMediaElement-sinkId-idl.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<html>
+<head>
+<meta charset=utf-8>
+<title>IDL check of sinkId on HTMLMediaElement</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="https://www.w3.org/TR/audio-output/#htmlmediaelement-extensions">
+</head>
+<body>
+
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test verifies the availability of <code>sinkId</code>/<code>setSinkId</code> on the HTMLMediaElement interface.</p>
+<div id='log'></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/WebIDLParser.js></script>
+<script src=/resources/idlharness.js></script>
+
+<!-- -->
+<script type="text/plain" id='untested_idl'>
+
+interface HTMLMediaElement {
+};
+
+interface HTMLAudioElement : HTMLMediaElement {
+};
+
+interface HTMLVideoElement : HTMLMediaElement {
+};
+</script>
+<script type="text/plain" id="idl">
+// The IDL is copied from the 15 December 2016 draft.
+partial interface HTMLMediaElement {
+ readonly attribute DOMString sinkId;
+ Promise<void> setSinkId(DOMString sinkId);
+};</script>
+<script>
+(function() {
+ var idl_array = new IdlArray();
+ idl_array.add_untested_idls(document.getElementById('untested_idl').textContent);
+ idl_array.add_idls(document.getElementById('idl').textContent);
+ window.audio = document.createElement("audio");
+ window.video = document.createElement("video");
+ idl_array.add_objects({"HTMLAudioElement": ["audio"], "HTMLVideoElement": ["video"]});
+ idl_array.test();
+ done();
+})();
+</script>
+</body>
+</html>
diff --git a/audio-output/setSinkId-manual.https.html b/audio-output/setSinkId-manual.https.html
new file mode 100644
index 0000000..a083cdf
--- /dev/null
+++ b/audio-output/setSinkId-manual.https.html
@@ -0,0 +1,132 @@
+<!doctype html>
+<html>
+<head>
+<title>Test setSinkId behavior with permissions / device changes</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="https://www.w3.org/TR/audio-output/#dom-htmlmediaelement-setsinkid">
+<meta name="timeout" content="long">
+
+</head>
+<body>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that <code>setSinkId</code> follows the algorithm, this includes manually checking the proper rendering on new output devices.</p>
+<p class="instructions">When prompted to access microphones, please accept as this is the only current way to get permissions for associated output devices.</p>
+<p class="instructions">For each authorized output device, check that selecting it makes the audio beat rendered on the corresponding device.</p>
+<p>Available but unauthorized devices (only those for which we can gain permission can be selected):</p>
+<ul id="available"></ul>
+<p>Authorized devices:</p>
+<ul id="authorized"></ul>
+<audio controls id="beat" src="/media/sound_5.mp3" loop></audio>
+
+<div id='log'></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+"use strict";
+
+const is_output = d => d.kind === "audiooutput";
+const by_id = (id) => d => d.groupId === id;
+const is_input = d => d.kind === "audioinput";
+const audio = document.getElementById("beat");
+const available = document.getElementById("available");
+const authorized = document.getElementById("authorized");
+
+let outputList;
+
+const selectDeviceTester = (t) => (e) => {
+ const groupId = e.target.dataset["groupid"];
+ const device = outputList.find(by_id(groupId));
+ if (audio.paused) audio.play();
+ promise_test(pt => audio.setSinkId(device.deviceId).then(() => {
+ assert_equals(device.deviceId, audio.sinkId);
+
+ const pass = document.createElement("button");
+ const fail = document.createElement("button");
+
+ const result = (bool) => () => {
+ assert_true(bool, "Sound rendered on right device");
+ fail.remove();
+ pass.remove();
+ audio.pause();
+ e.target.checked = false;
+ e.target.disabled = true;
+ t.done();
+ };
+
+ pass.style.backgroundColor = "#0f0";
+ pass.textContent = "\u2713 Sound plays on " + device.label;
+ pass.addEventListener("click", result(true));
+
+ fail.style.backgroundColor = "#f00";
+ fail.textContent = "\u274C Sound doesn't play on " + device.label;
+ fail.addEventListener("click", result(true));
+
+ const container = e.target.parentNode.parentNode;
+ container.appendChild(pass);
+ container.appendChild(fail);
+ }), "setSinkId for " + device.label + " resolves");
+};
+
+const addAuthorizedDevice = (groupId) => {
+ const device = outputList.find(by_id(groupId));
+ const async_t = async_test("Selecting output device " + device.label + " makes the audio rendered on the proper device");
+ const li = document.createElement("li");
+ const label = document.createElement("label");
+ const input = document.createElement("input");
+ input.type = "radio";
+ input.name = "device";
+ input.dataset["groupid"] = device.groupId;
+ input.addEventListener("change", selectDeviceTester(async_t));
+ const span = document.createElement("span");
+ span.textContent = device.label;
+ label.appendChild(input);
+ label.appendChild(span);
+ li.appendChild(label);
+ authorized.appendChild(li);
+};
+
+const authorizeDeviceTester = (t) => (e) => {
+ const groupId = e.target.dataset["groupid"];
+ navigator.mediaDevices.getUserMedia({audio: {groupId}})
+ .then( () => {
+ addAuthorizedDevice(groupId);
+ t.done();
+ });
+};
+
+promise_test(gum =>
+ navigator.mediaDevices.getUserMedia({audio: true}).then(
+ () => {
+ promise_test(t =>
+ navigator.mediaDevices.enumerateDevices().then(list => {
+ assert_not_equals(list.find(is_output), undefined, "media device list includes at least one audio output device");
+ outputList = list.filter(is_output);
+ outputList.forEach(d => {
+ const li = document.createElement("li");
+ assert_not_equals(d.label, "", "Audio Output Device Labels are available after permission grant");
+ li.textContent = d.label;
+ // Check permission
+ promise_test(perm => navigator.permissions.query({name: "speaker", deviceId: d.deviceId}).then(({state}) => {
+ if (state === "granted") {
+ addAuthorizedDevice(d.groupId);
+ } else if (state === "prompt") {
+ const inp = list.find(inp => inp.kind === "audioinput" && inp.groupId === d.groupId);
+ if (inp || true) {
+ const async_t = async_test("Authorizing output devices via permission requests for microphones works");
+ const button = document.createElement("button");
+ button.textContent = "Authorize access";
+ button.dataset["groupid"] = d.groupId;
+ button.addEventListener("click", async_t.step_func_done(authorizeDeviceTester(async_t)));
+ li.appendChild(button);
+ }
+ available.appendChild(li);
+ }
+ }, () => {
+ // if we can't query the permission, we assume it's granted :/
+ addAuthorizedDevice(d.groupId);
+ })
+ , "Query permission to use " + d.label);
+ });
+ }), "List media devices");
+ }), "Authorize mike access");
+</script>
diff --git a/audio-output/setSinkId.https.html b/audio-output/setSinkId.https.html
new file mode 100644
index 0000000..2ce0b48
--- /dev/null
+++ b/audio-output/setSinkId.https.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<html>
+<head>
+<title>Test setSinkId behavior </title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="https://www.w3.org/TR/audio-output/#dom-htmlmediaelement-setsinkid">
+</head>
+<body>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that <code>setSinkId</code> follows the algorithm (but does not consider actual rendering of the audio which needs to be manual).</p>
+<div id='log'></div>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+"use strict";
+
+const is_output = d => d.kind === "audiooutput";
+const audio = new Audio();
+
+promise_test(t => audio.setSinkId(""), "setSinkId on default audio output should always work");
+
+promise_test(t => promise_rejects(t, "NotFoundError", audio.setSinkId("inexistent_device_id")), "setSinkId fails with NotFoundError on made up deviceid");
+
+promise_test(t =>
+ navigator.mediaDevices.enumerateDevices().then(list => {
+ assert_not_equals(list.find(is_output), undefined, "media device list includes at least one audio output device");
+ // since we haven't gained any specific permission,
+ // for all listed audio output devices, calling setSinkId with device id can
+ // either create a security exception or work and thus reflect the deviceId
+ let acceptedDevice = 0;
+ list.filter(is_output).forEach((d,i) => promise_test(td => audio.setSinkId(d.deviceId).then(r => {
+ assert_equals(r, undefined, "setSinkId resolves with undefined");
+ assert_equals(audio.sinkId, d.deviceId, "when it resolves, setSinkId updates sinkId to the requested deviceId");
+ assert_equals(acceptedDevice, 0, "only one output device can be set without permission");
+ acceptedDevice++;
+ promise_test(t => audio.setSinkId(d.deviceId), "resetting sinkid on same current value should always work");
+ promise_test(t => audio.setSinkId(""), "resetting sinkid on default audio output should always work");
+ }, e => {
+ assert_equals(e.name, "SecurityError", "On known devices, the only possible failure of setSinkId is a securityerror"); // assuming AbortError can't happen in the test environment by default
+ }), "Correctly reacts to setting known deviceid as sinkid " + i));
+ }), "List media devices");
+
+</script>
+</body>
+</html>
diff --git a/common/css-paint-tests.js b/common/css-paint-tests.js
index 58a8e64..cd57332 100644
--- a/common/css-paint-tests.js
+++ b/common/css-paint-tests.js
@@ -2,11 +2,11 @@
// requestAnimationFrame. In the second frame, we take a screenshot, that makes
// sure that we already have a full frame.
function importPaintWorkletAndTerminateTestAfterAsyncPaint(code) {
- if (typeof paintWorklet == "undefined") {
+ if (typeof CSS.paintWorklet == "undefined") {
takeScreenshot();
} else {
var blob = new Blob([code], {type: 'text/javascript'});
- paintWorklet.addModule(URL.createObjectURL(blob)).then(function() {
+ CSS.paintWorklet.addModule(URL.createObjectURL(blob)).then(function() {
requestAnimationFrame(function() {
requestAnimationFrame(function() {
takeScreenshot();
diff --git a/common/performance-timeline-utils.js b/common/performance-timeline-utils.js
index 268bb11..3beb28e 100644
--- a/common/performance-timeline-utils.js
+++ b/common/performance-timeline-utils.js
@@ -31,10 +31,12 @@
function test_entries(actualEntries, expectedEntries) {
test_equals(actualEntries.length, expectedEntries.length)
expectedEntries.forEach(function (expectedEntry) {
- test_true(!!actualEntries.find(function (actualEntry) {
+ var foundEntry = actualEntries.find(function (actualEntry) {
return typeof Object.keys(expectedEntry).find(function (key) {
return actualEntry[key] !== expectedEntry[key]
}) === 'undefined'
- }))
+ })
+ test_true(!!foundEntry)
+ assert_object_equals(foundEntry.toJSON(), expectedEntry)
})
}
diff --git a/compat/OWNERS b/compat/OWNERS
new file mode 100644
index 0000000..d517a33
--- /dev/null
+++ b/compat/OWNERS
@@ -0,0 +1,3 @@
+@cdumez
+@foolip
+@miketaylr
diff --git a/compat/historical.html b/compat/historical.html
index 9200f57..a1d80f9 100644
--- a/compat/historical.html
+++ b/compat/historical.html
@@ -7,4 +7,12 @@
test(function() {
assert_false("getMatchedCSSRules" in window);
}, "getMatchedCSSRules() should not exist");
+
+test(function() {
+ assert_false("webkitHidden" in window);
+}, "webkitHidden should not exist");
+
+test(function() {
+ assert_false("webkitVisibilityState" in window);
+}, "webkitVisibilityState should not exist");
</script>
diff --git a/config.default.json b/config.default.json
index 47ad74b..a55b17c 100644
--- a/config.default.json
+++ b/config.default.json
@@ -9,7 +9,7 @@
"check_subdomains": true,
"log_level":"debug",
"bind_hostname": true,
- "ssl": {"type": "openssl",
+ "ssl": {"type": "pregenerated",
"encrypt_after_connect": false,
"openssl": {
"openssl_binary": "openssl",
@@ -18,8 +18,8 @@
"base_conf_path": null
},
"pregenerated": {
- "host_key_path": null,
- "host_cert_path": null
+ "host_key_path": "tools/certs/web-platform.test.key",
+ "host_cert_path": "tools/certs/web-platform.test.pem"
},
"none": {}
},
diff --git a/content-security-policy/script-src/javascript-window-open-blocked.html b/content-security-policy/script-src/javascript-window-open-blocked.html
new file mode 100644
index 0000000..a8dd14f
--- /dev/null
+++ b/content-security-policy/script-src/javascript-window-open-blocked.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Window.open should not open javascript url if not allowed.</title>
+ <script nonce='abc' src='/resources/testharness.js'></script>
+ <script nonce='abc' src='/resources/testharnessreport.js'></script>
+</head>
+<body>
+ <script nonce='abc'>
+ var t = async_test("Check that a securitypolicyviolation event is fired");
+ window.addEventListener('securitypolicyviolation', t.step_func_done(function(e) {
+ assert_equals(e.blockedURI, "inline");
+ assert_equals(e.violatedDirective, "script-src");
+ }));
+
+ window.open('javascript:test(function() { assert_unreached("FAIL")});', 'new');
+ </script>
+
+ <script nonce='abc' async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=script-src%20%27nonce-abc%27'></script>
+
+</body>
+</html>
diff --git a/content-security-policy/script-src/javascript-window-open-blocked.html.sub.headers b/content-security-policy/script-src/javascript-window-open-blocked.html.sub.headers
new file mode 100644
index 0000000..32fc4d5
--- /dev/null
+++ b/content-security-policy/script-src/javascript-window-open-blocked.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: javascript-window-open-blocked={{$id:uuid()}}; Path=/content-security-policy/script-src/
+Content-Security-Policy: script-src 'nonce-abc'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file
diff --git a/credential-management/credentialscontainer-create-basics.https.html b/credential-management/credentialscontainer-create-basics.https.html
index 89ae784..af304e2 100644
--- a/credential-management/credentialscontainer-create-basics.https.html
+++ b/credential-management/credentialscontainer-create-basics.https.html
@@ -73,6 +73,40 @@
}, "navigator.credentials.create() with bogus federated data");
promise_test(function(t) {
+ return promise_rejects(t, new TypeError(),
+ navigator.credentials.create({publicKey: "bogus publicKey data"}));
+}, "navigator.credentials.create() with bogus publicKey data");
+
+promise_test(function(t) {
+ var publicKey = {
+ challenge: new TextEncoder().encode("climb a mountain"),
+ rp: {
+ id: "1098237235409872",
+ name: "Acme"
+ },
+
+ user: {
+ id: "1098237235409872",
+ name: "avery.a.jones@example.com",
+ displayName: "Avery A. Jones",
+ icon: "https://pics.acme.com/00/p/aBjjjpqPb.png"
+ },
+
+ parameters: [{
+ type: "public-key",
+ algorithm: "ES256",
+ },],
+
+ timeout: 60000, // 1 minute
+ excludeList: [], // No excludeList
+ };
+
+ return navigator.credentials.create({publicKey}).then(r => {
+ assert_true(r instanceof PublicKeyCredential);
+ });
+}, "navigator.credentials.create() returns PublicKeyCredential");
+
+promise_test(function(t) {
var credential_data = {
id: 'id',
password: 'pencil',
@@ -99,6 +133,31 @@
}, "navigator.credentials.create() with bogus password and federated data");
promise_test(function(t) {
+ return promise_rejects(t, new TypeError(),
+ navigator.credentials.create({
+ federated: "bogus federated data",
+ publicKey: "bogus publicKey data",
+ }));
+}, "navigator.credentials.create() with bogus federated and publicKey data");
+
+promise_test(function(t) {
+ return promise_rejects(t, new TypeError(),
+ navigator.credentials.create({
+ password: "bogus password data",
+ publicKey: "bogus publicKey data",
+ }));
+}, "navigator.credentials.create() with bogus password and publicKey data");
+
+promise_test(function(t) {
+ return promise_rejects(t, new TypeError(),
+ navigator.credentials.create({
+ password: "bogus password data",
+ federated: "bogus federated data",
+ publicKey: "bogus publicKey data",
+ }));
+}, "navigator.credentials.create() with bogus password, federated, and publicKey data");
+
+promise_test(function(t) {
return promise_rejects(t, "NotSupportedError",
navigator.credentials.create({bogus_key: "bogus data"}));
}, "navigator.credentials.create() with bogus data");
diff --git a/css-paint-api/valid-image-after-load.html b/css-paint-api/valid-image-after-load.html
index b1ad0d2..1fe2407 100644
--- a/css-paint-api/valid-image-after-load.html
+++ b/css-paint-api/valid-image-after-load.html
@@ -30,7 +30,7 @@
var blob = new Blob([document.getElementById('code').textContent],
{type: 'text/javascript'});
var frame_cnt = 0;
-paintWorklet.addModule(URL.createObjectURL(blob)).then(function() {
+CSS.paintWorklet.addModule(URL.createObjectURL(blob)).then(function() {
var el = document.getElementById('output');
el.style.backgroundImage = 'paint(green)';
requestAnimationFrame(function() {
diff --git a/css-paint-api/valid-image-before-load.html b/css-paint-api/valid-image-before-load.html
index b4e6b0e..2232cd4 100644
--- a/css-paint-api/valid-image-before-load.html
+++ b/css-paint-api/valid-image-before-load.html
@@ -33,7 +33,7 @@
var blob = new Blob([document.getElementById('code').textContent],
{type: 'text/javascript'});
var frame_cnt = 0;
-paintWorklet.addModule(URL.createObjectURL(blob)).then(function() {
+CSS.paintWorklet.addModule(URL.createObjectURL(blob)).then(function() {
requestAnimationFrame(function() {
takeScreenshot(frame_cnt);
});
diff --git a/css-scoping/slotted-parsing.html b/css-scoping/slotted-parsing.html
new file mode 100644
index 0000000..308ff43
--- /dev/null
+++ b/css-scoping/slotted-parsing.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Scoping: ::slotted pseudo parsing</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#slotted-pseudo">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style id="styleElm">
+</style>
+<script>
+ function parse_selector(selector_text) {
+ try {
+ styleElm.sheet.insertRule(selector_text+"{}");
+ styleElm.sheet.deleteRule(0);
+ return true;
+ } catch (ex) {
+ return false;
+ }
+ }
+
+ function test_valid_selector(selector_text) {
+ test(function(){
+ assert_true(parse_selector(selector_text));
+ }, "Should be a valid selector: '" + selector_text + "'");
+ }
+
+ function test_invalid_selector(selector_text) {
+ test(function(){
+ assert_false(parse_selector(selector_text));
+ }, "Should be an invalid selector: '" + selector_text + "'");
+ }
+
+ test_invalid_selector("::slotted");
+ test_invalid_selector("::slotted()");
+ test_invalid_selector("::slotted(*).class");
+ test_invalid_selector("::slotted(*)#id {}");
+ test_invalid_selector("::slotted(*)[attr]");
+ test_invalid_selector("::slotted(*):hover");
+ test_invalid_selector("::slotted(*):read-only");
+ test_invalid_selector("::slotted(*)::slotted(*)");
+ test_invalid_selector("::slotted(*)::before::slotted(*)");
+ test_invalid_selector("::slotted(*) span");
+
+ test_valid_selector("::slotted(*)");
+ test_valid_selector("::slotted(div)");
+ test_valid_selector("::slotted([attr]:hover)");
+ test_valid_selector("::slotted(:not(.a))");
+
+ // Allow tree-abiding pseudo elements after ::slotted
+ test_valid_selector("::slotted(*)::before");
+ test_valid_selector("::slotted(*)::after");
+
+ // Other pseudo elements not valid after ::slotted
+ test_invalid_selector("::slotted(*)::first-line");
+ test_invalid_selector("::slotted(*)::first-letter");
+ test_invalid_selector("::slotted(*)::selection");
+</script>
diff --git a/css-scoping/slotted-with-pseudo-element-ref.html b/css-scoping/slotted-with-pseudo-element-ref.html
new file mode 100644
index 0000000..63677cf
--- /dev/null
+++ b/css-scoping/slotted-with-pseudo-element-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Scoping: pseudo element after ::slotted - reference</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+<div>PASS</div>
+<div>PASS</div>
+<div style="color:green">PASS</div>
diff --git a/css-scoping/slotted-with-pseudo-element.html b/css-scoping/slotted-with-pseudo-element.html
new file mode 100644
index 0000000..08e6dcc
--- /dev/null
+++ b/css-scoping/slotted-with-pseudo-element.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Scoping: pseudo element after ::slotted</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:rune@opera.com">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#slotted-pseudo">
+<link rel="match" href="slotted-with-pseudo-element-ref.html">
+<div id="host1"><span></span></div>
+<div id="host2"><span></span></div>
+<div id="host3"><span></span></div>
+<style>
+ #host3 > span::before { content: "PASS" }
+</style>
+<script>
+ function attachShadowWithSlottedStyle(host, styleString) {
+ var root = host.attachShadow({mode:"open"});
+ root.innerHTML = "<style>"+styleString+"</style><slot/>";
+ }
+
+ attachShadowWithSlottedStyle(host1, "::slotted(span)::before { content: 'PASS' }");
+ attachShadowWithSlottedStyle(host2, "::slotted(span)::after { content: 'PASS' }");
+ attachShadowWithSlottedStyle(host3, "::slotted(span)::before { content: 'FAIL'; color: green }");
+</script>
diff --git a/css/CSS2/tables/reference/table-anonymous-border-spacing-ref.xht b/css/CSS2/tables/reference/table-anonymous-border-spacing-ref.xht
new file mode 100644
index 0000000..ab82821
--- /dev/null
+++ b/css/CSS2/tables/reference/table-anonymous-border-spacing-ref.xht
@@ -0,0 +1,16 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <style><![CDATA[
+ #target { border-spacing: 20px; }
+ #target > * { display: table-cell; border: 1px solid black; }
+ ]]></style>
+ </head>
+<body>
+ <p>There should be 20px border-spacing in the table below.</p>
+ <div id="target">
+ <div>First cell</div>
+ <div>Second cell</div>
+ </div>
+</body>
+</html>
diff --git a/css/CSS2/tables/reference/table-anonymous-text-indent-ref.xht b/css/CSS2/tables/reference/table-anonymous-text-indent-ref.xht
new file mode 100644
index 0000000..08cffbf
--- /dev/null
+++ b/css/CSS2/tables/reference/table-anonymous-text-indent-ref.xht
@@ -0,0 +1,16 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <style><![CDATA[
+ #target { text-indent: 20px; display: table; }
+ #target > * { display: table-cell; border: 1px solid black; }
+ ]]></style>
+ </head>
+<body>
+ <p>There should be 20px text-indent in the table below.</p>
+ <div id="target">
+ <div>First cell</div>
+ Second cell (no element on purpose)
+ </div>
+</body>
+</html>
diff --git a/css/CSS2/tables/table-anonymous-border-spacing.xht b/css/CSS2/tables/table-anonymous-border-spacing.xht
new file mode 100644
index 0000000..d2c1bf8
--- /dev/null
+++ b/css/CSS2/tables/table-anonymous-border-spacing.xht
@@ -0,0 +1,26 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu"/>
+ <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes"/>
+ <link rel="match" href="reference/table-anonymous-border-spacing-ref.xht"/>
+ <meta name="flags" content='dom'/>
+ <script type="text/javascript"><![CDATA[
+ function doTest() {
+ var t = document.getElementById("target");
+ t.style.borderSpacing = '20px';
+ }
+ ]]></script>
+ <style><![CDATA[
+ #target { border-spacing: 0; }
+ #target > * { display: table-cell; border: 1px solid black; }
+ ]]></style>
+ </head>
+<body onload="doTest()">
+ <p>There should be 20px border-spacing in the table below.</p>
+ <div id="target">
+ <div>First cell</div>
+ <div>Second cell</div>
+ </div>
+</body>
+</html>
diff --git a/css/CSS2/tables/table-anonymous-text-indent.xht b/css/CSS2/tables/table-anonymous-text-indent.xht
new file mode 100644
index 0000000..1364f79
--- /dev/null
+++ b/css/CSS2/tables/table-anonymous-text-indent.xht
@@ -0,0 +1,26 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <link rel="author" title="Boris Zbarsky" href="mailto:bzbarsky@mit.edu"/>
+ <link rel="help" href="http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes"/>
+ <link rel="match" href="reference/table-anonymous-text-indent-ref.xht"/>
+ <meta name="flags" content='dom'/>
+ <script type="text/javascript"><![CDATA[
+ function doTest() {
+ var t = document.getElementById("target");
+ t.style.textIndent = '20px';
+ }
+ ]]></script>
+ <style><![CDATA[
+ #target { text-indent: 0; display: table; }
+ #target > * { display: table-cell; border: 1px solid black; }
+ ]]></style>
+</head>
+<body onload="doTest()">
+ <p>There should be 20px text-indent in the table below.</p>
+ <div id="target">
+ <div>First cell</div>
+ Second cell (no element on purpose)
+ </div>
+</body>
+</html>
diff --git a/css/css-fonts-3/test_datafont_same_origin.html b/css/css-fonts-3/test_datafont_same_origin.html
new file mode 100644
index 0000000..937c8c3
--- /dev/null
+++ b/css/css-fonts-3/test_datafont_same_origin.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>data:font same-origin test</title>
+ <link rel="author" title="Henry Chang" href="mailto:hchang@mozilla.com">
+ <link rel="help" href="http://www.w3.org/TR/css-fonts-3/#font-prop" />
+ <meta name="assert" content="tests data:font would be treated same origin." />
+ <script type="text/javascript" src="/resources/testharness.js"></script>
+ <script type="text/javascript" src="/resources/testharnessreport.js"></script>
+ <style type="text/css">
+ @font-face {
+ font-family: 'DataFont';
+ src: url(data:font/opentype;base64,AAEAAAANAIAAAwBQRkZUTU6u6MkAAAXcAAAAHE9TLzJWYWQKAAABWAAAAFZjbWFwAA8D7wAAAcAAAAFCY3Z0IAAhAnkAAAMEAAAABGdhc3D//wADAAAF1AAAAAhnbHlmCC6aTwAAAxQAAACMaGVhZO8ooBcAAADcAAAANmhoZWEIkAV9AAABFAAAACRobXR4EZQAhQAAAbAAAAAQbG9jYQBwAFQAAAMIAAAACm1heHAASQA9AAABOAAAACBuYW1lehAVOgAAA6AAAAIHcG9zdP+uADUAAAWoAAAAKgABAAAAAQAAMhPyuV8PPPUACwPoAAAAAMU4Lm0AAAAAxTgubQAh/5wFeAK8AAAACAACAAAAAAAAAAEAAAK8/5wAWgXcAAAAAAV4AAEAAAAAAAAAAAAAAAAAAAAEAAEAAAAEAAwAAwAAAAAAAgAAAAEAAQAAAEAALgAAAAAAAQXcAfQABQAAAooCvAAAAIwCigK8AAAB4AAxAQIAAAIABgkAAAAAAAAAAAABAAAAAAAAAAAAAAAAUGZFZABAAEEAQQMg/zgAWgK8AGQAAAABAAAAAAAABdwAIQAAAAAF3AAABdwAZAAAAAMAAAADAAAAHAABAAAAAAA8AAMAAQAAABwABAAgAAAABAAEAAEAAABB//8AAABB////wgABAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAnkAAAAqACoAKgBGAAAAAgAhAAABKgKaAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDtMgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIREnMxEjIQEJ6MfHApr9ZiECWAAAAwBk/5wFeAK8AAMABwALAAABNSEVATUhFQE1IRUB9AH0/UQDhPu0BRQB9MjI/tTIyP7UyMgAAAAAAA4ArgABAAAAAAAAACYATgABAAAAAAABAAUAgQABAAAAAAACAAYAlQABAAAAAAADACEA4AABAAAAAAAEAAUBDgABAAAAAAAFABABNgABAAAAAAAGAAUBUwADAAEECQAAAEwAAAADAAEECQABAAoAdQADAAEECQACAAwAhwADAAEECQADAEIAnAADAAEECQAEAAoBAgADAAEECQAFACABFAADAAEECQAGAAoBRwBDAG8AcAB5AHIAaQBnAGgAdAAgACgAYwApACAAMgAwADAAOAAgAE0AbwB6AGkAbABsAGEAIABDAG8AcgBwAG8AcgBhAHQAaQBvAG4AAENvcHlyaWdodCAoYykgMjAwOCBNb3ppbGxhIENvcnBvcmF0aW9uAABNAGEAcgBrAEEAAE1hcmtBAABNAGUAZABpAHUAbQAATWVkaXVtAABGAG8AbgB0AEYAbwByAGcAZQAgADIALgAwACAAOgAgAE0AYQByAGsAQQAgADoAIAA1AC0AMQAxAC0AMgAwADAAOAAARm9udEZvcmdlIDIuMCA6IE1hcmtBIDogNS0xMS0yMDA4AABNAGEAcgBrAEEAAE1hcmtBAABWAGUAcgBzAGkAbwBuACAAMAAwADEALgAwADAAMAAgAABWZXJzaW9uIDAwMS4wMDAgAABNAGEAcgBrAEEAAE1hcmtBAAAAAgAAAAAAAP+DADIAAAABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAQACACQAAAAAAAH//wACAAAAAQAAAADEPovuAAAAAMU4Lm0AAAAAxTgubQ==);
+ }
+ </style>
+</head>
+<body>
+<div id="log"></div>
+<pre id="display"></pre>
+<style type="text/css" id="testbox"></style>
+
+<script type="text/javascript">
+ async_test(function(t) {
+ var text = document.createElement('p');
+ // Cross-domain font will not load according to [1] so we try to apply
+ // data:font to this text and see if the font can be loaded.
+ // [1] https://www.w3.org/TR/css-fonts-3/#same-origin-restriction
+ text.style = 'font-family: DataFont';
+ text.innerHTML = "This text should trigger 'TestFont' to load.";
+ document.body.appendChild(text);
+
+ document.fonts.onloadingdone = function (fontFaceSetEvent) {
+ assert_equals(fontFaceSetEvent.fontfaces.length, 1, "Same origin font should be loaded.");
+ t.done();
+ };
+ document.fonts.onloadingerror = function (fontFaceSetEvent) {
+ assert_unreached("data:font is not same origin!");
+ };
+ }, "Test if data:font would be treated same origin.")
+</script>
+</body>
+</html>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-001.html b/css/css-grid-1/alignment/grid-content-distribution-001.html
index f8aa1c9..7b53a28 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-001.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-001.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-evenly' on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-evenly">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-002.html b/css/css-grid-1/alignment/grid-content-distribution-002.html
index d273c7b..194934b 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-002.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-002.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-between' on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-between">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-003.html b/css/css-grid-1/alignment/grid-content-distribution-003.html
index b5cf8c0..ed6e322 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-003.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-003.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-around' on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-around">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-004.html b/css/css-grid-1/alignment/grid-content-distribution-004.html
index a2e4930..419d0fb 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-004.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-004.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'stretch' on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-stretch">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-005.html b/css/css-grid-1/alignment/grid-content-distribution-005.html
index 0b544ff..2830aec 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-005.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-005.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution default value</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-stretch">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-006.html b/css/css-grid-1/alignment/grid-content-distribution-006.html
index 7cb2bc1..61cfe2b 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-006.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-006.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-evenly' and gaps on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-evenly">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-007.html b/css/css-grid-1/alignment/grid-content-distribution-007.html
index 34faa74..d4dce5d 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-007.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-007.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-evenly' on 3x3 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-evenly">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-008.html b/css/css-grid-1/alignment/grid-content-distribution-008.html
index f2a3f5d..3e381ec 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-008.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-008.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-evenly' and gaps on 3x3 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-evenly">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-009.html b/css/css-grid-1/alignment/grid-content-distribution-009.html
index 2acad9d..aa28a98 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-009.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-009.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-evenly' on 4x4 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-evenly">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-010.html b/css/css-grid-1/alignment/grid-content-distribution-010.html
index cc80076..cb08775 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-010.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-010.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-evenly' and gaps on 4x4 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-evenly">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-011.html b/css/css-grid-1/alignment/grid-content-distribution-011.html
index c5ef6d7..57eb3d0 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-011.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-011.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-between' and gaps on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-between">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-012.html b/css/css-grid-1/alignment/grid-content-distribution-012.html
index 2b41ea2..e45dbec 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-012.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-012.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-between' on 3x3 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-between">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-013.html b/css/css-grid-1/alignment/grid-content-distribution-013.html
index 1160cb7..3e1df65 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-013.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-013.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-between' and gaps on 3x3 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-between">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-014.html b/css/css-grid-1/alignment/grid-content-distribution-014.html
index 2d8d24a..423aad1 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-014.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-014.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-between' on 4x4 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-between">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-015.html b/css/css-grid-1/alignment/grid-content-distribution-015.html
index f754528..ef4c857 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-015.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-015.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-between' and gaps on 4x4 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-between">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-016.html b/css/css-grid-1/alignment/grid-content-distribution-016.html
index b372aae..11a6691 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-016.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-016.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-around' and gaps on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-around">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-017.html b/css/css-grid-1/alignment/grid-content-distribution-017.html
index 6155df7..9a490ad 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-017.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-017.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-around' on 3x3 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-around">
<link rel="match" href="../../reference/ref-filled-green-300px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-019.html b/css/css-grid-1/alignment/grid-content-distribution-019.html
index 2941b75..49ca22e 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-019.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-019.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-around' on 4x4 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-around">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-020.html b/css/css-grid-1/alignment/grid-content-distribution-020.html
index da5773b..249f57d 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-020.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-020.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-around' and gaps on 4x4 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-around">
<link rel="match" href="../../reference/ref-filled-green-300px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-021.html b/css/css-grid-1/alignment/grid-content-distribution-021.html
index 0f3bb84..6a5469f 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-021.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-021.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'stretch' and gaps on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-stretch">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-022.html b/css/css-grid-1/alignment/grid-content-distribution-022.html
index e660c2f..15a69c1 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-022.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-022.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'stretch' on 3x3 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-stretch">
<link rel="match" href="../../reference/ref-filled-green-300px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-023.html b/css/css-grid-1/alignment/grid-content-distribution-023.html
index 011e443..f5d07b7 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-023.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-023.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'stretch' and gaps on 3x3 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-stretch">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-024.html b/css/css-grid-1/alignment/grid-content-distribution-024.html
index 73ae83f..1b6932b 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-024.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-024.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'stretch' on 4x4 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-stretch">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-025.html b/css/css-grid-1/alignment/grid-content-distribution-025.html
index 1909192..f229a56 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-025.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-025.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'stretch' and gaps on 4x4 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-stretch">
<link rel="match" href="../../reference/ref-filled-green-300px-square.html">
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-001.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-001.html
index 72571da..ebd169c 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-001.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-001.html
@@ -2,9 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-evenly' with collapsed tracks on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#distribution-values">
-<link rel="help" href="https://drafts.csswg.org/css-grid/#collapsed-track">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution alignment ignore collapsed grid tracks and render correctly with a value of 'space-evenly'.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-002.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-002.html
index 7fd2661..a48c377 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-002.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-002.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-between' with collapsed tracks on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-grid/#collapsed-track">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution alignment ignore collapsed grid tracks and render correctly with a value of 'space-between'.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-003.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-003.html
index f0100c0..25c0302 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-003.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-003.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-around' with collapsed tracks on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-grid/#collapsed-track">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution alignment ignore collapsed grid tracks and render correctly with a value of 'space-around'.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-004.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-004.html
index d8b43b6..a280a69 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-004.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-004.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'stretch' with collapsed tracks on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-grid/#collapsed-track">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution alignment ignore collapsed grid tracks and render correctly with a value of 'stretch'.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-005.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-005.html
index d2a323e..34d6211 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-005.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-005.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-evenly' and gaps on 2x2 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-evenly">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-evenly' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-006.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-006.html
index ce3cbaf..151d34d 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-006.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-006.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-evenly' on 3x3 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-evenly">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-evenly' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-007.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-007.html
index 2e6a4af..061e93b 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-007.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-007.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-evenly' and gaps on 3x3 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-evenly">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-evenly' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-008.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-008.html
index b299552..1b7135e 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-008.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-008.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-evenly' on 4x4 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-evenly">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-evenly' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-009.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-009.html
index 0eb19a3..0bc4ada 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-009.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-009.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-evenly' and gaps on 4x4 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-evenly">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-evenly' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-010.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-010.html
index 16783fd..eccf004 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-010.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-010.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-between' and gaps on 2x2 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-between">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-between' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-011.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-011.html
index 7ba130a..fca4df5 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-011.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-011.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-between' on 3x3 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-between">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-between' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-012.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-012.html
index 0eb6c84..4250c14 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-012.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-012.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-between' and gaps on 3x3 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-between">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-between' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-013.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-013.html
index 1f70083..f20741e 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-013.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-013.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-between' on 4x4 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-between">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-between' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-014.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-014.html
index 8654369..78d2477 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-014.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-014.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-between' and gaps on 4x4 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-between">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-between' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-015.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-015.html
index 9d7be68..e8247e0 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-015.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-015.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-around' and gaps on 2x2 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-around">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-around' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-016.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-016.html
index bbaad00..511961f 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-016.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-016.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-around' on 3x3 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-around">
<link rel="match" href="../../reference/ref-filled-green-300px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-around' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-017.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-017.html
index ec2ea30..f587301 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-017.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-017.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-around' and gaps on 3x3 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-around">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-around' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-018.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-018.html
index 93ba652..d91c1c5 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-018.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-018.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-around' on 4x4 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-around">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-around' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-019.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-019.html
index 484ca20..7a09ce6 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-019.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-019.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'space-around' and gaps on 4x4 grid with collapsed tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-align-3/#valdef-align-content-space-around">
<link rel="match" href="../../reference/ref-filled-green-300px-square.html">
<meta name="assert" content="Content Distribution properties with 'space-around' value render correcly.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-020.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-020.html
index 7e9988c..f0a29e7 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-020.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-020.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'stretch' with collapsed tracks on 2x2 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-grid/#collapsed-track">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution alignment ignore collapsed grid tracks and render correctly with a value of 'stretch'.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-021.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-021.html
index 1c8cf66..d45f528 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-021.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-021.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'stretch' with collapsed tracks on 3x3 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-grid/#collapsed-track">
<link rel="match" href="../../reference/ref-filled-green-300px-square.html">
<meta name="assert" content="Content Distribution alignment ignore collapsed grid tracks and render correctly with a value of 'stretch'.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-022.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-022.html
index 3b9a208..7f837ba 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-022.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-022.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'stretch' and gaps with collapsed tracks on 3x3 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-grid/#collapsed-track">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution alignment ignore collapsed grid tracks and render correctly with a value of 'stretch'.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-023.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-023.html
index b39ea2c..11de67a 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-023.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-023.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'stretch' with collapsed tracks on 4x4 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-grid/#collapsed-track">
<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
<meta name="assert" content="Content Distribution alignment ignore collapsed grid tracks and render correctly with a value of 'stretch'.">
<style>
diff --git a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-024.html b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-024.html
index 97d9540..9afd058 100644
--- a/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-024.html
+++ b/css/css-grid-1/alignment/grid-content-distribution-with-collapsed-tracks-024.html
@@ -2,8 +2,9 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Content Distribution 'stretch' and gaps with collapsed tracks on 4x4 grid</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#grid-align">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#collapsed-track">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#content-distribution">
-<link rel="help" href="https://drafts.csswg.org/css-grid/#collapsed-track">
<link rel="match" href="../../reference/ref-filled-green-300px-square.html">
<meta name="assert" content="Content Distribution alignment ignore collapsed grid tracks and render correctly with a value of 'stretch'.">
<style>
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-001.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-001.html
index b25b471..e700587 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-001.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-001.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-002.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-002.html
index b9a354e..30413e0 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-002.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-002.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-003.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-003.html
index 0af4081..63ab28e 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-003.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-003.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-004.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-004.html
index ad76f52..6566e9c 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-004.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-004.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-005.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-005.html
index 68e794b..1689032 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-005.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-005.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-006.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-006.html
index 94c65d0..c090ef8 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-006.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-006.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-007.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-007.html
index 8cb459e..8a5161c 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-007.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-007.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-008.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-008.html
index 52337ee..d103a44 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-008.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-008.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-009.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-009.html
index 22bcc70..f971ddb 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-009.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-009.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-010.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-010.html
index 02e138e..1541a27 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-010.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-010.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-011.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-011.html
index 060e025..dc2738b 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-011.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-011.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-012.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-012.html
index 422d2eb..1c04de1 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-012.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-012.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-013.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-013.html
index ba88de0..d8acd61 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-013.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-013.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-014.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-014.html
index 9b3d64d..0b04808 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-014.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-014.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-015.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-015.html
index c7290f7..dc366eb 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-015.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-015.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-016.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-016.html
index 1365b70..27dab09 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-016.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-016.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-001.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-001.html
index ac300a5..5c05762 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-001.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-001.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-002.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-002.html
index 00e4ca7..abcf06e 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-002.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-002.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-003.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-003.html
index dd96194..ca185bd 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-003.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-003.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-004.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-004.html
index 8b5882c..bc0b8d6 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-004.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-004.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-005.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-005.html
index 4b29aa7..b155f5c 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-005.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-005.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-006.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-006.html
index a72fbd1..23ee979 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-006.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-006.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-007.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-007.html
index ab190d3..5496e78 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-007.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-007.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-008.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-008.html
index 5be8653..556ca0f 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-008.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-008.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-009.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-009.html
index f38ceb6..75433ad 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-009.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-009.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-010.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-010.html
index 6744c05..7ed26cf 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-010.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-010.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-011.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-011.html
index fa50dff..701d9a8 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-011.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-011.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-012.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-012.html
index 9477338..323ad98 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-012.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-012.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-013.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-013.html
index 25baada..dde2a18 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-013.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-013.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-014.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-014.html
index 19a8ede..2a80035 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-014.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-014.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-015.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-015.html
index 45c8b75..34e501f 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-015.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-015.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-016.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-016.html
index 2a47e02..5cc5a02 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-016.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-lr-016.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-001.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-001.html
index 1afde93..28a7830 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-001.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-001.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-002.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-002.html
index a7a1c97..297b5ff 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-002.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-002.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-003.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-003.html
index 96cbad3..e958ec2 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-003.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-003.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-004.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-004.html
index c93b7b6..ef2a7e6 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-004.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-004.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-005.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-005.html
index 73f7a6f..c512afe 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-005.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-005.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-006.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-006.html
index 0a6d003..584bfb9 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-006.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-006.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-007.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-007.html
index ac92314..257de49 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-007.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-007.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-008.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-008.html
index 4756eba..4e9c264 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-008.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-008.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on fixed-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-009.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-009.html
index 4d05c9f..cb66f61 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-009.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-009.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-010.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-010.html
index 1cb5b8d..b551d3e 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-010.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-010.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-011.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-011.html
index 535d57c..a56e83a 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-011.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-011.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-012.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-012.html
index 8ed59c7..91c8ce6 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-012.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-012.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-013.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-013.html
index 4070c2e..20eafb7 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-013.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-013.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-014.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-014.html
index d6a6b30..61038b2 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-014.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-014.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-015.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-015.html
index e6ef67d..3cadf7f 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-015.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-015.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
diff --git a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-016.html b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-016.html
index 5e26d14..03d6f7d 100644
--- a/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-016.html
+++ b/css/css-grid-1/alignment/grid-self-alignment-stretch-vertical-rl-016.html
@@ -2,9 +2,11 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Alignment and stretch on auto-sized tracks</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#alignment">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#propdef-justify-self">
<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-justify-self-stretch">
+<link rel="help" href="https://drafts.csswg.org/css-align/#valdef-align-self-stretch">
<meta name="assert" content="The stretched orthogonal grid items along the column and/or row axis include their defined padding-box.">
<style>
.grid {
diff --git a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-001.html b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-001.html
index 7660c08..a7d6906 100644
--- a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-001.html
+++ b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-001.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Baseline alignment may change grid area height</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#column-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-by-baseline">
<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
diff --git a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-002.html b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-002.html
index 1e7138c..48956dd 100644
--- a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-002.html
+++ b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-002.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Baseline alignment may change grid area height</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#column-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-by-baseline">
<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
diff --git a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-003.html b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-003.html
index ce81285..e4ea440 100644
--- a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-003.html
+++ b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-003.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Baseline alignment may change grid area height</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#column-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-by-baseline">
<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
diff --git a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-004.html b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-004.html
index 0dc218c..5730701 100644
--- a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-004.html
+++ b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-004.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Baseline alignment may change grid area height</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#column-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-by-baseline">
<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
diff --git a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-005.html b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-005.html
index 8570e7d..63057b2 100644
--- a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-005.html
+++ b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-005.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Baseline alignment may change grid area height</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#column-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-by-baseline">
<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
diff --git a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-006.html b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-006.html
index 0b298a1..4e0eae4 100644
--- a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-006.html
+++ b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-006.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Baseline alignment may change grid area height</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#column-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-by-baseline">
<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
diff --git a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-007.html b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-007.html
index a91395e..6fcd356 100644
--- a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-007.html
+++ b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-007.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Baseline alignment may change grid area width</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#row-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-by-baseline">
<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
diff --git a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-008.html b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-008.html
index a707160..9aee9b3 100644
--- a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-008.html
+++ b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-008.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Baseline alignment may change grid area width</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#row-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-by-baseline">
<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
diff --git a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-009.html b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-009.html
index 67f421f..155a20c 100644
--- a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-009.html
+++ b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-009.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Baseline alignment may change grid area width</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#row-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-by-baseline">
<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
diff --git a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-010.html b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-010.html
index 366cebb..6333bf3 100644
--- a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-010.html
+++ b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-010.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Baseline alignment may change grid area width</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#row-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-by-baseline">
<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
diff --git a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-011.html b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-011.html
index 5b40758..4f54208 100644
--- a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-011.html
+++ b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-011.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Baseline alignment may change grid area width</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#row-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-by-baseline">
<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
diff --git a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-012.html b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-012.html
index d3b1274..db7d45e 100644
--- a/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-012.html
+++ b/css/css-grid-1/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-012.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>CSS Grid Layout Test: Self-Baseline alignment may change grid area width</title>
<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#row-align">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#baseline-align-self">
<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-by-baseline">
<link rel="match" href="../../../reference/ref-filled-green-100px-square.xht">
diff --git a/css/css-position-3/position-sticky-root-scroller-ref.html b/css/css-position-3/position-sticky-root-scroller-ref.html
new file mode 100644
index 0000000..a6ded09
--- /dev/null
+++ b/css/css-position-3/position-sticky-root-scroller-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Reference for position:sticky should operate correctly for the root scroller</title>
+
+<style>
+body {
+ /* Assumption: 3000px is taller than any user agents test window size. */
+ height: 3000px;
+}
+
+.indicator {
+ background-color: green;
+ position: absolute;
+ top: 750px;
+}
+
+.box {
+ width: 200px;
+ height: 200px;
+}
+</style>
+
+<script>
+window.addEventListener('load', function() {
+ window.scrollTo(0, 700);
+});
+</script>
+
+<div class="indicator box"></div>
+
+<div style="position: absolute; top: 1000px;">You should see a green box above. No red should be visible.</div>
diff --git a/css/css-position-3/position-sticky-root-scroller.html b/css/css-position-3/position-sticky-root-scroller.html
new file mode 100644
index 0000000..35ab8dc
--- /dev/null
+++ b/css/css-position-3/position-sticky-root-scroller.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<title>position:sticky should operate correctly for the root scroller</title>
+<link rel="match" href="position-sticky-root-scroller-ref.html" />
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
+<meta name="assert" content="This test checks that position:sticky elements work when using the root (document) scroller" />
+
+<style>
+body {
+ /* Assumption: 3000px is taller than any user agents test window size. */
+ height: 3000px;
+}
+
+.indicator {
+ background-color: red;
+ position: absolute;
+}
+
+.sticky {
+ background-color: green;
+ position: sticky;
+ top: 50px;
+}
+
+.box {
+ width: 200px;
+ height: 200px;
+}
+</style>
+
+<script>
+window.addEventListener('load', function() {
+ window.scrollTo(0, 700);
+});
+</script>
+
+<div class="indicator box" style="top: 750px;"></div>
+<div class="sticky box"></div>
+
+<div style="position: absolute; top: 1000px;">You should see a green box above. No red should be visible.</div>
diff --git a/css/css-position-3/position-sticky-transforms-ref.html b/css/css-position-3/position-sticky-transforms-ref.html
new file mode 100644
index 0000000..f865a9b
--- /dev/null
+++ b/css/css-position-3/position-sticky-transforms-ref.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<title>Reference for transforms on position:sticky elements should apply after sticking</title>
+
+<style>
+.group {
+ display: inline-block;
+ position: relative;
+ width: 150px;
+ height: 250px;
+}
+
+.scroller {
+ position: relative;
+ width: 100px;
+ height: 200px;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.contents {
+ height: 500px;
+}
+
+.indicator {
+ background-color: green;
+ position: relative;
+}
+
+.box {
+ width: 100%;
+ height: 50px;
+}
+
+.rotated {
+ transform: rotateX(60deg);
+ height: 100px;
+ width: 100%;
+}
+
+.perspective {
+ transform: perspective(3px) translateZ(1px);
+ height: 50px;
+ width: 50px;
+}
+</style>
+
+<script>
+window.addEventListener('load', function() {
+ document.getElementById('scroller1').scrollTop = 50;
+ document.getElementById('scroller2').scrollTop = 50;
+ document.getElementById('scroller3').scrollTop = 50;
+});
+</script>
+
+<div class="group">
+ <div id="scroller1" class="scroller">
+ <div class="contents">
+ <div class="indicator box" style="height: 100px; top: 75px;"></div>
+ </div>
+ </div>
+</div>
+
+<div class="group">
+ <div id="scroller2" class="scroller">
+ <div class="contents">
+ <div class="rotated indicator" style="top: 100px;"></div>
+ </div>
+ </div>
+</div>
+
+<div class="group">
+ <div id="scroller3" class="scroller">
+ <!-- Required for blending. -->
+ <div class="perspective" style="position: absolute; background: red; top: 100px;"></div>
+ <div class="contents">
+ <div class="perspective indicator" style="top: 100px;"></div>
+ </div>
+ </div>
+</div>
+
+<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position-3/position-sticky-transforms-translate-ref.html b/css/css-position-3/position-sticky-transforms-translate-ref.html
new file mode 100644
index 0000000..b357795
--- /dev/null
+++ b/css/css-position-3/position-sticky-transforms-translate-ref.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>Reference for translations on position:sticky elements should apply after sticking</title>
+
+<style>
+.group {
+ display: inline-block;
+ position: relative;
+ width: 150px;
+ height: 250px;
+}
+
+.scroller {
+ position: relative;
+ width: 100px;
+ height: 200px;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.contents {
+ height: 500px;
+}
+
+.indicator {
+ background-color: green;
+ position: relative;
+}
+
+.box {
+ width: 100%;
+ height: 50px;
+}
+</style>
+
+<script>
+window.addEventListener('load', function() {
+ document.getElementById('scroller1').scrollTop = 50;
+ document.getElementById('scroller2').scrollTop = 70;
+ document.getElementById('scroller3').scrollTop = 50;
+});
+</script>
+
+<div class="group">
+ <div id="scroller1" class="scroller">
+ <div class="contents">
+ <div class="indicator box" style="top: 50px;"></div>
+ </div>
+ </div>
+</div>
+
+<div class="group">
+ <div id="scroller2" class="scroller">
+ <div class="contents">
+ <div class="indicator box" style="top: 50px;"></div>
+ </div>
+ </div>
+</div>
+
+<div class="group">
+ <div id="scroller3" class="scroller">
+ <div class="contents">
+ <div class="indicator box" style="top: 200px;"></div>
+ </div>
+ </div>
+</div>
+
+<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position-3/position-sticky-transforms-translate.html b/css/css-position-3/position-sticky-transforms-translate.html
new file mode 100644
index 0000000..076db9e
--- /dev/null
+++ b/css/css-position-3/position-sticky-transforms-translate.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<title>translations on position:sticky elements should apply after sticking</title>
+<link rel="match" href="position-sticky-transforms-translate-ref.html" />
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
+<meta name="assert" content="This test checks that translations on position:sticky elements are carried out on their stuck position" />
+
+<style>
+.group {
+ display: inline-block;
+ position: relative;
+ width: 150px;
+ height: 250px;
+}
+
+.scroller {
+ position: relative;
+ width: 100px;
+ height: 200px;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.contents {
+ height: 500px;
+}
+
+.container {
+ height: 150px;
+}
+
+.indicator {
+ background-color: red;
+ position: absolute;
+ left: 0;
+}
+
+.sticky {
+ background-color: green;
+ position: sticky;
+ top: 50px;
+}
+
+.box {
+ width: 100%;
+ height: 50px;
+}
+</style>
+
+<script>
+window.addEventListener('load', function() {
+ document.getElementById('scroller1').scrollTop = 50;
+ document.getElementById('scroller2').scrollTop = 70;
+ document.getElementById('scroller3').scrollTop = 50;
+});
+</script>
+
+<div class="group">
+ <div id="scroller1" class="scroller">
+ <div class="indicator box" style="top: 50px;"></div>
+ <div class="contents">
+ <div class="container">
+ <div class="sticky box" style="transform: translateY(-100%);"></div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- The pre-transform sticky is not allowed to escape its containing block. -->
+<div class="group">
+ <div id="scroller2" class="scroller">
+ <div class="indicator box" style="top: 50px;"></div>
+ <div class="contents">
+ <div class="container">
+ <div class="sticky box" style="transform: translateY(-100%);"></div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- The sticky element should stick before the container is transformed. -->
+<div class="group">
+ <div id="scroller3" class="scroller">
+ <div class="indicator box" style="top: 200px;"></div>
+ <div class="contents">
+ <div class="container" style="transform: translateY(100px);">
+ <div class="sticky box"></div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-position-3/position-sticky-transforms.html b/css/css-position-3/position-sticky-transforms.html
new file mode 100644
index 0000000..f9e6386
--- /dev/null
+++ b/css/css-position-3/position-sticky-transforms.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<title>transforms on position:sticky elements should apply after sticking</title>
+<link rel="match" href="position-sticky-transforms-ref.html" />
+<link rel="help" href="https://www.w3.org/TR/css-position-3/#sticky-pos" />
+<meta name="assert" content="This test checks that transforms on position:sticky elements are carried out on their stuck position" />
+
+<style>
+.group {
+ display: inline-block;
+ position: relative;
+ width: 150px;
+ height: 250px;
+}
+
+.scroller {
+ position: relative;
+ width: 100px;
+ height: 200px;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.contents {
+ height: 500px;
+}
+
+.container {
+ height: 150px;
+}
+
+.indicator {
+ background-color: red;
+ position: absolute;
+}
+
+.sticky {
+ background-color: green;
+ position: sticky;
+ top: 50px;
+}
+
+.box {
+ width: 100%;
+ height: 50px;
+}
+
+.rotated {
+ transform: rotateX(60deg);
+ width: 100%;
+ height: 100px;
+}
+
+.perspective {
+ transform: perspective(3px) translateZ(1px);
+ height: 50px;
+ width: 50px;
+}
+</style>
+
+<script>
+window.addEventListener('load', function() {
+ document.getElementById('scroller1').scrollTop = 50;
+ document.getElementById('scroller2').scrollTop = 50;
+ document.getElementById('scroller3').scrollTop = 50;
+});
+</script>
+
+<div class="group">
+ <div id="scroller1" class="scroller">
+ <div class="indicator box" style="height: 100px; top: 75px;"></div>
+ <div class="contents">
+ <div class="container">
+ <div class="sticky box" style="transform: scale(2);"></div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="group">
+ <div id="scroller2" class="scroller">
+ <div class="rotated indicator" style="top: 100px;"></div>
+ <div class="contents">
+ <div class="container" style="height: 250px;">
+ <div class="rotated sticky"></div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div class="group">
+ <div id="scroller3" class="scroller">
+ <div class="perspective indicator" style="top: 100px;"></div>
+ <div class="contents">
+ <div class="container">
+ <div class="perspective sticky"></div>
+ </div>
+ </div>
+ </div>
+</div>
+
+<div>You should see three green boxes above. No red should be visible.</div>
diff --git a/css/css-pseudo-4/marker-and-other-pseudo-elements-ref.html b/css/css-pseudo-4/marker-and-other-pseudo-elements-ref.html
new file mode 100644
index 0000000..796acbf
--- /dev/null
+++ b/css/css-pseudo-4/marker-and-other-pseudo-elements-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Test: ::marker interaction with ::before, ::after, and ::first-letter pseudo elements reference file</title>
+<link rel="author" title="Daniel Bates" href="mailto:dbates@webkit.org">
+<style>
+li {
+ color: green;
+ font-size: 20px;
+}
+
+.first-letter {
+ color: white;
+ background-color: green;
+}
+</style>
+</head>
+<body>
+<ol>
+ <li><span class="first-letter">P</span>ASSED if the list marker is green.</li>
+</ol>
+</body>
+</html>
diff --git a/css/css-pseudo-4/marker-and-other-pseudo-elements.html b/css/css-pseudo-4/marker-and-other-pseudo-elements.html
new file mode 100644
index 0000000..f393db5
--- /dev/null
+++ b/css/css-pseudo-4/marker-and-other-pseudo-elements.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Test: ::marker interaction with ::before, ::after, and ::first-letter pseudo elements</title>
+<link rel="author" title="Daniel Bates" href="mailto:dbates@webkit.org">
+<link rel="match" href="marker-and-other-pseudo-elements-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#marker-pseudo">
+<meta name="assert" content="Tests ::marker interaction with ::before, ::after, and ::first-letter pseudo elements">
+<style>
+li {
+ color: red;
+ font-size: 20px;
+}
+
+li::before {
+ color: green;
+ content: "PA";
+}
+
+li::after {
+ color: green;
+ content: "SSED if the list marker is green.";
+}
+
+li::marker {
+ color: green;
+}
+
+li::first-letter {
+ color: white;
+ background-color: green;
+}
+</style>
+</head>
+<body>
+<ol>
+ <li></li>
+</ol>
+</body>
+</html>
diff --git a/css/css-pseudo-4/marker-color-ref.html b/css/css-pseudo-4/marker-color-ref.html
new file mode 100644
index 0000000..f2269f7
--- /dev/null
+++ b/css/css-pseudo-4/marker-color-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Test: ::marker formatting with color property reference file</title>
+<link rel="author" title="Daniel Bates" href="mailto:dbates@webkit.org">
+<style>
+li {
+ color: green;
+ font-size: 40px;
+ list-style-type: square;
+}
+</style>
+</head>
+<body>
+<ol>
+ <li><!-- The list marker should be a green square.--></li>
+</ol>
+</body>
+</html>
diff --git a/css/css-pseudo-4/marker-color.html b/css/css-pseudo-4/marker-color.html
new file mode 100644
index 0000000..d45c766
--- /dev/null
+++ b/css/css-pseudo-4/marker-color.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Test: ::marker formatting with color property</title>
+<link rel="author" title="Daniel Bates" href="mailto:dbates@webkit.org">
+<link rel="match" href="marker-color-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#marker-pseudo">
+<meta name="assert" content="Tests ::marker rendering with color property">
+<style>
+li {
+ color: red;
+ font-size: 40px;
+ list-style-type: square;
+}
+
+li::marker {
+ color: green;
+}
+</style>
+</head>
+<body>
+<ol>
+ <li><!-- The list marker should be a green square.--></li>
+</ol>
+</body>
+</html>
diff --git a/css/css-pseudo-4/marker-font-properties-ref.html b/css/css-pseudo-4/marker-font-properties-ref.html
new file mode 100644
index 0000000..093674d
--- /dev/null
+++ b/css/css-pseudo-4/marker-font-properties-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Test: ::marker formatting with font properties reference file</title>
+<link rel="author" title="Daniel Bates" href="mailto:dbates@webkit.org">
+<style>
+li {
+ font-family: sans-serif;
+ font-size: 24px;
+ font-style: italic;
+ font-variant: small-caps;
+ font-weight: bold;
+ list-style-type: lower-alpha;
+ font-variant-numeric: tabular-nums;
+}
+</style>
+</head>
+<body>
+<ol>
+ <li></li>
+</ol>
+</body>
+</html>
diff --git a/css/css-pseudo-4/marker-font-properties.html b/css/css-pseudo-4/marker-font-properties.html
new file mode 100644
index 0000000..d2570eb
--- /dev/null
+++ b/css/css-pseudo-4/marker-font-properties.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Test: ::marker formatting with font properties</title>
+<link rel="author" title="Daniel Bates" href="mailto:dbates@webkit.org">
+<link rel="match" href="marker-font-properties-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#marker-pseudo">
+<meta name="assert" content="Tests ::marker rendering with font properties">
+<style>
+li {
+ list-style-type: lower-alpha;
+}
+
+li::marker {
+ font-family: sans-serif;
+ font-size: 24px;
+ font-style: italic;
+ font-variant: small-caps;
+ font-weight: bold;
+}
+</style>
+</head>
+<body>
+<ol>
+ <li><span style="font-size: 24px"><!-- FIXME: Needed to ensure consistent baseline position with expected result in WebKit (why?). --></span></li>
+</ol>
+</body>
+</html>
diff --git a/css/css-pseudo-4/marker-inherit-values-ref.html b/css/css-pseudo-4/marker-inherit-values-ref.html
new file mode 100644
index 0000000..6cce146
--- /dev/null
+++ b/css/css-pseudo-4/marker-inherit-values-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Test: ::marker inherits values from originating element reference file</title>
+<link rel="author" title="Daniel Bates" href="mailto:dbates@webkit.org">
+<style>
+li {
+ color: green;
+ font-family: sans-serif;
+ font-size: x-large;
+ font-style: italic;
+ font-variant: small-caps;
+ font-weight: bold;
+ list-style-type: lower-alpha;
+}
+</style>
+</head>
+<body>
+<ol>
+ <li></li>
+</ol>
+</body>
+</html>
diff --git a/css/css-pseudo-4/marker-inherit-values.html b/css/css-pseudo-4/marker-inherit-values.html
new file mode 100644
index 0000000..82456af
--- /dev/null
+++ b/css/css-pseudo-4/marker-inherit-values.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Test: ::marker inherits values from originating element</title>
+<link rel="author" title="Daniel Bates" href="mailto:dbates@webkit.org">
+<link rel="match" href="marker-inherit-values-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#marker-pseudo">
+<meta name="assert" content="Tests ::marker inherits values from originating element">
+<style>
+ol {
+ color: red;
+}
+
+li { /* Originating element */
+ color: green;
+ font-family: sans-serif;
+ font-size: x-large;
+ font-style: italic;
+ font-variant: small-caps;
+ font-weight: bold;
+ list-style-type: lower-alpha;
+}
+
+li::marker {
+ color: inherit;
+ font-family: inherit;
+ font-size: inherit;
+ font-style: inherit;
+ font-variant: inherit;
+ font-weight: inherit;
+}
+</style>
+</head>
+<body>
+<ol>
+ <li></li>
+</ol>
+</body>
+</html>
diff --git a/css/css-tables-3/fixed-layout-excess-width-distribution-001.html b/css/css-tables-3/fixed-layout-excess-width-distribution-001.html
new file mode 100644
index 0000000..a9a78bd
--- /dev/null
+++ b/css/css-tables-3/fixed-layout-excess-width-distribution-001.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/check-layout-th.js"></script>
+<link rel="author" title="David Grogan" href="dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#distributing-width-to-columns">
+
+<style>
+table {
+ width: 300px;
+ border-collapse: collapse;
+ table-layout: fixed;
+ height: 20px;
+}
+
+td {
+ padding: 0px;
+ background: lime;
+ outline: 1px solid blue;
+}
+
+td:nth-child(1) { width: 20px; }
+td:nth-child(2) { width: 10px; }
+td:nth-child(3) { width: 10%; }
+</style>
+
+<h2>Fixed layout tables with excess width and no auto columns</h2>
+
+FF/Edge give excess only to fixed columns, in proportion to their relative
+widths. This is what the spec dictates.
+<br>Chrome (62) gives excess to ALL columns, in proportion to their
+contribution to total width.
+<table id=theTable>
+ <tr>
+ <td data-expected-width=180></td>
+ <td data-expected-width=90></td>
+ <td data-expected-width=30></td>
+ </tr>
+</table>
+
+<script>
+checkLayout('#theTable')
+</script>
diff --git a/css/css-tables-3/support/check-layout-th.js b/css/css-tables-3/support/check-layout-th.js
new file mode 100644
index 0000000..07f7d5d
--- /dev/null
+++ b/css/css-tables-3/support/check-layout-th.js
@@ -0,0 +1,190 @@
+(function() {
+// Test is initiated from body.onload, so explicit done() call is required.
+setup({ explicit_done: true });
+
+function checkSubtreeExpectedValues(t, parent, prefix)
+{
+ var checkedLayout = checkExpectedValues(t, parent, prefix);
+ Array.prototype.forEach.call(parent.childNodes, function(node) {
+ checkedLayout |= checkSubtreeExpectedValues(t, node, prefix);
+ });
+ return checkedLayout;
+}
+
+function checkAttribute(output, node, attribute)
+{
+ var result = node.getAttribute && node.getAttribute(attribute);
+ output.checked |= !!result;
+ return result;
+}
+
+function assert_tolerance(actual, expected, message)
+{
+ if (isNaN(expected) || Math.abs(actual - expected) >= 1) {
+ assert_equals(actual, Number(expected), message);
+ }
+}
+
+function checkExpectedValues(t, node, prefix)
+{
+ var output = { checked: false };
+
+ var expectedWidth = checkAttribute(output, node, "data-expected-width");
+ if (expectedWidth) {
+ assert_tolerance(node.offsetWidth, expectedWidth, prefix + "width");
+ }
+
+ var expectedHeight = checkAttribute(output, node, "data-expected-height");
+ if (expectedHeight) {
+ assert_tolerance(node.offsetHeight, expectedHeight, prefix + "height");
+ }
+
+ var expectedOffset = checkAttribute(output, node, "data-offset-x");
+ if (expectedOffset) {
+ assert_tolerance(node.offsetLeft, expectedOffset, prefix + "offsetLeft");
+ }
+
+ var expectedOffset = checkAttribute(output, node, "data-offset-y");
+ if (expectedOffset) {
+ assert_tolerance(node.offsetTop, expectedOffset, prefix + "offsetTop");
+ }
+
+ var expectedWidth = checkAttribute(output, node, "data-expected-client-width");
+ if (expectedWidth) {
+ assert_tolerance(node.clientWidth, expectedWidth, prefix + "clientWidth");
+ }
+
+ var expectedHeight = checkAttribute(output, node, "data-expected-client-height");
+ if (expectedHeight) {
+ assert_tolerance(node.clientHeight, expectedHeight, prefix + "clientHeight");
+ }
+
+ var expectedWidth = checkAttribute(output, node, "data-expected-scroll-width");
+ if (expectedWidth) {
+ assert_tolerance(node.scrollWidth, expectedWidth, prefix + "scrollWidth");
+ }
+
+ var expectedHeight = checkAttribute(output, node, "data-expected-scroll-height");
+ if (expectedHeight) {
+ assert_tolerance(node.scrollHeight, expectedHeight, prefix + "scrollHeight");
+ }
+
+ var expectedOffset = checkAttribute(output, node, "data-total-x");
+ if (expectedOffset) {
+ var totalLeft = node.clientLeft + node.offsetLeft;
+ assert_tolerance(totalLeft, expectedOffset, prefix +
+ "clientLeft+offsetLeft (" + node.clientLeft + " + " + node.offsetLeft + ")");
+ }
+
+ var expectedOffset = checkAttribute(output, node, "data-total-y");
+ if (expectedOffset) {
+ var totalTop = node.clientTop + node.offsetTop;
+ assert_tolerance(totalTop, expectedOffset, prefix +
+ "clientTop+offsetTop (" + node.clientTop + " + " + node.offsetTop + ")");
+ }
+
+ var expectedDisplay = checkAttribute(output, node, "data-expected-display");
+ if (expectedDisplay) {
+ var actualDisplay = getComputedStyle(node).display;
+ assert_equals(actualDisplay, expectedDisplay, prefix + "display");
+ }
+
+ var expectedPaddingTop = checkAttribute(output, node, "data-expected-padding-top");
+ if (expectedPaddingTop) {
+ var actualPaddingTop = getComputedStyle(node).paddingTop;
+ // Trim the unit "px" from the output.
+ actualPaddingTop = actualPaddingTop.slice(0, -2);
+ assert_equals(actualPaddingTop, expectedPaddingTop, prefix + "padding-top");
+ }
+
+ var expectedPaddingBottom = checkAttribute(output, node, "data-expected-padding-bottom");
+ if (expectedPaddingBottom) {
+ var actualPaddingBottom = getComputedStyle(node).paddingBottom;
+ // Trim the unit "px" from the output.
+ actualPaddingBottom = actualPaddingBottom.slice(0, -2);
+ assert_equals(actualPaddingBottom, expectedPaddingBottom, prefix + "padding-bottom");
+ }
+
+ var expectedPaddingLeft = checkAttribute(output, node, "data-expected-padding-left");
+ if (expectedPaddingLeft) {
+ var actualPaddingLeft = getComputedStyle(node).paddingLeft;
+ // Trim the unit "px" from the output.
+ actualPaddingLeft = actualPaddingLeft.slice(0, -2);
+ assert_equals(actualPaddingLeft, expectedPaddingLeft, prefix + "padding-left");
+ }
+
+ var expectedPaddingRight = checkAttribute(output, node, "data-expected-padding-right");
+ if (expectedPaddingRight) {
+ var actualPaddingRight = getComputedStyle(node).paddingRight;
+ // Trim the unit "px" from the output.
+ actualPaddingRight = actualPaddingRight.slice(0, -2);
+ assert_equals(actualPaddingRight, expectedPaddingRight, prefix + "padding-right");
+ }
+
+ var expectedMarginTop = checkAttribute(output, node, "data-expected-margin-top");
+ if (expectedMarginTop) {
+ var actualMarginTop = getComputedStyle(node).marginTop;
+ // Trim the unit "px" from the output.
+ actualMarginTop = actualMarginTop.slice(0, -2);
+ assert_equals(actualMarginTop, expectedMarginTop, prefix + "margin-top");
+ }
+
+ var expectedMarginBottom = checkAttribute(output, node, "data-expected-margin-bottom");
+ if (expectedMarginBottom) {
+ var actualMarginBottom = getComputedStyle(node).marginBottom;
+ // Trim the unit "px" from the output.
+ actualMarginBottom = actualMarginBottom.slice(0, -2);
+ assert_equals(actualMarginBottom, expectedMarginBottom, prefix + "margin-bottom");
+ }
+
+ var expectedMarginLeft = checkAttribute(output, node, "data-expected-margin-left");
+ if (expectedMarginLeft) {
+ var actualMarginLeft = getComputedStyle(node).marginLeft;
+ // Trim the unit "px" from the output.
+ actualMarginLeft = actualMarginLeft.slice(0, -2);
+ assert_equals(actualMarginLeft, expectedMarginLeft, prefix + "margin-left");
+ }
+
+ var expectedMarginRight = checkAttribute(output, node, "data-expected-margin-right");
+ if (expectedMarginRight) {
+ var actualMarginRight = getComputedStyle(node).marginRight;
+ // Trim the unit "px" from the output.
+ actualMarginRight = actualMarginRight.slice(0, -2);
+ assert_equals(actualMarginRight, expectedMarginRight, prefix + "margin-right");
+ }
+
+ return output.checked;
+}
+
+window.checkLayout = function(selectorList, outputContainer)
+{
+ if (!selectorList) {
+ console.error("You must provide a CSS selector of nodes to check.");
+ return;
+ }
+ var nodes = document.querySelectorAll(selectorList);
+ var testNumber = 0;
+ nodes = Array.prototype.slice.call(nodes);
+ nodes.reverse();
+ var checkedLayout = false;
+ Array.prototype.forEach.call(nodes, function(node) {
+ test(function(t) {
+ var container = node.parentNode.className == 'container' ? node.parentNode : node;
+ var prefix = "\n" + container.outerHTML + "\n";
+ var passed = false;
+ try {
+ checkedLayout |= checkExpectedValues(t, node.parentNode, prefix);
+ checkedLayout |= checkSubtreeExpectedValues(t, node, prefix);
+ passed = true;
+ } finally {
+ checkedLayout |= !passed;
+ }
+ }, selectorList + ' ' + String(++testNumber));
+ });
+ if (!checkedLayout) {
+ console.error("No valid data-* attributes found in selector list : " + selectorList);
+ }
+ done();
+};
+
+})();
diff --git a/css/css-tables-3/visibility-collapse-col-004-dynamic.html b/css/css-tables-3/visibility-collapse-col-004-dynamic.html
new file mode 100644
index 0000000..cb868ba
--- /dev/null
+++ b/css/css-tables-3/visibility-collapse-col-004-dynamic.html
@@ -0,0 +1,81 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<link rel="author" title="Joy Yu" href="mailto:joysyu@mit.edu">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">
+<style>
+ table {
+ border: 5px solid black;
+ }
+ table span {
+ display: inline-block;
+ vertical-align: top;
+ background: lime;
+ margin: 3px;
+ height: 100px;
+ width: 100px;
+ }
+</style>
+<main>
+ <h1>Dynamic Visibility collapse</h1>
+ <a href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">Spec</a>
+ <p>
+ Setting a column to visibility:collapse changes table width but not height.
+ Bottom table is identical to top except left column has been collapsed.
+ </p>
+ <table id="test">
+ <colgroup id="collapse"></colgroup>
+ <tbody>
+ <tr>
+ <td>
+ <span>row 1</span>
+ </td>
+ <td>
+ <span></span>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span>row 2</span>
+ </td>
+ <td>
+ <span></span>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <span>row 3</span>
+ </td>
+ <td>
+ <span style="height:50px"></span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</main>
+
+<script>
+ colgroup = document.getElementById("collapse");
+ colgroup.style.visibility = "collapse";
+
+ tests = [
+ [
+ document.getElementById('test').offsetHeight,
+ 342,
+ "col visibility:collapse doesn't change table height",
+ ],
+ [
+ document.getElementById('test').offsetWidth,
+ 122,
+ "col visibility:collapse changes table width"
+ ]];
+ for (i = 0; i< tests.length; i++) {
+ test(function()
+ {
+ assert_equals.apply(this, tests[i]);
+ },
+ tests[i][2]);
+ };
+</script>
+</html>
diff --git a/css/css-tables-3/visibility-collapse-col-004.html b/css/css-tables-3/visibility-collapse-col-004.html
deleted file mode 100644
index 7c91354..0000000
--- a/css/css-tables-3/visibility-collapse-col-004.html
+++ /dev/null
@@ -1,110 +0,0 @@
-<!doctype html>
-<meta charset="utf-8">
-<script src='/resources/testharness.js'></script>
-<script src='/resources/testharnessreport.js'></script>
-<link rel='stylesheet' href='support/base.css' />
-<link rel="author" title="Joy Yu" href="mailto:joysyu@mit.edu">
-<link rel="help" href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">
-<style>
- x-table {
- border: 5px solid black;
- }
- x-table span {
- display: inline-block;
- vertical-align: top;
- background: lime;
- margin: 3px;
- height: 100px;
- width: 100px;
- }
-</style>
-<main>
- <h1>Dynamic Visibility collapse</h1>
- <a href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">Spec</a>
- <p>
- Setting a column to visibility:collapse changes table width but not height.
- </p>
- <x-table id="one">
- <x-tbody>
- <x-tr>
- <x-td>
- <span>row 1</span>
- </x-td>
- <x-td>
- <span></span>
- </x-td>
- </x-tr>
- <x-tr>
- <x-td>
- <span>row 2</span>
- </x-td>
- <x-td>
- <span></span>
- </x-td>
- </x-tr>
- <x-tr>
- <x-td>
- <span>row 3</span>
- </x-td>
- <x-td>
- <span style="height:50px"></span>
- </x-td>
- </x-tr>
- </x-tbody>
- </x-table>
- Bottom table is identical to top except left column has been collapsed.
- <x-table id="two">
- <x-colgroup id="collapse"></x-colgroup>
- <x-tbody>
- <x-tr>
- <x-td>
- <span>row 1</span>
- </x-td>
- <x-td>
- <span></span>
- </x-td>
- </x-tr>
- <x-tr>
- <x-td>
- <span>row 2</span>
- </x-td>
- <x-td>
- <span></span>
- </x-td>
- </x-tr>
- <x-tr>
- <x-td>
- <span>row 3</span>
- </x-td>
- <x-td>
- <span style="height:50px"></span>
- </x-td>
- </x-tr>
- </x-tbody>
- </x-table>
-</main>
-
-<script>
- colgroup = document.getElementById("collapse");
- colgroup.style.visibility = "collapse";
-
- tests = [
- [
- document.getElementById('two').offsetHeight,
- document.getElementById('one').offsetHeight,
- "col visibility:collapse doesn't change table height",
- ],
- [
- document.getElementById('two').offsetWidth,
- 116,
- "col visibility:collapse changes table width"
- ]];
- for (i = 0; i< tests.length; i++) {
- test(function()
- {
- assert_equals.apply(this, tests[i]);
- },
- tests[i][2]);
- };
-</script>
-</html>
diff --git a/css/css-tables-3/visibility-collapse-col-005.html b/css/css-tables-3/visibility-collapse-col-005.html
new file mode 100644
index 0000000..b674cdd
--- /dev/null
+++ b/css/css-tables-3/visibility-collapse-col-005.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<link rel="author" title="Joy Yu" href="mailto:joysyu@mit.edu">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">
+<style>
+ td {
+ padding: 0;
+ }
+</style>
+<h1>Visibility collapse</h1>
+<a href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">Spec</a>
+<p>
+Setting a spanning column to visibility:collapse changes table width but
+not height. The right two columns here have been collapsed.
+</p>
+<table id="test" style="border-collapse: collapse;">
+<colgroup>
+ <col style="background:#00F"/>
+ <col span="2" style="background:#F00; visibility: collapse"/>
+</colgroup>
+<tbody>
+ <tr>
+ <td><div style="width: 100px; height: 100px"></div></td>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ <td><div style="width: 1px; height: 1px"></div></td>
+ </tr>
+ <tr>
+ <td><div style="width: 100px; height: 100px"></div></td>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ <td><div style="width: 1px; height: 1px"></div></td>
+ </tr>
+</tbody>
+</table>
+<script>
+ tests = [
+ [
+ document.getElementById('test').offsetHeight,
+ 200,
+ "col visibility:collapse doesn't change table height",
+ ],
+ [
+ document.getElementById('test').offsetWidth,
+ 100,
+ "col visibility:collapse changes table width"
+ ]];
+ for (i = 0; i< tests.length; i++) {
+ test(function()
+ {
+ assert_equals.apply(this, tests[i]);
+ },
+ tests[i][2]);
+ };
+</script>
\ No newline at end of file
diff --git a/css/css-tables-3/visibility-collapse-colspan-001.html b/css/css-tables-3/visibility-collapse-colspan-001.html
new file mode 100644
index 0000000..0b59f20
--- /dev/null
+++ b/css/css-tables-3/visibility-collapse-colspan-001.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<link rel="author" title="Joy Yu" href="mailto:joysyu@mit.edu">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">
+<style>
+ td {
+ padding: 0;
+ }
+</style>
+<h1>Visibility collapse</h1>
+<a href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">Spec</a>
+<p>
+Setting a spanning column to visibility:collapse changes table width but
+not height. The middle column has been collapsed.
+</p>
+<table id="test" style="border-spacing: 0;">
+ <col style="background-color:red;">
+ <col style="background-color: blue; visibility: collapse;">
+ <col style="background-color: green;">
+ <tr>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ </tr>
+ <tr>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ </tr>
+ <tr>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ </tr>
+ <tr>
+ <td colspan="3"><div style="width: 30px; height: 10px"></div></td>
+ </tr>
+</table>
+
+<script>
+ tests = [
+ [
+ document.getElementById('test').offsetHeight,
+ 40,
+ "col visibility:collapse doesn't change table height",
+ ],
+ [
+ document.getElementById('test').offsetWidth,
+ 20,
+ "col visibility:collapse changes table width"
+ ]];
+ for (i = 0; i< tests.length; i++) {
+ test(function()
+ {
+ assert_equals.apply(this, tests[i]);
+ },
+ tests[i][2]);
+ };
+</script>
+</html>
\ No newline at end of file
diff --git a/css/css-tables-3/visibility-collapse-colspan-002.html b/css/css-tables-3/visibility-collapse-colspan-002.html
new file mode 100644
index 0000000..0203749
--- /dev/null
+++ b/css/css-tables-3/visibility-collapse-colspan-002.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<link rel="author" title="Joy Yu" href="mailto:joysyu@mit.edu">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">
+<style>
+ td {
+ padding: 0;
+ }
+</style>
+<h1>Visibility collapse</h1>
+<a href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">Spec</a>
+<p>
+Setting a spanning column to visibility:collapse changes table width but
+not height. The first column has been collapsed and column width is the widest cell
+in the column.
+</p>
+<table id="test" style="border-spacing: 0;">
+ <col style="background-color:red; visibility: collapse;">
+ <col style="background-color: blue;">
+ <col style="background-color: green;">
+ <tr>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ <td><div style="width: 20px; height: 10px"></div></td>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ </tr>
+ <tr>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ <td><div style="width: 20px; height: 10px"></div></td>
+ <td><div style="width: 20px; height: 10px"></div></td>
+ </tr>
+ <tr>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ <td><div style="width: 20px; height: 10px"></div></td>
+ <td><div style="width: 30px; height: 10px"></div></td>
+ </tr>
+ <tr>
+ <td colspan="3"><div style="width: 25px; height: 10px"></div></td>
+ </tr>
+</table>
+<script>
+ tests = [
+ [
+ document.getElementById('test').offsetHeight,
+ 40,
+ "col visibility:collapse doesn't change table height",
+ ],
+ [
+ document.getElementById('test').offsetWidth,
+ 50,
+ "col visibility:collapse changes table width"
+ ]];
+ for (i = 0; i< tests.length; i++) {
+ test(function()
+ {
+ assert_equals.apply(this, tests[i]);
+ },
+ tests[i][2]);
+ };
+</script>
+</html>
\ No newline at end of file
diff --git a/css/css-tables-3/visibility-collapse-colspan-003-ref.html b/css/css-tables-3/visibility-collapse-colspan-003-ref.html
new file mode 100644
index 0000000..4617a3a
--- /dev/null
+++ b/css/css-tables-3/visibility-collapse-colspan-003-ref.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test Reference: Overflow clipping in cells that span columns</title>
+<link rel="author" title="Joy Yu" href="mailto:joysyu@mit.edu">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#visibility-collapse-cell-rendering">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">
+<style>
+ .firstCol {
+ width: 65px;
+ }
+ .thirdCol {
+ width: 160px;
+ }
+</style>
+<table>
+ <col style="background-color:red;">
+ <col style="background-color: blue; visibility:collapse;">
+ <col style="background-color: green;">
+ <tr>
+ <td class="firstCol">Row 1</td>
+ <td>Row 1 wordword</td>
+ <td class="thirdCol">Row 1 wordwordword</td>
+ </tr>
+ <tr>
+ <td class="firstCol">Row 2</td>
+ <td>Row 2 wordwordword</td>
+ <td class="thirdCol">Row 2 wordword</td>
+ </tr>
+ <tr>
+ <td class="firstCol">Row 3</td>
+ <td>Row 3 word</td>
+ <td class="thirdCol">Row 3 wordwordwordword</td>
+ </tr>
+ <tr>
+ <td colspan="3">superlongwordsuperlongwordsuper</td>
+ </tr>
+</table>
+
diff --git a/css/css-tables-3/visibility-collapse-colspan-003.html b/css/css-tables-3/visibility-collapse-colspan-003.html
new file mode 100644
index 0000000..3709281
--- /dev/null
+++ b/css/css-tables-3/visibility-collapse-colspan-003.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: Overflow clipping in cells that span columns</title>
+<link rel="author" title="Joy Yu" href="mailto:joysyu@mit.edu">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#visibility-collapse-cell-rendering">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">
+<link rel="match" href="visibility-collapse-colspan-003-ref.html">
+<style>
+ .firstCol {
+ width: 65px;
+ }
+ .thirdCol {
+ width: 160px;
+ }
+</style>
+<table>
+ <col style="background-color:red;">
+ <col style="background-color: blue; visibility:collapse;">
+ <col style="background-color: green;">
+ <tr>
+ <td class="firstCol">Row 1</td>
+ <td>Row 1 wordword</td>
+ <td class="thirdCol">Row 1 wordwordword</td>
+ </tr>
+ <tr>
+ <td class="firstCol">Row 2</td>
+ <td>Row 2 wordwordword</td>
+ <td class="thirdCol">Row 2 wordword</td>
+ </tr>
+ <tr>
+ <td class="firstCol">Row 3</td>
+ <td>Row 3 word</td>
+ <td class="thirdCol">Row 3 wordwordwordword</td>
+ </tr>
+ <tr>
+ <td colspan="3">superlongwordsuperlongwordsuper shouldbeclipped</td>
+ </tr>
+</table>
diff --git a/css/css-tables-3/visibility-collapse-colspan-crash.html b/css/css-tables-3/visibility-collapse-colspan-crash.html
new file mode 100644
index 0000000..591fbd9
--- /dev/null
+++ b/css/css-tables-3/visibility-collapse-colspan-crash.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Joy Yu" href="mailto:joysyu@mit.edu">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#visibility-collapse-cell-rendering">
+<h1>Visibility collapse</h1>
+<a href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">Spec</a>
+<p>
+When a row is collapsed, a cell spanning the row may get clipped. When the row is then uncollapsed,
+the browser should not crash because the cell's children should be correctly updated and laid out.
+</p>
+<table>
+ <col style="background-color:red;">
+ <col id="colToCollapse" style="background-color: blue;">
+ <col style="background-color: green;">
+ <tr>
+ <td class="firstCol">Row 1</td>
+ <td>Row 1 wordword</td>
+ <td class="thirdCol">Row 1 wordwordword</td>
+ </tr>
+ <tr>
+ <td class="firstCol">Row 2</td>
+ <td>Row 2 wordwordword</td>
+ <td class="thirdCol">Row 2 wordword</td>
+ </tr>
+ <tr>
+ <td class="firstCol">Row 3</td>
+ <td>Row 3 word</td>
+ <td class="thirdCol">Row 3 wordwordwordword</td>
+ </tr>
+ <tr>
+ <td colspan="3">superlongwordsuperlongwordsuper shouldbeclipped</td>
+ </tr>
+</table>
+
+<script>
+ test(() => {
+ col = document.getElementById("colToCollapse");
+ col.style.visibility = "collapse";
+ step_timeout(function(){ col.style.visibility = "visible"; }, 500);
+ step_timeout(function(){ col.style.visibility = "collapse"; }, 1000);
+ }, "No crash or assertion failure. crbug.com/174167");
+</script>
\ No newline at end of file
diff --git a/css/css-tables-3/visibility-collapse-rowcol-001.html b/css/css-tables-3/visibility-collapse-rowcol-001.html
new file mode 100644
index 0000000..912ab0d
--- /dev/null
+++ b/css/css-tables-3/visibility-collapse-rowcol-001.html
@@ -0,0 +1,71 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<link rel="author" title="Joy Yu" href="mailto:joysyu@mit.edu">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#computing-the-table-height">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">
+<style>
+table, td {
+ border: 1px solid;
+ height: 32px;
+ min-width: 32px;
+ white-space: nowrap;
+ padding: 0;
+}
+</style>
+<h1>Visibility collapse</h1>
+<a href="https://drafts.csswg.org/css-tables-3/#computing-the-table-height">Spec</a>
+<p>
+Setting row and column to visibility:collapse changes both height and width. The
+collapsed result should have whitespace in the bottom right corner.
+</p>
+<table id="test">
+ <col style="background-color: pink">
+ <col style="background-color: purple;">
+ <col style="background-color: black; visibility: collapse;">
+ <col style="background-color: grey; ">
+ <tbody>
+ <tr>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td colspan="2" rowspan="2"></td>
+ <td></td>
+ <td></td>
+ </tr>
+ <tr style="visibility:collapse">
+ <td></td>
+ <td></td>
+ </tr>
+ <tr>
+ <td></td>
+ <td></td>
+ <td colspan="2"></td>
+ </tr>
+ </tbody>
+</table>
+<script>
+ tests = [
+ [
+ document.getElementById('test').offsetWidth,
+ 112,
+ "spanning col visibility:collapse changes table width"
+ ],
+ [
+ document.getElementById('test').offsetHeight,
+ 112,
+ "spanning row visibility:collapse changes height"
+ ]];
+ for (i = 0; i< tests.length; i++) {
+ test(function()
+ {
+ assert_equals.apply(this, tests[i]);
+ },
+ tests[i][2]);
+ };
+</script>
+</html>
diff --git a/css/css-tables-3/visibility-collapse-rowcol-002.html b/css/css-tables-3/visibility-collapse-rowcol-002.html
new file mode 100644
index 0000000..6876a40
--- /dev/null
+++ b/css/css-tables-3/visibility-collapse-rowcol-002.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<link rel="author" title="Joy Yu" href="mailto:joysyu@mit.edu">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#computing-the-table-height">
+<link rel="help" href="https://drafts.csswg.org/css-tables-3/#computing-the-table-width">
+<style>
+ td {
+ padding: 0;
+ }
+</style>
+<h1>Visibility collapse</h1>
+<a href="https://drafts.csswg.org/css-tables-3/#computing-the-table-height">Spec</a>
+<p>
+Setting row and spanning column to visibility:collapse changes height and width.
+</p>
+<table id="test" style="border-spacing:0;">
+ <col style="background-color: pink;">
+ <col style="background-color: green;">
+ <col span="2" style="background-color: purple; visibility: collapse;">
+ <tr>
+ <td><div style="width: 25px; height: 25px"></div></td>
+ <td><div style="width: 20px; height: 20px"></div></td>
+ <td><div style="width: 15px; height: 15px"></div></td>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ </tr>
+ <tr>
+ <td rowspan="2" colspan="3"><div style="width: 45px; height: 25px"></div></td>
+ <td><div style="width: 10px; height: 10px"></div></td>
+ </tr>
+ <tr style="visibility: collapse;">
+ <td><div style="width: 70px; height: 40px"></div></td>
+ </tr>
+</table>
+<script>
+ borderWidth = 2;
+ tests = [
+ [
+ document.getElementById('test').offsetWidth,
+ 45,
+ "spanning row visibility:collapse doesn't change table width"
+ ],
+ [
+ document.getElementById('test').offsetHeight,
+ 35,
+ "spanning row visibility:collapse doesn't change height in this case"
+ ]];
+ for (i = 0; i< tests.length; i++) {
+ test(function()
+ {
+ assert_equals.apply(this, tests[i]);
+ },
+ tests[i][2]);
+ };
+</script>
+</html>
diff --git a/css/css-transforms-2/transforms-support-calc.html b/css/css-transforms-2/transforms-support-calc.html
new file mode 100644
index 0000000..19bacd9
--- /dev/null
+++ b/css/css-transforms-2/transforms-support-calc.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>CSS Transform Module Level 2: calc values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-transforms-2/">
+<link rel="help" href="https://drafts.csswg.org/css-values-3/#calc-notation">
+<meta name="assert" content="calc values are supported in css-transforms properties.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #container {
+ width: 600px;
+ height: 500px;
+ transform-style: preserve-3d;
+ }
+ #target {
+ width: 300px;
+ height: 200px;
+ font-size: 20px;
+ }
+</style>
+</head>
+<body>
+<div id="container">
+ <div id="target"></div>
+</div>
+<script>
+'use strict';
+
+test(function(){
+ target.style = 'translate: calc(30px + 20%) calc(-200px + 100%);';
+ assert_equals(getComputedStyle(target).translate, '90px 0px');
+}, 'translate supports calc');
+
+test(function(){
+ target.style = 'rotate: calc(1turn - 100grad);';
+ assert_equals(getComputedStyle(target).rotate, '270deg');
+}, 'rotate supports calc');
+
+test(function(){
+ target.style = 'scale: calc(5 + 2) calc(5 - 2) calc(5 * 2);';
+ assert_equals(getComputedStyle(target).scale, '7 3 10');
+}, 'scale supports calc');
+
+test(function(){
+ target.style = 'perspective: calc(100px - 3em);';
+ assert_equals(getComputedStyle(target).perspective, '40px');
+}, 'perspective supports calc');
+
+test(function(){
+ target.style = 'perspective-origin: calc(30px + 20%) calc(-200px + 100%);';
+ assert_equals(getComputedStyle(target).perspectiveOrigin, '90px 0px');
+}, 'perspective-origin supports calc');
+
+test(function(){
+ target.style = 'transform: translate(calc(30px + 20%), calc(-200px + 100%)) rotate(calc(1turn - 400grad)) scale(calc(5 + 2), calc(5 - 2));';
+ assert_equals(getComputedStyle(target).transform, 'matrix(7, 0, 0, 3, 90, 0)');
+}, 'transform supports calc');
+
+test(function(){
+ target.style = 'transform-origin: calc(30px + 20%) calc(-200px + 100%);';
+ assert_equals(getComputedStyle(target).transformOrigin, '90px 0px');
+}, 'transform-origin supports calc');
+
+</script>
+</body>
+</html>
diff --git a/css/css-ui-3/cursor-auto-005.html b/css/css-ui-3/cursor-auto-005.html
index 40c466b..ad11863 100644
--- a/css/css-ui-3/cursor-auto-005.html
+++ b/css/css-ui-3/cursor-auto-005.html
@@ -2,7 +2,7 @@
<title>CSS Basic User Interface Test: cursor:auto on form elements</title>
<link rel="author" title="Florian Rivoal" href="mailto:florian@rivoal.net">
<link rel="help" href="http://www.w3.org/TR/css3-ui/#cursor">
-<meta name="flags" content="interact may">
+<meta name="flags" content="interact">
<meta charset="UTF-8">
<meta name="assert" content="The 'auto' cursor value does the same as 'default' over everything other than text, such as form elements.">
<style>
diff --git a/css-values/unset-value-storage.html b/css/css-values-3/unset-value-storage.html
similarity index 89%
rename from css-values/unset-value-storage.html
rename to css/css-values-3/unset-value-storage.html
index 9cf13b2..5869e9e 100644
--- a/css-values/unset-value-storage.html
+++ b/css/css-values-3/unset-value-storage.html
@@ -2,6 +2,7 @@
<meta charset="utf-8">
<title>Storage of "unset" value</title>
<meta name="author" title="Xidorn Quan" href="https://www.upsuper.org">
+<link rel="help" href="https://drafts.csswg.org/css-values-3/#common-keywords"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
diff --git a/css/css-writing-modes-3/background-position-vrl-018-ref.xht b/css/css-writing-modes-3/background-position-vrl-018-ref.xht
index e75ef35..bda2fb6 100644
--- a/css/css-writing-modes-3/background-position-vrl-018-ref.xht
+++ b/css/css-writing-modes-3/background-position-vrl-018-ref.xht
@@ -11,6 +11,11 @@
<meta content="image" name="flags" />
<style type="text/css"><![CDATA[
+ html
+ {
+ line-height: 1;
+ }
+
div#expected
{
text-align: right;
diff --git a/css/css-writing-modes-3/support/text-orientation.js b/css/css-writing-modes-3/support/text-orientation.js
index 6e007ce..fb6eb7d 100644
--- a/css/css-writing-modes-3/support/text-orientation.js
+++ b/css/css-writing-modes-3/support/text-orientation.js
@@ -69,13 +69,12 @@
}});
function Results(name) {
- var block = document.createElement("details");
- this.summary = appendChildElement(block, "summary");
+ this.details = document.createElement("details");
+ this.summary = appendChildElement(this.details, "summary");
this.summary.textContent = name;
- var typeList = appendChildElement(block, "ul");
+ var typeList = appendChildElement(this.details, "ul");
this.failList = appendChildElement(appendChildElement(typeList, "li", "Failures"), "ol");
this.inconclusiveList = appendChildElement(appendChildElement(typeList, "li", "Inconclusives"), "ol");
- details.appendChild(block);
this.passCount = 0;
this.failCount = 0;
this.inconclusiveCount = 0;
@@ -101,6 +100,7 @@
this.summary.textContent += " (" + this.passCount + " passes, " +
this.failCount + " fails, " +
this.inconclusiveCount + " inconclusives)";
+ details.appendChild(this.details);
assert_equals(this.failCount, 0, "Fail count");
assert_greater_than(this.passCount, 0, "Pass count");
test.done();
diff --git a/css/css3-color/t424-hsl-parsing-f.xht b/css/css3-color/t424-hsl-parsing-f.xht
index 5e28245..ca89eae 100644
--- a/css/css3-color/t424-hsl-parsing-f.xht
+++ b/css/css3-color/t424-hsl-parsing-f.xht
@@ -13,8 +13,6 @@
p { color: hsl(120, 100%, 25%); }
p { color: hsl(0, 255, 128); }
p { color: hsl(0%, 100%, 50%); }
- p { color: hsl(0, 100%, 50%, 1); }
- p { color: hsl(0deg, 100%, 50%); }
p { color: hsl(0px, 100%, 50%); }
]]></style>
</head>
diff --git a/css/css3-color/t425-hsla-parsing-f.xht b/css/css3-color/t425-hsla-parsing-f.xht
index 18d22cb..67ce55f 100644
--- a/css/css3-color/t425-hsla-parsing-f.xht
+++ b/css/css3-color/t425-hsla-parsing-f.xht
@@ -11,14 +11,10 @@
<style type="text/css"><![CDATA[
html, body { background: white; }
p { color: hsla(120, 100%, 25%, 1.0); }
- p { color: hsla(0, 100%, 25%); }
p { color: hsla(0, 100%, 25%, 1.0, 1.0); }
p { color: hsla(0, 100%, 25%, 1.0,); }
p { color: hsla(0, 255, 128, 1.0); }
p { color: hsla(0%, 100%, 50%, 1.0); }
- p { color: hsla(0, 100%, 50%, 1%); }
- p { color: hsla(0, 100%, 50%, 0%); }
- p { color: hsla(0deg, 100%, 50%, 1.0); }
p { color: hsla(0px, 100%, 50%, 1.0); }
]]></style>
</head>
diff --git a/css/geometry-1/DOMMatrix-attributes.html b/css/geometry-1/DOMMatrix-attributes.html
new file mode 100644
index 0000000..4b3db9d
--- /dev/null
+++ b/css/geometry-1/DOMMatrix-attributes.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<title>Geometry Interfaces: DOMMatrix attributes</title>
+<link rel="help" href="https://drafts.fxtf.org/geometry/#dommatrix-attributes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const initial = {
+ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0,
+ m11: 1, m12: 0, m13: 0, m14: 0,
+ m21: 0, m22: 1, m23: 0, m24: 0,
+ m31: 0, m32: 0, m33: 1, m34: 0,
+ m41: 0, m42: 0, m43: 0, m44: 1,
+};
+
+// Attributes that always preserve is2D.
+["a", "b", "c", "d", "e", "f",
+ "m11", "m12", "m21", "m22", "m41", "m42" ].forEach(attribute => {
+ test(() => {
+ let m = new DOMMatrix();
+ assert_true(m.is2D);
+ assert_equals(m[attribute], initial[attribute]);
+ m[attribute] = 42;
+ assert_true(m.is2D);
+ assert_equals(m[attribute], 42);
+ }, `DOMMatrix.${attribute}`);
+});
+
+// Attributes that clear is2D for values other than 0 or -0.
+["m13", "m14", "m23", "m24", "m31", "m32", "m34", "m43" ].forEach(attribute => {
+ test(() => {
+ let m = new DOMMatrix();
+ assert_true(m.is2D);
+ assert_equals(m[attribute], initial[attribute]);
+ m[attribute] = 0;
+ assert_true(m.is2D, "0 preserves is2D");
+ assert_equals(m[attribute], 0);
+ m[attribute] = -0;
+ assert_true(m.is2D, "-0 preserves is2D");
+ assert_equals(m[attribute], -0);
+ m[attribute] = 42;
+ assert_false(m.is2D, "a value other than 0 or -0 clears is2D");
+ assert_equals(m[attribute], 42);
+ m[attribute] = 0;
+ assert_false(m.is2D, "is2D can never be set to true after having been set to false");
+ assert_equals(m[attribute], 0);
+ }, `DOMMatrix.${attribute}`);
+});
+
+// Attributes that clear is2D for values other than 1.
+["m33", "m44" ].forEach(attribute => {
+ test(() => {
+ let m = new DOMMatrix();
+ assert_true(m.is2D);
+ assert_equals(m[attribute], initial[attribute]);
+ m[attribute] = 1;
+ assert_true(m.is2D, "1 preserves is2D");
+ assert_equals(m[attribute], 1);
+ m[attribute] = 42;
+ assert_false(m.is2D, "a value other than 1 clears is2D");
+ assert_equals(m[attribute], 42);
+ m[attribute] = 1;
+ assert_false(m.is2D, "is2D can never be set to true after having been set to false");
+ assert_equals(m[attribute], 1);
+ }, `DOMMatrix.${attribute}`);
+});
+</script>
diff --git a/css/geometry-1/DOMMatrix2DInit-validate-fixup.html b/css/geometry-1/DOMMatrix2DInit-validate-fixup.html
new file mode 100644
index 0000000..523f2a1
--- /dev/null
+++ b/css/geometry-1/DOMMatrix2DInit-validate-fixup.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<title>Geometry Interfaces: DOMMatrix2DInit validate and fixup</title>
+<link rel="help" href="https://drafts.fxtf.org/geometry/#dommatrixinit-dictionary">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-context-2d-settransform">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/dommatrix-test-util.js"></script>
+<canvas id=canvas hidden></canvas>
+<script>
+setup(() => {
+ window.canvas = document.getElementById('canvas');
+ window.ctx = canvas.getContext('2d');
+});
+
+[
+ {a: 1, m11: 2},
+ {b: 0, m12: -1},
+ {c: Infinity, m21: -Infinity},
+ {d: 0, m22: NaN},
+ {e: 1, m41: 1.00000001},
+ {f: 0, m42: Number.MIN_VALUE},
+].forEach(dict => {
+ test(() => {
+ ctx.resetTransform();
+ assert_throws(new TypeError(), () => ctx.setTransform(dict));
+ }, `${format_dict(dict)} (invalid)`);
+});
+
+test(() => {
+ ctx.resetTransform();
+ ctx.setTransform(1, 2, 3, 4, 5, 6);
+ const matrix = ctx.getTransform();
+ checkMatrix(matrix, matrix2D({m11: 1, m12: 2, m21: 3, m22: 4, m41: 5, m42: 6}));
+}, `Sanity check without dictionary`);
+
+[
+ // Input dict that would throw if ignore3D was false
+ [{m13: 1, is2D: true}, matrix2D({})],
+ [{m14: 1, is2D: true}, matrix2D({})],
+ [{m23: 1, is2D: true}, matrix2D({})],
+ [{m24: 1, is2D: true}, matrix2D({})],
+ [{m31: 1, is2D: true}, matrix2D({})],
+ [{m32: 1, is2D: true}, matrix2D({})],
+ [{m33: 0, is2D: true}, matrix2D({})],
+ [{m33: -0, is2D: true}, matrix2D({})],
+ [{m33: -1, is2D: true}, matrix2D({})],
+ [{m34: 1, is2D: true}, matrix2D({})],
+ [{m43: 1, is2D: true}, matrix2D({})],
+ [{m44: 0, is2D: true}, matrix2D({})],
+
+ // Input dict that are 2D
+ [{}, matrix2D({})],
+ [{is2D: undefined}, matrix2D({})],
+ [{a: 1, m11: 1}, matrix2D({m11: 1})],
+ [{b: 0, m12: undefined}, matrix2D({m12: 0})],
+ [{c: 0, m21: 0}, matrix2D({m21: 0})],
+ [{c: 0, m21: -0}, matrix2D({m21: -0})],
+ [{c: -0, m21: 0}, matrix2D({m21: 0})],
+ [{c: -0, m21: -0}, matrix2D({m21: -0})],
+ [{d: Infinity, m22: Infinity}, matrix2D({m22: Infinity})],
+ [{e: -Infinity, m41: -Infinity}, matrix2D({m41: -Infinity})],
+ [{f: NaN, m42: NaN}, matrix2D({m42: NaN})],
+ [{f: NaN, m42: NaN, is2D: true}, matrix2D({m42: NaN})],
+ [{f: 0, m42: null}, matrix2D({m42: 0})], // null is converted to 0
+ [{f: -0, m42: null}, matrix2D({m42: 0})], // null is converted to 0
+ [{a: 2}, matrix2D({m11: 2})],
+ [{b: 2}, matrix2D({m12: 2})],
+ [{c: 2}, matrix2D({m21: 2})],
+ [{d: 2}, matrix2D({m22: 2})],
+ [{e: 2}, matrix2D({m41: 2})],
+ [{f: 2}, matrix2D({m42: 2})],
+ [{a: -0, b: -0, c: -0, d: -0, e: -0, f: -0},
+ matrix2D({m11: -0, m12: -0, m21: -0, m22: -0, m41: -0, m42: -0})],
+ [{a: -0, b: -0, c: -0, d: -0, e: -0, f: -0, is2D: true},
+ matrix2D({m11: -0, m12: -0, m21: -0, m22: -0, m41: -0, m42: -0})],
+ [{m11:2}, matrix2D({m11: 2})],
+ [{m12:2}, matrix2D({m12: 2})],
+ [{m21:2}, matrix2D({m21: 2})],
+ [{m22:2}, matrix2D({m22: 2})],
+ [{m41:2}, matrix2D({m41: 2})],
+ [{m42:2}, matrix2D({m42: 2})],
+ [{m11: -0, m12: -0, m21: -0, m22: -0, m41: -0, m42: -0},
+ matrix2D({m11: -0, m12: -0, m21: -0, m22: -0, m41: -0, m42: -0})],
+ [{m11: -0, m12: -0, m21: -0, m22: -0, m41: -0, m42: -0, is2D: true},
+ matrix2D({m11: -0, m12: -0, m21: -0, m22: -0, m41: -0, m42: -0})],
+ [{m13: 0, is2D: true}, matrix2D({})],
+ [{m13: -0, is2D: true}, matrix2D({})],
+ [{m14: 0, is2D: true}, matrix2D({})],
+ [{m14: -0, is2D: true}, matrix2D({})],
+ [{m23: 0, is2D: true}, matrix2D({})],
+ [{m23: -0, is2D: true}, matrix2D({})],
+ [{m24: 0, is2D: true}, matrix2D({})],
+ [{m24: -0, is2D: true}, matrix2D({})],
+ [{m31: 0, is2D: true}, matrix2D({})],
+ [{m31: -0, is2D: true}, matrix2D({})],
+ [{m32: 0, is2D: true}, matrix2D({})],
+ [{m32: -0, is2D: true}, matrix2D({})],
+ [{m33: 1, is2D: true}, matrix2D({})],
+ [{m34: 0, is2D: true}, matrix2D({})],
+ [{m34: -0, is2D: true}, matrix2D({})],
+ [{m43: 0, is2D: true}, matrix2D({})],
+ [{m43: -0, is2D: true}, matrix2D({})],
+ [{m44: 1, is2D: true}, matrix2D({})],
+ [{is2D: true}, matrix2D({})],
+
+ // Input dict that are 3D, but 3D members are ignored
+ [{m13: 1, is2D: false}, matrix2D({})],
+ [{m14: 1, is2D: false}, matrix2D({})],
+ [{m23: 1, is2D: false}, matrix2D({})],
+ [{m24: 1, is2D: false}, matrix2D({})],
+ [{m31: 1, is2D: false}, matrix2D({})],
+ [{m32: 1, is2D: false}, matrix2D({})],
+ [{m33: 0, is2D: false}, matrix2D({})],
+ [{m33: -0, is2D: false}, matrix2D({})],
+ [{m33: -1, is2D: false}, matrix2D({})],
+ [{m34: 1, is2D: false}, matrix2D({})],
+ [{m43: 1, is2D: false}, matrix2D({})],
+ [{m44: 0, is2D: false}, matrix2D({})],
+ [{m13: 1}, matrix2D({})],
+ [{m14: 1}, matrix2D({})],
+ [{m23: 1}, matrix2D({})],
+ [{m24: 1}, matrix2D({})],
+ [{m31: 1}, matrix2D({})],
+ [{m32: 1}, matrix2D({})],
+ [{m33: 0}, matrix2D({})],
+ [{m34: 1}, matrix2D({})],
+ [{m43: 1}, matrix2D({})],
+ [{m44: 0}, matrix2D({})],
+ [{is2D: false}, matrix2D({})],
+ [{is2D: null}, matrix2D({})],
+].forEach(([dict, expected]) => {
+ test(() => {
+ ctx.resetTransform();
+ ctx.setTransform(dict);
+ const matrix = ctx.getTransform();
+ checkMatrix(matrix, expected);
+ }, `${format_dict(dict)}`);
+});
+</script>
diff --git a/css/geometry-1/DOMMatrixInit-validate-fixup.html b/css/geometry-1/DOMMatrixInit-validate-fixup.html
index cf511cd..8835c1b 100644
--- a/css/geometry-1/DOMMatrixInit-validate-fixup.html
+++ b/css/geometry-1/DOMMatrixInit-validate-fixup.html
@@ -3,50 +3,8 @@
<link rel="help" href="https://drafts.fxtf.org/geometry/#dommatrixinit-dictionary">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
+<script src="support/dommatrix-test-util.js"></script>
<script>
-// This formats dict as a string suitable as test name.
-// format_value() is provided by testharness.js,
-// which also preserves sign for -0.
-function format_dict(dict) {
- const props = [];
- for (let prop in dict) {
- props.push(`${prop}: ${format_value(dict[prop])}`);
- }
- return `{${props.join(', ')}}`;
-}
-
-// Create a normal JS object with the expected properties
-// from a dict with only m11..m44 specified (not a..f).
-function matrix3D(dict) {
- const matrix = {m11: 1, m12: 0, m13: 0, m14: 0,
- m21: 0, m22: 1, m23: 0, m24: 0,
- m31: 0, m32: 0, m33: 1, m34: 0,
- m41: 0, m42: 0, m43: 0, m44: 1}
- matrix.is2D = false;
- for (let member in dict) {
- matrix[member] = dict[member];
- }
- matrix.a = matrix.m11;
- matrix.b = matrix.m12;
- matrix.c = matrix.m21;
- matrix.d = matrix.m22;
- matrix.e = matrix.m41;
- matrix.f = matrix.m42;
- return matrix;
-}
-
-function matrix2D(dict) {
- const matrix = matrix3D(dict);
- matrix.is2D = true;
- return matrix;
-}
-
-function checkMatrix(actual, expected) {
- for (let member in expected) {
- assert_equals(actual[member], expected[member], member);
- }
-}
-
[
{a: 1, m11: 2},
{b: 0, m12: -1},
diff --git a/css/geometry-1/support/dommatrix-test-util.js b/css/geometry-1/support/dommatrix-test-util.js
index 294db8e..b5d7827 100644
--- a/css/geometry-1/support/dommatrix-test-util.js
+++ b/css/geometry-1/support/dommatrix-test-util.js
@@ -1,4 +1,47 @@
+// This formats dict as a string suitable as test name.
+// format_value() is provided by testharness.js,
+// which also preserves sign for -0.
+function format_dict(dict) {
+ const props = [];
+ for (let prop in dict) {
+ props.push(`${prop}: ${format_value(dict[prop])}`);
+ }
+ return `{${props.join(', ')}}`;
+}
+// Create a normal JS object with the expected properties
+// from a dict with only m11..m44 specified (not a..f).
+function matrix3D(dict) {
+ const matrix = {m11: 1, m12: 0, m13: 0, m14: 0,
+ m21: 0, m22: 1, m23: 0, m24: 0,
+ m31: 0, m32: 0, m33: 1, m34: 0,
+ m41: 0, m42: 0, m43: 0, m44: 1}
+ matrix.is2D = false;
+ for (let member in dict) {
+ matrix[member] = dict[member];
+ }
+ matrix.a = matrix.m11;
+ matrix.b = matrix.m12;
+ matrix.c = matrix.m21;
+ matrix.d = matrix.m22;
+ matrix.e = matrix.m41;
+ matrix.f = matrix.m42;
+ return matrix;
+}
+
+function matrix2D(dict) {
+ const matrix = matrix3D(dict);
+ matrix.is2D = true;
+ return matrix;
+}
+
+function checkMatrix(actual, expected) {
+ for (let member in expected) {
+ assert_equals(actual[member], expected[member], member);
+ }
+}
+
+// checkMatrix and checkDOMMatrix should probably be merged...
function checkDOMMatrix(m, exp, is2D) {
if (is2D === undefined) {
is2D = exp.is2D;
diff --git a/css/motion-1/offset-supports-calc.html b/css/motion-1/offset-supports-calc.html
new file mode 100644
index 0000000..79aa9a9
--- /dev/null
+++ b/css/motion-1/offset-supports-calc.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Motion Path Module Level 1: calc values</title>
+<link rel="author" title="Eric Willigers" href="mailto:ericwilligers@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/motion-1/">
+<link rel="help" href="https://drafts.csswg.org/css-values-3/#calc-notation">
+<meta name="assert" content="calc values are supported in offset properties.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #target {
+ font-size: 20px;
+ }
+</style>
+</head>
+<body>
+<div id="target"></div>
+<script>
+'use strict';
+
+test(function(){
+ target.style = 'offset-position: calc(30px + 20%) calc(-200px + 8em + 100%);';
+ assert_equals(getComputedStyle(target).offsetPosition, 'calc(30px + 20%) calc(-40px + 100%)');
+}, 'offset-position supports calc');
+
+test(function(){
+ target.style = 'offset-path: ray(calc(1turn - 100grad) closest-side);';
+ assert_equals(getComputedStyle(target).offsetPath, 'ray(270deg closest-side)');
+}, 'offset-path supports calc');
+
+test(function(){
+ target.style = 'offset-distance: calc(-100px + 50%);';
+ assert_equals(getComputedStyle(target).offsetDistance, 'calc(-100px + 50%)');
+}, 'offset-distance supports calc');
+
+test(function(){
+ target.style = 'offset-rotate: auto calc(1turn - 100grad);';
+ assert_equals(getComputedStyle(target).offsetRotate, 'auto 270deg');
+}, 'offset-rotate supports calc');
+
+test(function(){
+ target.style = 'offset-anchor: calc(30px + 20%) calc(-200px + 8em + 100%);';
+ assert_equals(getComputedStyle(target).offsetAnchor, 'calc(30px + 20%) calc(-40px + 100%)');
+}, 'offset-anchor supports calc');
+
+</script>
+</body>
+</html>
diff --git a/css/tools/w3ctestlib b/css/tools/w3ctestlib
index dec3bd9..03dbe84 160000
--- a/css/tools/w3ctestlib
+++ b/css/tools/w3ctestlib
@@ -1 +1 @@
-Subproject commit dec3bd93ba4a5c7a3bc4b803f4ac5034e11022a2
+Subproject commit 03dbe8407789c6d70d9067eb287aadc15130065e
diff --git a/cssom-view/elementFromPoint-002.html b/cssom-view/elementFromPoint-002.html
new file mode 100644
index 0000000..ebab52f
--- /dev/null
+++ b/cssom-view/elementFromPoint-002.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Checking whether dynamic changes to visibility interact correctly with
+ table anonymous boxes</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>
+#overlay {
+ display: table;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: white;
+ z-index: 999
+}
+
+#wrapper { position: relative; }
+</style>
+<div id=log></div>
+<div id="wrapper">
+ <div id="overlay"><div></div></div>
+ <div id="target">Some text</div>
+</div>
+<script>
+ test(function() {
+ var t = document.querySelector("#target");
+ var rect = t.getBoundingClientRect();
+ var hit = document.elementFromPoint(rect.x + rect.width/2,
+ rect.y + rect.height/2);
+ assert_equals(hit, t.previousElementSibling,
+ "Should hit the overlay first.");
+ t.previousElementSibling.style.visibility = "hidden";
+ hit = document.elementFromPoint(rect.x + rect.width/2,
+ rect.y + rect.height/2);
+ assert_equals(hit, t,
+ "Should hit our target now that the overlay is hidden.");
+ });
+</script>
diff --git a/cssom-view/elementFromPoint-003.html b/cssom-view/elementFromPoint-003.html
new file mode 100644
index 0000000..0a1ac40
--- /dev/null
+++ b/cssom-view/elementFromPoint-003.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Checking whether dynamic changes to visibility interact correctly with
+ table anonymous boxes</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<style>
+#overlay {
+ display: table;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: white;
+ z-index: 999
+}
+
+#wrapper { position: relative; }
+</style>
+<div id=log></div>
+<div id="wrapper">
+ <div id="overlay"><div></div></div>
+ <div id="target">Some text</div>
+</div>
+<script>
+ test(function() {
+ // Make sure we have boxes constructed already.
+ document.body.offsetWidth;
+ var overlay = document.querySelector("#overlay");
+ overlay.insertBefore(document.createElement("div"), overlay.firstChild);
+ overlay.appendChild(document.createElement("div"));
+ // Make sure we have boxes constructed for those inserts/appends
+ document.body.offsetWidth;
+ overlay.firstChild.nextSibling.remove();
+ var t = document.querySelector("#target");
+ var rect = t.getBoundingClientRect();
+ var hit = document.elementFromPoint(rect.x + rect.width/2,
+ rect.y + rect.height/2);
+ assert_equals(hit, t.previousElementSibling,
+ "Should hit the overlay first.");
+ t.previousElementSibling.style.visibility = "hidden";
+ hit = document.elementFromPoint(rect.x + rect.width/2,
+ rect.y + rect.height/2);
+ assert_equals(hit, t,
+ "Should hit our target now that the overlay is hidden.");
+ });
+</script>
diff --git a/cssom/getComputedStyle-pseudo.html b/cssom/getComputedStyle-pseudo.html
index fa2a03c..c8a4506 100644
--- a/cssom/getComputedStyle-pseudo.html
+++ b/cssom/getComputedStyle-pseudo.html
@@ -55,6 +55,9 @@
assert_equals(getComputedStyle(has_no_pseudos, pseudo).position, "static",
"Nonexistent " + pseudo + " pseudo-element shouldn't claim to have " +
"the same style as the originating element");
+ assert_equals(getComputedStyle(has_no_pseudos, pseudo).width, "auto",
+ "Nonexistent " + pseudo + " pseudo-element shouldn't claim to have " +
+ "definite size");
});
}, "Resolution of nonexistent pseudo-element styles");
test(function() {
diff --git a/cssom/stylesheet-same-origin.sub.html b/cssom/stylesheet-same-origin.sub.html
index b259e43..9df0a54 100644
--- a/cssom/stylesheet-same-origin.sub.html
+++ b/cssom/stylesheet-same-origin.sub.html
@@ -8,10 +8,12 @@
<link id="crossorigin" href="http://www1.{{host}}:{{ports[http][1]}}/stylesheet-same-origin.css" rel="stylesheet">
<link id="sameorigin" href="stylesheet-same-origin.css" rel="stylesheet">
+ <link id="sameorigindata" href="data:text/css,.green-text{color:rgb(0, 255, 0)}" rel="stylesheet">
<script>
var crossorigin = document.getElementById("crossorigin").sheet;
var sameorigin = document.getElementById("sameorigin").sheet;
+ var sameorigindata = document.getElementById("sameorigindata").sheet;
test(function() {
assert_throws("SecurityError",
@@ -32,13 +34,22 @@
"Cross origin stylesheet.deleteRule should throw SecurityError.");
}, "Origin-clean check in cross-origin CSSOM Stylesheets");
+ function doOriginCleanCheck(sheet, name) {
+ assert_equals(sheet.cssRules.length, 1, name + " stylesheet.cssRules should be accessible.");
+ sheet.insertRule("#test { margin: 10px; }", 1);
+ assert_equals(sheet.cssRules.length, 2, name + " stylesheet.insertRule should be accessible.");
+ sheet.deleteRule(0);
+ assert_equals(sheet.cssRules.length, 1, name + " stylesheet.deleteRule should be accessible.");
+ }
+
test(function() {
- assert_equals(sameorigin.cssRules.length, 1, "Same origin stylesheet.cssRules should be accessible.");
- sameorigin.insertRule("#test { margin: 10px; }", 1);
- assert_equals(sameorigin.cssRules.length, 2, "Same origin stylesheet.insertRule should be accessible.");
- sameorigin.deleteRule(0);
- assert_equals(sameorigin.cssRules.length, 1, "Same origin stylesheet.deleteRule should be accessible.");
+ doOriginCleanCheck(sameorigin, "Same-origin");
}, "Origin-clean check in same-origin CSSOM Stylesheets");
+
+ test(function() {
+ doOriginCleanCheck(sameorigindata, "data:css");
+ }, "Origin-clean check in data:css CSSOM Stylesheets");
+
</script>
</head>
<body>
diff --git a/dom/nodes/Element-closest.html b/dom/nodes/Element-closest.html
index e5af1a4..386e3bd 100644
--- a/dom/nodes/Element-closest.html
+++ b/dom/nodes/Element-closest.html
@@ -56,11 +56,10 @@
do_test(":first-child" , "test12", "test3");
do_test(":invalid" , "test11", "test2");
- do_scope_test(":scope" , "test4");
- do_scope_test("select > :scope" , "test4");
- do_scope_test("div > :scope" , "test4");
- do_scope_test(":has(> :scope)" , "test4");
-
+ do_test(":scope" , "test4", "test4");
+ do_test("select > :scope" , "test4", "test4");
+ do_test("div > :scope" , "test4", "");
+ do_test(":has(> :scope)" , "test4", "test3");
function do_test(aSelector, aElementId, aTargetId) {
test(function() {
var el = document.getElementById(aElementId).closest(aSelector);
@@ -71,13 +70,4 @@
}
}, "Element.closest with context node '" + aElementId + "' and selector '" + aSelector + "'");
}
-
-function do_scope_test(aSelector, aElementId) {
- test(function() {
- var el = document.getElementById(aElementId);
- assert_throws("SYNTAX_ERR", function() {
- el.closest(aSelector);
- });
- }, "Element.closest with context node '" + aElementId + "' and selector '" + aSelector + "' should throw");
-}
</script>
diff --git a/dom/nodes/selectors.js b/dom/nodes/selectors.js
index 7d527d8..7f99a42 100644
--- a/dom/nodes/selectors.js
+++ b/dom/nodes/selectors.js
@@ -306,14 +306,20 @@
{name: "Descendant combinator, not matching element with id that is not a descendant of an element with id", selector: "#descendant-div1 #descendant-div4", expect: [] /*no matches*/, level: 1, testType: TEST_QSA},
{name: "Descendant combinator, whitespace characters", selector: "#descendant\t\r\n#descendant-div2", expect: ["descendant-div2"], level: 1, testType: TEST_QSA | TEST_MATCH},
- // - Descendant combinator '>>'
- {name: "Descendant combinator '>>', matching element that is a descendant of an element with id", selector: "#descendant>>div", expect: ["descendant-div1", "descendant-div2", "descendant-div3", "descendant-div4"], level: 1, testType: TEST_QSA | TEST_MATCH},
- {name: "Descendant combinator '>>', matching element with id that is a descendant of an element", selector: "body>>#descendant-div1", expect: ["descendant-div1"], exclude: ["detached", "fragment"], level: 1, testType: TEST_QSA | TEST_MATCH},
- {name: "Descendant combinator '>>', matching element with id that is a descendant of an element", selector: "div>>#descendant-div1", expect: ["descendant-div1"], level: 1, testType: TEST_QSA | TEST_MATCH},
- {name: "Descendant combinator '>>', matching element with id that is a descendant of an element with id", selector: "#descendant>>#descendant-div2", expect: ["descendant-div2"], level: 1, testType: TEST_QSA | TEST_MATCH},
- {name: "Descendant combinator '>>', matching element with class that is a descendant of an element with id", selector: "#descendant>>.descendant-div2", expect: ["descendant-div2"], level: 1, testType: TEST_QSA | TEST_MATCH},
- {name: "Descendant combinator '>>', matching element with class that is a descendant of an element with class", selector: ".descendant-div1>>.descendant-div3", expect: ["descendant-div3"], level: 1, testType: TEST_QSA | TEST_MATCH},
- {name: "Descendant combinator '>>', not matching element with id that is not a descendant of an element with id", selector: "#descendant-div1>>#descendant-div4", expect: [] /*no matches*/, level: 1, testType: TEST_QSA},
+ /* The future of this combinator is uncertain, see
+ * https://github.com/w3c/csswg-drafts/issues/641
+ * These tests are commented out until a final decision is made on whether to
+ * keep the feature in the spec.
+ */
+
+ // // - Descendant combinator '>>'
+ // {name: "Descendant combinator '>>', matching element that is a descendant of an element with id", selector: "#descendant>>div", expect: ["descendant-div1", "descendant-div2", "descendant-div3", "descendant-div4"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with id that is a descendant of an element", selector: "body>>#descendant-div1", expect: ["descendant-div1"], exclude: ["detached", "fragment"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with id that is a descendant of an element", selector: "div>>#descendant-div1", expect: ["descendant-div1"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with id that is a descendant of an element with id", selector: "#descendant>>#descendant-div2", expect: ["descendant-div2"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with class that is a descendant of an element with id", selector: "#descendant>>.descendant-div2", expect: ["descendant-div2"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with class that is a descendant of an element with class", selector: ".descendant-div1>>.descendant-div3", expect: ["descendant-div3"], level: 1, testType: TEST_QSA | TEST_MATCH},
+ // {name: "Descendant combinator '>>', not matching element with id that is not a descendant of an element with id", selector: "#descendant-div1>>#descendant-div4", expect: [] /*no matches*/, level: 1, testType: TEST_QSA},
// - Child combinator '>'
{name: "Child combinator, matching element that is a child of an element with id", selector: "#child>div", expect: ["child-div1", "child-div4"], level: 2, testType: TEST_QSA | TEST_MATCH},
@@ -675,14 +681,14 @@
{name: "Descendant combinator, not matching element with id that is not a descendant of an element with id", selector: "#descendant-div1 #descendant-div4", ctx: "", expect: [] /*no matches*/, level: 1, testType: TEST_FIND},
{name: "Descendant combinator, whitespace characters (1)", selector: "#descendant\t\r\n#descendant-div2", ctx: "", expect: ["descendant-div2"], level: 1, testType: TEST_FIND | TEST_MATCH},
- // - Descendant combinator '>>'
- {name: "Descendant combinator '>>', matching element that is a descendant of an element with id (1)", selector: "#descendant>>div", ctx: "", expect: ["descendant-div1", "descendant-div2", "descendant-div3", "descendant-div4"], level: 1, testType: TEST_FIND | TEST_MATCH},
- {name: "Descendant combinator '>>', matching element with id that is a descendant of an element (1)", selector: "body>>#descendant-div1", ctx: "", expect: ["descendant-div1"], exclude: ["detached", "fragment"], level: 1, testType: TEST_FIND | TEST_MATCH},
- {name: "Descendant combinator '>>', matching element with id that is a descendant of an element (1)", selector: "div>>#descendant-div1", ctx: "", expect: ["descendant-div1"], level: 1, testType: TEST_FIND | TEST_MATCH},
- {name: "Descendant combinator '>>', matching element with id that is a descendant of an element with id (1)", selector: "#descendant>>#descendant-div2", ctx: "", expect: ["descendant-div2"], level: 1, testType: TEST_FIND | TEST_MATCH},
- {name: "Descendant combinator '>>', matching element with class that is a descendant of an element with id (1)", selector: "#descendant>>.descendant-div2", ctx: "", expect: ["descendant-div2"], level: 1, testType: TEST_FIND | TEST_MATCH},
- {name: "Descendant combinator, '>>', matching element with class that is a descendant of an element with class (1)", selector: ".descendant-div1>>.descendant-div3", ctx: "", expect: ["descendant-div3"], level: 1, testType: TEST_FIND | TEST_MATCH},
- {name: "Descendant combinator '>>', not matching element with id that is not a descendant of an element with id", selector: "#descendant-div1>>#descendant-div4", ctx: "", expect: [] /*no matches*/, level: 1, testType: TEST_FIND},
+ // // - Descendant combinator '>>'
+ // {name: "Descendant combinator '>>', matching element that is a descendant of an element with id (1)", selector: "#descendant>>div", ctx: "", expect: ["descendant-div1", "descendant-div2", "descendant-div3", "descendant-div4"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with id that is a descendant of an element (1)", selector: "body>>#descendant-div1", ctx: "", expect: ["descendant-div1"], exclude: ["detached", "fragment"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with id that is a descendant of an element (1)", selector: "div>>#descendant-div1", ctx: "", expect: ["descendant-div1"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with id that is a descendant of an element with id (1)", selector: "#descendant>>#descendant-div2", ctx: "", expect: ["descendant-div2"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ // {name: "Descendant combinator '>>', matching element with class that is a descendant of an element with id (1)", selector: "#descendant>>.descendant-div2", ctx: "", expect: ["descendant-div2"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ // {name: "Descendant combinator, '>>', matching element with class that is a descendant of an element with class (1)", selector: ".descendant-div1>>.descendant-div3", ctx: "", expect: ["descendant-div3"], level: 1, testType: TEST_FIND | TEST_MATCH},
+ // {name: "Descendant combinator '>>', not matching element with id that is not a descendant of an element with id", selector: "#descendant-div1>>#descendant-div4", ctx: "", expect: [] /*no matches*/, level: 1, testType: TEST_FIND},
// - Child combinator '>'
{name: "Child combinator, matching element that is a child of an element with id (1)", selector: "#child>div", ctx: "", expect: ["child-div1", "child-div4"], level: 2, testType: TEST_FIND | TEST_MATCH},
diff --git a/feature-policy/README.md b/feature-policy/README.md
index 695a532..775a354 100644
--- a/feature-policy/README.md
+++ b/feature-policy/README.md
@@ -1,9 +1,7 @@
# Feature Policy Guide
## How to Test a New Feature with Feature Policy
-This directory contains a framework to test features with feature policy. Please
-refer to `/content/common/feature_policy/README.md` for more details on feature
-policy.
+This directory contains a framework to test features with feature policy.
When adding a new feature to feature policy, the following cases should be tested:
* feature enabled by header policy [HTTP tests]
diff --git a/feature-policy/resources/feature-policy-webvr.html b/feature-policy/resources/feature-policy-webvr.html
new file mode 100644
index 0000000..64a152b
--- /dev/null
+++ b/feature-policy/resources/feature-policy-webvr.html
@@ -0,0 +1,9 @@
+<script>
+'use strict';
+
+Promise.resolve().then(() => navigator.getVRDisplays()).then(displays => {
+ window.parent.postMessage({ enabled: true }, '*');
+}, error => {
+ window.parent.postMessage({ enabled: false }, '*');
+});
+</script>
diff --git a/feature-policy/resources/featurepolicy.js b/feature-policy/resources/featurepolicy.js
index 658b254..1d7395a 100644
--- a/feature-policy/resources/featurepolicy.js
+++ b/feature-policy/resources/featurepolicy.js
@@ -28,7 +28,7 @@
frame.src = src;
if (typeof feature_name !== 'undefined') {
- frame.allow.add(feature_name);
+ frame.allow = frame.allow.concat(";" + feature_name);
}
if (typeof allow_attribute !== 'undefined') {
diff --git a/fetch/api/abort/general.any.js b/fetch/api/abort/general.any.js
index df77d56..e2c6e8d 100644
--- a/fetch/api/abort/general.any.js
+++ b/fetch/api/abort/general.any.js
@@ -320,7 +320,7 @@
mode: 'no-cors'
});
- const stashTakeURL = new URL(`../resources/stash-take.py?key=${stateKey}`);
+ const stashTakeURL = new URL(`../resources/stash-take.py?key=${stateKey}`, location);
stashTakeURL.hostname = 'www1.' + stashTakeURL.hostname;
const beforeAbortResult = await fetch(stashTakeURL).then(r => r.json());
@@ -440,6 +440,10 @@
const signal = controller.signal;
const response = await fetch(`../resources/empty.txt`, { signal });
+
+ // Read whole response to ensure close signal has sent.
+ await response.clone().text();
+
const reader = response.body.getReader();
controller.abort();
diff --git a/fetch/api/cors/cors-preflight-cache.any.js b/fetch/api/cors/cors-preflight-cache.any.js
new file mode 100644
index 0000000..ce6a169
--- /dev/null
+++ b/fetch/api/cors/cors-preflight-cache.any.js
@@ -0,0 +1,46 @@
+// META: script=/common/utils.js
+// META: script=../resources/utils.js
+// META: script=/common/get-host-info.sub.js
+
+var cors_url = get_host_info().HTTP_REMOTE_ORIGIN +
+ dirname(location.pathname) +
+ RESOURCES_DIR +
+ "preflight.py";
+
+promise_test((test) => {
+ var uuid_token = token();
+ var request_url =
+ cors_url + "?token=" + uuid_token + "&max_age=12000&allow_methods=POST" +
+ "&allow_headers=x-test-header";
+ return fetch(cors_url + "?token=" + uuid_token + "&clear-stash")
+ .then(() => {
+ return fetch(
+ new Request(request_url,
+ {
+ mode: "cors",
+ method: "POST",
+ headers: [["x-test-header", "test1"]]
+ }));
+ })
+ .then((resp) => {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ assert_equals(resp.headers.get("x-did-preflight"), "1", "Preflight request has been made");
+ return fetch(cors_url + "?token=" + uuid_token + "&clear-stash");
+ })
+ .then((res) => res.text())
+ .then((txt) => {
+ assert_equals(txt, "1", "Server stash must be cleared.");
+ return fetch(
+ new Request(request_url,
+ {
+ mode: "cors",
+ method: "POST",
+ headers: [["x-test-header", "test2"]]
+ }));
+ })
+ .then((resp) => {
+ assert_equals(resp.status, 200, "Response's status is 200");
+ assert_equals(resp.headers.get("x-did-preflight"), "0", "Preflight request has not been made");
+ return fetch(cors_url + "?token=" + uuid_token + "&clear-stash");
+ });
+});
diff --git a/fetch/api/headers/headers-record.html b/fetch/api/headers/headers-record.html
index a91b9b5..85dfadd 100644
--- a/fetch/api/headers/headers-record.html
+++ b/fetch/api/headers/headers-record.html
@@ -251,59 +251,21 @@
ownKeys: function() {
return [ "a", "c", "a", "c" ];
},
- getCalls: 0,
- gotCOnce: false,
- get: function(target, name, receiver) {
- if (name == "c") {
- this.gotCOnce = true;
- }
- if (typeof name == "string") {
- return ++this.getCalls;
- }
- return Reflect.get(target, name, receiver);
- },
- getOwnPropertyDescriptor: function(target, name) {
- var desc = Reflect.getOwnPropertyDescriptor(target, name);
- if (name == "c" && this.gotCOnce) {
- desc.enumerable = false;
- }
- return desc;
- }
};
var lyingProxy = new Proxy(record, lyingHandler);
var proxy = new Proxy(lyingProxy, loggingHandler);
- var h = new Headers(proxy);
- assert_equals(log.length, 9);
+ // Returning duplicate keys from ownKeys() throws a TypeError.
+ assert_throws(new TypeError(),
+ function() { var h = new Headers(proxy); });
+
+ assert_equals(log.length, 2);
// The first thing is the [[Get]] of Symbol.iterator to figure out whether
// we're a sequence, during overload resolution.
assert_array_equals(log[0], ["get", lyingProxy, Symbol.iterator, proxy]);
// Then we have the [[OwnPropertyKeys]] from
// https://heycam.github.io/webidl/#es-to-record step 4.
assert_array_equals(log[1], ["ownKeys", lyingProxy]);
- // Then the [[GetOwnProperty]] from step 5.1.
- assert_array_equals(log[2], ["getOwnPropertyDescriptor", lyingProxy, "a"]);
- // Then the [[Get]] from step 5.2.
- assert_array_equals(log[3], ["get", lyingProxy, "a", proxy]);
- // Then the second [[GetOwnProperty]] from step 5.1.
- assert_array_equals(log[4], ["getOwnPropertyDescriptor", lyingProxy, "c"]);
- // Then the second [[Get]] from step 5.2.
- assert_array_equals(log[5], ["get", lyingProxy, "c", proxy]);
- // Then the third [[GetOwnProperty]] from step 5.1.
- assert_array_equals(log[6], ["getOwnPropertyDescriptor", lyingProxy, "a"]);
- // Then the third [[Get]] from step 5.2.
- assert_array_equals(log[7], ["get", lyingProxy, "a", proxy]);
- // Then the fourth [[GetOwnProperty]] from step 5.1.
- assert_array_equals(log[8], ["getOwnPropertyDescriptor", lyingProxy, "c"]);
- // No [[Get]] because not enumerable.
-
- // Check the results.
- assert_equals([...h].length, 2);
- assert_array_equals([...h.keys()], ["a", "c"]);
- assert_true(h.has("a"));
- assert_equals(h.get("a"), "3");
- assert_true(h.has("c"));
- assert_equals(h.get("c"), "2");
}, "Correct operation ordering with repeated keys");
test(function() {
diff --git a/fetch/api/resources/preflight.py b/fetch/api/resources/preflight.py
index 857ce8f..6263eae 100644
--- a/fetch/api/resources/preflight.py
+++ b/fetch/api/resources/preflight.py
@@ -12,6 +12,12 @@
else:
headers.append(("Access-Control-Allow-Origin", "*"))
+ if "clear-stash" in request.GET:
+ if request.server.stash.take(token) is not None:
+ return headers, "1"
+ else:
+ return headers, "0"
+
if "credentials" in request.GET:
headers.append(("Access-Control-Allow-Credentials", "true"))
diff --git a/fetch/api/resources/utils.js b/fetch/api/resources/utils.js
index f290c22..213d01a 100644
--- a/fetch/api/resources/utils.js
+++ b/fetch/api/resources/utils.js
@@ -81,6 +81,26 @@
});
}
+function validateStreamFromPartialString(reader, expectedValue, retrievedArrayBuffer) {
+ return reader.read().then(function(data) {
+ if (!data.done) {
+ assert_true(data.value instanceof Uint8Array, "Fetch ReadableStream chunks should be Uint8Array");
+ var newBuffer;
+ if (retrievedArrayBuffer) {
+ newBuffer = new ArrayBuffer(data.value.length + retrievedArrayBuffer.length);
+ newBuffer.set(retrievedArrayBuffer, 0);
+ newBuffer.set(data.value, retrievedArrayBuffer.length);
+ } else {
+ newBuffer = data.value;
+ }
+ return validateStreamFromPartialString(reader, expectedValue, newBuffer);
+ }
+
+ var string = new TextDecoder("utf-8").decode(retrievedArrayBuffer);
+ return assert_true(string.search(expectedValue) != -1, "Retrieve and verify stream");
+ });
+}
+
// From streams tests
function delay(milliseconds)
{
diff --git a/fetch/api/response/response-consume-stream.html b/fetch/api/response/response-consume-stream.html
index 21424f2..8a97fa0 100644
--- a/fetch/api/response/response-consume-stream.html
+++ b/fetch/api/response/response-consume-stream.html
@@ -57,14 +57,15 @@
promise_test(function(test) {
var response = new Response(formData);
- return validateStreamFromString(response.body.getReader(), "name=value");
+ return validateStreamFromPartialString(response.body.getReader(),
+ "Content-Disposition: form-data; name=\"name\"\r\n\r\nvalue");
}, "Read form data response's body as readableStream");
test(function() {
assert_equals(Response.error().body, null);
}, "Getting an error Response stream");
-promise_test(function(test) {
+test(function() {
assert_equals(Response.redirect("/").body, null);
}, "Getting a redirect Response stream");
diff --git a/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html b/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html
new file mode 100644
index 0000000..3b13fcc
--- /dev/null
+++ b/fetch/security/dangling-markup-mitigation-data-url.tentative.sub.html
@@ -0,0 +1,228 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+ function readableURL(url) {
+ return url.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
+ }
+
+ // For each of the following tests, we'll inject a frame containing the HTML we'd like to poke at
+ // as a `srcdoc` attribute. Because we're injecting markup via `srcdoc`, we need to entity-escape
+ // the content we'd like to treat as "raw" (e.g. `\n` => ` `, `<` => `<`), and
+ // double-escape the "escaped" content.
+ var rawBrace = "<";
+ var escapedBrace = "&lt;";
+ var doubleEscapedBrace = "&amp;lt;";
+ var rawNewline = " ";
+ var escapedNewline = "&#10;";
+ var doubleEscapedNewline = "&amp;#10;";
+
+ function appendFrameAndGetElement(test, frame) {
+ return new Promise((resolve, reject) => {
+ frame.onload = test.step_func(_ => {
+ frame.onload = null;
+ resolve(frame.contentDocument.querySelector('#dangling'));
+ });
+ document.body.appendChild(frame);
+ });
+ }
+
+ function assert_img_loaded(test, frame) {
+ appendFrameAndGetElement(test, frame)
+ .then(test.step_func_done(img => {
+ assert_equals(img.naturalHeight, 1, "Height");
+ frame.remove();
+ }));
+ }
+
+ function assert_img_not_loaded(test, frame) {
+ appendFrameAndGetElement(test, frame)
+ .then(test.step_func_done(img => {
+ assert_equals(img.naturalHeight, 0, "Height");
+ assert_equals(img.naturalWidth, 0, "Width");
+ }));
+ }
+
+ function assert_nested_img_not_loaded(test, frame) {
+ window.addEventListener('message', test.step_func(e => {
+ if (e.source != frame.contentWindow)
+ return;
+
+ assert_equals(e.data, 'error');
+ test.done();
+ }));
+ appendFrameAndGetElement(test, frame);
+ }
+
+ function assert_nested_img_loaded(test, frame) {
+ window.addEventListener('message', test.step_func(e => {
+ if (e.source != frame.contentWindow)
+ return;
+
+ assert_equals(e.data, 'loaded');
+ test.done();
+ }));
+ appendFrameAndGetElement(test, frame);
+ }
+
+ function createFrame(markup) {
+ var i = document.createElement('iframe');
+ i.srcdoc = `${markup}sekrit`;
+ return i;
+ }
+
+ // Subresource requests:
+ [
+ // Data URLs don't themselves trigger blocking:
+ `<img id="dangling" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`,
+ `<img id="dangling" src="data:image/png;base64,${rawNewline}iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`,
+ `<img id="dangling" src="data:image/png;base64,i${rawNewline}VBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`,
+
+ // Data URLs with visual structure don't trigger blocking
+ `<img id="dangling" src="data:image/svg+xml;utf8,
+ <svg width='1' height='1' xmlns='http://www.w3.org/2000/svg'>
+ <rect width='100%' height='100%' fill='rebeccapurple'/>
+ <rect x='10%' y='10%' width='80%' height='80%' fill='lightgreen'/>
+ </svg>">`
+ ].forEach(markup => {
+ async_test(t => {
+ var i = createFrame(`${markup} <element attr="" another=''>`);
+ assert_img_loaded(t, i);
+ }, readableURL(markup));
+ });
+
+ // Nested subresource requests:
+ //
+ // The following tests load a given HTML string into `<iframe srcdoc="...">`, so we'll
+ // end up with a frame with an ID of `dangling` inside the srcdoc frame. That frame's
+ // `src` is a `data:` URL that resolves to an HTML document containing an `<img>`. The
+ // error/load handlers on that image are piped back up to the top-level document to
+ // determine whether the tests' expectations were met. *phew*
+
+ // Allowed:
+ [
+ // Just a newline:
+ `<iframe id="dangling"
+ src="data:text/html,
+ <img
+ onload='window.parent.postMessage("loaded", "*");'
+ onerror='window.parent.postMessage("error", "*");'
+ src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png'>
+ ">
+ </iframe>`,
+
+ // Just a brace:
+ `<iframe id="dangling"
+ src="data:text/html,
+ <img
+ onload='window.parent.postMessage("loaded", "*");'
+ onerror='window.parent.postMessage("error", "*");'
+ src='http://{{host}}:{{ports[http][0]}}/images/green-256x256.png?${rawBrace}'>
+ ">
+ </iframe>`,
+
+ // Newline and escaped brace.
+ `<iframe id="dangling"
+ src="data:text/html,
+ <img
+ onload='window.parent.postMessage("loaded", "*");'
+ onerror='window.parent.postMessage("error", "*");'
+ src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${doubleEscapedBrace}'>
+ ">
+ </iframe>`,
+
+ // Brace and escaped newline:
+ `<iframe id="dangling"
+ src="data:text/html,
+ <img
+ onload='window.parent.postMessage("loaded", "*");'
+ onerror='window.parent.postMessage("error", "*");'
+ src='http://{{host}}:{{ports[http][0]}}/images/green-256x256.png?${doubleEscapedNewline}${rawBrace}'>
+ ">
+ </iframe>`,
+ ].forEach(markup => {
+ async_test(t => {
+ var i = createFrame(`
+ <script>
+ // Repeat the message so that the parent can track this frame as the source.
+ window.onmessage = e => window.parent.postMessage(e.data, '*');
+ </scr`+`ipt>
+ ${markup}
+ `);
+ assert_nested_img_loaded(t, i);
+ }, readableURL(markup));
+ });
+
+ // Nested requests that should fail:
+ [
+ // Newline and brace:
+ `<iframe id="dangling"
+ src="data:text/html,
+ <img
+ onload='window.parent.postMessage("loaded", "*");'
+ onerror='window.parent.postMessage("error", "*");'
+ src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'>
+ ">
+ </iframe>`,
+
+ // Leading whitespace:
+ `<iframe id="dangling"
+ src=" data:text/html,
+ <img
+ onload='window.parent.postMessage("loaded", "*");'
+ onerror='window.parent.postMessage("error", "*");'
+ src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'>
+ ">
+ </iframe>`,
+
+ // Leading newline:
+ `<iframe id="dangling"
+ src="\ndata:text/html,
+ <img
+ onload='window.parent.postMessage("loaded", "*");'
+ onerror='window.parent.postMessage("error", "*");'
+ src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'>
+ ">
+ </iframe>`,
+ `<iframe id="dangling"
+ src="${rawNewline}data:text/html,
+ <img
+ onload='window.parent.postMessage("loaded", "*");'
+ onerror='window.parent.postMessage("error", "*");'
+ src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'>
+ ">
+ </iframe>`,
+
+ // Leading tab:
+ `<iframe id="dangling"
+ src="\tdata:text/html,
+ <img
+ onload='window.parent.postMessage("loaded", "*");'
+ onerror='window.parent.postMessage("error", "*");'
+ src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'>
+ ">
+ </iframe>`,
+
+ // Leading carrige return:
+ `<iframe id="dangling"
+ src="\rdata:text/html,
+ <img
+ onload='window.parent.postMessage("loaded", "*");'
+ onerror='window.parent.postMessage("error", "*");'
+ src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'>
+ ">
+ </iframe>`,
+ ].forEach(markup => {
+ async_test(t => {
+ var i = createFrame(`
+ <script>
+ // Repeat the message so that the parent can track this frame as the source.
+ window.onmessage = e => window.parent.postMessage(e.data, '*');
+ </scr`+`ipt>
+ ${markup}
+ `);
+ assert_nested_img_not_loaded(t, i);
+ }, readableURL(markup));
+ });
+</script>
diff --git a/fetch/security/dangling-markup-mitigation.tentative.html b/fetch/security/dangling-markup-mitigation.tentative.html
index 53d74ba..61a9316 100644
--- a/fetch/security/dangling-markup-mitigation.tentative.html
+++ b/fetch/security/dangling-markup-mitigation.tentative.html
@@ -1,7 +1,6 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
-<script src="./resources/helper.js"></script>
<body>
<script>
function readableURL(url) {
@@ -118,16 +117,6 @@
/images/green-1x1.png?img=${escapedNewline}
">
`,
-
- // Data URLs don't trigger blocking:
- `<img id="dangling" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`,
- `<img id="dangling" src="data:image/png;base64,${rawNewline}iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`,
- `<img id="dangling" src="data:image/png;base64,i${rawNewline}VBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`,
- `<img id="dangling" src="data:image/svg+xml;utf8,
- <svg width='1' height='1' xmlns='http://www.w3.org/2000/svg'>
- <rect width='100%' height='100%' fill='rebeccapurple'/>
- <rect x='10%' y='10%' width='80%' height='80%' fill='lightgreen'/>
- </svg>">`
];
should_load.forEach(markup => {
diff --git a/hr-time/timeOrigin.html b/hr-time/timeOrigin.html
index 415d745..20aea75 100644
--- a/hr-time/timeOrigin.html
+++ b/hr-time/timeOrigin.html
@@ -9,10 +9,11 @@
const windowOrigin = performance.timeOrigin;
test(() => {
+ // Use a 30ms cushion when comparing with Date() to account for inaccuracy.
const startTime = Date.now();
- assert_greater_than_equal(startTime + 1, windowOrigin, 'Date.now() should be at least as large as the window timeOrigin.');
+ assert_greater_than_equal(startTime + 30, windowOrigin, 'Date.now() should be at least as large as the window timeOrigin.');
const startNow = performance.now();
- assert_less_than_equal(startTime, windowOrigin + startNow + 1, 'Date.now() should be close to window timeOrigin.');
+ assert_less_than_equal(startTime, windowOrigin + startNow + 30, 'Date.now() should be close to window timeOrigin.');
}, 'Window timeOrigin is close to Date.now() when there is no system clock adjustment.');
const workerScript = 'postMessage({timeOrigin: performance.timeOrigin})';
diff --git a/html/README.md b/html/README.md
new file mode 100644
index 0000000..11f1716
--- /dev/null
+++ b/html/README.md
@@ -0,0 +1,7 @@
+# HTML
+
+This directory contains tests for [HTML](https://html.spec.whatwg.org/multipage/).
+
+Sub-directory names should be based on the URL of the corresponding part of the
+multipage-version specification. For example, The URL of
+"8.3 Base64 utility methods" is [https://html.spec.whatwg.org/multipage/webappapis.html#atob](https://html.spec.whatwg.org/multipage/webappapis.html#atob). So the directory in WPT is [webappsapis/atob/](webappsapis/atob).
diff --git a/html/browsers/history/the-history-interface/history_properties_only_fully_active.html b/html/browsers/history/the-history-interface/history_properties_only_fully_active.html
new file mode 100644
index 0000000..0404a6b
--- /dev/null
+++ b/html/browsers/history/the-history-interface/history_properties_only_fully_active.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>history properties should throw SecurityError when not in a fully active Document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+ <iframe id="child"></iframe>
+</body>
+<script>
+ test(function(t) {
+ var ifr = document.getElementById("child");
+ var cached_history = ifr.contentWindow.history;
+ ifr.remove();
+ assert_throws("SecurityError", function() { cached_history.length; });
+ assert_throws("SecurityError", function() { cached_history.scrollRestoration; });
+ assert_throws("SecurityError", function() { cached_history.state; });
+ assert_throws("SecurityError", function() { cached_history.go(0); });
+ assert_throws("SecurityError", function() { cached_history.back(); });
+ assert_throws("SecurityError", function() { cached_history.forward(); });
+ assert_throws("SecurityError", function() { cached_history.pushState(1, document.title, "?x=1"); });
+ assert_throws("SecurityError", function() { cached_history.replaceState(2, document.title, "?x=2"); });
+ });
+</script>
diff --git a/html/browsers/origin/origin-of-data-document.html b/html/browsers/origin/origin-of-data-document.html
index cedb251..345a3a6 100644
--- a/html/browsers/origin/origin-of-data-document.html
+++ b/html/browsers/origin/origin-of-data-document.html
@@ -10,17 +10,28 @@
<body>
<script>
async_test(function (t) {
- window.addEventListener("message", t.step_func_done(function (e) {
- assert_equals(e.origin, "null", "Messages sent from a 'data:' URL should have an opaque origin (which serializes to 'null').");
- assert_throws("SecurityError", function () {
- var couldAccessCrossOriginProperty = e.source.location.href;
- }, "The 'data:' frame should be cross-origin.")
- }));
-
var i = document.createElement('iframe');
i.src = "data:text/html,<script>" +
" window.parent.postMessage('Hello!', '*');" +
"</scr" + "ipt>";
+
+ window.addEventListener("message", t.step_func_done(function (e) {
+ assert_equals(e.origin, "null", "Messages sent from a 'data:' URL should have an opaque origin (which serializes to 'null').");
+ assert_throws("SecurityError", function () {
+ var couldAccessCrossOriginProperty = e.source.location.href;
+ }, "The 'data:' frame should be cross-origin: 'window.location.href'");
+
+ // Try to access contentDocument of the 'data: ' frame. Some browsers
+ // (i.e. Firefox, Safari) will return |null| and some (i.e. Chrome)
+ // will throw an exception.
+ var dataFrameContentDocument = null;
+ try {
+ dataFrameContentDocument = i.contentDocument;
+ } catch (ex) {
+ }
+ assert_equals(dataFrameContentDocument, null, "The 'data:' iframe should be unable to access its contentDocument.");
+ }));
+
document.body.appendChild(i);
}, "The origin of a 'data:' document in a frame is opaque.");
</script>
diff --git a/html/dom/elements-embedded.js b/html/dom/elements-embedded.js
index 1b6e421..64e7416 100644
--- a/html/dom/elements-embedded.js
+++ b/html/dom/elements-embedded.js
@@ -1,5 +1,5 @@
-// Up-to-date as of 2013-04-06.
var embeddedElements = {
+ picture: {},
img: {
// Conforming
alt: "string",
@@ -92,7 +92,6 @@
preload: {type: "enum", keywords: ["none", "metadata", "auto"], nonCanon: {"": "auto"}, defaultVal: null},
autoplay: "boolean",
loop: "boolean",
- mediaGroup: "string",
controls: "boolean",
defaultMuted: {type: "boolean", domAttrName: "muted"},
@@ -108,13 +107,14 @@
preload: {type: "enum", keywords: ["none", "metadata", "auto"], nonCanon: {"": "auto"}, defaultVal: null},
autoplay: "boolean",
loop: "boolean",
- mediaGroup: "string",
controls: "boolean",
defaultMuted: {type: "boolean", domAttrName: "muted"}
},
source: {
src: "url",
type: "string",
+ srcset: "string",
+ sizes: "string",
media: "string"
},
track: {
diff --git a/html/dom/elements-forms.js b/html/dom/elements-forms.js
index 06dcd66..fc45535 100644
--- a/html/dom/elements-forms.js
+++ b/html/dom/elements-forms.js
@@ -1,9 +1,22 @@
-// Up-to-date as of 2013-04-07.
+var inputModeKeywords = [
+ "verbatim",
+ "latin",
+ "latin-name",
+ "latin-prose",
+ "full-width-latin",
+ "kana",
+ "kana-name",
+ "katakana",
+ "numeric",
+ "tel",
+ "email",
+ "url",
+];
var formElements = {
form: {
acceptCharset: {type: "string", domAttrName: "accept-charset"},
- // TODO: action is special
- // action: "url",
+ // "action" has magic hard-coded in reflection.js
+ action: "url",
autocomplete: {type: "enum", keywords: ["on", "off"], defaultVal: "on"},
enctype: {type: "enum", keywords: ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"], defaultVal: "application/x-www-form-urlencoded"},
encoding: {type: "enum", keywords: ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"], defaultVal: "application/x-www-form-urlencoded", domAttrName: "enctype"},
@@ -27,41 +40,39 @@
// Conforming
accept: "string",
alt: "string",
- // TODO: autocomplete is special.
- // autocomplete: {type: "enum", keywords: ["on", "off"], defaultVal: "on"},
+ autocomplete: {type: "string", customGetter: true},
autofocus: "boolean",
defaultChecked: {type: "boolean", domAttrName: "checked"},
dirName: "string",
disabled: "boolean",
- // TODO: formAction is special
- // formAction: "url",
+ // "formAction" has magic hard-coded in reflection.js
+ formAction: "url",
formEnctype: {type: "enum", keywords: ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"], invalidVal: "application/x-www-form-urlencoded"},
formMethod: {type: "enum", keywords: ["get", "post"], invalidVal: "get"},
formNoValidate: "boolean",
formTarget: "string",
- //TODO: only reflected on setting
- //height: "unsigned long",
- inputMode: {type: "enum", keywords: ["verbatim", "latin", "latin-name", "latin-prose", "full-width-latin", "kana", "katakana", "numeric", "tel", "email", "url"]},
+ height: {type: "unsigned long", customGetter: true},
+ inputMode: {type: "enum", keywords: inputModeKeywords},
max: "string",
maxLength: "limited long",
min: "string",
+ minLength: "limited long",
multiple: "boolean",
name: "string",
pattern: "string",
placeholder: "string",
readOnly: "boolean",
required: "boolean",
- // https://html.spec.whatwg.org/multipage/#attr-input-size
+ // https://html.spec.whatwg.org/#attr-input-size
size: {type: "limited unsigned long", defaultVal: 20},
src: "url",
step: "string",
type: {type: "enum", keywords: ["hidden", "text", "search", "tel",
- "url", "email", "password", "datetime", "date", "month", "week",
+ "url", "email", "password", "date", "month", "week",
"time", "datetime-local", "number", "range", "color", "checkbox",
"radio", "file", "submit", "image", "reset", "button"], defaultVal:
"text"},
- //TODO: only reflected on setting
- //width: "unsigned long",
+ width: {type: "unsigned long", customGetter: true},
defaultValue: {type: "string", domAttrName: "value"},
// Obsolete
@@ -71,8 +82,8 @@
button: {
autofocus: "boolean",
disabled: "boolean",
- // TODO: formAction is special
- // formAction: "url",
+ // "formAction" has magic hard-coded in reflection.js
+ formAction: "url",
formEnctype: {type: "enum", keywords: ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"], invalidVal: "application/x-www-form-urlencoded"},
formMethod: {type: "enum", keywords: ["get", "post", "dialog"], invalidVal: "get"},
formNoValidate: "boolean",
@@ -82,6 +93,7 @@
value: "string"
},
select: {
+ autocomplete: {type: "string", customGetter: true},
autofocus: "boolean",
disabled: "boolean",
multiple: "boolean",
@@ -101,14 +113,14 @@
value: {type: "string", customGetter: true},
},
textarea: {
- // TODO: autocomplete is special.
- // autocomplete: {type: "enum", keywords: ["on", "off"], defaultVal: "on"},
+ autocomplete: {type: "string", customGetter: true},
autofocus: "boolean",
cols: {type: "limited unsigned long with fallback", defaultVal: 20},
dirName: "string",
disabled: "boolean",
- inputMode: {type: "enum", keywords: ["verbatim", "latin", "latin-name", "latin-prose", "full-width-latin", "kana", "katakana", "numeric", "tel", "email", "url"]},
+ inputMode: {type: "enum", keywords: inputModeKeywords},
maxLength: "limited long",
+ minLength: "limited long",
name: "string",
placeholder: "string",
readOnly: "boolean",
@@ -116,19 +128,6 @@
rows: {type: "limited unsigned long with fallback", defaultVal: 2},
wrap: "string",
},
- keygen: {
- autofocus: "boolean",
- challenge: "string",
- disabled: "boolean",
- // The invalid value default is the "unknown" state, which for our
- // purposes seems to be the same as having no invalid value default.
- // The missing value default depends on whether "rsa" is implemented,
- // so we use null, which is magically reserved for "don't try testing
- // this", since no one default is required. (TODO: we could test that
- // it's either the RSA state or the unknown state.)
- keytype: {type: "enum", keywords: ["rsa"], defaultVal: null},
- name: "string",
- },
output: {
htmlFor: {type: "settable tokenlist", domAttrName: "for" },
name: "string",
@@ -136,7 +135,14 @@
progress: {
max: {type: "limited double", defaultVal: 1.0},
},
- meter: {},
+ meter: {
+ value: {type: "double", customGetter: true},
+ min: {type: "double", customGetter: true},
+ max: {type: "double", customGetter: true},
+ low: {type: "double", customGetter: true},
+ high: {type: "double", customGetter: true},
+ optimum: {type: "double", customGetter: true},
+ },
};
mergeElements(formElements);
diff --git a/html/dom/elements-grouping.js b/html/dom/elements-grouping.js
index c33f49d..4c9a291 100644
--- a/html/dom/elements-grouping.js
+++ b/html/dom/elements-grouping.js
@@ -1,4 +1,3 @@
-// Up-to-date as of 2013-04-08.
var groupingElements = {
p: {
// Obsolete
@@ -22,8 +21,6 @@
ol: {
// Conforming
reversed: "boolean",
- // TODO: This should have a default value of the list's length if the
- // reversed attribute is set.
start: {type: "long", defaultVal: 1},
type: "string",
diff --git a/html/dom/elements-metadata.js b/html/dom/elements-metadata.js
index f3b32f7..f304590 100644
--- a/html/dom/elements-metadata.js
+++ b/html/dom/elements-metadata.js
@@ -1,9 +1,8 @@
-// Up-to-date as of 2013-04-08.
var metadataElements = {
head: {},
title: {},
base: {
- // XXX href is weird. href: "url",
+ href: {type: "url", customGetter: true},
target: "string",
},
link: {
@@ -11,20 +10,13 @@
href: "url",
crossOrigin: {type: "enum", keywords: ["anonymous", "use-credentials"], nonCanon:{"": "anonymous"}, isNullable: true, defaultVal: null, invalidVal: "anonymous"},
rel: "string",
- relList: {type: "tokenlist", domAttrName: "rel"},
as: {
type: "enum",
keywords: ["fetch", "audio", "document", "embed", "font", "image", "manifest", "object", "report", "script", "serviceworker", "sharedworker", "style", "track", "video", "worker", "xslt"],
defaultVal: "",
invalidVal: ""
},
- scope: "string",
- updateViaCache: {
- type: "enum",
- keywords: ["imports", "all", "none"],
- defaultVal: "imports",
- invalidVal: "imports"
- },
+ relList: {type: "tokenlist", domAttrName: "rel"},
media: "string",
nonce: "string",
integrity: "string",
@@ -32,12 +24,19 @@
type: "string",
sizes: "settable tokenlist",
referrerPolicy: {type: "enum", keywords: ["", "no-referrer", "no-referrer-when-downgrade", "same-origin", "origin", "strict-origin", "origin-when-cross-origin", "strict-origin-when-cross-origin", "unsafe-url"]},
+ scope: "string",
workerType: {
type: "enum",
keywords: ["classic", "module"],
defaultVal: "classic",
invalidVal: "",
},
+ updateViaCache: {
+ type: "enum",
+ keywords: ["imports", "all", "none"],
+ defaultVal: "imports",
+ invalidVal: "imports"
+ },
// Obsolete
charset: "string",
@@ -55,6 +54,7 @@
},
style: {
media: "string",
+ nonce: "string",
type: "string",
},
};
diff --git a/html/dom/elements-misc.js b/html/dom/elements-misc.js
index eb68e10..43cdf5f 100644
--- a/html/dom/elements-misc.js
+++ b/html/dom/elements-misc.js
@@ -1,4 +1,3 @@
-// Up-to-date as of 2013-04-09.
var miscElements = {
// "The root element" section
html: {
@@ -24,6 +23,11 @@
},
noscript: {},
+ template: {},
+ slot: {
+ name: "string",
+ },
+
// "Edits" section
ins: {
cite: "url",
diff --git a/html/dom/elements-obsolete.js b/html/dom/elements-obsolete.js
index 3b6ae36..e37e342 100644
--- a/html/dom/elements-obsolete.js
+++ b/html/dom/elements-obsolete.js
@@ -1,6 +1,4 @@
-// Up-to-date as of 2013-04-13.
var obsoleteElements = {
- // https://html.spec.whatwg.org/multipage/#the-applet-element
applet: {
align: "string",
alt: "string",
@@ -14,7 +12,6 @@
vspace: "unsigned long",
width: "string",
},
- // https://html.spec.whatwg.org/multipage/#the-marquee-element-2
marquee: {
behavior: "string",
bgColor: "string",
@@ -27,12 +24,10 @@
vspace: "unsigned long",
width: "string",
},
- // https://html.spec.whatwg.org/multipage/#frameset
frameset: {
cols: "string",
rows: "string",
},
- // https://html.spec.whatwg.org/multipage/#frame
frame: {
name: "string",
scrolling: "string",
@@ -43,11 +38,9 @@
marginHeight: {type: "string", treatNullAsEmptyString: true},
marginWidth: {type: "string", treatNullAsEmptyString: true},
},
- // https://html.spec.whatwg.org/multipage/#htmldirectoryelement
dir: {
compact: "boolean",
},
- // https://html.spec.whatwg.org/multipage/#htmlfontelement
font: {
color: {type: "string", treatNullAsEmptyString: true},
face: "string",
diff --git a/html/dom/elements-sections.js b/html/dom/elements-sections.js
index a22aed9..bbad85e 100644
--- a/html/dom/elements-sections.js
+++ b/html/dom/elements-sections.js
@@ -1,4 +1,3 @@
-// Up-to-date as of 2013-04-12.
var sectionElements = {
body: {
// Obsolete
@@ -54,6 +53,8 @@
ReflectionTests.reflects({type: "string", treatNullAsEmptyString: true}, "vlinkColor", document, "vlink", document.body);
ReflectionTests.reflects({type: "string", treatNullAsEmptyString: true}, "alinkColor", document, "alink", document.body);
ReflectionTests.reflects({type: "string", treatNullAsEmptyString: true}, "bgColor", document, "bgcolor", document.body);
+ // Edge remains RTL if we don't do this, despite removing the attribute
+ document.dir = "ltr";
// Don't mess up the colors :)
document.documentElement.removeAttribute("dir");
var attrs = ["text", "bgcolor", "link", "alink", "vlink"];
diff --git a/html/dom/elements-tabular.js b/html/dom/elements-tabular.js
index db7683c..88fc8d3 100644
--- a/html/dom/elements-tabular.js
+++ b/html/dom/elements-tabular.js
@@ -70,7 +70,7 @@
// HTMLTableCellElement (Conforming)
colSpan: {type: "clamped unsigned long", defaultVal: 1, min: 1, max: 1000},
rowSpan: {type: "clamped unsigned long", defaultVal: 1, min: 0, max: 65534},
- headers: "settable tokenlist",
+ headers: "string",
scope: {type: "enum", keywords: ["row", "col", "rowgroup", "colgroup"]},
abbr: "string",
@@ -89,7 +89,7 @@
// HTMLTableCellElement (Conforming)
colSpan: {type: "clamped unsigned long", defaultVal: 1, min: 1, max: 1000},
rowSpan: {type: "clamped unsigned long", defaultVal: 1, min: 0, max: 65534},
- headers: "settable tokenlist",
+ headers: "string",
scope: {type: "enum", keywords: ["row", "col", "rowgroup", "colgroup"]},
abbr: "string",
diff --git a/html/dom/elements-text.js b/html/dom/elements-text.js
index 56157fa..f71df48 100644
--- a/html/dom/elements-text.js
+++ b/html/dom/elements-text.js
@@ -31,6 +31,9 @@
},
dfn: {},
abbr: {},
+ ruby: {},
+ rt: {},
+ rp: {},
data: {
value: "string",
},
@@ -38,9 +41,7 @@
dateTime: "string",
},
code: {},
- // Opera 11.50 doesn't allow unquoted "var" here, although ES5 does and
- // other browsers support it.
- "var": {},
+ var: {},
samp: {},
kbd: {},
sub: {},
@@ -49,9 +50,6 @@
b: {},
u: {},
mark: {},
- ruby: {},
- rt: {},
- rp: {},
bdi: {},
bdo: {},
span: {},
diff --git a/html/dom/original-harness.js b/html/dom/original-harness.js
index 113da5f..89a8067 100644
--- a/html/dom/original-harness.js
+++ b/html/dom/original-harness.js
@@ -141,7 +141,8 @@
try {
fn();
} catch (e) {
- if (e instanceof DOMException && e.code == DOMException[exceptionName]) {
+ if (e instanceof DOMException && (e.code == DOMException[exceptionName] ||
+ e.name == exceptionName)) {
this.increment(this.passed);
return true;
}
diff --git a/html/dom/reflection.js b/html/dom/reflection.js
index d1b3abe..337e053 100644
--- a/html/dom/reflection.js
+++ b/html/dom/reflection.js
@@ -630,6 +630,12 @@
if (defaultVal === undefined) {
defaultVal = typeInfo.defaultVal;
}
+ if ((domObj.localName === "form" && domName === "action") ||
+ (["button", "input"].includes(domObj.localName) &&
+ domName === "formAction")) {
+ // Hard-coded special case
+ defaultVal = domObj.ownerDocument.URL;
+ }
if (defaultVal !== null || data.isNullable) {
ReflectionHarness.test(function() {
ReflectionHarness.assertEquals(idlObj[idlName], defaultVal);
@@ -776,24 +782,43 @@
idlDomExpected = idlDomExpected.filter(function(element, index, array) { return idlIdlExpected[index] < 1000; });
idlIdlExpected = idlIdlExpected.filter(function(element, index, array) { return idlIdlExpected[index] < 1000; });
}
-
- if (!data.customGetter) {
+ if ((domObj.localName === "form" && domName === "action") ||
+ (["button", "input"].includes(domObj.localName) &&
+ domName === "formAction")) {
+ // Hard-coded special case
for (var i = 0; i < domTests.length; i++) {
- if (domExpected[i] === null && !data.isNullable) {
- // If you follow all the complicated logic here, you'll find that
- // this will only happen if there's no expected value at all (like
- // for tabIndex, where the default is too complicated). So skip
- // the test.
- continue;
+ if (domTests[i] === "") {
+ domExpected[i] = domObj.ownerDocument.URL;
}
- ReflectionHarness.test(function() {
- domObj.setAttribute(domName, domTests[i]);
- ReflectionHarness.assertEquals(domObj.getAttribute(domName),
- String(domTests[i]), "getAttribute()");
- ReflectionHarness.assertEquals(idlObj[idlName], domExpected[i],
- "IDL get");
- }, "setAttribute() to " + ReflectionHarness.stringRep(domTests[i]));
}
+ for (var i = 0; i < idlTests.length; i++) {
+ if (idlTests[i] === "") {
+ idlIdlExpected[i] = domObj.ownerDocument.URL;
+ }
+ }
+ }
+ if (data.customGetter) {
+ // These are reflected only on setting, not getting
+ domTests = [];
+ domExpected = [];
+ idlIdlExpected = idlIdlExpected.map(() => null);
+ }
+
+ for (var i = 0; i < domTests.length; i++) {
+ if (domExpected[i] === null && !data.isNullable) {
+ // If you follow all the complicated logic here, you'll find that
+ // this will only happen if there's no expected value at all (like
+ // for tabIndex, where the default is too complicated). So skip
+ // the test.
+ continue;
+ }
+ ReflectionHarness.test(function() {
+ domObj.setAttribute(domName, domTests[i]);
+ ReflectionHarness.assertEquals(domObj.getAttribute(domName),
+ String(domTests[i]), "getAttribute()");
+ ReflectionHarness.assertEquals(idlObj[idlName], domExpected[i],
+ "IDL get");
+ }, "setAttribute() to " + ReflectionHarness.stringRep(domTests[i]));
}
for (var i = 0; i < idlTests.length; i++) {
diff --git a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-001.html b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-001.html
index 4a5fd41..7dd8913 100644
--- a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-001.html
+++ b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-001.html
@@ -10,6 +10,8 @@
<script>
var test = document.getElementById("test");
- test.focus(); test.blur(); //alternative to forceSpellCheck(), due to general lack of support
+ // Force spellcheck by focus and then blur
+ test.focus();
+ test.blur();
test.removeAttribute("contenteditable");
</script>
diff --git a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-002.html b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-002.html
index 44f1ea8..b361b93 100644
--- a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-002.html
+++ b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-002.html
@@ -10,7 +10,9 @@
<script>
var test = document.getElementById("test");
- test.focus(); test.blur(); // Alternative to forceSpellCheck(), due to general lack of support
+ // Force spellcheck by focus and then blur
+ test.focus();
+ test.blur();
var child = document.getElementById("child");
child.setAttribute("contenteditable", false);
</script>
diff --git a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-003.html b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-003.html
index 9c88660..d1a6aa3 100644
--- a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-003.html
+++ b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-003.html
@@ -10,6 +10,8 @@
<script>
var test = document.getElementById("test");
- test.focus(); test.blur(); // Alternative to forceSpellCheck(), due to general lack of support
+ // Force spellcheck by focus and then blur
+ test.focus();
+ test.blur();
test.setAttribute("spellcheck", false);
</script>
diff --git a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-004.html b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-004.html
index fdeb906..c718e77 100644
--- a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-004.html
+++ b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-004.html
@@ -10,7 +10,9 @@
<script>
var test = document.getElementById("test");
- test.focus(); test.blur(); // Alternative to forceSpellCheck(), due to general lack of support
+ // Force spellcheck by focus then blur
+ test.focus();
+ test.blur();
var child = document.getElementById("child");
child.setAttribute("spellcheck", false);
</script>
diff --git a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-005.html b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-005.html
index 9ab7a3e..705ee7b 100644
--- a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-005.html
+++ b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-005.html
@@ -10,7 +10,9 @@
<script>
var test = document.getElementById("test");
- test.focus(); test.blur(); // Alternative to forceSpellCheck(), due to general lack of support
+ // Force spellcheck by focus then blur
+ test.focus();
+ test.blur();
var child = document.getElementById("child");
child.setAttribute("spellcheck", false);
</script>
diff --git a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-006.html b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-006.html
index f0b54da..512d473 100644
--- a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-006.html
+++ b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-006.html
@@ -12,7 +12,9 @@
<script>
var test = document.getElementById("test");
- test.focus(); test.blur(); // Alternative to forceSpellCheck(), due to general lack of support
+ // Force spellcheck by focus then blur
+ test.focus();
+ test.blur();
var p = document.getElementById("parent");
p.setAttribute("spellcheck", false);
</script>
diff --git a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-007.html b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-007.html
index 4bbeca9..31b3755 100644
--- a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-007.html
+++ b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-007.html
@@ -20,6 +20,8 @@
<script>
var test = document.getElementById("test");
- test.focus(); test.blur(); // Alternative to forceSpellCheck(), due to general lack of support
+ // Force spellcheck by focus then blur
+ test.focus();
+ test.blur();
test.setAttribute("readonly", true);
</script>
diff --git a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-008.html b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-008.html
index 5ed72ab..f891acf 100644
--- a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-008.html
+++ b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-008.html
@@ -20,6 +20,8 @@
<script>
var test = document.getElementById("test");
- test.focus(); test.blur(); // Alternative to forceSpellCheck(), due to general lack of support
+ // Force spellcheck by focus then blur
+ test.focus();
+ test.blur();
test.setAttribute("disabled", true);
</script>
diff --git a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-009.html b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-009.html
index 577ffd8..96eb87d 100644
--- a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-009.html
+++ b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-009.html
@@ -20,6 +20,8 @@
<script>
var test = document.getElementById("test");
- test.focus(); test.blur(); // Alternative to forceSpellCheck(), due to general lack of support
+ // Force spellcheck by focus then blur
+ test.focus();
+ test.blur();
test.setAttribute("readonly", true);
</script>
diff --git a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-010.html b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-010.html
index 6a2e104..16275f1 100644
--- a/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-010.html
+++ b/html/editing/editing-0/spelling-and-grammar-checking/spelling-markers-010.html
@@ -20,6 +20,8 @@
<script>
var test = document.getElementById("test");
- test.focus(); test.blur(); // Alternative to forceSpellCheck(), due to general lack of support
+ // Force spellcheck by focus then blur
+ test.focus();
+ test.blur();
test.setAttribute("disabled", true);
</script>
diff --git a/html/user-interaction/focus/tabindex-focus-flag.html b/html/editing/focus/tabindex-focus-flag.html
similarity index 100%
rename from html/user-interaction/focus/tabindex-focus-flag.html
rename to html/editing/focus/tabindex-focus-flag.html
diff --git a/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-scrolldelay.html b/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-scrolldelay.html
index 9bc769f..3941586 100644
--- a/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-scrolldelay.html
+++ b/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-scrolldelay.html
@@ -23,7 +23,9 @@
test(function() {
var mq = document.getElementById("test3");
- assert_equals(mq.scrollDelay, 60, "The delay time should be 60ms.");
+ assert_equals(mq.scrollDelay, 1,
+ "The delay time should be 1ms (although this doesn't " +
+ "match rendering).");
}, "The scrolldelay attribute is less than 60");
test(function() {
diff --git a/html/semantics/document-metadata/the-style-element/style_media_change.html b/html/semantics/document-metadata/the-style-element/style_media_change.html
new file mode 100644
index 0000000..8b7e844
--- /dev/null
+++ b/html/semantics/document-metadata/the-style-element/style_media_change.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Dynamically changing HTMLStyleElement.media should change the rendering accordingly</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-style-element">
+ <style>
+ span {
+ color: red;
+ }
+ </style>
+ <style id="text-style" media="none">
+ span {
+ color: green;
+ }
+ </style>
+ <style id="body-style" media="aural">
+ body {
+ color: green;
+ }
+ </style>
+ </head>
+ <body>
+ <span>text</span>
+ <script>
+ test(function() {
+ var element = document.querySelector("span");
+ assert_equals(getComputedStyle(element).color, "rgb(255, 0, 0)");
+ document.getElementById("text-style").media = 'all';
+ assert_equals(getComputedStyle(element).color, "rgb(0, 128, 0)");
+ }, "change media value dynamically");
+
+ test(function() {
+ var style = document.getElementById("body-style");
+ assert_not_equals(getComputedStyle(document.querySelector("body")).color, "rgb(0, 128, 0)");
+ style.removeAttribute("media");
+ assert_equals(getComputedStyle(document.querySelector("body")).color, "rgb(0, 128, 0)");
+ }, "removing media attribute");
+ </script>
+ </body>
+</html>
\ No newline at end of file
diff --git a/html/semantics/document-metadata/the-style-element/style_non_matching_media.html b/html/semantics/document-metadata/the-style-element/style_non_matching_media.html
new file mode 100644
index 0000000..74d3554
--- /dev/null
+++ b/html/semantics/document-metadata/the-style-element/style_non_matching_media.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>HTML Test: Non-matching media type should have stylesheet</title>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#the-style-element">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style media="unknown">
+ body { color: green }
+ </style>
+ </head>
+ <body>
+ <script>
+ test(function() {
+ assert_equals(document.styleSheets.length, 1);
+ });
+ </script>
+ </body>
+</html>
diff --git a/html/semantics/embedded-content/media-elements/track/track-element/track-data-url.html b/html/semantics/embedded-content/media-elements/track/track-element/track-data-url.html
new file mode 100644
index 0000000..26ff90d
--- /dev/null
+++ b/html/semantics/embedded-content/media-elements/track/track-element/track-data-url.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<title>track element data: URL</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+[null, "anonymous", "use-credentials"].forEach(function(crossOriginValue) {
+ async_test(function() {
+ var video = document.createElement('video');
+ if (crossOriginValue !== null) {
+ video.setAttribute('crossorigin', crossOriginValue);
+ }
+ document.body.appendChild(video);
+ var t = document.createElement('track');
+ t.onload = this.step_func_done(function() {
+ assert_equals(t.track.cues.length, 1);
+ assert_equals(t.track.cues[0].startTime, 1);
+ assert_equals(t.track.cues[0].endTime, 2);
+ assert_equals(t.track.cues[0].id, 'x');
+ assert_equals(t.track.cues[0].text, 'test');
+ });
+ t.onerror = this.step_func(function() {
+ assert_unreached('got error event');
+ });
+ t.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\nx\n00:00:01.000 --> 00:00:02.000\ntest\n\n');
+ t.track.mode = 'showing';
+ video.appendChild(t);
+ }, document.title + ' ' + (crossOriginValue ? crossOriginValue : 'No CORS'));
+});
+</script>
diff --git a/html/semantics/links/following-hyperlinks/active-document.window.js b/html/semantics/links/following-hyperlinks/active-document.window.js
new file mode 100644
index 0000000..efa16e7
--- /dev/null
+++ b/html/semantics/links/following-hyperlinks/active-document.window.js
@@ -0,0 +1,23 @@
+["a",
+ "area",
+ "link"].forEach(type => {
+ async_test(t => {
+ const frame = document.createElement("iframe"),
+ link = document.createElement(type);
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func(() => {
+ // See https://github.com/whatwg/html/issues/490
+ if(frame.contentWindow.location.href === "about:blank")
+ return;
+ link.click(); // must be ignored because document is not active
+ t.step_timeout(() => {
+ assert_equals(frame.contentWindow.location.pathname, "/common/blank.html");
+ t.done();
+ }, 500);
+ });
+ document.body.appendChild(frame);
+ frame.contentDocument.body.appendChild(link);
+ link.href = "/";
+ frame.src = "/common/blank.html";
+ }, "<" + type + "> in navigated away <iframe>'s document cannot follow hyperlinks");
+});
diff --git a/html/semantics/scripting-1/the-script-element/module/errorhandling.html b/html/semantics/scripting-1/the-script-element/module/errorhandling.html
index 6f36d2c..5315df0 100644
--- a/html/semantics/scripting-1/the-script-element/module/errorhandling.html
+++ b/html/semantics/scripting-1/the-script-element/module/errorhandling.html
@@ -55,7 +55,6 @@
assert_unreached("This script should not have loaded!");
}));
document.body.appendChild(script_wrongMimetype_import);
-
</script>
</body>
</html>
diff --git a/images/smiley.png.headers b/images/smiley.png.headers
new file mode 100644
index 0000000..7296361
--- /dev/null
+++ b/images/smiley.png.headers
@@ -0,0 +1 @@
+Timing-Allow-Origin: *
diff --git a/interfaces/geometry.idl b/interfaces/geometry.idl
index fe0a769..c0aed76 100644
--- a/interfaces/geometry.idl
+++ b/interfaces/geometry.idl
@@ -247,7 +247,7 @@
[Exposed=Window] DOMMatrix setMatrixValue(DOMString transformList);
};
-dictionary DOMMatrixInit {
+dictionary DOMMatrix2DInit {
unrestricted double a;
unrestricted double b;
unrestricted double c;
@@ -256,18 +256,21 @@
unrestricted double f;
unrestricted double m11;
unrestricted double m12;
- unrestricted double m13 = 0;
- unrestricted double m14 = 0;
unrestricted double m21;
unrestricted double m22;
+ unrestricted double m41;
+ unrestricted double m42;
+};
+
+dictionary DOMMatrixInit : DOMMatrix2DInit {
+ unrestricted double m13 = 0;
+ unrestricted double m14 = 0;
unrestricted double m23 = 0;
unrestricted double m24 = 0;
unrestricted double m31 = 0;
unrestricted double m32 = 0;
unrestricted double m33 = 1;
unrestricted double m34 = 0;
- unrestricted double m41;
- unrestricted double m42;
unrestricted double m43 = 0;
unrestricted double m44 = 1;
boolean is2D;
diff --git a/interfaces/html.idl b/interfaces/html.idl
index 79e27f3..48e41da 100644
--- a/interfaces/html.idl
+++ b/interfaces/html.idl
@@ -1113,7 +1113,7 @@
[NewObject] DOMMatrix getTransform();
void setTransform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f);
- void setTransform(optional DOMMatrixInit transform);
+ void setTransform(optional DOMMatrix2DInit transform);
void resetTransform();
};
@@ -1271,7 +1271,7 @@
[Exposed=(Window,Worker)]
interface CanvasPattern {
// opaque object
- void setTransform(optional DOMMatrixInit transform);
+ void setTransform(optional DOMMatrix2DInit transform);
};
interface TextMetrics {
@@ -1305,9 +1305,10 @@
[Constructor,
Constructor(Path2D path),
Constructor(sequence<Path2D> paths, optional CanvasFillRule fillRule = "nonzero"),
- Constructor(DOMString d), Exposed=(Window,Worker)]
+ Constructor(DOMString d),
+ Exposed=(Window,Worker)]
interface Path2D {
- void addPath(Path2D path, optional DOMMatrixInit transform);
+ void addPath(Path2D path, optional DOMMatrix2DInit transform);
};
Path2D implements CanvasPath;
diff --git a/interfaces/webidl.idl b/interfaces/webidl.idl
new file mode 100644
index 0000000..8eb1d06
--- /dev/null
+++ b/interfaces/webidl.idl
@@ -0,0 +1,44 @@
+typedef (Int8Array or Int16Array or Int32Array or
+ Uint8Array or Uint16Array or Uint32Array or Uint8ClampedArray or
+ Float32Array or Float64Array or DataView) ArrayBufferView;
+
+typedef (ArrayBufferView or ArrayBuffer) BufferSource;
+
+[
+ Exposed=(Window,Worker),
+ Constructor(optional DOMString message = "", optional DOMString name = "Error")]
+interface DOMException { // but see below note about ECMAScript binding
+ readonly attribute DOMString name;
+ readonly attribute DOMString message;
+ readonly attribute unsigned short code;
+
+ const unsigned short INDEX_SIZE_ERR = 1;
+ const unsigned short DOMSTRING_SIZE_ERR = 2;
+ const unsigned short HIERARCHY_REQUEST_ERR = 3;
+ const unsigned short WRONG_DOCUMENT_ERR = 4;
+ const unsigned short INVALID_CHARACTER_ERR = 5;
+ const unsigned short NO_DATA_ALLOWED_ERR = 6;
+ const unsigned short NO_MODIFICATION_ALLOWED_ERR = 7;
+ const unsigned short NOT_FOUND_ERR = 8;
+ const unsigned short NOT_SUPPORTED_ERR = 9;
+ const unsigned short INUSE_ATTRIBUTE_ERR = 10;
+ const unsigned short INVALID_STATE_ERR = 11;
+ const unsigned short SYNTAX_ERR = 12;
+ const unsigned short INVALID_MODIFICATION_ERR = 13;
+ const unsigned short NAMESPACE_ERR = 14;
+ const unsigned short INVALID_ACCESS_ERR = 15;
+ const unsigned short VALIDATION_ERR = 16;
+ const unsigned short TYPE_MISMATCH_ERR = 17;
+ const unsigned short SECURITY_ERR = 18;
+ const unsigned short NETWORK_ERR = 19;
+ const unsigned short ABORT_ERR = 20;
+ const unsigned short URL_MISMATCH_ERR = 21;
+ const unsigned short QUOTA_EXCEEDED_ERR = 22;
+ const unsigned short TIMEOUT_ERR = 23;
+ const unsigned short INVALID_NODE_TYPE_ERR = 24;
+ const unsigned short DATA_CLONE_ERR = 25;
+};
+
+typedef unsigned long long DOMTimeStamp;
+callback Function = any (any... arguments);
+callback VoidFunction = void ();
diff --git a/intersection-observer/timestamp.html b/intersection-observer/timestamp.html
index 9003256..be19800 100644
--- a/intersection-observer/timestamp.html
+++ b/intersection-observer/timestamp.html
@@ -18,28 +18,17 @@
<div id="trailing-space" class="spacer"></div>
<script>
+// Pick this number to be comfortably greater than the length of two frames at 60Hz.
+var timeSkew = 40;
+
var topWindowEntries = [];
var iframeWindowEntries = [];
var targetIframe;
-var topWindowTimeOnTestStart;
-var topWindowTimeBeforeCreatingIframe;
var topWindowTimeBeforeNotification;
var iframeWindowTimeBeforeNotification;
-var timeSkew;
-
-function waitFor(numFrames, callback) {
- if (numFrames <= 0) {
- callback();
- return;
- }
- window.requestAnimationFrame(waitFor.bind(null, numFrames - 1, callback));
-}
async_test(function(t) {
- topWindowTimeOnTestStart = performance.now();
- waitFor(3, function () {
- topWindowTimeBeforeCreatingIframe = performance.now();
- timeSkew = topWindowTimeBeforeCreatingIframe - topWindowTimeOnTestStart;
+ t.step_timeout(function() {
targetIframe = document.createElement("iframe");
assert_true(!!targetIframe, "iframe exists");
targetIframe.src = "resources/timestamp-subframe.html";
@@ -66,7 +55,7 @@
runTestCycle(step1, "First rAF after iframe is loaded.");
t.done();
};
- });
+ }, timeSkew);
}, "Check that timestamps correspond to the to execution context that created the observer.");
function step1() {
@@ -84,16 +73,14 @@
var topWindowTimeAfterNotification = performance.now();
var iframeWindowTimeAfterNotification = targetIframe.contentWindow.performance.now();
- // Test results are only significant if there's a gap between
- // top window time and iframe window time.
- assert_greater_than(topWindowTimeBeforeNotification, iframeWindowTimeAfterNotification,
- "Time ranges for top and iframe windows are disjoint. Times: " +
- [topWindowTimeOnTestStart, topWindowTimeBeforeCreatingIframe,
- topWindowTimeBeforeNotification, topWindowTimeAfterNotification,
- iframeWindowTimeBeforeNotification, iframeWindowTimeAfterNotification,
- topWindowEntries[1].time - topWindowTimeBeforeNotification,
- iframeWindowEntries[1].time - iframeWindowTimeBeforeNotification
- ]);
+ assert_approx_equals(
+ topWindowEntries[1].time - topWindowTimeBeforeNotification,
+ iframeWindowEntries[1].time - iframeWindowTimeBeforeNotification,
+ // Since all intersections are computed in a tight loop between 2 frames,
+ // an epsilon of 16ms (the length of one frame at 60Hz) turned out to be
+ // reliable, even at slow frame rates.
+ 16,
+ "Notification times are relative to the expected time origins");
assert_equals(topWindowEntries.length, 2, "Top window observer has two notifications.");
assert_between_inclusive(
diff --git a/lint.whitelist b/lint.whitelist
index 7443bc6..f2eb7a6 100644
--- a/lint.whitelist
+++ b/lint.whitelist
@@ -177,6 +177,7 @@
SET TIMEOUT: pointerevents/pointerevent_support.js
SET TIMEOUT: preload/single-download-preload.html
SET TIMEOUT: resource-timing/resource-timing.js
+SET TIMEOUT: resource-timing/single-entry-per-resource.html
SET TIMEOUT: screen-orientation/lock-bad-argument.html
SET TIMEOUT: screen-orientation/onchange-event.html
SET TIMEOUT: screen-orientation/resources/sandboxed-iframe-locking.html
diff --git a/offscreen-canvas/the-offscreen-canvas/offscreencanvas.resize.html b/offscreen-canvas/the-offscreen-canvas/offscreencanvas.resize.html
index 2906f80..3d40129 100644
--- a/offscreen-canvas/the-offscreen-canvas/offscreencanvas.resize.html
+++ b/offscreen-canvas/the-offscreen-canvas/offscreencanvas.resize.html
@@ -3,7 +3,7 @@
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/canvas-tests.js"></script>
-
+<body></body>
<script>
test(function() {
var canvas = new OffscreenCanvas(10, 20);
@@ -38,6 +38,24 @@
}, "Verify that writing to the width and height attributes of an OffscreenCanvas works when there is a webgl context attached.");
test(function() {
+ var placeholder = document.createElement('canvas');
+ placeholder.width = 2;
+ placeholder.height = 2;
+ var offscreen = placeholder.transferControlToOffscreen();
+ assert_throws("InvalidStateError", () => { placeholder.width = 1; });
+ assert_throws("InvalidStateError", () => { placeholder.height = 1; });
+}, "Verify that writing to the width or height attribute of a placeholder canvas throws an exception");
+
+test(function() {
+ var placeholder = document.createElement('canvas');
+ placeholder.width = 1;
+ placeholder.height = 1;
+ var offscreen = placeholder.transferControlToOffscreen();
+ assert_throws("InvalidStateError", () => { placeholder.width = 1; });
+ assert_throws("InvalidStateError", () => { placeholder.height = 1; });
+}, "Verify that writing to the width or height attribute of a placeholder canvas throws an exception even when not changing the value of the attribute.");
+
+test(function() {
var canvas = new OffscreenCanvas(10, 20);
var ctx = canvas.getContext('2d');
ctx.lineWidth = 5;
@@ -46,7 +64,6 @@
ctx.lineWidth = 5;
canvas.height = 40;
assert_equals(ctx.lineWidth, 1);
-
}, "Verify that resizing a 2d context resets its state.");
test(function() {
@@ -62,6 +79,7 @@
async_test(function(t) {
var placeholder = document.createElement('canvas');
+ document.body.appendChild(placeholder); // So that we can check computed style/
placeholder.width = 10;
placeholder.height = 20;
var offscreen = placeholder.transferControlToOffscreen();
@@ -98,15 +116,11 @@
setTimeout(function(){
t.step(function() {
// Verify that commit() asynchronously updates the size of its placeholder canvas.
- assert_equals(placeholder.width, 10);
- assert_equals(placeholder.height, 20);
- });
- t.step(function() {
- // Verify that width/height attributes are still settable even though they have no effect.
- placeholder.width = 50;
- placeholder.height = 60;
- assert_equals(placeholder.width, 50);
- assert_equals(placeholder.height, 60);
+ assert_equals(placeholder.width, 30);
+ assert_equals(placeholder.height, 40);
+ var computedStyle = window.getComputedStyle(placeholder);
+ assert_equals(computedStyle.getPropertyValue('width'), "30px");
+ assert_equals(computedStyle.getPropertyValue('height'), "40px");
});
createImageBitmap(placeholder).then(image => {
t.step(function() {
@@ -124,6 +138,7 @@
async_test(function(t) {
var placeholder = document.createElement('canvas');
+ document.body.appendChild(placeholder); // So that we can check computed style/
placeholder.width = 10;
placeholder.height = 20;
var offscreen = placeholder.transferControlToOffscreen();
@@ -160,15 +175,11 @@
setTimeout(function(){
t.step(function() {
// Verify that commit() asynchronously updates the size of its placeholder canvas.
- assert_equals(placeholder.width, 10);
- assert_equals(placeholder.height, 20);
- });
- t.step(function() {
- // Verify that width/height attributes are still settable even though they have no effect.
- placeholder.width = 50;
- placeholder.height = 60;
- assert_equals(placeholder.width, 50);
- assert_equals(placeholder.height, 60);
+ assert_equals(placeholder.width, 30);
+ assert_equals(placeholder.height, 40);
+ var computedStyle = window.getComputedStyle(placeholder);
+ assert_equals(computedStyle.getPropertyValue('width'), "30px");
+ assert_equals(computedStyle.getPropertyValue('height'), "40px");
});
createImageBitmap(placeholder).then(image => {
t.step(function() {
@@ -204,14 +215,13 @@
var pixel2 = testCtx.getImageData(9, 10, 1, 1).data;
var pixel3 = testCtx.getImageData(10, 9, 1, 1).data;
t.step(function() {
- assert_equals(placeholder.width, 1);
- assert_equals(placeholder.height, 1);
+ assert_equals(placeholder.width, 10);
+ assert_equals(placeholder.height, 10);
assert_array_equals(pixel1, [0, 255, 0, 255]);
assert_array_equals(pixel2, [0, 0, 0, 0]);
assert_array_equals(pixel3, [0, 0, 0, 0]);
});
t.done();
});
-
}, "Verify that drawImage uses the size of the committed frame as the intinsic size of a placeholder canvas.");
</script>
diff --git a/offscreen-canvas/the-offscreen-canvas/offscreencanvas.transfercontrol.to.offscreen.html b/offscreen-canvas/the-offscreen-canvas/offscreencanvas.transfercontrol.to.offscreen.html
index 433dd45..2eacf3f 100644
--- a/offscreen-canvas/the-offscreen-canvas/offscreencanvas.transfercontrol.to.offscreen.html
+++ b/offscreen-canvas/the-offscreen-canvas/offscreencanvas.transfercontrol.to.offscreen.html
@@ -30,55 +30,5 @@
assert_throws("InvalidStateError", function() { placeholder.transferControlToOffscreen(); });
}, "Test that calling transferControlToOffscreen twice throws an exception");
-async_test(function(t) {
- var placeholder = document.createElement('canvas');
- placeholder.width = 10;
- placeholder.height = 20;
- var offscreenCanvas = placeholder.transferControlToOffscreen();
- var ctx = offscreenCanvas.getContext('2d');
- t.step(function() {
- offscreenCanvas.width = 30;
- offscreenCanvas.height = 40;
- ctx.fillStyle = '#0f0';
- ctx.fillRect(0, 0, 30, 40);
- assert_equals(offscreenCanvas.width, 30);
- assert_equals(offscreenCanvas.height, 40);
- var image = offscreenCanvas.transferToImageBitmap();
- assert_equals(image.width, 30);
- assert_equals(image.height, 40);
- });
- t.step(function() {
- // Setting the size of an OffscreenCanvas does not directly update the size of its placeholder canvas.
- assert_equals(placeholder.width, 10);
- assert_equals(placeholder.height, 20);
- });
- ctx.commit();
- t.step(function() {
- // commit() doesnt't synchronously update the size of its placeholder canvas
- assert_equals(placeholder.width, 10);
- assert_equals(placeholder.height, 20);
- });
- // Set timeout acts as a sync barrier to allow commit to propagate
- setTimeout(function() {
- t.step(function() {
- // commit() asynchronously updates the size of its placeholder canvas
- assert_equals(placeholder.width, 30);
- assert_equals(placeholder.height, 40);
- });
- createImageBitmap(placeholder).then(image => {
- t.step(function() {
- assert_equals(image.width, 30);
- assert_equals(image.height, 40);
- var canvas = document.createElement('canvas');
- canvas.width = canvas.height = 50;
- var context = canvas.getContext('2d');
- context.drawImage(image, 0, 0);
- _assertPixel(canvas, 15,20, 0,255,0,255, "15,20", "0,255,0,255");
- t.done();
- });
- });
- }, 0);
-}, "Verify that resizing an OffscreenCanvas with a 2d context propagates the new size to its placeholder canvas asynchronously, upon commit.");
-
</script>
diff --git a/payment-request/OWNERS b/payment-request/OWNERS
index ce59356..ccd8925 100644
--- a/payment-request/OWNERS
+++ b/payment-request/OWNERS
@@ -1,3 +1,2 @@
@edenchuang
-@alphan102
@marcoscaceres
diff --git a/payment-request/historical.https.html b/payment-request/historical.https.html
index ba518f4..b7880a2 100644
--- a/payment-request/historical.https.html
+++ b/payment-request/historical.https.html
@@ -24,4 +24,16 @@
assert_false(member in window[interf].prototype);
}, member + ' in ' + interf);
});
+
+// https://github.com/w3c/payment-request/pull/551
+test(() => {
+ const methods = [];
+ const expectedError = {name: 'toString should be called'};
+ const unexpectedError = {name: 'sequence<DOMString> conversion is not allowed'};
+ methods.toString = () => { throw expectedError; };
+ Object.defineProperty(methods, '0', { get: () => { throw unexpectedError; } });
+ assert_throws(expectedError, () => {
+ new PaymentRequest([{supportedMethods: methods}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}});
+ });
+}, 'supportedMethods must not support sequence<DOMString>');
</script>
diff --git a/payment-request/payment-allowed-by-feature-policy.https.sub.html.headers b/payment-request/payment-allowed-by-feature-policy.https.sub.html.headers
index 5517dd3..a5c010b 100644
--- a/payment-request/payment-allowed-by-feature-policy.https.sub.html.headers
+++ b/payment-request/payment-allowed-by-feature-policy.https.sub.html.headers
@@ -1 +1 @@
-Feature-Policy: {"payment": ["*"]}
+Feature-Policy: payment *
diff --git a/payment-request/payment-disabled-by-feature-policy.https.sub.html.headers b/payment-request/payment-disabled-by-feature-policy.https.sub.html.headers
index 8f8b482..a283677 100644
--- a/payment-request/payment-disabled-by-feature-policy.https.sub.html.headers
+++ b/payment-request/payment-disabled-by-feature-policy.https.sub.html.headers
@@ -1 +1 @@
-Feature-Policy: {"payment": []}
+Feature-Policy: payment 'none'
diff --git a/payment-request/payment-request-constructor.https.html b/payment-request/payment-request-constructor.https.html
index 6c8e3df..5568c18 100644
--- a/payment-request/payment-request-constructor.https.html
+++ b/payment-request/payment-request-constructor.https.html
@@ -529,17 +529,6 @@
assert_throws(new TypeError(), () => {
new PaymentRequest(defaultMethods, modifiedDetails);
});
- assert_throws(new TypeError(), () => {
- const modifiedDetails = Object.assign({}, defaultDetails, {
- modifiers: [
- {
- supportedMethods: "https://wpt.fyi/payment-request",
- data: function() {},
- },
- ],
- });
- new PaymentRequest(defaultMethods, modifiedDetails);
- });
}, "Rethrow any exceptions of JSON-serializing modifier.data");
//Setting ShippingType attribute during construction
diff --git a/payment-request/payment-request-ctor-currency-code-checks.https.html b/payment-request/payment-request-ctor-currency-code-checks.https.html
index 69a375c..3a5e0e7 100644
--- a/payment-request/payment-request-ctor-currency-code-checks.https.html
+++ b/payment-request/payment-request-ctor-currency-code-checks.https.html
@@ -162,7 +162,7 @@
};
const displayItem = {
amount,
- label: "valid currency",
+ label: "invalid currency",
};
const details = {
total: defaultTotal,
diff --git a/payment-request/payment-request-ctor-pmi-handling.https.html b/payment-request/payment-request-ctor-pmi-handling.https.html
index c9330c7..8173929 100644
--- a/payment-request/payment-request-ctor-pmi-handling.https.html
+++ b/payment-request/payment-request-ctor-pmi-handling.https.html
@@ -7,58 +7,130 @@
<script src="/resources/testharnessreport.js"></script>
<script>
"use strict";
-const testMethod = Object.freeze({
- supportedMethods: "https://wpt.fyi/payment-request",
-});
-const defaultMethods = Object.freeze([testMethod]);
-const defaultAmount = Object.freeze({
+const validAmount = Object.freeze({
currency: "USD",
value: "1.0",
});
-const defaultTotal = Object.freeze({
+const validTotal = Object.freeze({
label: "Default Total",
- amount: defaultAmount,
+ amount: validAmount,
});
const defaultDetails = Object.freeze({
- total: defaultTotal,
+ total: validTotal,
});
-// Avoid false positives, this should always pass
-function smokeTest() {
- new PaymentRequest(defaultMethods, defaultDetails);
-}
+test(() => {
+ const validMethods = [
+ "https://wpt",
+ "https://wpt.fyi/",
+ "https://wpt.fyi/payment",
+ "https://wpt.fyi/payment-request",
+ "https://wpt.fyi/payment-request?",
+ "https://wpt.fyi/payment-request?this=is",
+ "https://wpt.fyi/payment-request?this=is&totally",
+ "https://wpt.fyi:443/payment-request?this=is&totally",
+ "https://wpt.fyi:443/payment-request?this=is&totally#fine",
+ "https://:@wpt.fyi:443/payment-request?this=is&totally#👍",
+ " \thttps://wpt\n ",
+ "https://xn--c1yn36f",
+ "https://點看",
+ ];
+ for (const validMethod of validMethods) {
+ try {
+ const methods = [{ supportedMethods: validMethod }];
+ new PaymentRequest(methods, defaultDetails);
+ } catch (err) {
+ assert_unreached(
+ `Unexpected exception with valid standardized PMI: ${validMethod}. ${err}`
+ );
+ }
+ }
+}, "Must support valid standard URL PMIs");
test(() => {
- smokeTest();
+ const validMethods = [
+ "e",
+ "n6jzof05mk2g4lhxr-u-q-w1-c-i-pa-ty-bdvs9-ho-ae7-p-md8-s-wq3-h-qd-e-q-sa",
+ "a-b-q-n-s-pw0",
+ "m-u",
+ "s-l5",
+ "k9-f",
+ "m-l",
+ "u4-n-t",
+ "i488jh6-g18-fck-yb-v7-i",
+ "x-x-t-t-c34-o",
+ "basic-card",
+ ];
+ for (const validMethod of validMethods) {
+ try {
+ const methods = [{ supportedMethods: validMethod }];
+ new PaymentRequest(methods, defaultDetails);
+ } catch (err) {
+ assert_unreached(
+ `Unexpected exception with valid standardized PMI: ${validMethod}. ${err}`
+ );
+ }
+ }
+}, "Must not throw on syntactically valid standardized payment method identifiers, even if they are not supported");
+
+test(() => {
const invalidMethods = [
- {
- supportedMethods: "http://username:password@example.com/pay",
- },
- {
- supportedMethods: "http://foo.com:100000000/pay",
- },
- {
- supportedMethods: "basic-💳",
- },
- {
- supportedMethods: "not-https://wpt.fyi/payment-request",
- },
- {
- supportedMethods: "¡basic-*-card!",
- },
- {
- supportedMethods: "Basic-Card",
- },
+ "basic-💳",
+ "¡basic-*-card!",
+ "Basic-Card",
+ "0",
+ "-",
+ "--",
+ "a--b",
+ "-a--b",
+ "a-b-",
+ "0-",
+ "0-a",
+ "a0--",
+ "A-",
+ "A-B",
+ "A-b",
+ "a-0",
+ "a-0b",
+ " a-b",
+ "\t\na-b",
+ "a-b ",
+ "a-b\n\t",
];
for (const invalidMethod of invalidMethods) {
assert_throws(
new RangeError(),
() => {
- new PaymentRequest([invalidMethod], defaultDetails);
+ const methods = [{ supportedMethods: invalidMethod }];
+ new PaymentRequest(methods, defaultDetails);
},
- `expected RangeError processing invalid PMI "${invalidMethod}"`
+ `expected RangeError processing invalid standardized PMI "${invalidMethod}"`
);
}
-}, "Constructor MUST throw if given an invalid payment method identifier");
+});
+
+test(() => {
+ const invalidMethods = [
+ "https://username@example.com/pay",
+ "https://:password@example.com/pay",
+ "https://username:password@example.com/pay",
+ "http://username:password@example.com/pay",
+ "http://foo.com:100000000/pay",
+ "not-https://wpt.fyi/payment-request",
+ "../realitive/url",
+ "/absolute/../path?",
+ "https://",
+ ];
+ for (const invalidMethod of invalidMethods) {
+ assert_throws(
+ new RangeError(),
+ () => {
+ const methods = [{ supportedMethods: invalidMethod }];
+ new PaymentRequest(methods, defaultDetails);
+ },
+ `expected RangeError processing invalid URL PMI "${invalidMethod}"`
+ );
+ }
+}, "Constructor MUST throw if given an invalid URL-based payment method identifier");
</script>
diff --git a/payment-request/payment-request-id-attribute.https.html b/payment-request/payment-request-id-attribute.https.html
new file mode 100644
index 0000000..8e774bd
--- /dev/null
+++ b/payment-request/payment-request-id-attribute.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for PaymentRequest id attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+ const methods = [{ supportedMethods: "foo" }];
+ const total = { label: "label", amount: { currency: "USD", value: "5.00" } };
+ const request1 = new PaymentRequest(methods, {
+ id: "pass",
+ total,
+ });
+ assert_equals(request1.id, "pass", "Expected PaymentRequest.id to be 'pass'");
+ const request2 = new PaymentRequest(methods, {
+ total,
+ });
+ assert_true(
+ request2.id && typeof request2.id === "string",
+ "Expected PaymentRequest.id to be some auto-generated truthy string"
+ );
+}, "PaymentRequest id attribute");
+</script>
diff --git a/payment-request/payment-request-id.https.html b/payment-request/payment-request-id.https.html
deleted file mode 100644
index 7a99a03..0000000
--- a/payment-request/payment-request-id.https.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html>
-<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
-<meta charset="utf-8">
-<title>Test for PaymentRequest identifier usage</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<iframe id="iframe" allowpaymentrequest></iframe>
-<script>
-async_test((t) => {
- onload = t.step_func_done(() => {
- var request = new window[0].PaymentRequest([{supportedMethods: 'foo'}], {id: 'my_payment_id', total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}});
- assert_equals(request.id, 'my_payment_id', 'Payment identifier is not reflected correctly in PaymentRequest.id');
-
- request = new window[0].PaymentRequest([{supportedMethods: 'foo'}], {total: {label: 'label', amount: {currency: 'USD', value: '5.00'}}});
- assert_equals(request.id.length, 36, 'Generated payment identifier is not of correct length.');
- });
-});
-</script>
diff --git a/payment-request/payment-response/complete-method-manual.https.html b/payment-request/payment-response/complete-method-manual.https.html
new file mode 100644
index 0000000..5ce4c18
--- /dev/null
+++ b/payment-request/payment-response/complete-method-manual.https.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-complete()">
+<title>
+ PaymentResponse.prototype.complete() method
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helpers.js"></script>
+<script>
+async function runManualTest({ completeWith: result }, button) {
+ button.disabled = true;
+ const { response, request } = await getPaymentRequestResponse();
+ promise_test(async () => {
+ try {
+ // We .complete() as normal, using the passed test value
+ const promise = response.complete(result);
+ assert_true(promise instanceof Promise, "returns a promise");
+ const returnedValue = await promise;
+ assert_equals(
+ returnedValue,
+ undefined,
+ "Returned value must always be undefined"
+ );
+ // We now call .complete() again, to force an exception
+ // because [[completeCalled]] is true.
+ try {
+ await response.complete(result);
+ assert_unreached("Expected InvalidStateError to be thrown");
+ } catch (err) {
+ assert_equals(
+ err.code,
+ DOMException.INVALID_STATE_ERR,
+ "Must throw an InvalidStateError"
+ );
+ }
+ button.innerHTML = `✅ ${button.textContent}`;
+ } catch (err) {
+ button.innerHTML = `❌ ${button.textContent}`;
+ assert_unreached("Unexpected exception: " + err.message);
+ }
+ }, button.textContent.trim());
+}
+</script>
+
+<h2>
+ Manual Tests for PaymentResponse.complete() - Please run in order!
+</h2>
+<p>
+ When presented with the payment sheet, use any credit card select to "Pay".
+ Also confirm any prompts that come up.
+</p>
+<ol>
+ <li>
+ <button onclick="runManualTest({completeWith: 'success'}, this)">
+ If the value of the internal slot [[completeCalled]] is true,
+ reject promise with an "InvalidStateError" DOMException.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest({completeWith: 'unknown'}, this)">
+ Passing no argument defaults to "unknown",
+ eventually closing the sheet and doesn't throw.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest({completeWith: 'success'}, this)">
+ Passing "success" eventually closes the sheet and doesn't throw.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest({completeWith: 'fail'}, this).then(done)">
+ Passing "fail" eventually closes the sheet and doesn't throw.
+ </button>
+ </li>
+</ol>
diff --git a/payment-request/payment-response/helpers.js b/payment-request/payment-response/helpers.js
new file mode 100644
index 0000000..0c22066
--- /dev/null
+++ b/payment-request/payment-response/helpers.js
@@ -0,0 +1,79 @@
+setup({ explicit_done: true, explicit_timeout: true });
+
+/**
+ * Pops up a payment sheet, allowing options to be
+ * passed in if particular values are needed.
+ *
+ * @param PaymentOptions options
+ */
+async function getPaymentResponse(options, id) {
+ const { response } = getPaymentRequestResponse(options, id);
+ return response;
+}
+
+/**
+ * Creates a payment request and response pair.
+ * It also shows the payment sheet.
+ *
+ * @param {PaymentOptions?} options
+ * @param {String?} id
+ */
+async function getPaymentRequestResponse(options, id) {
+ const methods = [{ supportedMethods: "basic-card" }];
+ const details = {
+ id,
+ total: {
+ label: "Total due",
+ amount: { currency: "USD", value: "1.0" },
+ },
+ shippingOptions: [
+ {
+ id: "fail1",
+ label: "Fail option 1",
+ amount: { currency: "USD", value: "5.00" },
+ selected: false,
+ },
+ {
+ id: "pass",
+ label: "Pass option",
+ amount: { currency: "USD", value: "5.00" },
+ selected: true,
+ },
+ {
+ id: "fail2",
+ label: "Fail option 2",
+ amount: { currency: "USD", value: "5.00" },
+ selected: false,
+ },
+ ],
+ };
+ const request = new PaymentRequest(methods, details, options);
+ request.onshippingaddresschange = ev => ev.updateWith(details);
+ request.onshippingoptionchange = ev => ev.updateWith(details);
+ const response = await request.show();
+ return { request, response };
+}
+
+/**
+ * Runs a manual test for payment
+ *
+ * @param {HTMLButtonElement} button The HTML button.
+ * @param {PaymentOptions?} options.
+ * @param {Object} expected What property values are expected to pass the test.
+ * @param {String?} id And id for the request/response pair.
+ */
+async function runManualTest(button, options, expected = {}, id = undefined) {
+ button.disabled = true;
+ const { request, response } = await getPaymentRequestResponse(options, id);
+ await response.complete();
+ test(() => {
+ assert_equals(response.requestId, request.id, `Expected ids to match`);
+ for (const [attribute, value] of Object.entries(expected)) {
+ assert_equals(
+ response[attribute],
+ value,
+ `Expected response ${attribute} attribute to be ${value}`
+ );
+ }
+ }, button.textContent.trim());
+}
diff --git a/payment-request/payment-response/methodName-attribute-manual.https.html b/payment-request/payment-response/methodName-attribute-manual.https.html
new file mode 100644
index 0000000..f27671d
--- /dev/null
+++ b/payment-request/payment-response/methodName-attribute-manual.https.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-methodname">
+<title>
+ PaymentResponse.prototype.methodName attribute
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helpers.js"></script>
+<h2>methodName attribute</h2>
+<p>
+ Use any credit card and any values.
+</p>
+<ol>
+ <li>
+ <button onclick="runManualTest(this, {}, { methodName: 'basic-card' }).then(done)">
+ Expect the payment method identifier to be 'basic-card'.
+ </button>
+ </li>
+</ol>
diff --git a/payment-request/payment-response/payerEmail-attribute-manual.https.html b/payment-request/payment-response/payerEmail-attribute-manual.https.html
new file mode 100644
index 0000000..3aa0565
--- /dev/null
+++ b/payment-request/payment-response/payerEmail-attribute-manual.https.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-payeremail">
+<title>
+ PaymentResponse.prototype.payerEmail attribute
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helpers.js"></script>
+<h2>payerEmail attribute</h2>
+<p>
+ When requested, please use "wpt@w3.org" as the email.
+</p>
+<ol>
+ <li>
+ <button onclick="runManualTest(this, undefined, { payerEmail: null })">
+ payerEmail attribute is null when options undefined.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestPayerEmail: undefined }, { payerEmail: null })">
+ payerEmail attribute is null when requestPayerEmail is undefined.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestPayerEmail: false }, { payerEmail: null })">
+ payerEmail attribute is null when requestPayerEmail is false.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestPayerEmail: true }, { payerEmail: 'wpt@w3.org' })">
+ payerEmail attribute is 'wpt@w3.org' when requestPayerEmail is true.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestPayerEmail: 'yep' }, { payerEmail: 'wpt@w3.org' }).then(done)">
+ payerEmail attribute is 'wpt@w3.org' when requestPayerEmail is truthy.
+ </button>
+ </li>
+</ol>
diff --git a/payment-request/payment-response/payerName-attribute-manual.https.html b/payment-request/payment-response/payerName-attribute-manual.https.html
new file mode 100644
index 0000000..4da0233
--- /dev/null
+++ b/payment-request/payment-response/payerName-attribute-manual.https.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-payername">
+<title>
+ PaymentResponse.prototype.payerName attribute
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helpers.js"></script>
+<h2>payerName attribute</h2>
+<p>
+ When requested, please use "web platform test" as the payer name.
+</p>
+<ol>
+ <li>
+ <button onclick="runManualTest(this, undefined, { payerName: null })">
+ payerName attribute is null when option is undefined.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestPayerName: undefined }, { payerName: null })">
+ payerName attribute is null when requestPayerName is undefined.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestPayerName: false }, { payerName: null })">
+ payerName attribute is null when requestPayerName is false.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestPayerName: true }, { payerName: 'web platform test' })">
+ payerName attribute is 'web platform test' when requestPayerName is true.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestPayerName: 'yep' }, { payerName: 'web platform test' }).then(done)">
+ payerName attribute is 'web platform test' when requestPayerName is truthy.
+ </button>
+ </li>
+</ol>
diff --git a/payment-request/payment-response/payerPhone-attribute-manual.https.html b/payment-request/payment-response/payerPhone-attribute-manual.https.html
new file mode 100644
index 0000000..4bfac7a
--- /dev/null
+++ b/payment-request/payment-response/payerPhone-attribute-manual.https.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-payerphone">
+<title>
+ PaymentResponse.prototype.payerPhone attribute
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helpers.js"></script>
+<h2>payerPhone attribute</h2>
+<p>
+ When prompted, please use +12345678910 as the phone number.
+</p>
+<ol>
+ <li>
+ <button onclick="runManualTest(this, undefined, { payerPhone: null })">
+ payerPhone attribute is null when options is undefined.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestPayerPhone: undefined }, { payerPhone: null })">
+ payerPhone attribute is null when requestPayerPhone is undefined.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestPayerPhone: false }, { payerPhone: null })">
+ payerPhone attribute is null when requestPayerPhone is false.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestPayerPhone: true }, { payerPhone: '+12345678910' })">
+ payerPhone attribute is '+12345678910' when requestPayerPhone is true.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestPayerPhone: 'yep' }, { payerPhone: '+12345678910' }).then(done)">
+ payerPhone attribute is '+12345678910' when requestPayerPhone is truthy.
+ </button>
+ </li>
+</ol>
diff --git a/payment-request/payment-response/requestId-attribute-manual.https.html b/payment-request/payment-response/requestId-attribute-manual.https.html
new file mode 100644
index 0000000..50a2c49
--- /dev/null
+++ b/payment-request/payment-response/requestId-attribute-manual.https.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-requestid">
+<title>
+ PaymentResponse.prototype.requestId attribute
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helpers.js"></script>
+<h2>requestId attribute</h2>
+<p>
+ When presented with the payment sheet, use any credit card select to "Pay".
+ Also confirm any prompts that come up.
+</p>
+<ol>
+ <li>
+ <button onclick="runManualTest(this, {}, {})">
+ Must mirror the payment request's automatically set id
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, {}, {requestId: 'pass'}, 'pass').then(done)">
+ Must mirror the payment request's explicitly set id
+ </button>
+ </li>
+</ol>
diff --git a/payment-request/payment-response/shippingAddress-attribute-manual.https.html b/payment-request/payment-response/shippingAddress-attribute-manual.https.html
new file mode 100644
index 0000000..659e8d6
--- /dev/null
+++ b/payment-request/payment-response/shippingAddress-attribute-manual.https.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-shippingaddress">
+<title>
+ PaymentResponse.prototype.shippingAddress
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helpers.js"></script>
+<script>
+async function checkNullShippingAddress(button, options) {
+ button.disabled = true;
+ const { request, response } = await getPaymentRequestResponse(options);
+ await response.complete();
+ test(() => {
+ assert_equals(
+ response.shippingAddress,
+ null,
+ "Expected response.shippingAddress to be null"
+ );
+ assert_equals(
+ request.shippingAddress,
+ null,
+ "Expected request.shippingAddress to be null"
+ );
+ }, button.textContent.trim());
+}
+
+async function runManualTest(button, options = {}, expected = {}, id) {
+ button.disabled = true;
+ const { request, response } = await getPaymentRequestResponse(options, id);
+ await response.complete();
+ test(() => {
+ assert_equals(response.requestId, request.id, `Expected ids to match`);
+ const { shippingAddress: addr } = request;
+ assert_true(
+ addr instanceof PaymentAddress,
+ "Expect instance of PaymentAddress"
+ );
+ try {
+ addr.toJSON();
+ } catch (err) {
+ assert_unreached(
+ `Unexpected exception calling PaymentAddress.toJSON(): ${err}`
+ );
+ }
+ if (expected.shippingAddress === null) {
+ return;
+ }
+ assert_equals(addr.country, "AF", "Expected AF for country");
+ assert_true(
+ addr.addressLine instanceof Array,
+ "Expected addressLine to be an array"
+ );
+ assert_equals(
+ addr.addressLine.toString(),
+ "1 wpt street",
+ "Expected '1 wpt street' for addressLine"
+ );
+ assert_equals(addr.city, "Kabul", "Expected city to be Kabul");
+ assert_equals(addr.postalCode, "1001", "Expect postalCode to be 1001");
+ assert_equals(addr.recipient, "web platform test");
+ }, button.textContent.trim());
+}
+</script>
+<h2>shippingAddress attribute</h2>
+<p>
+ When prompted, please enter "web platform test" as recipient, at address "1 wpt street" in "Kabul, Afghanistan", zip/postal code 1001.
+</p>
+<ol>
+ <li>
+ <button onclick="checkNullShippingAddress(this, undefined)">
+ If options is undefined, then shippingAddress must be null.
+ </button>
+ </li>
+ <li>
+ <button onclick="checkNullShippingAddress(this, {})">
+ If the requestShipping member is missing, then shippingAddress must be null.
+ </button>
+ </li>
+ <li>
+ <button onclick="checkNullShippingAddress(this, {requestShipping: false})">
+ If the requestShipping member is false, then shippingAddress must be null.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, {requestShipping: true}).then(done)">
+ If the requestShipping member is true, then shippingAddress must be "1 wpt street" in "Kabul, Afghanistan", zip code 1001.
+ </button>
+ </li>
+</ol>
diff --git a/payment-request/payment-response/shippingOption-attribute-manual.https.html b/payment-request/payment-response/shippingOption-attribute-manual.https.html
new file mode 100644
index 0000000..a9b2fd3
--- /dev/null
+++ b/payment-request/payment-response/shippingOption-attribute-manual.https.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-shippingoption">
+<title>
+ PaymentResponse.prototype.complete() method
+</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="helpers.js"></script>
+<h2>shippingOption attribute</h2>
+<p>
+ For the last test, please select the only available shipping option and select "Pay".
+</p>
+<ol>
+ <li>
+ <button onclick="runManualTest(this, undefined, { shippingOption: null })">
+ If the options is undefined, then shippingOption must be null.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestShipping: undefined }, { shippingOption: null })">
+ If the requestShipping member is missing, then shippingOption must be null.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestShipping: false }, { shippingOption: null })">
+ If the requestShipping member is false, then shippingOption must be null.
+ </button>
+ </li>
+ <li>
+ <button onclick="runManualTest(this, { requestShipping: true }, { shippingOption: 'pass' }).then(done)">
+ If the requestShipping member is true, then shippingOption must be the id of the selected shipping option ("pass").
+ </button>
+ </li>
+</ol>
diff --git a/payment-request/updateWith-method-pmi-handling-manual.https.html b/payment-request/updateWith-method-pmi-handling-manual.https.html
new file mode 100644
index 0000000..36a23b5
--- /dev/null
+++ b/payment-request/updateWith-method-pmi-handling-manual.https.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<!-- Copyright © 2017 Mozilla and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). -->
+<meta charset="utf-8">
+<title>Test for validity of payment method identifiers when calling updateWith() method</title>
+<link rel="help" href="https://www.w3.org/TR/payment-request/#updatewith()-method">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+setup({ explicit_done: true, explicit_timeout: true });
+const validMethod = Object.freeze({
+ supportedMethods: "https://:@wpt.fyi:443/payment-request",
+});
+
+const validMethods = Object.freeze([validMethod]);
+
+const validAmount = Object.freeze({
+ currency: "USD",
+ value: "1.0",
+});
+
+const validTotal = Object.freeze({
+ label: "Default Total",
+ amount: validAmount,
+});
+
+const validShippingOption = Object.freeze({
+ id: "standard",
+ label: "Shipping option",
+ amount: validAmount,
+ selected: true,
+});
+
+const validDetails = Object.freeze({
+ total: validTotal,
+ shippingOptions: [validShippingOption],
+});
+
+const validModifier = Object.freeze({
+ supportedMethods: "basic-card",
+ total: validTotal,
+});
+
+test(() => {
+ try {
+ new PaymentRequest(validMethods, validDetails);
+ } catch (err) {
+ done();
+ throw err;
+ }
+}, "smoke test");
+
+function manualTest(button, { invalidMethod }) {
+ button.disabled = true;
+ promise_test(async t => {
+ const request = new PaymentRequest(
+ [{ supportedMethods: "basic-card" }],
+ validDetails,
+ { requestShipping: true }
+ );
+ const listener = ev => {
+ const invalidModifier = Object.assign({}, validModifier, {
+ supportedMethods: invalidMethod,
+ });
+ const invalidDetails = Object.assign({}, validDetails, {
+ modifiers: [validModifier, invalidModifier],
+ });
+ ev.updateWith(invalidDetails);
+ };
+ // We test against a valid and an invalid modifier
+ request.addEventListener("shippingaddresschange", listener, { once: true });
+ const showPromise = request.show();
+ await promise_rejects(t, new RangeError(), showPromise);
+ }, button.textContent.trim());
+}
+</script>
+<h2>updateWith() method: test validity of payment method identifiers.</h2>
+<p>
+ When shown a payment sheet, select a different address.
+</p>
+<ol>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'https://:password@example.com'});">
+ Must throw if the URL has a password.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'https://username@example.com'});">
+ Must throw if the URL has a username.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'https://username:password@example.com/pay'});">
+ Must throw if the URL has a username and a password.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'http://username:password@example.com/pay'});">
+ Must throw if it's http, and has a username and password.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'http://foo.com:100000000/pay'});">
+ Must throw if the URL is invalid (port range).
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'basic-💳'});">
+ Must throw if the PMI contains characters that are out of range.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'not-https://wpt.fyi/payment-request'});">
+ Must throw if not https.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: '¡basic-*-card!'});">
+ Must throw if the standardized PMI contains characters outside the ascii range.
+ </button>
+ </li>
+ <li>
+ <button onclick="manualTest(this, {invalidMethod: 'Basic-Card'}); done();">
+ Must throw if standardized PMI has uppercase characters.
+ </button>
+ </li>
+</ol>
diff --git a/pointerevents/pointerevent_support.js b/pointerevents/pointerevent_support.js
index b56670a..33f2667 100644
--- a/pointerevents/pointerevent_support.js
+++ b/pointerevents/pointerevent_support.js
@@ -90,15 +90,13 @@
assert_greater_than_equal(event.pressure, 0, "pressure is greater than or equal to 0");
assert_less_than_equal(event.pressure, 1, "pressure is less than or equal to 1");
- if (event.type === "pointerup") {
- assert_equals(event.pressure, 0, "pressure is 0 during pointerup");
+ if (event.buttons === 0) {
+ assert_equals(event.pressure, 0, "pressure is 0 for mouse with no buttons pressed");
}
// TA: 1.7, 1.8
if (event.pointerType === "mouse") {
- if (event.buttons === 0) {
- assert_equals(event.pressure, 0, "pressure is 0 for mouse with no buttons pressed");
- } else {
+ if (event.buttons !== 0) {
assert_equals(event.pressure, 0.5, "pressure is 0.5 for mouse with a button pressed");
}
}
diff --git a/preload/link-header-on-subresource.html b/preload/link-header-on-subresource.html
new file mode 100644
index 0000000..22351e3
--- /dev/null
+++ b/preload/link-header-on-subresource.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/preload/resources/preload_helper.js"></script>
+<script>
+ var t = async_test('Makes sure that Link headers on subresources preload resources');
+</script>
+<link rel=stylesheet href="resources/dummy-preloads-subresource.css?link-header-on-subresource">
+<script src="resources/dummy.js?pipe=trickle(d5)&link-header-on-subresources"></script>
+<script>
+ window.addEventListener("load", t.step_func(function() {
+ verifyPreloadAndRTSupport();
+ verifyNumberOfDownloads("/preload/resources/CanvasTest.ttf?link-header-on-subresource", 1);
+ t.done();
+ }));
+</script>
+
diff --git a/preload/resources/dummy-preloads-subresource.css b/preload/resources/dummy-preloads-subresource.css
new file mode 100644
index 0000000..5097166
--- /dev/null
+++ b/preload/resources/dummy-preloads-subresource.css
@@ -0,0 +1 @@
+/* This is just a dummy, empty CSS file */
diff --git a/preload/resources/dummy-preloads-subresource.css.sub.headers b/preload/resources/dummy-preloads-subresource.css.sub.headers
new file mode 100644
index 0000000..4ada08c
--- /dev/null
+++ b/preload/resources/dummy-preloads-subresource.css.sub.headers
@@ -0,0 +1,2 @@
+Cache-Control: max-age=1000
+Link: </preload/resources/CanvasTest.ttf?link-header-on-subresource>; rel=preload;as=font;crossorigin
diff --git a/referrer-policy/generic/link-rel-prefetch.html b/referrer-policy/generic/link-rel-prefetch.html
new file mode 100644
index 0000000..5496314
--- /dev/null
+++ b/referrer-policy/generic/link-rel-prefetch.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Referrer policies for resources loaded via link rel prefetch</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <!-- Common global functions for referrer-policy tests. -->
+ <script src="/referrer-policy/generic/common.js"></script>
+ <meta name="referrer" content="origin">
+ <link rel="prefetch" href="/referrer-policy/generic/subresource/image.py">
+ </head>
+ <body>
+ <p>Check that resources loaded via link rel prefetch use the referrer
+ and referrer policy from the document.</p>
+
+ <script>
+ var prefetch_test = async_test("Prefetched image.");
+
+ var img_url = "/referrer-policy/generic/subresource/image.py";
+ prefetch_test.step_timeout(
+ function() {
+ loadImageInWindow(img_url, function (img) {
+ var message = decodeImageData(extractImageData(img));
+ prefetch_test.step(function() { assert_equals(message.headers.referer, document.location.origin + "/")});
+ prefetch_test.done();
+ }, null, window);
+ },
+ 1000);
+ </script>
+
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/resource-timing/resources/blue.png b/resource-timing/resources/blue.png
new file mode 100644
index 0000000..820f8ca
--- /dev/null
+++ b/resource-timing/resources/blue.png
Binary files differ
diff --git a/resource-timing/resources/gzip_xml.py b/resource-timing/resources/gzip_xml.py
index 3346da6..31a769e 100644
--- a/resource-timing/resources/gzip_xml.py
+++ b/resource-timing/resources/gzip_xml.py
@@ -1,8 +1,11 @@
import gzip as gzip_module
from cStringIO import StringIO
+import os
def main(request, response):
- f = open('resource-timing/resources/resource_timing_test0.xml', 'r')
+ dir_path = os.path.dirname(os.path.realpath(__file__))
+ file_path = os.path.join(dir_path, 'resource_timing_test0.xml')
+ f = open(file_path, 'r')
output = f.read()
out = StringIO()
diff --git a/resource-timing/single-entry-per-resource.html b/resource-timing/single-entry-per-resource.html
new file mode 100644
index 0000000..39d7d5b
--- /dev/null
+++ b/resource-timing/single-entry-per-resource.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>One resource when reusing data</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+ var img_entries = 0;
+ var observed = 0;
+ var img_url = "resources/blue.png";
+ var observer = new PerformanceObserver(
+ function (entryList, obs) {
+ var entries = entryList.getEntriesByType("resource");
+ for (var i = 0; i < entries.length; ++i) {
+ ++observed;
+ if (entries[i].name.indexOf(img_url) != -1)
+ ++img_entries;
+ }
+ });
+ observer.observe({entryTypes: ["resource"]});
+ window.onload = function() {
+ // A timeout is needed as PerformanceObserver is not guaranteed to run before onload triggered.
+ setTimeout(function() {
+ test(function(){
+ assert_equals(observed, 1);
+ assert_equals(img_entries, 1);
+ }, "Only one resource entry per resource");
+ },0)
+ };
+ // Images are added dynamically to make sure the observer is registered before their download finishes.
+ var img1 = document.createElement("img");
+ img1.src = img_url;
+ document.body.appendChild(img1);
+ var img2 = document.createElement("img");
+ img2.src = img_url;
+ document.body.appendChild(img2);
+</script>
+<h1>One resource when reusing data</h1>
+<p>
+If the user agent is to reuse the data from another existing or completed fetch initiated from the current document, abort the remaining steps.
+</p>
+<div id="log"></div>
+</body>
+
diff --git a/resources/idlharness.js b/resources/idlharness.js
index dc3302f..5ba6989 100644
--- a/resources/idlharness.js
+++ b/resources/idlharness.js
@@ -1324,6 +1324,14 @@
// "Otherwise, if A is declared with the [LegacyArrayClass] extended
// attribute, then return %ArrayPrototype%.
// "Otherwise, return %ObjectPrototype%.
+ //
+ // "In the ECMAScript binding, the DOMException type has some additional
+ // requirements:
+ //
+ // "Unlike normal interface types, the interface prototype object
+ // for DOMException must have as its [[Prototype]] the intrinsic
+ // object %ErrorPrototype%."
+ //
if (this.name === "Window") {
assert_class_string(Object.getPrototypeOf(self[this.name].prototype),
'WindowProperties',
@@ -1340,6 +1348,9 @@
} else if (this.has_extended_attribute('LegacyArrayClass')) {
inherit_interface = 'Array';
inherit_interface_has_interface_object = true;
+ } else if (this.name === "DOMException") {
+ inherit_interface = 'Error';
+ inherit_interface_has_interface_object = true;
} else {
inherit_interface = 'Object';
inherit_interface_has_interface_object = true;
@@ -2277,6 +2288,11 @@
IdlInterface.prototype.has_stringifier = function()
//@{
{
+ if (this.name === "DOMException") {
+ // toString is inherited from Error, so don't assume we have the
+ // default stringifer
+ return true;
+ }
if (this.members.some(function(member) { return member.stringifier; })) {
return true;
}
diff --git a/resources/sriharness.js b/resources/sriharness.js
new file mode 100644
index 0000000..9d7fa76
--- /dev/null
+++ b/resources/sriharness.js
@@ -0,0 +1,100 @@
+var SRIScriptTest = function(pass, name, src, integrityValue, crossoriginValue, nonce) {
+ this.pass = pass;
+ this.name = "Script: " + name;
+ this.src = src;
+ this.integrityValue = integrityValue;
+ this.crossoriginValue = crossoriginValue;
+ this.nonce = nonce;
+}
+
+SRIScriptTest.prototype.execute = function() {
+ var test = async_test(this.name);
+ var e = document.createElement("script");
+ e.src = this.src;
+ e.setAttribute("integrity", this.integrityValue);
+ if(this.crossoriginValue) {
+ e.setAttribute("crossorigin", this.crossoriginValue);
+ }
+ if(this.nonce) {
+ e.setAttribute("nonce", this.nonce);
+ }
+ if(this.pass) {
+ e.addEventListener("load", function() {test.done()});
+ e.addEventListener("error", function() {
+ test.step(function(){ assert_unreached("Good load fired error handler.") })
+ });
+ } else {
+ e.addEventListener("load", function() {
+ test.step(function() { assert_unreached("Bad load succeeded.") })
+ });
+ e.addEventListener("error", function() {test.done()});
+ }
+ document.body.appendChild(e);
+};
+
+// <link> tests
+// Style tests must be done synchronously because they rely on the presence
+// and absence of global style, which can affect later tests. Thus, instead
+// of executing them one at a time, the style tests are implemented as a
+// queue that builds up a list of tests, and then executes them one at a
+// time.
+var SRIStyleTest = function(queue, pass, name, attrs, customCallback, altPassValue) {
+ this.pass = pass;
+ this.name = "Style: " + name;
+ this.customCallback = customCallback || function () {};
+ this.attrs = attrs || {};
+ this.passValue = altPassValue || "rgb(255, 255, 0)";
+
+ this.test = async_test(this.name);
+
+ this.queue = queue;
+ this.queue.push(this);
+}
+
+SRIStyleTest.prototype.execute = function() {
+ var that = this;
+ var container = document.getElementById("container");
+ while (container.hasChildNodes()) {
+ container.removeChild(container.firstChild);
+ }
+
+ var test = this.test;
+
+ var div = document.createElement("div");
+ div.className = "testdiv";
+ var e = document.createElement("link");
+ this.attrs.rel = this.attrs.rel || "stylesheet";
+ for (var key in this.attrs) {
+ if (this.attrs.hasOwnProperty(key)) {
+ e.setAttribute(key, this.attrs[key]);
+ }
+ }
+
+ if(this.pass) {
+ e.addEventListener("load", function() {
+ test.step(function() {
+ var background = window.getComputedStyle(div, null).getPropertyValue("background-color");
+ assert_equals(background, that.passValue);
+ test.done();
+ });
+ });
+ e.addEventListener("error", function() {
+ test.step(function(){ assert_unreached("Good load fired error handler.") })
+ });
+ } else {
+ e.addEventListener("load", function() {
+ test.step(function() { assert_unreached("Bad load succeeded.") })
+ });
+ e.addEventListener("error", function() {
+ test.step(function() {
+ var background = window.getComputedStyle(div, null).getPropertyValue("background-color");
+ assert_not_equals(background, that.passValue);
+ test.done();
+ });
+ });
+ }
+ container.appendChild(div);
+ container.appendChild(e);
+ this.customCallback(e, container);
+};
+
diff --git a/resources/testharness.js b/resources/testharness.js
index 5182eab..087dbf4 100644
--- a/resources/testharness.js
+++ b/resources/testharness.js
@@ -579,12 +579,21 @@
var waitingFor = null;
+ // This is null unless we are recording all events, in which case it
+ // will be an Array object.
+ var recordedEvents = null;
+
var eventHandler = test.step_func(function(evt) {
assert_true(!!waitingFor,
'Not expecting event, but got ' + evt.type + ' event');
assert_equals(evt.type, waitingFor.types[0],
'Expected ' + waitingFor.types[0] + ' event, but got ' +
evt.type + ' event instead');
+
+ if (Array.isArray(recordedEvents)) {
+ recordedEvents.push(evt);
+ }
+
if (waitingFor.types.length > 1) {
// Pop first event from array
waitingFor.types.shift();
@@ -595,7 +604,10 @@
// need to set waitingFor.
var resolveFunc = waitingFor.resolve;
waitingFor = null;
- resolveFunc(evt);
+ // Likewise, we should reset the state of recordedEvents.
+ var result = recordedEvents || evt;
+ recordedEvents = null;
+ resolveFunc(result);
});
for (var i = 0; i < eventTypes.length; i++) {
@@ -605,14 +617,36 @@
/**
* Returns a Promise that will resolve after the specified event or
* series of events has occured.
+ *
+ * @param options An optional options object. If the 'record' property
+ * on this object has the value 'all', when the Promise
+ * returned by this function is resolved, *all* Event
+ * objects that were waited for will be returned as an
+ * array.
+ *
+ * For example,
+ *
+ * ```js
+ * const watcher = new EventWatcher(t, div, [ 'animationstart',
+ * 'animationiteration',
+ * 'animationend' ]);
+ * return watcher.wait_for([ 'animationstart', 'animationend' ],
+ * { record: 'all' }).then(evts => {
+ * assert_equals(evts[0].elapsedTime, 0.0);
+ * assert_equals(evts[1].elapsedTime, 2.0);
+ * });
+ * ```
*/
- this.wait_for = function(types) {
+ this.wait_for = function(types, options) {
if (waitingFor) {
return Promise.reject('Already waiting for an event or events');
}
if (typeof types == 'string') {
types = [types];
}
+ if (options && options.record && options.record === 'all') {
+ recordedEvents = [];
+ }
return new Promise(function(resolve, reject) {
waitingFor = {
types: types,
diff --git a/selection/removeAllRanges.html b/selection/removeAllRanges.html
index 286876f..026280d 100644
--- a/selection/removeAllRanges.html
+++ b/selection/removeAllRanges.html
@@ -13,8 +13,13 @@
var range = rangeFromEndpoints([paras[0].firstChild, 0, paras[0].firstChild, 1]);
function testRange(rangeDesc, method) {
+ var endpoints = eval(testRanges[i]);
+ if (endpoints.length && (!isSelectableNode(endpoints[0]) ||
+ !isSelectableNode(endpoints[2]))) {
+ return;
+ }
test(function() {
- setSelectionForwards(eval(rangeDesc));
+ setSelectionForwards(endpoints);
selection[method]();
assert_equals(selection.rangeCount, 0,
"After " + method + "(), rangeCount must be 0");
@@ -28,7 +33,7 @@
// Copy-pasted from above
test(function() {
- setSelectionBackwards(eval(rangeDesc));
+ setSelectionBackwards(endpoints);
selection[method]();
assert_equals(selection.rangeCount, 0,
"After " + method + "(), rangeCount must be 0");
diff --git a/server-timing/resources/blue.png.sub.headers b/server-timing/resources/blue.png.sub.headers
index 5f3e543..23988ed 100644
--- a/server-timing/resources/blue.png.sub.headers
+++ b/server-timing/resources/blue.png.sub.headers
@@ -1 +1 @@
-Server-Timing: metric2=3.4;blue.png
+Server-Timing: metric2=2.1;blue.png
diff --git a/server-timing/resources/green.png.sub.headers b/server-timing/resources/green.png.sub.headers
index 1c6d745..d559754 100644
--- a/server-timing/resources/green.png.sub.headers
+++ b/server-timing/resources/green.png.sub.headers
@@ -1 +1 @@
-Server-Timing: metric3=5.6;green.png
+Server-Timing: metric3=3.1;green.png
diff --git a/server-timing/test_server_timing.html b/server-timing/test_server_timing.html
index 6a6c897..fa10030 100644
--- a/server-timing/test_server_timing.html
+++ b/server-timing/test_server_timing.html
@@ -9,14 +9,18 @@
setup({explicit_done: true})
window.addEventListener('load', function() {
- // there should be exactly two server-timing entries, 1 for document, 1 for img#one
+ // there should be exactly three server-timing entries, 2 for document, 1 for img#one
test_entries(performance.getEntriesByType('navigation')[0].serverTiming, [{
+ duration: 1.1,
+ name: 'metric1',
+ description: 'document',
+ }, {
duration: 1.2,
name: 'metric1',
description: 'document',
}])
test_entries(performance.getEntriesByName(document.querySelector('img#one').src)[0].serverTiming, [{
- duration: 3.4,
+ duration: 2.1,
name: 'metric2',
description: 'blue.png',
}])
@@ -24,7 +28,7 @@
new PerformanceObserver(function(entryList, observer) {
// there should be exactly one server-timing entry, 1 for img#two
test_entries(entryList.getEntriesByName(document.querySelector('img#two').src)[0].serverTiming, [{
- duration: 5.6,
+ duration: 3.1,
name: 'metric3',
description: 'green.png',
}])
diff --git a/server-timing/test_server_timing.html.sub.headers b/server-timing/test_server_timing.html.sub.headers
index ddff591..c539669 100644
--- a/server-timing/test_server_timing.html.sub.headers
+++ b/server-timing/test_server_timing.html.sub.headers
@@ -1 +1 @@
-Server-Timing: metric1=1.2;document
+Server-Timing: metric1=1.1;document, metric1=1.2;document
diff --git a/service-workers/service-worker/claim-shared-worker-fetch.https.html b/service-workers/service-worker/claim-shared-worker-fetch.https.html
new file mode 100644
index 0000000..a07db7b
--- /dev/null
+++ b/service-workers/service-worker/claim-shared-worker-fetch.https.html
@@ -0,0 +1,68 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+promise_test(function(t) {
+ var frame;
+ var resource = 'simple.txt';
+
+ var worker;
+ var scope = 'resources/';
+ var script = 'resources/claim-worker.js';
+
+ return Promise.resolve()
+ // Create the test iframe with a shared worker.
+ .then(() => with_iframe('resources/claim-shared-worker-fetch-iframe.html'))
+ .then(f => frame = f)
+
+ // Check the controller and test with fetch in the shared worker.
+ .then(() => assert_equals(frame.contentWindow.navigator.controller,
+ undefined,
+ 'Should have no controller.'))
+ .then(() => frame.contentWindow.fetch_in_shared_worker(resource))
+ .then(response_text => assert_equals(response_text,
+ 'a simple text file\n',
+ 'fetch() should not be intercepted.'))
+ // Register a service worker.
+ .then(() => service_worker_unregister_and_register(t, script, scope))
+ .then(r => worker = r.installing)
+ .then(() => wait_for_state(t, worker, 'activated'))
+
+ // Let the service worker claim the iframe and the shared worker.
+ .then(() => {
+ var channel = new MessageChannel();
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data, 'PASS',
+ 'Worker call to claim() should fulfill.');
+ resolve();
+ });
+ });
+ worker.postMessage({port: channel.port2}, [channel.port2]);
+ return saw_message;
+ })
+
+ // Check the controller and test with fetch in the shared worker.
+ .then(() => frame.contentWindow.navigator.serviceWorker.getRegistration(scope))
+ .then(r => assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
+ r.active,
+ 'Test iframe should be claimed.'))
+ // TODO(horo): Check the SharedWorker's navigator.seviceWorker.controller.
+ .then(() => frame.contentWindow.fetch_in_shared_worker(resource))
+ .then(response_text =>
+ assert_equals(response_text,
+ 'Intercepted!',
+ 'fetch() in the shared worker should be intercepted.'))
+
+ // Cleanup this testcase.
+ .then(() => frame.remove())
+ .then(() => service_worker_unregister_and_done(t, scope));
+}, 'fetch() in SharedWorker should be intercepted after the client is claimed.')
+
+</script>
+</body>
diff --git a/service-workers/service-worker/claim-worker-fetch.https.html b/service-workers/service-worker/claim-worker-fetch.https.html
new file mode 100644
index 0000000..2bc6536
--- /dev/null
+++ b/service-workers/service-worker/claim-worker-fetch.https.html
@@ -0,0 +1,68 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<body>
+<script>
+
+promise_test(function(t) {
+ var frame;
+ var resource = 'simple.txt';
+
+ var worker;
+ var scope = 'resources/';
+ var script = 'resources/claim-worker.js';
+
+ return Promise.resolve()
+ // Create the test iframe with a dedicated worker.
+ .then(() => with_iframe('resources/claim-worker-fetch-iframe.html'))
+ .then(f => frame = f)
+
+ // Check the controller and test with fetch in the worker.
+ .then(() => assert_equals(frame.contentWindow.navigator.controller,
+ undefined,
+ 'Should have no controller.'))
+ .then(() => frame.contentWindow.fetch_in_worker(resource))
+ .then(response_text => assert_equals(response_text,
+ 'a simple text file\n',
+ 'fetch() should not be intercepted.'))
+ // Register a service worker.
+ .then(() => service_worker_unregister_and_register(t, script, scope))
+ .then(r => worker = r.installing)
+ .then(() => wait_for_state(t, worker, 'activated'))
+
+ // Let the service worker claim the iframe and the worker.
+ .then(() => {
+ var channel = new MessageChannel();
+ var saw_message = new Promise(function(resolve) {
+ channel.port1.onmessage = t.step_func(function(e) {
+ assert_equals(e.data, 'PASS',
+ 'Worker call to claim() should fulfill.');
+ resolve();
+ });
+ });
+ worker.postMessage({port: channel.port2}, [channel.port2]);
+ return saw_message;
+ })
+
+ // Check the controller and test with fetch in the worker.
+ .then(() => frame.contentWindow.navigator.serviceWorker.getRegistration(scope))
+ .then(r => assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
+ r.active,
+ 'Test iframe should be claimed.'))
+ // TODO(horo): Check the worker's navigator.seviceWorker.controller.
+ .then(() => frame.contentWindow.fetch_in_worker(resource))
+ .then(response_text =>
+ assert_equals(response_text,
+ 'Intercepted!',
+ 'fetch() in the worker should be intercepted.'))
+
+ // Cleanup this testcase.
+ .then(() => frame.remove())
+ .then(() => service_worker_unregister_and_done(t, scope));
+}, 'fetch() in Worker should be intercepted after the client is claimed.')
+
+</script>
+</body>
diff --git a/service-workers/service-worker/clients-matchall-client-types.https.html b/service-workers/service-worker/clients-matchall-client-types.https.html
index 5bb50ec..2496717 100644
--- a/service-workers/service-worker/clients-matchall-client-types.https.html
+++ b/service-workers/service-worker/clients-matchall-client-types.https.html
@@ -19,9 +19,12 @@
var expected_only_dedicated_worker = [
[undefined, undefined, new URL(dedicated_worker_url, location).href, 'worker', 'none']
];
+
+// These are explicitly sorted by URL in the service worker script.
var expected_all_clients = [
- expected_only_window[0], expected_only_shared_worker[0],
- expected_only_dedicated_worker[0]
+ expected_only_dedicated_worker[0],
+ expected_only_window[0],
+ expected_only_shared_worker[0],
];
function test_matchall(frame, expected, query_options) {
diff --git a/service-workers/service-worker/link-element-register-basic.https.html b/service-workers/service-worker/link-element-register-basic.https.html
new file mode 100644
index 0000000..83f3f48
--- /dev/null
+++ b/service-workers/service-worker/link-element-register-basic.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Service Worker: Register via link element (basic)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/registration-tests-basic.js"></script>
+<body>
+<script>
+registration_tests_basic(register_using_link, false);
+</script>
+</body>
diff --git a/service-workers/service-worker/link-element-register-mime-types.https.html b/service-workers/service-worker/link-element-register-mime-types.https.html
new file mode 100644
index 0000000..beb7d58
--- /dev/null
+++ b/service-workers/service-worker/link-element-register-mime-types.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Service Worker: Register via link element (MIME types)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/registration-tests-mime-types.js"></script>
+<body>
+<script>
+registration_tests_mime_types(register_using_link, false);
+</script>
+</body>
diff --git a/service-workers/service-worker/link-element-register-scope.https.html b/service-workers/service-worker/link-element-register-scope.https.html
new file mode 100644
index 0000000..1b75c38
--- /dev/null
+++ b/service-workers/service-worker/link-element-register-scope.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Service Worker: Register via link element (scope)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/registration-tests-scope.js"></script>
+<body>
+<script>
+registration_tests_scope(register_using_link, false);
+</script>
+</body>
+
diff --git a/service-workers/service-worker/link-element-register-script-url.https.html b/service-workers/service-worker/link-element-register-script-url.https.html
new file mode 100644
index 0000000..48cb935
--- /dev/null
+++ b/service-workers/service-worker/link-element-register-script-url.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Service Worker: Register via link element (scriptURL)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/registration-tests-script-url.js"></script>
+<body>
+<script>
+registration_tests_script_url(register_using_link, false);
+</script>
+</body>
diff --git a/service-workers/service-worker/link-element-register-script.https.html b/service-workers/service-worker/link-element-register-script.https.html
new file mode 100644
index 0000000..4a2818d
--- /dev/null
+++ b/service-workers/service-worker/link-element-register-script.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Service Worker: Register via link element (script)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/registration-tests-script.js"></script>
+<body>
+<script>
+registration_tests_script(register_using_link, false);
+</script>
+</body>
diff --git a/service-workers/service-worker/link-element-register-security-error.https.html b/service-workers/service-worker/link-element-register-security-error.https.html
new file mode 100644
index 0000000..eae421d
--- /dev/null
+++ b/service-workers/service-worker/link-element-register-security-error.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Service Worker: Register via link element (SecurityError)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/registration-tests-security-error.js"></script>
+<body>
+<script>
+registration_tests_security_error(register_using_link, false);
+</script>
+</body>
diff --git a/service-workers/service-worker/register-link-element.https.html b/service-workers/service-worker/register-link-element.https.html
deleted file mode 100644
index a4a4a19..0000000
--- a/service-workers/service-worker/register-link-element.https.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.sub.js"></script>
-<script src="resources/registration-tests.js"></script>
-<body>
-<script>
-
-function registerUsingLink(script, options) {
- var scope = options.scope;
- var link = document.createElement('link');
- link.setAttribute('rel', 'serviceworker');
- link.setAttribute('href', script);
- link.setAttribute('scope', scope);
- document.getElementsByTagName('head')[0].appendChild(link);
- return new Promise(function(resolve, reject) {
- link.onload = resolve;
- link.onerror = reject;
- })
- .then(() => navigator.serviceWorker.getRegistration(scope));
-}
-
-registration_tests(registerUsingLink, false);
-
-</script>
diff --git a/service-workers/service-worker/registration-basic.https.html b/service-workers/service-worker/registration-basic.https.html
new file mode 100644
index 0000000..1411a5d
--- /dev/null
+++ b/service-workers/service-worker/registration-basic.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Service Worker: Registration (basic)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/registration-tests-basic.js"></script>
+<script>
+registration_tests_basic((script, options) => navigator.serviceWorker.register(script, options), true);
+</script>
diff --git a/service-workers/service-worker/registration-mime-types.https.html b/service-workers/service-worker/registration-mime-types.https.html
new file mode 100644
index 0000000..9ae5f09
--- /dev/null
+++ b/service-workers/service-worker/registration-mime-types.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Service Worker: Registration (MIME types)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/registration-tests-mime-types.js"></script>
+<script>
+registration_tests_mime_types((script, options) => navigator.serviceWorker.register(script, options), true);
+</script>
diff --git a/service-workers/service-worker/registration-scope.https.html b/service-workers/service-worker/registration-scope.https.html
new file mode 100644
index 0000000..f4a77d7
--- /dev/null
+++ b/service-workers/service-worker/registration-scope.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Service Worker: Registration (scope)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/registration-tests-scope.js"></script>
+<script>
+registration_tests_scope((script, options) => navigator.serviceWorker.register(script, options), true);
+</script>
diff --git a/service-workers/service-worker/registration-script-url.https.html b/service-workers/service-worker/registration-script-url.https.html
new file mode 100644
index 0000000..ea9ae7f
--- /dev/null
+++ b/service-workers/service-worker/registration-script-url.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Service Worker: Registration (scriptURL)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/registration-tests-script-url.js"></script>
+<script>
+registration_tests_script_url((script, options) => navigator.serviceWorker.register(script, options), true);
+</script>
diff --git a/service-workers/service-worker/registration-script.https.html b/service-workers/service-worker/registration-script.https.html
new file mode 100644
index 0000000..d1b3f0e
--- /dev/null
+++ b/service-workers/service-worker/registration-script.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Service Worker: Registration (script)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/registration-tests-script.js"></script>
+<script>
+registration_tests_script((script, options) => navigator.serviceWorker.register(script, options), true);
+</script>
diff --git a/service-workers/service-worker/registration-security-error.https.html b/service-workers/service-worker/registration-security-error.https.html
new file mode 100644
index 0000000..8f0609b
--- /dev/null
+++ b/service-workers/service-worker/registration-security-error.https.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Service Worker: Registration (SecurityError)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-helpers.sub.js"></script>
+<script src="resources/registration-tests-security-error.js"></script>
+<script>
+registration_tests_security_error((script, options) => navigator.serviceWorker.register(script, options), true);
+</script>
diff --git a/service-workers/service-worker/registration.https.html b/service-workers/service-worker/registration.https.html
deleted file mode 100644
index 587cac2..0000000
--- a/service-workers/service-worker/registration.https.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<!DOCTYPE html>
-<title>Service Worker: Registration</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="resources/test-helpers.sub.js"></script>
-<script src="resources/registration-tests.js"></script>
-<script>
-registration_tests((script, options) => navigator.serviceWorker.register(script, options), true);
-</script>
diff --git a/service-workers/service-worker/resources/claim-shared-worker-fetch-iframe.html b/service-workers/service-worker/resources/claim-shared-worker-fetch-iframe.html
new file mode 100644
index 0000000..ad865b8
--- /dev/null
+++ b/service-workers/service-worker/resources/claim-shared-worker-fetch-iframe.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<script>
+var worker = new SharedWorker('./claim-shared-worker-fetch-worker.js');
+
+function fetch_in_shared_worker(url) {
+ return new Promise((resolve) => {
+ worker.port.onmessage = (event) => {
+ resolve(event.data);
+ };
+ worker.port.postMessage(url);
+ });
+}
+</script>
diff --git a/service-workers/service-worker/resources/claim-shared-worker-fetch-worker.js b/service-workers/service-worker/resources/claim-shared-worker-fetch-worker.js
new file mode 100644
index 0000000..ddc8bea
--- /dev/null
+++ b/service-workers/service-worker/resources/claim-shared-worker-fetch-worker.js
@@ -0,0 +1,8 @@
+self.onconnect = (event) => {
+ var port = event.ports[0];
+ event.ports[0].onmessage = (evt) => {
+ fetch(evt.data)
+ .then(response => response.text())
+ .then(text => port.postMessage(text));
+ };
+};
diff --git a/service-workers/service-worker/resources/claim-worker-fetch-iframe.html b/service-workers/service-worker/resources/claim-worker-fetch-iframe.html
new file mode 100644
index 0000000..92c5d15
--- /dev/null
+++ b/service-workers/service-worker/resources/claim-worker-fetch-iframe.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<script>
+var worker = new Worker('./claim-worker-fetch-worker.js');
+
+function fetch_in_worker(url) {
+ return new Promise((resolve) => {
+ worker.onmessage = (event) => {
+ resolve(event.data);
+ };
+ worker.postMessage(url);
+ });
+}
+</script>
diff --git a/service-workers/service-worker/resources/claim-worker-fetch-worker.js b/service-workers/service-worker/resources/claim-worker-fetch-worker.js
new file mode 100644
index 0000000..7080181
--- /dev/null
+++ b/service-workers/service-worker/resources/claim-worker-fetch-worker.js
@@ -0,0 +1,5 @@
+self.onmessage = (event) => {
+ fetch(event.data)
+ .then(response => response.text())
+ .then(text => self.postMessage(text));
+};
diff --git a/service-workers/service-worker/resources/registration-tests-basic.js b/service-workers/service-worker/resources/registration-tests-basic.js
new file mode 100644
index 0000000..eedb980
--- /dev/null
+++ b/service-workers/service-worker/resources/registration-tests-basic.js
@@ -0,0 +1,44 @@
+// Basic registration tests that succeed. We don't want too many successful
+// registration tests in the same file since starting a service worker can be
+// slow.
+function registration_tests_basic(register_method, check_error_types) {
+ promise_test(function(t) {
+ var script = 'resources/registration-worker.js';
+ var scope = 'resources/registration/normal';
+ return register_method(script, {scope: scope})
+ .then(function(registration) {
+ assert_true(
+ registration instanceof ServiceWorkerRegistration,
+ 'Successfully registered.');
+ return registration.unregister();
+ });
+ }, 'Registering normal scope');
+
+ promise_test(function(t) {
+ var script = 'resources/registration-worker.js';
+ var scope = 'resources/registration/scope-with-fragment#ref';
+ return register_method(script, {scope: scope})
+ .then(function(registration) {
+ assert_true(
+ registration instanceof ServiceWorkerRegistration,
+ 'Successfully registered.');
+ assert_equals(
+ registration.scope,
+ normalizeURL('resources/registration/scope-with-fragment'),
+ 'A fragment should be removed from scope')
+ return registration.unregister();
+ });
+ }, 'Registering scope with fragment');
+
+ promise_test(function(t) {
+ var script = 'resources/registration-worker.js';
+ var scope = 'resources/';
+ return register_method(script, {scope: scope})
+ .then(function(registration) {
+ assert_true(
+ registration instanceof ServiceWorkerRegistration,
+ 'Successfully registered.');
+ return registration.unregister();
+ });
+ }, 'Registering same scope as the script directory');
+}
diff --git a/service-workers/service-worker/resources/registration-tests-mime-types.js b/service-workers/service-worker/resources/registration-tests-mime-types.js
new file mode 100644
index 0000000..1b8ea9b
--- /dev/null
+++ b/service-workers/service-worker/resources/registration-tests-mime-types.js
@@ -0,0 +1,86 @@
+// Registration tests that mostly verify the MIME type.
+//
+// This file tests every MIME type so it necessarily starts many service
+// workers, so it may be slow.
+function registration_tests_mime_types(register_method, check_error_types) {
+ promise_test(function(t) {
+ var script = 'resources/mime-type-worker.py';
+ var scope = 'resources/scope/no-mime-type-worker/';
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Registration of no MIME type script should fail.');
+ }, 'Registering script with no MIME type');
+
+ promise_test(function(t) {
+ var script = 'resources/mime-type-worker.py?mime=text/plain';
+ var scope = 'resources/scope/bad-mime-type-worker/';
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Registration of plain text script should fail.');
+ }, 'Registering script with bad MIME type');
+
+ promise_test(function(t) {
+ var script = 'resources/import-mime-type-worker.py';
+ var scope = 'resources/scope/no-mime-type-worker/';
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Registration of no MIME type imported script should fail.');
+ }, 'Registering script that imports script with no MIME type');
+
+ promise_test(function(t) {
+ var script = 'resources/import-mime-type-worker.py?mime=text/plain';
+ var scope = 'resources/scope/bad-mime-type-worker/';
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Registration of plain text imported script should fail.');
+ }, 'Registering script that imports script with bad MIME type');
+
+ const validMimeTypes = [
+ 'application/ecmascript',
+ 'application/javascript',
+ 'application/x-ecmascript',
+ 'application/x-javascript',
+ 'text/ecmascript',
+ 'text/javascript',
+ 'text/javascript1.0',
+ 'text/javascript1.1',
+ 'text/javascript1.2',
+ 'text/javascript1.3',
+ 'text/javascript1.4',
+ 'text/javascript1.5',
+ 'text/jscript',
+ 'text/livescript',
+ 'text/x-ecmascript',
+ 'text/x-javascript'
+ ];
+
+ for (const validMimeType of validMimeTypes) {
+ promise_test(() => {
+ var script = `resources/mime-type-worker.py?mime=${validMimeType}`;
+ var scope = 'resources/scope/good-mime-type-worker/';
+
+ return register_method(script, {scope}).then(registration => {
+ assert_true(
+ registration instanceof ServiceWorkerRegistration,
+ 'Successfully registered.');
+ return registration.unregister();
+ });
+ }, `Registering script with good MIME type ${validMimeType}`);
+
+ promise_test(() => {
+ var script = `resources/import-mime-type-worker.py?mime=${validMimeType}`;
+ var scope = 'resources/scope/good-mime-type-worker/';
+
+ return register_method(script, { scope }).then(registration => {
+ assert_true(
+ registration instanceof ServiceWorkerRegistration,
+ 'Successfully registered.');
+ return registration.unregister();
+ });
+ }, `Registering script that imports script with good MIME type ${validMimeType}`);
+ }
+}
diff --git a/service-workers/service-worker/resources/registration-tests-scope.js b/service-workers/service-worker/resources/registration-tests-scope.js
new file mode 100644
index 0000000..51b0ab4
--- /dev/null
+++ b/service-workers/service-worker/resources/registration-tests-scope.js
@@ -0,0 +1,102 @@
+// Registration tests that mostly exercise the scope option.
+function registration_tests_scope(register_method, check_error_types) {
+ promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/scope%2fencoded-slash-in-scope';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'URL-encoded slash in the scope should be rejected.');
+ }, 'Scope including URL-encoded slash');
+
+ promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/scope%5cencoded-slash-in-scope';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'URL-encoded backslash in the scope should be rejected.');
+ }, 'Scope including URL-encoded backslash');
+
+ promise_test(function(t) {
+ // URL-encoded full-width 'scope'.
+ var name = '%ef%bd%93%ef%bd%83%ef%bd%8f%ef%bd%90%ef%bd%85';
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/' + name + '/escaped-multibyte-character-scope';
+ return register_method(script, {scope: scope})
+ .then(function(registration) {
+ assert_equals(
+ registration.scope,
+ normalizeURL(scope),
+ 'URL-encoded multibyte characters should be available.');
+ return registration.unregister();
+ });
+ }, 'Scope including URL-encoded multibyte characters');
+
+ promise_test(function(t) {
+ // Non-URL-encoded full-width "scope".
+ var name = String.fromCodePoint(0xff53, 0xff43, 0xff4f, 0xff50, 0xff45);
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/' + name + '/non-escaped-multibyte-character-scope';
+ return register_method(script, {scope: scope})
+ .then(function(registration) {
+ assert_equals(
+ registration.scope,
+ normalizeURL(scope),
+ 'Non-URL-encoded multibyte characters should be available.');
+ return registration.unregister();
+ });
+ }, 'Scope including non-escaped multibyte characters');
+
+ promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/././scope/self-reference-in-scope';
+ return register_method(script, {scope: scope})
+ .then(function(registration) {
+ assert_equals(
+ registration.scope,
+ normalizeURL('resources/scope/self-reference-in-scope'),
+ 'Scope including self-reference should be normalized.');
+ return registration.unregister();
+ });
+ }, 'Scope including self-reference');
+
+ promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/../resources/scope/parent-reference-in-scope';
+ return register_method(script, {scope: scope})
+ .then(function(registration) {
+ assert_equals(
+ registration.scope,
+ normalizeURL('resources/scope/parent-reference-in-scope'),
+ 'Scope including parent-reference should be normalized.');
+ return registration.unregister();
+ });
+ }, 'Scope including parent-reference');
+
+ promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/scope////consecutive-slashes-in-scope';
+ return register_method(script, {scope: scope})
+ .then(function(registration) {
+ // Although consecutive slashes in the scope are not unified, the
+ // scope is under the script directory and registration should
+ // succeed.
+ assert_equals(
+ registration.scope,
+ normalizeURL(scope),
+ 'Should successfully be registered.');
+ return registration.unregister();
+ })
+ }, 'Scope including consecutive slashes');
+
+ promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'filesystem:' + normalizeURL('resources/scope/filesystem-scope-url');
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Registering with the scope that has same-origin filesystem: URL ' +
+ 'should fail with SecurityError.');
+ }, 'Scope URL is same-origin filesystem: URL');
+}
diff --git a/service-workers/service-worker/resources/registration-tests-script-url.js b/service-workers/service-worker/resources/registration-tests-script-url.js
new file mode 100644
index 0000000..8d777a8
--- /dev/null
+++ b/service-workers/service-worker/resources/registration-tests-script-url.js
@@ -0,0 +1,64 @@
+// Registration tests that mostly exercise the scriptURL parameter.
+function registration_tests_script_url(register_method, check_error_types) {
+ promise_test(function(t) {
+ var script = 'resources%2fempty-worker.js';
+ var scope = 'resources/scope/encoded-slash-in-script-url';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'URL-encoded slash in the script URL should be rejected.');
+ }, 'Script URL including URL-encoded slash');
+
+ promise_test(function(t) {
+ var script = 'resources%2Fempty-worker.js';
+ var scope = 'resources/scope/encoded-slash-in-script-url';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'URL-encoded slash in the script URL should be rejected.');
+ }, 'Script URL including uppercase URL-encoded slash');
+
+ promise_test(function(t) {
+ var script = 'resources%5cempty-worker.js';
+ var scope = 'resources/scope/encoded-slash-in-script-url';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'URL-encoded backslash in the script URL should be rejected.');
+ }, 'Script URL including URL-encoded backslash');
+
+ promise_test(function(t) {
+ var script = 'resources%5Cempty-worker.js';
+ var scope = 'resources/scope/encoded-slash-in-script-url';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'URL-encoded backslash in the script URL should be rejected.');
+ }, 'Script URL including uppercase URL-encoded backslash');
+
+ promise_test(function(t) {
+ var script = 'resources/././empty-worker.js';
+ var scope = 'resources/scope/parent-reference-in-script-url';
+ return register_method(script, {scope: scope})
+ .then(function(registration) {
+ assert_equals(
+ get_newest_worker(registration).scriptURL,
+ normalizeURL('resources/empty-worker.js'),
+ 'Script URL including self-reference should be normalized.');
+ return registration.unregister();
+ });
+ }, 'Script URL including self-reference');
+
+ promise_test(function(t) {
+ var script = 'resources/../resources/empty-worker.js';
+ var scope = 'resources/scope/parent-reference-in-script-url';
+ return register_method(script, {scope: scope})
+ .then(function(registration) {
+ assert_equals(
+ get_newest_worker(registration).scriptURL,
+ normalizeURL('resources/empty-worker.js'),
+ 'Script URL including parent-reference should be normalized.');
+ return registration.unregister();
+ });
+ }, 'Script URL including parent-reference');
+}
diff --git a/service-workers/service-worker/resources/registration-tests-script.js b/service-workers/service-worker/resources/registration-tests-script.js
new file mode 100644
index 0000000..0e6859b
--- /dev/null
+++ b/service-workers/service-worker/resources/registration-tests-script.js
@@ -0,0 +1,88 @@
+// Registration tests that mostly exercise the service worker script contents or
+// response.
+function registration_tests_script(register_method, check_error_types) {
+ promise_test(function(t) {
+ var script = 'resources/invalid-chunked-encoding.py';
+ var scope = 'resources/scope/invalid-chunked-encoding/';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'Registration of invalid chunked encoding script should fail.');
+ }, 'Registering invalid chunked encoding script');
+
+ promise_test(function(t) {
+ var script = 'resources/invalid-chunked-encoding-with-flush.py';
+ var scope = 'resources/scope/invalid-chunked-encoding-with-flush/';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'Registration of invalid chunked encoding script should fail.');
+ }, 'Registering invalid chunked encoding script with flush');
+
+ promise_test(function(t) {
+ var script = 'resources/malformed-worker.py?parse-error';
+ var scope = 'resources/scope/parse-error';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'Registration of script including parse error should fail.');
+ }, 'Registering script including parse error');
+
+ promise_test(function(t) {
+ var script = 'resources/malformed-worker.py?undefined-error';
+ var scope = 'resources/scope/undefined-error';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'Registration of script including undefined error should fail.');
+ }, 'Registering script including undefined error');
+
+ promise_test(function(t) {
+ var script = 'resources/malformed-worker.py?uncaught-exception';
+ var scope = 'resources/scope/uncaught-exception';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'Registration of script including uncaught exception should fail.');
+ }, 'Registering script including uncaught exception');
+
+ promise_test(function(t) {
+ var script = 'resources/malformed-worker.py?import-malformed-script';
+ var scope = 'resources/scope/import-malformed-script';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'Registration of script importing malformed script should fail.');
+ }, 'Registering script importing malformed script');
+
+ promise_test(function(t) {
+ var script = 'resources/no-such-worker.js';
+ var scope = 'resources/scope/no-such-worker';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'Registration of non-existent script should fail.');
+ }, 'Registering non-existent script');
+
+ promise_test(function(t) {
+ var script = 'resources/malformed-worker.py?import-no-such-script';
+ var scope = 'resources/scope/import-no-such-script';
+ return promise_rejects(t,
+ check_error_types ? new TypeError : null,
+ register_method(script, {scope: scope}),
+ 'Registration of script importing non-existent script should fail.');
+ }, 'Registering script importing non-existent script');
+
+ promise_test(function(t) {
+ var script = 'resources/malformed-worker.py?caught-exception';
+ var scope = 'resources/scope/caught-exception';
+ return register_method(script, {scope: scope})
+ .then(function(registration) {
+ assert_true(
+ registration instanceof ServiceWorkerRegistration,
+ 'Successfully registered.');
+ return registration.unregister();
+ });
+ }, 'Registering script including caught exception');
+
+}
diff --git a/service-workers/service-worker/resources/registration-tests-security-error.js b/service-workers/service-worker/resources/registration-tests-security-error.js
new file mode 100644
index 0000000..c84bb66
--- /dev/null
+++ b/service-workers/service-worker/resources/registration-tests-security-error.js
@@ -0,0 +1,78 @@
+// Registration tests that mostly exercise SecurityError cases.
+function registration_tests_security_error(register_method, check_error_types) {
+ promise_test(function(t) {
+ var script = 'resources/registration-worker.js';
+ var scope = 'resources';
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Registering same scope as the script directory without the last ' +
+ 'slash should fail with SecurityError.');
+ }, 'Registering same scope as the script directory without the last slash');
+
+ promise_test(function(t) {
+ var script = 'resources/registration-worker.js';
+ var scope = 'different-directory/';
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Registration scope outside the script directory should fail ' +
+ 'with SecurityError.');
+ }, 'Registration scope outside the script directory');
+
+ promise_test(function(t) {
+ var script = 'resources/registration-worker.js';
+ var scope = 'http://example.com/';
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Registration scope outside domain should fail with SecurityError.');
+ }, 'Registering scope outside domain');
+
+ promise_test(function(t) {
+ var script = 'http://example.com/worker.js';
+ var scope = 'http://example.com/scope/';
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Registration script outside domain should fail with SecurityError.');
+ }, 'Registering script outside domain');
+
+ promise_test(function(t) {
+ var script = 'resources/redirect.py?Redirect=' +
+ encodeURIComponent('/resources/registration-worker.js');
+ var scope = 'resources/scope/redirect/';
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Registration of redirected script should fail.');
+ }, 'Registering redirected script');
+
+ promise_test(function(t) {
+ var script = 'resources/empty-worker.js';
+ var scope = 'resources/../scope/parent-reference-in-scope';
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Scope not under the script directory should be rejected.');
+ }, 'Scope including parent-reference and not under the script directory');
+
+ promise_test(function(t) {
+ var script = 'resources////empty-worker.js';
+ var scope = 'resources/scope/consecutive-slashes-in-script-url';
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Consecutive slashes in the script url should not be unified.');
+ }, 'Script URL including consecutive slashes');
+
+ promise_test(function(t) {
+ var script = 'filesystem:' + normalizeURL('resources/empty-worker.js');
+ var scope = 'resources/scope/filesystem-script-url';
+ return promise_rejects(t,
+ check_error_types ? 'SecurityError' : null,
+ register_method(script, {scope: scope}),
+ 'Registering a script which has same-origin filesystem: URL should ' +
+ 'fail with SecurityError.');
+ }, 'Script URL is same-origin filesystem: URL');
+}
diff --git a/service-workers/service-worker/resources/test-helpers.sub.js b/service-workers/service-worker/resources/test-helpers.sub.js
index 1d7643a..1df4363 100644
--- a/service-workers/service-worker/resources/test-helpers.sub.js
+++ b/service-workers/service-worker/resources/test-helpers.sub.js
@@ -226,3 +226,32 @@
function get_websocket_url() {
return 'wss://{{host}}:{{ports[wss][0]}}/echo';
}
+
+// The navigator.serviceWorker.register() method guarantees that the newly
+// installing worker is available as registration.installing when its promise
+// resolves. However some tests test installation using a <link> element where
+// it is possible for the installing worker to have already become the waiting
+// or active worker. So this method is used to get the newest worker when these
+// tests need access to the ServiceWorker itself.
+function get_newest_worker(registration) {
+ if (registration.installing)
+ return registration.installing;
+ if (registration.waiting)
+ return registration.waiting;
+ if (registration.active)
+ return registration.active;
+}
+
+function register_using_link(script, options) {
+ var scope = options.scope;
+ var link = document.createElement('link');
+ link.setAttribute('rel', 'serviceworker');
+ link.setAttribute('href', script);
+ link.setAttribute('scope', scope);
+ document.getElementsByTagName('head')[0].appendChild(link);
+ return new Promise(function(resolve, reject) {
+ link.onload = resolve;
+ link.onerror = reject;
+ })
+ .then(() => navigator.serviceWorker.getRegistration(scope));
+}
diff --git a/streams/byte-length-queuing-strategy.js b/streams/byte-length-queuing-strategy.js
index 54407af..e96e68e 100644
--- a/streams/byte-length-queuing-strategy.js
+++ b/streams/byte-length-queuing-strategy.js
@@ -104,4 +104,11 @@
}, 'ByteLengthQueuingStrategy\'s highWaterMark property can be set to anything');
+test(() => {
+
+ assert_equals(ByteLengthQueuingStrategy.name, 'ByteLengthQueuingStrategy',
+ 'ByteLengthQueuingStrategy.name must be "ByteLengthQueuingStrategy"');
+
+}, 'ByteLengthQueuingStrategy.name is correct');
+
done();
diff --git a/streams/count-queuing-strategy.js b/streams/count-queuing-strategy.js
index 5ae0063..8da8eb6 100644
--- a/streams/count-queuing-strategy.js
+++ b/streams/count-queuing-strategy.js
@@ -103,4 +103,11 @@
}, 'CountQueuingStrategy\'s highWaterMark property can be set to anything');
+test(() => {
+
+ assert_equals(CountQueuingStrategy.name, 'CountQueuingStrategy',
+ 'CountQueuingStrategy.name must be "CountQueuingStrategy"');
+
+}, 'CountQueuingStrategy.name is correct');
+
done();
diff --git a/streams/readable-byte-streams/properties.dedicatedworker.html b/streams/readable-byte-streams/properties.dedicatedworker.html
new file mode 100644
index 0000000..50d63ed
--- /dev/null
+++ b/streams/readable-byte-streams/properties.dedicatedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>properties.js dedicated worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new Worker('properties.js'));
+</script>
diff --git a/streams/readable-byte-streams/properties.html b/streams/readable-byte-streams/properties.html
new file mode 100644
index 0000000..41ab6b2
--- /dev/null
+++ b/streams/readable-byte-streams/properties.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>properties.js browser context wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script src="../resources/rs-utils.js"></script>
+
+<script src="properties.js"></script>
diff --git a/streams/readable-byte-streams/properties.js b/streams/readable-byte-streams/properties.js
new file mode 100644
index 0000000..21549bc
--- /dev/null
+++ b/streams/readable-byte-streams/properties.js
@@ -0,0 +1,147 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('../resources/rs-utils.js');
+ self.importScripts('/resources/testharness.js');
+}
+
+let ReadableStreamBYOBReader;
+
+test(() => {
+
+ // It's not exposed globally, but we test a few of its properties here.
+ ReadableStreamBYOBReader = (new ReadableStream({ type: 'bytes' }))
+ .getReader({ mode: 'byob' }).constructor;
+
+}, 'Can get the ReadableStreamBYOBReader constructor indirectly');
+
+test(() => {
+
+ assert_throws(new TypeError(), () => new ReadableStreamBYOBReader('potato'));
+ assert_throws(new TypeError(), () => new ReadableStreamBYOBReader({}));
+ assert_throws(new TypeError(), () => new ReadableStreamBYOBReader());
+
+}, 'ReadableStreamBYOBReader constructor should get a ReadableStream object as argument');
+
+test(() => {
+
+ const methods = ['cancel', 'constructor', 'read', 'releaseLock'];
+ const properties = methods.concat(['closed']).sort();
+
+ const rsReader = new ReadableStreamBYOBReader(new ReadableStream({ type: 'bytes' }));
+ const proto = Object.getPrototypeOf(rsReader);
+
+ assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
+
+ for (const m of methods) {
+ const propDesc = Object.getOwnPropertyDescriptor(proto, m);
+ assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
+ assert_equals(propDesc.configurable, true, 'method should be configurable');
+ assert_equals(propDesc.writable, true, 'method should be writable');
+ assert_equals(typeof rsReader[m], 'function', 'should have be a method');
+ const expectedName = m === 'constructor' ? 'ReadableStreamBYOBReader' : m;
+ assert_equals(rsReader[m].name, expectedName, 'method should have the correct name');
+ }
+
+ const closedPropDesc = Object.getOwnPropertyDescriptor(proto, 'closed');
+ assert_equals(closedPropDesc.enumerable, false, 'closed should be non-enumerable');
+ assert_equals(closedPropDesc.configurable, true, 'closed should be configurable');
+ assert_not_equals(closedPropDesc.get, undefined, 'closed should have a getter');
+ assert_equals(closedPropDesc.set, undefined, 'closed should not have a setter');
+
+ assert_equals(rsReader.cancel.length, 1, 'cancel has 1 parameter');
+ assert_not_equals(rsReader.closed, undefined, 'has a non-undefined closed property');
+ assert_equals(typeof rsReader.closed.then, 'function', 'closed property is thenable');
+ assert_equals(rsReader.constructor.length, 1, 'constructor has 1 parameter');
+ assert_equals(rsReader.read.length, 1, 'read has 1 parameter');
+ assert_equals(rsReader.releaseLock.length, 0, 'releaseLock has no parameters');
+
+}, 'ReadableStreamBYOBReader instances should have the correct list of properties');
+
+promise_test(t => {
+
+ const rs = new ReadableStream({
+ pull(controller) {
+ return t.step(() => {
+ const byobRequest = controller.byobRequest;
+
+ const methods = ['constructor', 'respond', 'respondWithNewView'];
+ const properties = methods.concat(['view']).sort();
+
+ const proto = Object.getPrototypeOf(byobRequest);
+
+ assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
+
+ for (const m of methods) {
+ const propDesc = Object.getOwnPropertyDescriptor(proto, m);
+ assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
+ assert_equals(propDesc.configurable, true, 'method should be configurable');
+ assert_equals(propDesc.writable, true, 'method should be writable');
+ assert_equals(typeof byobRequest[m], 'function', 'should have a method');
+ const expectedName = m === 'constructor' ? 'ReadableStreamBYOBRequest' : m;
+ assert_equals(byobRequest[m].name, expectedName, 'method should have the correct name');
+ }
+
+ const viewPropDesc = Object.getOwnPropertyDescriptor(proto, 'view');
+ assert_equals(viewPropDesc.enumerable, false, 'view should be non-enumerable');
+ assert_equals(viewPropDesc.configurable, true, 'view should be configurable');
+ assert_not_equals(viewPropDesc.get, undefined, 'view should have a getter');
+ assert_equals(viewPropDesc.set, undefined, 'view should not have a setter');
+ assert_not_equals(byobRequest.view, undefined, 'has a non-undefined view property');
+ assert_equals(byobRequest.constructor.length, 2, 'constructor has 1 parameter');
+ assert_equals(byobRequest.respond.length, 1, 'respond has 1 parameter');
+ assert_equals(byobRequest.respondWithNewView.length, 1, 'releaseLock has 1 parameter');
+
+ byobRequest.respond(1);
+
+ });
+ },
+ type: 'bytes' });
+ const reader = rs.getReader({ mode: 'byob' });
+ return reader.read(new Uint8Array(1));
+
+}, 'ReadableStreamBYOBRequest instances should have the correct list of properties');
+
+test(() => {
+
+ const methods = ['close', 'constructor', 'enqueue', 'error'];
+ const accessors = ['byobRequest', 'desiredSize'];
+ const properties = methods.concat(accessors).sort();
+
+ let controller;
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+ const proto = Object.getPrototypeOf(controller);
+
+ assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
+
+ for (const m of methods) {
+ const propDesc = Object.getOwnPropertyDescriptor(proto, m);
+ assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
+ assert_equals(propDesc.configurable, true, 'method should be configurable');
+ assert_equals(propDesc.writable, true, 'method should be writable');
+ assert_equals(typeof controller[m], 'function', 'should have be a method');
+ const expectedName = m === 'constructor' ? 'ReadableByteStreamController' : m;
+ assert_equals(controller[m].name, expectedName, 'method should have the correct name');
+ }
+
+ for (const a of accessors) {
+ const propDesc = Object.getOwnPropertyDescriptor(proto, a);
+ assert_equals(propDesc.enumerable, false, `${a} should be non-enumerable`);
+ assert_equals(propDesc.configurable, true, `${a} should be configurable`);
+ assert_not_equals(propDesc.get, undefined, `${a} should have a getter`);
+ assert_equals(propDesc.set, undefined, `${a} should not have a setter`);
+ }
+
+ assert_equals(controller.close.length, 0, 'cancel has no parameters');
+ assert_equals(controller.constructor.length, 3, 'constructor has 3 parameters');
+ assert_equals(controller.enqueue.length, 1, 'enqueue has 1 parameter');
+ assert_equals(controller.error.length, 1, 'releaseLock has 1 parameter');
+
+}, 'ReadableByteStreamController instances should have the correct list of properties');
+
+done();
diff --git a/streams/readable-byte-streams/properties.serviceworker.https.html b/streams/readable-byte-streams/properties.serviceworker.https.html
new file mode 100644
index 0000000..ba5c513
--- /dev/null
+++ b/streams/readable-byte-streams/properties.serviceworker.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>properties.js service worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+
+<script>
+'use strict';
+service_worker_test('properties.js', 'Service worker test setup');
+</script>
diff --git a/streams/readable-byte-streams/properties.sharedworker.html b/streams/readable-byte-streams/properties.sharedworker.html
new file mode 100644
index 0000000..42fb3e5
--- /dev/null
+++ b/streams/readable-byte-streams/properties.sharedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>properties.js shared worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new SharedWorker('properties.js'));
+</script>
diff --git a/streams/readable-streams/default-reader.js b/streams/readable-streams/default-reader.js
index 1a5d3a4..7543545 100644
--- a/streams/readable-streams/default-reader.js
+++ b/streams/readable-streams/default-reader.js
@@ -38,6 +38,8 @@
assert_equals(propDesc.configurable, true, 'method should be configurable');
assert_equals(propDesc.writable, true, 'method should be writable');
assert_equals(typeof rsReader[m], 'function', 'should have be a method');
+ const expectedName = m === 'constructor' ? 'ReadableStreamDefaultReader' : m;
+ assert_equals(rsReader[m].name, expectedName, 'method should have the correct name');
}
const closedPropDesc = Object.getOwnPropertyDescriptor(proto, 'closed');
diff --git a/streams/readable-streams/general.js b/streams/readable-streams/general.js
index 472fb4b..5150c36 100644
--- a/streams/readable-streams/general.js
+++ b/streams/readable-streams/general.js
@@ -56,6 +56,8 @@
assert_true(propDesc.configurable, 'method should be configurable');
assert_true(propDesc.writable, 'method should be writable');
assert_equals(typeof rs[m], 'function', 'method should be a function');
+ const expectedName = m === 'constructor' ? 'ReadableStream' : m;
+ assert_equals(rs[m].name, expectedName, 'method should have the correct name');
}
const lockedPropDesc = Object.getOwnPropertyDescriptor(proto, 'locked');
@@ -115,6 +117,8 @@
assert_false(propDesc.enumerable, m + ' should be non-enumerable');
assert_true(propDesc.configurable, m + ' should be configurable');
assert_true(propDesc.writable, m + ' should be writable');
+ const expectedName = m === 'constructor' ? 'ReadableStreamDefaultController' : m;
+ assert_equals(controller[m].name, expectedName, 'method should have the correct name');
}
const desiredSizePropDesc = Object.getOwnPropertyDescriptor(proto, 'desiredSize');
diff --git a/streams/writable-streams/properties.js b/streams/writable-streams/properties.js
index 6ccb1a2..99bd09a 100644
--- a/streams/writable-streams/properties.js
+++ b/streams/writable-streams/properties.js
@@ -127,8 +127,10 @@
`${name} should take ${properties[name].length} arguments`);
if (type === 'constructor') {
assert_true(IsConstructor(prototype[name]), `${name} should be a constructor`);
+ assert_equals(prototype[name].name, c, `${name}.name should be '${c}'`);
} else {
assert_false(IsConstructor(prototype[name]), `${name} should not be a constructor`);
+ assert_equals(prototype[name].name, name, `${name}.name should be '${name}`);
}
}, `${fullName} should be a ${type}`);
break;
diff --git a/subresource-integrity/ed25519-broken-signature.js b/subresource-integrity/ed25519-broken-signature.js
new file mode 100644
index 0000000..32bf457
--- /dev/null
+++ b/subresource-integrity/ed25519-broken-signature.js
@@ -0,0 +1 @@
+ed25519_broken_signature="trollololo";
diff --git a/subresource-integrity/ed25519-broken-signature.js.headers b/subresource-integrity/ed25519-broken-signature.js.headers
new file mode 100644
index 0000000..47d4f9e
--- /dev/null
+++ b/subresource-integrity/ed25519-broken-signature.js.headers
@@ -0,0 +1 @@
+Integrity: ed25519-dY4xEJDd1AMMbFNIhAMzJO6uhp6gZJOhchjJXDB8yY67rYrF4QUmUWS9gkvdY0Cxo8Rnb2kIdoUiigodoatKDQ==
diff --git a/subresource-integrity/ed25519-multi-signature-headers.js b/subresource-integrity/ed25519-multi-signature-headers.js
new file mode 100644
index 0000000..3a8db87
--- /dev/null
+++ b/subresource-integrity/ed25519-multi-signature-headers.js
@@ -0,0 +1 @@
+ed25519_signature=true;
diff --git a/subresource-integrity/ed25519-multi-signature-headers.js.headers b/subresource-integrity/ed25519-multi-signature-headers.js.headers
new file mode 100644
index 0000000..dad973c
--- /dev/null
+++ b/subresource-integrity/ed25519-multi-signature-headers.js.headers
@@ -0,0 +1,4 @@
+Integrity: sha256-Potato
+Integrity: ed25519-Potato
+Integrity: ed25519-dY4xEJDd1AMMbFNIhAMzJO6uhp6gZJOhchjJXDB8yY67rYrF4QUmUWS9gkvdY0Cxo8Rnb2kIdoUiigodoatKDQ==
+Integrity: ed25519-PotatoPotato
diff --git a/subresource-integrity/ed25519-multi-signature.js b/subresource-integrity/ed25519-multi-signature.js
new file mode 100644
index 0000000..3a8db87
--- /dev/null
+++ b/subresource-integrity/ed25519-multi-signature.js
@@ -0,0 +1 @@
+ed25519_signature=true;
diff --git a/subresource-integrity/ed25519-multi-signature.js.headers b/subresource-integrity/ed25519-multi-signature.js.headers
new file mode 100644
index 0000000..49098cc
--- /dev/null
+++ b/subresource-integrity/ed25519-multi-signature.js.headers
@@ -0,0 +1 @@
+Integrity: sha256-Potato ed25519-Potato ed25519-dY4xEJDd1AMMbFNIhAMzJO6uhp6gZJOhchjJXDB8yY67rYrF4QUmUWS9gkvdY0Cxo8Rnb2kIdoUiigodoatKDQ== ed25519-PotatoPotato
diff --git a/subresource-integrity/ed25519-multi-signature2.js b/subresource-integrity/ed25519-multi-signature2.js
new file mode 100644
index 0000000..3a8db87
--- /dev/null
+++ b/subresource-integrity/ed25519-multi-signature2.js
@@ -0,0 +1 @@
+ed25519_signature=true;
diff --git a/subresource-integrity/ed25519-multi-signature2.js.headers b/subresource-integrity/ed25519-multi-signature2.js.headers
new file mode 100644
index 0000000..00f88d9
--- /dev/null
+++ b/subresource-integrity/ed25519-multi-signature2.js.headers
@@ -0,0 +1 @@
+Integrity: sha256-Potato, ed25519-Potato ed25519-dY4xEJDd1AMMbFNIhAMzJO6uhp6gZJOhchjJXDB8yY67rYrF4QUmUWS9gkvdY0Cxo8Rnb2kIdoUiigodoatKDQ== ,ed25519-PotatoPotato
diff --git a/subresource-integrity/ed25519-no-signature.js b/subresource-integrity/ed25519-no-signature.js
new file mode 100644
index 0000000..2997560
--- /dev/null
+++ b/subresource-integrity/ed25519-no-signature.js
@@ -0,0 +1 @@
+ed25519_no_signature=true;
diff --git a/subresource-integrity/ed25519-signature.js b/subresource-integrity/ed25519-signature.js
new file mode 100644
index 0000000..3a8db87
--- /dev/null
+++ b/subresource-integrity/ed25519-signature.js
@@ -0,0 +1 @@
+ed25519_signature=true;
diff --git a/subresource-integrity/ed25519-signature.js.headers b/subresource-integrity/ed25519-signature.js.headers
new file mode 100644
index 0000000..47d4f9e
--- /dev/null
+++ b/subresource-integrity/ed25519-signature.js.headers
@@ -0,0 +1 @@
+Integrity: ed25519-dY4xEJDd1AMMbFNIhAMzJO6uhp6gZJOhchjJXDB8yY67rYrF4QUmUWS9gkvdY0Cxo8Rnb2kIdoUiigodoatKDQ==
diff --git a/subresource-integrity/ed25519-signature2.js b/subresource-integrity/ed25519-signature2.js
new file mode 100644
index 0000000..3a8db87
--- /dev/null
+++ b/subresource-integrity/ed25519-signature2.js
@@ -0,0 +1 @@
+ed25519_signature=true;
diff --git a/subresource-integrity/ed25519-signature2.js.headers b/subresource-integrity/ed25519-signature2.js.headers
new file mode 100644
index 0000000..4338679
--- /dev/null
+++ b/subresource-integrity/ed25519-signature2.js.headers
@@ -0,0 +1 @@
+Integrity: ed25519-jMATgofD8LM8FWYjBhryikPzo9bUJOgBlJLOS0su1vjMrVmemh5AqPWIGxroEOuyjHj/TH2jsyy4nh6Ti8iECw==
diff --git a/subresource-integrity/ed25519-style-multi-signature-headers.css b/subresource-integrity/ed25519-style-multi-signature-headers.css
new file mode 100644
index 0000000..3cde4df
--- /dev/null
+++ b/subresource-integrity/ed25519-style-multi-signature-headers.css
@@ -0,0 +1 @@
+.testdiv{ background-color: yellow }
diff --git a/subresource-integrity/ed25519-style-multi-signature-headers.css.headers b/subresource-integrity/ed25519-style-multi-signature-headers.css.headers
new file mode 100644
index 0000000..3e1d2b6
--- /dev/null
+++ b/subresource-integrity/ed25519-style-multi-signature-headers.css.headers
@@ -0,0 +1,4 @@
+Integrity: sha256-Potato
+Integrity: ed25519-Potato
+Integrity: ed25519-k+0f30qLFYl2l2/jK7VgDo6YoWyzWoyGKGgmXxxGUUkaQbvj/n0ABXQqRbHQr+EMXOaJU206t1SjkbSSBPN5CQ==
+Integrity: ed25519-PotatoPotato
diff --git a/subresource-integrity/ed25519-style-multi-signature.css b/subresource-integrity/ed25519-style-multi-signature.css
new file mode 100644
index 0000000..3cde4df
--- /dev/null
+++ b/subresource-integrity/ed25519-style-multi-signature.css
@@ -0,0 +1 @@
+.testdiv{ background-color: yellow }
diff --git a/subresource-integrity/ed25519-style-multi-signature.css.headers b/subresource-integrity/ed25519-style-multi-signature.css.headers
new file mode 100644
index 0000000..e951492
--- /dev/null
+++ b/subresource-integrity/ed25519-style-multi-signature.css.headers
@@ -0,0 +1 @@
+Integrity: sha256-Potato ed25519-Potato ed25519-k+0f30qLFYl2l2/jK7VgDo6YoWyzWoyGKGgmXxxGUUkaQbvj/n0ABXQqRbHQr+EMXOaJU206t1SjkbSSBPN5CQ== ed25519-PotatoPotato
diff --git a/subresource-integrity/ed25519-style-multi-signature2.css b/subresource-integrity/ed25519-style-multi-signature2.css
new file mode 100644
index 0000000..3cde4df
--- /dev/null
+++ b/subresource-integrity/ed25519-style-multi-signature2.css
@@ -0,0 +1 @@
+.testdiv{ background-color: yellow }
diff --git a/subresource-integrity/ed25519-style-multi-signature2.css.headers b/subresource-integrity/ed25519-style-multi-signature2.css.headers
new file mode 100644
index 0000000..1f1e463
--- /dev/null
+++ b/subresource-integrity/ed25519-style-multi-signature2.css.headers
@@ -0,0 +1 @@
+Integrity: sha256-Potato, ed25519-Potato ed25519-k+0f30qLFYl2l2/jK7VgDo6YoWyzWoyGKGgmXxxGUUkaQbvj/n0ABXQqRbHQr+EMXOaJU206t1SjkbSSBPN5CQ== ,ed25519-PotatoPotato
diff --git a/subresource-integrity/ed25519-style-no-signature.css b/subresource-integrity/ed25519-style-no-signature.css
new file mode 100644
index 0000000..3cde4df
--- /dev/null
+++ b/subresource-integrity/ed25519-style-no-signature.css
@@ -0,0 +1 @@
+.testdiv{ background-color: yellow }
diff --git a/subresource-integrity/ed25519-style-wrong-signature.css b/subresource-integrity/ed25519-style-wrong-signature.css
new file mode 100644
index 0000000..3cde4df
--- /dev/null
+++ b/subresource-integrity/ed25519-style-wrong-signature.css
@@ -0,0 +1 @@
+.testdiv{ background-color: yellow }
diff --git a/subresource-integrity/ed25519-style-wrong-signature.css.headers b/subresource-integrity/ed25519-style-wrong-signature.css.headers
new file mode 100644
index 0000000..47d4f9e
--- /dev/null
+++ b/subresource-integrity/ed25519-style-wrong-signature.css.headers
@@ -0,0 +1 @@
+Integrity: ed25519-dY4xEJDd1AMMbFNIhAMzJO6uhp6gZJOhchjJXDB8yY67rYrF4QUmUWS9gkvdY0Cxo8Rnb2kIdoUiigodoatKDQ==
diff --git a/subresource-integrity/ed25519-style.css b/subresource-integrity/ed25519-style.css
new file mode 100644
index 0000000..3cde4df
--- /dev/null
+++ b/subresource-integrity/ed25519-style.css
@@ -0,0 +1 @@
+.testdiv{ background-color: yellow }
diff --git a/subresource-integrity/ed25519-style.css.headers b/subresource-integrity/ed25519-style.css.headers
new file mode 100644
index 0000000..de14163
--- /dev/null
+++ b/subresource-integrity/ed25519-style.css.headers
@@ -0,0 +1,2 @@
+Integrity: ed25519-k+0f30qLFYl2l2/jK7VgDo6YoWyzWoyGKGgmXxxGUUkaQbvj/n0ABXQqRbHQr+EMXOaJU206t1SjkbSSBPN5CQ==
+
diff --git a/subresource-integrity/subresource-css-ed25519.tentative.html b/subresource-integrity/subresource-css-ed25519.tentative.html
new file mode 100644
index 0000000..7604acb
--- /dev/null
+++ b/subresource-integrity/subresource-css-ed25519.tentative.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Subresource Integrity</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/sriharness.js"></script>
+
+<div id="log"></div>
+
+<div id="container"></div>
+<script>
+ var public_key = "otDax00eEy6QTMK61lfzrHgZDsXw++rdJkYi02N6X0c="
+ var style_tests = [];
+
+ new SRIStyleTest(
+ style_tests,
+ true,
+ "Passes, with correct key + signature.",
+ {
+ href: "ed25519-style.css?1",
+ integrity: "ed25519-" + public_key
+ }
+ );
+ new SRIStyleTest(
+ style_tests,
+ false,
+ "Fails, because the key is malformed.",
+ {
+ href: "ed25519-style.css?2",
+ integrity: "ed25519-PotatoPotatoPotato"
+ }
+ );
+ new SRIStyleTest(
+ style_tests,
+ false,
+ "Fails because of wrong key.",
+ {
+ href: "ed25519-style.css?3",
+ integrity: "ed25519-PotatoPotatoPotatoPotatoPotatoAvocadoPotato="
+
+ }
+ );
+ new SRIStyleTest(
+ style_tests,
+ false,
+ "Fails, because of missing key.",
+ {
+ href: "ed25519-style-no-signature.css",
+ integrity: "ed25519-" + public_key
+ }
+ );
+ new SRIStyleTest(
+ style_tests,
+ false,
+ "Fails, because of wrong key.",
+ {
+ href: "ed25519-style-wrong-signature.css",
+ integrity: "ed25519-" + public_key
+ }
+ );
+ new SRIStyleTest(
+ style_tests,
+ true,
+ "Passes, because the first of two keys passes.",
+ {
+ href: "ed25519-style.css?1",
+ integrity: "ed25519-" + public_key +
+ " ed25519-PotatoPotatoPotatoPotatoPotatoAvocadoPotato="
+ }
+ );
+ new SRIStyleTest(
+ style_tests,
+ true,
+ "Passes, because the second of two keys passes.",
+ {
+ href: "ed25519-style.css?1",
+ integrity: "ed25519-PotatoPotatoPotatoPotatoPotatoAvocadoPotato= " +
+ "ed25519-" + public_key
+ }
+ );
+ new SRIStyleTest(
+ style_tests,
+ true,
+ "Passes, because at least one signature matches.",
+ {
+ href: "ed25519-style-multi-signature.css",
+ integrity: "ed25519-" + public_key
+ }
+ );
+ new SRIStyleTest(
+ style_tests,
+ true,
+ "Passes (as above), with commas between values.",
+ {
+ href: "ed25519-style-multi-signature2.css",
+ integrity: "ed25519-" + public_key
+ }
+ );
+ new SRIStyleTest(
+ style_tests,
+ true,
+ "Passes (as above), with multiple headers.",
+ {
+ href: "ed25519-style-multi-signature-headers.css",
+ integrity: "ed25519-" + public_key
+ }
+ );
+ new SRIStyleTest(
+ style_tests,
+ true,
+ "Passes, with multiple signatures + multiple keys.",
+ {
+ href: "ed25519-style-multi-signature.css?2",
+ integrity: "ed25519-PotatoPotatoPotatoPotatoPotatoAvocadoPotato= " +
+ "ed25519-" + public_key
+ }
+ );
+
+ // Run all style_tests in sequence.
+ function execute_next_style_test() {
+ if (style_tests.length > 0)
+ style_tests.shift().execute();
+ }
+ add_result_callback(execute_next_style_test);
+ execute_next_style_test();
+</script>
diff --git a/subresource-integrity/subresource-ed25519-with-csp.tentative.html b/subresource-integrity/subresource-ed25519-with-csp.tentative.html
new file mode 100644
index 0000000..ca80c22
--- /dev/null
+++ b/subresource-integrity/subresource-ed25519-with-csp.tentative.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta http-equiv="Content-Security-Policy"
+ content="script-src 'unsafe-inline' 'nonce-abcd' 'ed25519-qGFmwTxlocg707D1cX4w60iTwtfwbMLf8ITDyfko7s0='">
+
+<title>Subresource Integrity with Ed25519 plus Content Security Policy</title>
+<script src="/resources/testharness.js" nonce="abcd"></script>
+<script src="/resources/testharnessreport.js" nonce="abcd"></script>
+<script src="/resources/sriharness.js" nonce="abcd"></script>
+
+<div id="log"></div>
+<div id="container"></div>
+<script nonce="abcd">
+ // This needs to be the same key as in this doc's content security policy.
+ var public_key = "qGFmwTxlocg707D1cX4w60iTwtfwbMLf8ITDyfko7s0=";
+ new SRIScriptTest(
+ true,
+ "Ed25519-with-CSP, passes, valid key, valid signature.",
+ "ed25519-signature.js",
+ "ed25519-" + public_key
+ ).execute();
+
+ new SRIScriptTest(
+ false,
+ "Ed25519-with-CSP, fails, valid key, invalid signature.",
+ "ed25519-broken-signature.js",
+ "ed25519-" + public_key
+ ).execute();
+
+ // The first of these uses the nonce rather than the signature to pass CSP.
+ // That doesn't test anything useful about the Ed25519 feature, but is here
+ // to test the precondition for the next test. So if this test passes and
+ // the second one fails, then we can be sure that the 2nd test failed only
+ // because of the CSP key mismatch, as that's the only difference between
+ // the tests.
+ var key_not_in_csp = "5MVHFfs/9Ri+YSwH4FwneSFp88t1ljryPoLxdiyTKks=";
+ new SRIScriptTest(
+ true,
+ "Ed25519-with-CSP, passes, alternative key.",
+ "ed25519-signature2.js",
+ "ed25519-" + key_not_in_csp,
+ /* cross origin */ undefined,
+ /* nonce */ "abcd").execute();
+ new SRIScriptTest(
+ false,
+ "Ed25519-with-CSP, fails, valid key, valid signature, key not in CSP.",
+ "ed25519-signature2.js",
+ "ed25519-" + key_not_in_csp,
+ ).execute();
+</script>
+
+
diff --git a/subresource-integrity/subresource-ed25519.tentative.html b/subresource-integrity/subresource-ed25519.tentative.html
new file mode 100644
index 0000000..1f091eb
--- /dev/null
+++ b/subresource-integrity/subresource-ed25519.tentative.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Subresource Integrity</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/sriharness.js"></script>
+
+<div id="log"></div>
+
+<div id="container"></div>
+<script>
+ var public_key = "qGFmwTxlocg707D1cX4w60iTwtfwbMLf8ITDyfko7s0="
+ new SRIScriptTest(
+ true,
+ "Ed255519 signature, passes.",
+ "ed25519-signature.js?1",
+ "ed25519-" + public_key
+ ).execute();
+ new SRIScriptTest(
+ false,
+ "Ed255519 signature, fails because key is malformed.",
+ "ed25519-signature.js?2",
+ "ed25519-PotatoPotatoPotato"
+ ).execute();
+ new SRIScriptTest(
+ false,
+ "Ed255519 signature, fails because wrong key.",
+ "ed25519-signature.js?3",
+ "ed25519-PotatoPotatoPotatoPotatoPotatoAvocadoPotato="
+ ).execute();
+ new SRIScriptTest(
+ false,
+ "Ed255519 signature, fails because no signature in response header.",
+ "ed25519-no-signature.js",
+ "ed25519-" + public_key
+ ).execute();
+ new SRIScriptTest(
+ false,
+ "Ed255519 signature, fails because incorrect signature in response.",
+ "ed25519-broken-signature.js",
+ "ed25519-" + public_key
+ ).execute();
+ new SRIScriptTest(
+ true,
+ "Ed255519 signature, passes if any (first) of two keys passes.",
+ "ed25519-signature.js?4",
+ "ed25519-" + public_key +
+ " ed25519-PotatoPotatoPotatoPotatoPotatoAvocadoPotato="
+ ).execute();
+ new SRIScriptTest(
+ true,
+ "Ed255519 signature, passes if any (second) of two keys passes.",
+ "ed25519-signature.js?5",
+ "ed25519-PotatoPotatoPotatoPotatoPotatoAvocadoPotato= ed25519-" +
+ public_key
+ ).execute();
+ new SRIScriptTest(
+ true,
+ "Ed255519 signature, passes because at least one signature matches.",
+ "ed25519-multi-signature.js",
+ "ed25519-" + public_key
+ ).execute();
+ new SRIScriptTest(
+ true,
+ "Ed255519 signature, passes (as above), with commas between values.",
+ "ed25519-multi-signature2.js",
+ "ed25519-" + public_key
+ ).execute();
+ new SRIScriptTest(
+ true,
+ "Ed255519 signature, passes (as above), with multiple headers.",
+ "ed25519-multi-signature-headers.js",
+ "ed25519-" + public_key
+ ).execute();
+ new SRIScriptTest(
+ true,
+ "Ed255519 signature, passes, with multiple signature + multiple keys.",
+ "ed25519-multi-signature.js?2",
+ "ed25519-PotatoPotatoPotatoPotatoPotatoAvocadoPotato= ed25519-" +
+ public_key
+ ).execute();
+</script>
+
diff --git a/subresource-integrity/subresource-integrity.sub.html b/subresource-integrity/subresource-integrity.sub.html
index 89ae018..658ea6f 100644
--- a/subresource-integrity/subresource-integrity.sub.html
+++ b/subresource-integrity/subresource-integrity.sub.html
@@ -3,6 +3,7 @@
<title>Subresource Integrity</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/sriharness.js"></script>
<div id="log"></div>
@@ -46,36 +47,6 @@
+ '//' + www_host_and_port
+ '/subresource-integrity/crossorigin-ineligible-script.js';
- var SRIScriptTest = function(pass, name, src, integrityValue, crossoriginValue) {
- this.pass = pass;
- this.name = "Script: " + name;
- this.src = src;
- this.integrityValue = integrityValue;
- this.crossoriginValue = crossoriginValue;
- }
-
- SRIScriptTest.prototype.execute = function() {
- var test = async_test(this.name);
- var e = document.createElement("script");
- e.src = this.src;
- e.setAttribute("integrity", this.integrityValue);
- if(this.crossoriginValue) {
- e.setAttribute("crossorigin", this.crossoriginValue);
- }
- if(this.pass) {
- e.addEventListener("load", function() {test.done()});
- e.addEventListener("error", function() {
- test.step(function(){ assert_unreached("Good load fired error handler.") })
- });
- } else {
- e.addEventListener("load", function() {
- test.step(function() { assert_unreached("Bad load succeeded.") })
- });
- e.addEventListener("error", function() {test.done()});
- }
- document.body.appendChild(e);
- };
-
// Note that all of these style URLs have query parameters started, so any
// additional parameters should be appended starting with '&'.
var xorigin_anon_style = location.protocol
@@ -91,72 +62,6 @@
+ '//' + www_host_and_port
+ '/subresource-integrity/crossorigin-ineligible-style.css?';
- // <link> tests
- // Style tests must be done synchronously because they rely on the presence
- // and absence of global style, which can affect later tests. Thus, instead
- // of executing them one at a time, the style tests are implemented as a
- // queue that builds up a list of tests, and then executes them one at a
- // time.
- var SRIStyleTest = function(queue, pass, name, attrs, customCallback, altPassValue) {
- this.pass = pass;
- this.name = "Style: " + name;
- this.customCallback = customCallback || function () {};
- this.attrs = attrs || {};
- this.passValue = altPassValue || "rgb(255, 255, 0)";
-
- this.test = async_test(this.name);
-
- this.queue = queue;
- this.queue.push(this);
- }
-
- SRIStyleTest.prototype.execute = function() {
- var that = this;
- var container = document.getElementById("container");
- while (container.hasChildNodes()) {
- container.removeChild(container.firstChild);
- }
-
- var test = this.test;
-
- var div = document.createElement("div");
- div.className = "testdiv";
- var e = document.createElement("link");
- this.attrs.rel = this.attrs.rel || "stylesheet";
- for (var key in this.attrs) {
- if (this.attrs.hasOwnProperty(key)) {
- e.setAttribute(key, this.attrs[key]);
- }
- }
-
- if(this.pass) {
- e.addEventListener("load", function() {
- test.step(function() {
- var background = window.getComputedStyle(div, null).getPropertyValue("background-color");
- assert_equals(background, that.passValue);
- test.done();
- });
- });
- e.addEventListener("error", function() {
- test.step(function(){ assert_unreached("Good load fired error handler.") })
- });
- } else {
- e.addEventListener("load", function() {
- test.step(function() { assert_unreached("Bad load succeeded.") })
- });
- e.addEventListener("error", function() {
- test.step(function() {
- var background = window.getComputedStyle(div, null).getPropertyValue("background-color");
- assert_not_equals(background, that.passValue);
- test.done();
- });
- });
- }
- container.appendChild(div);
- container.appendChild(e);
- this.customCallback(e, container);
- };
-
var style_tests = [];
style_tests.execute = function() {
if (this.length > 0) {
diff --git a/subresource-integrity/tools/ed25519.py b/subresource-integrity/tools/ed25519.py
new file mode 100644
index 0000000..8497786
--- /dev/null
+++ b/subresource-integrity/tools/ed25519.py
@@ -0,0 +1,109 @@
+# The original version of this file was downloaded from
+# http://ed25519.cr.yp.to/software.html, and came with the following copyright
+# statement:
+# The Ed25519 software is in the public domain.
+
+import hashlib
+
+b = 256
+q = 2**255 - 19
+l = 2**252 + 27742317777372353535851937790883648493
+
+def H(m):
+ return hashlib.sha512(m).digest()
+
+def expmod(b,e,m):
+ if e == 0: return 1
+ t = expmod(b,e/2,m)**2 % m
+ if e & 1: t = (t*b) % m
+ return t
+
+def inv(x):
+ return expmod(x,q-2,q)
+
+d = -121665 * inv(121666)
+I = expmod(2,(q-1)/4,q)
+
+def xrecover(y):
+ xx = (y*y-1) * inv(d*y*y+1)
+ x = expmod(xx,(q+3)/8,q)
+ if (x*x - xx) % q != 0: x = (x*I) % q
+ if x % 2 != 0: x = q-x
+ return x
+
+By = 4 * inv(5)
+Bx = xrecover(By)
+B = [Bx % q,By % q]
+
+def edwards(P,Q):
+ x1 = P[0]
+ y1 = P[1]
+ x2 = Q[0]
+ y2 = Q[1]
+ x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2)
+ y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2)
+ return [x3 % q,y3 % q]
+
+def scalarmult(P,e):
+ if e == 0: return [0,1]
+ Q = scalarmult(P,e/2)
+ Q = edwards(Q,Q)
+ if e & 1: Q = edwards(Q,P)
+ return Q
+
+def encodeint(y):
+ bits = [(y >> i) & 1 for i in range(b)]
+ return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)])
+
+def encodepoint(P):
+ x = P[0]
+ y = P[1]
+ bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1]
+ return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)])
+
+def bit(h,i):
+ return (ord(h[i/8]) >> (i%8)) & 1
+
+def publickey(sk):
+ h = H(sk)
+ a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
+ A = scalarmult(B,a)
+ return encodepoint(A)
+
+def Hint(m):
+ h = H(m)
+ return sum(2**i * bit(h,i) for i in range(2*b))
+
+def signature(m,sk,pk):
+ h = H(sk)
+ a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2))
+ r = Hint(''.join([h[i] for i in range(b/8,b/4)]) + m)
+ R = scalarmult(B,r)
+ S = (r + Hint(encodepoint(R) + pk + m) * a) % l
+ return encodepoint(R) + encodeint(S)
+
+def isoncurve(P):
+ x = P[0]
+ y = P[1]
+ return (-x*x + y*y - 1 - d*x*x*y*y) % q == 0
+
+def decodeint(s):
+ return sum(2**i * bit(s,i) for i in range(0,b))
+
+def decodepoint(s):
+ y = sum(2**i * bit(s,i) for i in range(0,b-1))
+ x = xrecover(y)
+ if x & 1 != bit(s,b-1): x = q-x
+ P = [x,y]
+ if not isoncurve(P): raise Exception("decoding point that is not on curve")
+ return P
+
+def checkvalid(s,m,pk):
+ if len(s) != b/4: raise Exception("signature length is wrong")
+ if len(pk) != b/8: raise Exception("public-key length is wrong")
+ R = decodepoint(s[0:b/8])
+ A = decodepoint(pk)
+ S = decodeint(s[b/8:b/4])
+ h = Hint(encodepoint(R) + pk + m)
+ if scalarmult(B,S) != edwards(R,scalarmult(A,h)):
+ raise Exception("signature does not pass verification")
diff --git a/subresource-integrity/tools/list_hashes.py b/subresource-integrity/tools/list_hashes.py
index 5e3830a..44fb940 100644
--- a/subresource-integrity/tools/list_hashes.py
+++ b/subresource-integrity/tools/list_hashes.py
@@ -1,6 +1,8 @@
from os import path, listdir
from hashlib import sha512, sha384, sha256, md5
from base64 import b64encode
+from random import randint
+import ed25519
import re
DIR = path.normpath(path.join(__file__, "..", ".."))
@@ -38,20 +40,38 @@
return "sha256-%s" % format_digest(sha256(content).digest())
'''
+Generate an encoded ed25519 signature.
+'''
+def ed25519_signature(private_public_key, content):
+ signature = ed25519.signature(content, *private_public_key)
+ return "ed25519-%s" % format_digest(signature)
+
+'''
+Generate private + public key pair for ed25519 signatures.
+'''
+def ed25519_key_pair():
+ secret_key = ''.join(chr(randint(0, 255)) for _ in range(0,32))
+ public_key = ed25519.publickey(secret_key)
+ return (secret_key, public_key)
+
+'''
Generate an encoded md5 digest URI.
'''
def md5_uri(content):
return "md5-%s" % format_digest(md5(content).digest())
def main():
+ ed25519_key = ed25519_key_pair()
for file in js_and_css_files():
print "Listing hash values for %s" % file
with open(file, "r") as content_file:
content = content_file.read()
- print "\tSHA512 integrity: %s" % sha512_uri(content)
- print "\tSHA384 integrity: %s" % sha384_uri(content)
- print "\tSHA256 integrity: %s" % sha256_uri(content)
- print "\tMD5 integrity: %s" % md5_uri(content)
+ print "\tSHA512 integrity: %s" % sha512_uri(content)
+ print "\tSHA384 integrity: %s" % sha384_uri(content)
+ print "\tSHA256 integrity: %s" % sha256_uri(content)
+ print "\tMD5 integrity: %s" % md5_uri(content)
+ print "\tEd25519 integrity: %s" % ed25519_signature(ed25519_key, content)
+ print "\nEd25519 public key (used above): %s" % format_digest(ed25519_key[1])
if __name__ == "__main__":
main()
diff --git a/tools/certs/cacert.pem b/tools/certs/cacert.pem
new file mode 100644
index 0000000..24a61c4
--- /dev/null
+++ b/tools/certs/cacert.pem
@@ -0,0 +1,83 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: CN=web-platform-tests
+ Validity
+ Not Before: Dec 22 12:09:15 2014 GMT
+ Not After : Dec 21 12:09:15 2024 GMT
+ Subject: CN=web-platform-tests
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c0:3e:c3:bd:b7:9e:2e:3e:ec:1f:a5:ae:9f:85:
+ 8d:c0:59:a4:40:a8:fe:66:cf:ef:7d:1f:4a:91:52:
+ e6:55:0f:e5:69:b8:44:da:62:17:c7:88:28:ad:ec:
+ 6d:b0:00:cd:a1:69:0e:e8:19:84:58:ad:e9:ce:31:
+ 50:6c:a4:82:14:91:08:5a:a3:ae:c8:49:13:19:18:
+ 7b:5b:2b:44:30:eb:bf:c7:7c:bb:d4:32:17:6a:4d:
+ eb:84:f1:65:9b:9d:21:3e:91:ae:74:75:b3:7c:b4:
+ cd:fa:98:73:d4:7c:d1:5d:1b:89:72:f7:d4:52:bd:
+ 05:a3:c1:cf:b6:58:e7:51:ec:c2:71:c7:f6:0b:00:
+ 97:58:f9:cb:c3:18:46:7b:55:0f:90:bf:da:a3:7d:
+ d6:c5:48:ea:b3:b5:a8:12:96:ac:38:65:10:b9:b1:
+ 69:cb:4e:3b:4c:c3:83:74:33:63:b4:cc:fe:65:c1:
+ ad:12:51:f1:02:72:50:49:03:ab:a6:28:20:41:15:
+ ca:77:15:d9:85:55:69:9d:31:c1:db:12:be:46:db:
+ e6:d3:8f:2f:66:2d:9b:61:08:30:57:6c:d9:4f:b1:
+ 6a:a8:e5:90:e3:e2:68:96:45:6e:3f:de:5e:13:fe:
+ 8a:bd:f1:3f:7f:26:ec:3c:1a:80:b0:a8:ec:52:c5:
+ 11:03
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:TRUE
+ X509v3 Subject Key Identifier:
+ 6A:AB:53:64:92:36:87:23:34:B3:1D:6F:85:4B:F5:DF:5A:5C:74:8F
+ X509v3 Authority Key Identifier:
+ keyid:6A:AB:53:64:92:36:87:23:34:B3:1D:6F:85:4B:F5:DF:5A:5C:74:8F
+ DirName:/CN=web-platform-tests
+ serial:01
+
+ X509v3 Key Usage:
+ Certificate Sign
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication
+ Signature Algorithm: sha256WithRSAEncryption
+ 46:af:02:04:97:2f:5b:00:11:c5:8f:c4:e1:2b:23:62:d1:61:
+ 4a:28:ed:82:82:57:d7:28:65:88:a5:61:16:20:37:02:90:16:
+ 0b:05:43:46:db:bd:3d:b4:4d:1c:6e:85:ff:5d:dc:0f:a4:a4:
+ 29:98:24:ae:39:ab:e4:97:a9:10:bc:a5:b9:4b:c1:2e:5a:ce:
+ 89:32:00:f1:fc:94:35:a6:71:c8:9c:d9:05:43:44:6c:68:62:
+ ae:b1:71:20:17:5f:c4:fb:ae:05:e6:26:e5:41:88:cc:db:51:
+ 55:ed:85:a0:c9:e5:68:65:a7:fa:7a:db:8f:81:61:60:50:0b:
+ 16:b0:10:49:19:bb:70:0e:37:09:03:20:e8:a2:b9:e5:eb:c2:
+ 6a:7b:4f:60:cd:fb:22:0b:27:c6:0d:2d:e2:32:cc:28:de:c6:
+ e2:14:6a:ad:3f:c4:6e:78:9d:71:24:9b:56:c4:54:28:e9:ec:
+ 09:6e:34:cc:6d:5c:bc:e6:68:96:44:ff:62:d0:54:a0:a4:37:
+ d8:f7:9f:bc:bb:dc:ad:2c:49:e2:64:b9:4d:aa:e4:22:e6:df:
+ 3a:17:23:13:c1:aa:0e:94:27:46:5d:11:b9:0b:dc:3d:cf:93:
+ 20:eb:18:56:c5:ac:e3:92:eb:55:d3:cb:ce:e4:9c:21:85:d6:
+ 21:93:92:4f
+-----BEGIN CERTIFICATE-----
+MIIDTzCCAjegAwIBAgIBATANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJ3ZWIt
+cGxhdGZvcm0tdGVzdHMwHhcNMTQxMjIyMTIwOTE1WhcNMjQxMjIxMTIwOTE1WjAd
+MRswGQYDVQQDDBJ3ZWItcGxhdGZvcm0tdGVzdHMwggEiMA0GCSqGSIb3DQEBAQUA
+A4IBDwAwggEKAoIBAQDAPsO9t54uPuwfpa6fhY3AWaRAqP5mz+99H0qRUuZVD+Vp
+uETaYhfHiCit7G2wAM2haQ7oGYRYrenOMVBspIIUkQhao67ISRMZGHtbK0Qw67/H
+fLvUMhdqTeuE8WWbnSE+ka50dbN8tM36mHPUfNFdG4ly99RSvQWjwc+2WOdR7MJx
+x/YLAJdY+cvDGEZ7VQ+Qv9qjfdbFSOqztagSlqw4ZRC5sWnLTjtMw4N0M2O0zP5l
+wa0SUfECclBJA6umKCBBFcp3FdmFVWmdMcHbEr5G2+bTjy9mLZthCDBXbNlPsWqo
+5ZDj4miWRW4/3l4T/oq98T9/Juw8GoCwqOxSxREDAgMBAAGjgZkwgZYwDAYDVR0T
+BAUwAwEB/zAdBgNVHQ4EFgQUaqtTZJI2hyM0sx1vhUv131pcdI8wRQYDVR0jBD4w
+PIAUaqtTZJI2hyM0sx1vhUv131pcdI+hIaQfMB0xGzAZBgNVBAMMEndlYi1wbGF0
+Zm9ybS10ZXN0c4IBATALBgNVHQ8EBAMCAgQwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
+DQYJKoZIhvcNAQELBQADggEBAEavAgSXL1sAEcWPxOErI2LRYUoo7YKCV9coZYil
+YRYgNwKQFgsFQ0bbvT20TRxuhf9d3A+kpCmYJK45q+SXqRC8pblLwS5azokyAPH8
+lDWmccic2QVDRGxoYq6xcSAXX8T7rgXmJuVBiMzbUVXthaDJ5Whlp/p624+BYWBQ
+CxawEEkZu3AONwkDIOiiueXrwmp7T2DN+yILJ8YNLeIyzCjexuIUaq0/xG54nXEk
+m1bEVCjp7AluNMxtXLzmaJZE/2LQVKCkN9j3n7y73K0sSeJkuU2q5CLm3zoXIxPB
+qg6UJ0ZdEbkL3D3PkyDrGFbFrOOS61XTy87knCGF1iGTkk8=
+-----END CERTIFICATE-----
diff --git a/tools/certs/cakey.pem b/tools/certs/cakey.pem
new file mode 100644
index 0000000..58b5ff7
--- /dev/null
+++ b/tools/certs/cakey.pem
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIb9ES7h6YGBcCAggA
+MBQGCCqGSIb3DQMHBAi2ZVLgq1XvagSCBMjAmGEVvjwXvF00veuQmJsmcjVHB/qP
+VqSXjCQ94orZb89UfFnPO9zXdvLWxrwb5WP6bbQv+Sh4htExXCD5XZi1AzWNCybe
+da0vvQGgjdzUh2fCrG4K7J0w20lrYgw3HVSj/WtmdbdZFdoX+BgXrxcqkE3M5opZ
+3UD3yIQeXSxUkh3iv6zzZaWujxjDI2JpwxRmMVbrr8OeBrKJsqB2DnKmq+emmvEF
+iXTN3Ww/Aj6GIqfPZ8jpVdwcVN5QpeHAh7b2lszt7GEOGcBhutPq4Aqy8PIiDR80
+sUYI7V8OXm+Y45DnfkvsogZEifOiUrQ2U+aGDu+Zt88661wVzjq+voJlz8EaIPCE
+B/NS2SgNqI2/DrjEEecn6hjgHWIUBwOfeNoSi1Tri6KZFyxG26LE/V8Cd50yodx9
+pBgFxdCbmYLeRcVeXW2bu0ZMjPddRlR5MHfrkM5ZAze7nRxoiyWnB/U8pPf+bQvx
+K4P9KcwCOeHigkaCYZKq7nmZyEy4km89zIugT/YWhMWyVwylTpagaiiJwYLjug8n
+CbFZWAkORBIl2g/YCuTBUJtC2IWX8kw+nYVwqBszpZyC6nbha2UmhQDfMAowQA5v
+n1LnV8I6f7u6HidbB8WX2UZoh03A4beCBz+dq2VaUquLTL4KQTIz+6rw7nEysrnH
+TIb8SlwsYAlzzwyyM9dSWt7iQeNjmH7zL0MozMs3LKHIrsWi7ZZh8BUYnT2vKdNV
+2ZLOMcR0tYVmVZ8uYkR9kny/fbZcKN54xScohA2UX261W+sWiEgN+RaBsQ79pFgi
+vYldfjaGNSvftXa590xn2tlS6/suB5MxiW5g3PuBg5XtVZ95l0f1n376Xh41sJv8
+YHrCtFHOlSpDJULGiXVh/wXBmS7qJ8DhnUUG699EdlsFf6Qg22WB3AZRvEJdYC4z
+P8W+jZ15NTDbHg3Hv7/CFYVzbXv2w0jkiqQgDF/wc6t/EdLD+2hzcN+nJGjtxZbn
+xjbXcg98CUMU+dc/aD4N45K9e9rPg3+iZLwvsRvwx+MszmgxxPv05pNyRO7RVk8r
+gkyyp9/CJFme+4nFKUc0dUy2yNXZtklTX0XKm/YNKin6uUMlIArIa54Cfvt9QslV
+iD+SxU1ZHmzwKT82+5ZeIRLNWvFV/9E4nD+BTagK2Fdwnsu1S2k7ItD9lK/cBPGS
+0tz1HWv4Auj3wMPZklp3SQluOl6eAIVqqI9GaX/d42DctBQWLTa27YibWyNIcw7o
+3N8GDREMawTBdDRwlZ3oT+yiGLX1c8ds2o0/4IcJlOkDoxXErmdlZo9oVe6z4R7g
+62yR53atVTLoUnAjxHXx0bJiyayv9Y3wjOEvuhuqdd9F+HOhTtAHr/BJQNhEk+z8
+531CZTJjb1p11PbOtHGV2IeB0S82mxkkXRykEXOb89ZpDHNRiMinThRkoCmuRI9r
+dTiES9B02yMPxJ3sLQyDxCoS5mwfcAqKTeK+yCvTvBy+t5tw63DbWlMp/7Ahy65K
+rWMHdwqwfoB+ZYw5sYZdPvuBVAT01I2JbOqX36RacQultFns2OinxOJHa1HjtXyS
+cPVEkMa7ci3Ym9j5RQNLVsgJe7YK9HixX5HjQFAowAH2pXZ5pKJIJYxPIUKtZlsz
+qbM=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tools/certs/web-platform.test.key b/tools/certs/web-platform.test.key
new file mode 100644
index 0000000..a49f422
--- /dev/null
+++ b/tools/certs/web-platform.test.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCzhNaLAVkYhdHc
+Mt8495CFGz6lXoE+L/w6X3937yO7OognD74lRs1jfcuV2KVQENKi0reX0Q1s+/kF
+6G+oS72VZ557lFipbZP94BLFzbSKZFIxXw7jiYRx2pjdS+wCJaV9Nf5j2rOs7KVG
+Dw1kI1xt8+zMKGMjwEua7I/B7rGiPnJNcLUJweu0EFU8i+oblH5LdOb0n0+mRTC1
+8Li00VlQZQqGU+pMn570WGwx9Rc6b1eLy1/wKAtFko0wIEn/UuYsyxia1+buPk80
+NRUTxQLaxV+++1vOjb+1NXY8fOacOyaHTY2A5hbGJ/JQSbZydENJSUQ4u3hDI+4W
+Ptli5qXXAgMBAAECggEBAIcwDQSnIjo2ZECHytQykpG6X6XXEksLhc1Lp0lhPC49
+uNR5pX6a4AcBb3PLr0opMQZO2tUoKA0ff3t0e8loKD+/xXhY0Z/dlioEOP7elwv0
+2nS1mhe9spCuxpk4GGXRhdtR8t2tj8s0do3YvgPgITXoEDX6YBZHNGhZpzSrFPgQ
+/c3eGCVmzWYuLFfdj5OPQ9bwTaY4JSvDLZT0/WTgiica7VySwfz3HP1fFqNykTiK
+ACQREvtxfk5Ym2nT6oni7CM2zOEJL9SXicXI5HO4bERH0ZYh//F3g6mwGiFXUJPd
+NKgaTM1oT9kRGkUaEYsRWrddwR8d5mXLvBuTJbgIsSECgYEA1+2uJSYRW1OqbhYP
+ms59YQHSs3VjpJpnCV2zNa2Wixs57KS2cOH7B6KrQCogJFLtgCDVLtyoErfVkD7E
+FivTgYr1pVCRppJddQzXik31uOINOBVffr7/09g3GcRN+ubHPZPq3K+dD6gHa3Aj
+0nH1EjEEV0QpSTQFn87OF2mc9wcCgYEA1NVqMbbzd+9Xft5FXuSbX6E+S02dOGat
+SgpnkTM80rjqa6eHdQzqk3JqyteHPgdi1vdYRlSPOj/X+6tySY0Ej9sRnYOfddA2
+kpiDiVkmiqVolyJPY69Utj+E3TzJ1vhCQuYknJmB7zP9tDcTxMeq0l/NaWvGshEK
+yC4UTQog1rECgYASOFILfGzWgfbNlzr12xqlRtwanHst9oFfPvLSQrWDQ2bd2wAy
+Aj+GY2mD3oobxouX1i1m6OOdwLlalJFDNauBMNKNgoDnx03vhIfjebSURy7KXrNS
+JJe9rm7n07KoyzRgs8yLlp3wJkOKA0pihY8iW9R78JpzPNqEo5SsURMXnQKBgBlV
+gfuC9H4tPjP6zzUZbyk1701VYsaI6k2q6WMOP0ox+q1v1p7nN7DvaKjWeOG4TVqb
+PKW6gQYE/XeWk9cPcyCQigs+1KdYbnaKsvWRaBYO1GFREzQhdarv6qfPCZOOH40J
+Cgid+Sp4/NULzU2aGspJ3xCSZKdjge4MFhyJfRkxAoGBAJlwqY4nue0MBLGNpqcs
+WwDtSasHvegKAcxGBKL5oWPbLBk7hk+hdqc8f6YqCkCNqv/ooBspL15ESItL+6yT
+zt0YkK4oH9tmLDb+rvqZ7ZdXbWSwKITMoCyyHUtT6OKt/RtA0Vdy9LPnP27oSO/C
+dk8Qf7KgKZLWo0ZNkvw38tEC
+-----END PRIVATE KEY-----
diff --git a/tools/certs/web-platform.test.pem b/tools/certs/web-platform.test.pem
new file mode 100644
index 0000000..965e887
--- /dev/null
+++ b/tools/certs/web-platform.test.pem
@@ -0,0 +1,86 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 2 (0x2)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: CN=web-platform-tests
+ Validity
+ Not Before: Dec 22 12:09:16 2014 GMT
+ Not After : Dec 21 12:09:16 2024 GMT
+ Subject: CN=web-platform.test
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:b3:84:d6:8b:01:59:18:85:d1:dc:32:df:38:f7:
+ 90:85:1b:3e:a5:5e:81:3e:2f:fc:3a:5f:7f:77:ef:
+ 23:bb:3a:88:27:0f:be:25:46:cd:63:7d:cb:95:d8:
+ a5:50:10:d2:a2:d2:b7:97:d1:0d:6c:fb:f9:05:e8:
+ 6f:a8:4b:bd:95:67:9e:7b:94:58:a9:6d:93:fd:e0:
+ 12:c5:cd:b4:8a:64:52:31:5f:0e:e3:89:84:71:da:
+ 98:dd:4b:ec:02:25:a5:7d:35:fe:63:da:b3:ac:ec:
+ a5:46:0f:0d:64:23:5c:6d:f3:ec:cc:28:63:23:c0:
+ 4b:9a:ec:8f:c1:ee:b1:a2:3e:72:4d:70:b5:09:c1:
+ eb:b4:10:55:3c:8b:ea:1b:94:7e:4b:74:e6:f4:9f:
+ 4f:a6:45:30:b5:f0:b8:b4:d1:59:50:65:0a:86:53:
+ ea:4c:9f:9e:f4:58:6c:31:f5:17:3a:6f:57:8b:cb:
+ 5f:f0:28:0b:45:92:8d:30:20:49:ff:52:e6:2c:cb:
+ 18:9a:d7:e6:ee:3e:4f:34:35:15:13:c5:02:da:c5:
+ 5f:be:fb:5b:ce:8d:bf:b5:35:76:3c:7c:e6:9c:3b:
+ 26:87:4d:8d:80:e6:16:c6:27:f2:50:49:b6:72:74:
+ 43:49:49:44:38:bb:78:43:23:ee:16:3e:d9:62:e6:
+ a5:d7
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 2D:98:A3:99:39:1C:FE:E9:9A:6D:17:94:D2:3A:96:EE:C8:9E:04:22
+ X509v3 Authority Key Identifier:
+ keyid:6A:AB:53:64:92:36:87:23:34:B3:1D:6F:85:4B:F5:DF:5A:5C:74:8F
+
+ X509v3 Key Usage:
+ Digital Signature, Non Repudiation, Key Encipherment
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication
+ X509v3 Subject Alternative Name:
+ DNS:web-platform.test, DNS:www.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--lve-6lad.web-platform.test, DNS:www2.web-platform.test, DNS:www1.web-platform.test
+ Signature Algorithm: sha256WithRSAEncryption
+ 33:db:f7:f0:f6:92:16:4f:2d:42:bc:b8:aa:e6:ab:5e:f9:b9:
+ b0:48:ae:b5:8d:cc:02:7b:e9:6f:4e:75:f7:17:a0:5e:7b:87:
+ 06:49:48:83:c5:bb:ca:95:07:37:0e:5d:e3:97:de:9e:0c:a4:
+ 82:30:11:81:49:5d:50:29:72:92:a5:ca:17:b1:7c:f1:32:11:
+ 17:57:e6:59:c1:ac:e3:3b:26:d2:94:97:50:6a:b9:54:88:84:
+ 9b:6f:b1:06:f5:80:04:22:10:14:b1:f5:97:25:fc:66:d6:69:
+ a3:36:08:85:23:ff:8e:3c:2b:e0:6d:e7:61:f1:00:8f:61:3d:
+ b0:87:ad:72:21:f6:f0:cc:4f:c9:20:bf:83:11:0f:21:f4:b8:
+ c0:dd:9c:51:d7:bb:27:32:ec:ab:a4:62:14:28:32:da:f2:87:
+ 80:68:9c:ea:ac:eb:f5:7f:f5:de:f4:c0:39:91:c8:76:a4:ee:
+ d0:a8:50:db:c1:4b:f9:c4:3d:d9:e8:8e:b6:3f:c0:96:79:12:
+ d8:fa:4d:0a:b3:36:76:aa:4e:b2:82:2f:a2:d4:0d:db:fd:64:
+ 77:6f:6e:e9:94:7f:0f:c8:3a:3c:96:3d:cd:4d:6c:ba:66:95:
+ f7:b4:9d:a4:94:9f:97:b3:9a:0d:dc:18:8c:11:0b:56:65:8e:
+ 46:4c:e6:5e
+-----BEGIN CERTIFICATE-----
+MIID2jCCAsKgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAdMRswGQYDVQQDDBJ3ZWIt
+cGxhdGZvcm0tdGVzdHMwHhcNMTQxMjIyMTIwOTE2WhcNMjQxMjIxMTIwOTE2WjAc
+MRowGAYDVQQDExF3ZWItcGxhdGZvcm0udGVzdDCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBALOE1osBWRiF0dwy3zj3kIUbPqVegT4v/Dpff3fvI7s6iCcP
+viVGzWN9y5XYpVAQ0qLSt5fRDWz7+QXob6hLvZVnnnuUWKltk/3gEsXNtIpkUjFf
+DuOJhHHamN1L7AIlpX01/mPas6zspUYPDWQjXG3z7MwoYyPAS5rsj8HusaI+ck1w
+tQnB67QQVTyL6huUfkt05vSfT6ZFMLXwuLTRWVBlCoZT6kyfnvRYbDH1FzpvV4vL
+X/AoC0WSjTAgSf9S5izLGJrX5u4+TzQ1FRPFAtrFX777W86Nv7U1djx85pw7JodN
+jYDmFsYn8lBJtnJ0Q0lJRDi7eEMj7hY+2WLmpdcCAwEAAaOCASQwggEgMAkGA1Ud
+EwQCMAAwHQYDVR0OBBYEFC2Yo5k5HP7pmm0XlNI6lu7IngQiMB8GA1UdIwQYMBaA
+FGqrU2SSNocjNLMdb4VL9d9aXHSPMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggr
+BgEFBQcDATCBsAYDVR0RBIGoMIGlghF3ZWItcGxhdGZvcm0udGVzdIIVd3d3Lndl
+Yi1wbGF0Zm9ybS50ZXN0gil4bi0tbjhqNmRzNTNsd3drcnFodjI4YS53ZWItcGxh
+dGZvcm0udGVzdIIeeG4tLWx2ZS02bGFkLndlYi1wbGF0Zm9ybS50ZXN0ghZ3d3cy
+LndlYi1wbGF0Zm9ybS50ZXN0ghZ3d3cxLndlYi1wbGF0Zm9ybS50ZXN0MA0GCSqG
+SIb3DQEBCwUAA4IBAQAz2/fw9pIWTy1CvLiq5qte+bmwSK61jcwCe+lvTnX3F6Be
+e4cGSUiDxbvKlQc3Dl3jl96eDKSCMBGBSV1QKXKSpcoXsXzxMhEXV+ZZwazjOybS
+lJdQarlUiISbb7EG9YAEIhAUsfWXJfxm1mmjNgiFI/+OPCvgbedh8QCPYT2wh61y
+IfbwzE/JIL+DEQ8h9LjA3ZxR17snMuyrpGIUKDLa8oeAaJzqrOv1f/Xe9MA5kch2
+pO7QqFDbwUv5xD3Z6I62P8CWeRLY+k0KszZ2qk6ygi+i1A3b/WR3b27plH8PyDo8
+lj3NTWy6ZpX3tJ2klJ+Xs5oN3BiMEQtWZY5GTOZe
+-----END CERTIFICATE-----
diff --git a/tools/webdriver/webdriver/client.py b/tools/webdriver/webdriver/client.py
index f40d487..4534ab3 100644
--- a/tools/webdriver/webdriver/client.py
+++ b/tools/webdriver/webdriver/client.py
@@ -1,8 +1,12 @@
+import json
import urlparse
import error
import transport
+from mozlog import get_default_logger
+
+logger = get_default_logger()
element_key = "element-6066-11e4-a52e-4f735466cecf"
@@ -245,26 +249,30 @@
@property
@command
def size(self):
+ """Gets the window size as a tuple of `(width, height)`."""
rect = self.rect
return (rect["width"], rect["height"])
@size.setter
@command
- def size(self, data):
- width, height = data
+ def size(self, new_size):
+ """Set window size by passing a tuple of `(width, height)`."""
+ width, height = new_size
body = {"width": width, "height": height}
self.session.send_session_command("POST", "window/rect", body)
@property
@command
def position(self):
+ """Gets the window position as a tuple of `(x, y)`."""
rect = self.rect
return (rect["x"], rect["y"])
@position.setter
@command
- def position(self, data):
- data = x, y
+ def position(self, new_position):
+ """Set window position by passing a tuple of `(x, y)`."""
+ x, y = new_position
body = {"x": x, "y": y}
self.session.send_session_command("POST", "window/rect", body)
@@ -425,7 +433,12 @@
an error.
"""
response = self.transport.send(method, url, body)
- value = response.body["value"]
+
+ if "value" in response.body:
+ value = response.body["value"]
+ else:
+ raise error.UnknownErrorException("No 'value' key in response body:\n%s" %
+ json.dumps(response.body))
if response.status != 200:
cls = error.get(value.get("error"))
diff --git a/tools/webdriver/webdriver/transport.py b/tools/webdriver/webdriver/transport.py
index a241264..206fd3c 100644
--- a/tools/webdriver/webdriver/transport.py
+++ b/tools/webdriver/webdriver/transport.py
@@ -2,6 +2,8 @@
import json
import urlparse
+import error
+
class Response(object):
"""Describes an HTTP response received from a remote en"Describes an HTTP
response received from a remote end whose body has been read and parsed as
@@ -27,21 +29,21 @@
# > "application/json; charset=utf-8"
# > "cache-control"
# > "no-cache"
- assert http_response.getheader("Content-Type") == "application/json; charset=utf-8"
- assert http_response.getheader("Cache-Control") == "no-cache"
if body:
- body = json.loads(body)
-
- # SpecID: dfn-send-a-response
- #
- # > 4. If data is not null, let response's body be a JSON Object
- # with a key `value` set to the JSON Serialization of data.
- assert "value" in body
+ try:
+ body = json.loads(body)
+ except:
+ raise error.UnknownErrorException("Failed to decode body as json:\n%s" % body)
return cls(status, body)
+class ToJsonEncoder(json.JSONEncoder):
+ def default(self, obj):
+ return getattr(obj.__class__, "json", json.JSONEncoder().default)(obj)
+
+
class HTTPWireProtocol(object):
"""Transports messages (commands and responses) over the WebDriver
wire protocol.
@@ -79,7 +81,7 @@
body = {}
if isinstance(body, dict):
- body = json.dumps(body)
+ body = json.dumps(body, cls=ToJsonEncoder)
if isinstance(body, unicode):
body = body.encode("utf-8")
diff --git a/tools/wpt/browser.py b/tools/wpt/browser.py
index ebfada0..68ffb2a 100644
--- a/tools/wpt/browser.py
+++ b/tools/wpt/browser.py
@@ -301,6 +301,29 @@
raise NotImplementedError
+class InternetExplorer(Browser):
+ """Internet Explorer-specific interface.
+
+ Includes installation, webdriver installation, and wptrunner setup methods.
+ """
+
+ product = "ie"
+ requirements = "requirements_ie.txt"
+
+ def install(self, dest=None):
+ raise NotImplementedError
+
+ def find_webdriver(self):
+ return find_executable("IEDriverServer.exe")
+
+ def install_webdriver(self, dest=None):
+ """Install latest Webdriver."""
+ raise NotImplementedError
+
+ def version(self):
+ raise NotImplementedError
+
+
class Servo(Browser):
"""Servo-specific interface.
diff --git a/tools/wpt/run.py b/tools/wpt/run.py
index a7cd48d..1567a72 100644
--- a/tools/wpt/run.py
+++ b/tools/wpt/run.py
@@ -65,7 +65,17 @@
kwargs.set_if_none("metadata_root", wpt_root)
kwargs.set_if_none("manifest_update", True)
- if kwargs["ssl_type"] == "openssl":
+ if kwargs["ssl_type"] in (None, "pregenerated"):
+ cert_root = os.path.join(wpt_root, "tools", "certs")
+ if kwargs["ca_cert_path"] is None:
+ kwargs["ca_cert_path"] = os.path.join(cert_root, "cacert.pem")
+
+ if kwargs["host_key_path"] is None:
+ kwargs["host_key_path"] = os.path.join(cert_root, "web-platform.test.key")
+
+ if kwargs["host_cert_path"] is None:
+ kwargs["host_cert_path"] = os.path.join(cert_root, "web-platform.test.pem")
+ elif kwargs["ssl_type"] == "openssl":
if not find_executable(kwargs["openssl_binary"]):
if os.uname()[0] == "Windows":
raise WptrunError("""OpenSSL binary not found. If you need HTTPS tests, install OpenSSL from
@@ -254,6 +264,27 @@
kwargs["webdriver_binary"] = webdriver_binary
+class InternetExplorer(BrowserSetup):
+ name = "ie"
+ browser_cls = browser.InternetExplorer
+
+ def install(self, venv):
+ raise NotImplementedError
+
+ def setup_kwargs(self, kwargs):
+ if kwargs["webdriver_binary"] is None:
+ webdriver_binary = self.browser.find_webdriver()
+
+ if webdriver_binary is None:
+ raise WptrunError("""Unable to find WebDriver and we aren't yet clever enough to work out which
+version to download. Please go to the following URL and install the driver for Internet Explorer
+somewhere on the %PATH%:
+
+https://selenium-release.storage.googleapis.com/index.html
+""")
+ kwargs["webdriver_binary"] = webdriver_binary
+
+
class Sauce(BrowserSetup):
name = "sauce"
browser_cls = browser.Sauce
@@ -287,6 +318,7 @@
"firefox": Firefox,
"chrome": Chrome,
"edge": Edge,
+ "ie": InternetExplorer,
"servo": Servo,
"sauce": Sauce,
}
diff --git a/tools/wptrunner/requirements_ie.txt b/tools/wptrunner/requirements_ie.txt
new file mode 100644
index 0000000..a2f5442
--- /dev/null
+++ b/tools/wptrunner/requirements_ie.txt
@@ -0,0 +1,2 @@
+mozprocess >= 0.19
+selenium >= 2.41.0
diff --git a/tools/wptrunner/wptrunner/browsers/__init__.py b/tools/wptrunner/wptrunner/browsers/__init__.py
index e3606d2..c4ea971 100644
--- a/tools/wptrunner/wptrunner/browsers/__init__.py
+++ b/tools/wptrunner/wptrunner/browsers/__init__.py
@@ -25,6 +25,7 @@
product_list = ["chrome",
"edge",
"firefox",
+ "ie",
"sauce",
"servo",
"servodriver"]
diff --git a/tools/wptrunner/wptrunner/browsers/chrome.py b/tools/wptrunner/wptrunner/browsers/chrome.py
index a1bbd56..215fccf 100644
--- a/tools/wptrunner/wptrunner/browsers/chrome.py
+++ b/tools/wptrunner/wptrunner/browsers/chrome.py
@@ -3,13 +3,15 @@
from ..executors import executor_kwargs as base_executor_kwargs
from ..executors.executorselenium import (SeleniumTestharnessExecutor,
SeleniumRefTestExecutor)
+from ..executors.executorchrome import ChromeDriverWdspecExecutor
__wptrunner__ = {"product": "chrome",
"check_args": "check_args",
"browser": "ChromeBrowser",
"executor": {"testharness": "SeleniumTestharnessExecutor",
- "reftest": "SeleniumRefTestExecutor"},
+ "reftest": "SeleniumRefTestExecutor",
+ "wdspec": "ChromeDriverWdspecExecutor"},
"browser_kwargs": "browser_kwargs",
"executor_kwargs": "executor_kwargs",
"env_extras": "env_extras",
@@ -47,6 +49,8 @@
if test_type == "testharness":
capabilities["chromeOptions"]["useAutomationExtension"] = False
capabilities["chromeOptions"]["excludeSwitches"] = ["enable-automation"]
+ if test_type == "wdspec":
+ capabilities["chromeOptions"]["w3c"] = True
executor_kwargs["capabilities"] = capabilities
return executor_kwargs
diff --git a/tools/wptrunner/wptrunner/browsers/firefox.py b/tools/wptrunner/wptrunner/browsers/firefox.py
index 5ec2b70..1ed2c73 100644
--- a/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -93,9 +93,6 @@
executor_kwargs["reftest_internal"] = kwargs["reftest_internal"]
executor_kwargs["reftest_screenshot"] = kwargs["reftest_screenshot"]
if test_type == "wdspec":
- executor_kwargs["binary"] = kwargs["binary"]
- executor_kwargs["webdriver_binary"] = kwargs.get("webdriver_binary")
- executor_kwargs["webdriver_args"] = kwargs.get("webdriver_args")
fxOptions = {}
if kwargs["binary"]:
fxOptions["binary"] = kwargs["binary"]
diff --git a/tools/wptrunner/wptrunner/browsers/ie.py b/tools/wptrunner/wptrunner/browsers/ie.py
new file mode 100644
index 0000000..c981024
--- /dev/null
+++ b/tools/wptrunner/wptrunner/browsers/ie.py
@@ -0,0 +1,81 @@
+from .base import Browser, ExecutorBrowser, require_arg
+from ..webdriver_server import InternetExplorerDriverServer
+from ..executors import executor_kwargs as base_executor_kwargs
+from ..executors.executorselenium import (SeleniumTestharnessExecutor,
+ SeleniumRefTestExecutor)
+from ..executors.executorinternetexplorer import InternetExplorerDriverWdspecExecutor
+
+__wptrunner__ = {"product": "ie",
+ "check_args": "check_args",
+ "browser": "InternetExplorerBrowser",
+ "executor": {"testharness": "SeleniumTestharnessExecutor",
+ "reftest": "SeleniumRefTestExecutor",
+ "wdspec": "InternetExplorerDriverWdspecExecutor"},
+ "browser_kwargs": "browser_kwargs",
+ "executor_kwargs": "executor_kwargs",
+ "env_extras": "env_extras",
+ "env_options": "env_options"}
+
+
+def check_args(**kwargs):
+ require_arg(kwargs, "webdriver_binary")
+
+def browser_kwargs(test_type, run_info_data, **kwargs):
+ return {"webdriver_binary": kwargs["webdriver_binary"],
+ "webdriver_args": kwargs.get("webdriver_args")}
+
+def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
+ **kwargs):
+ from selenium.webdriver import DesiredCapabilities
+
+ ieOptions = {}
+ ieOptions["requireWindowFocus"] = True
+ capabilities = {}
+ capabilities["browserName"] = "internet explorer"
+ capabilities["platformName"] = "windows"
+ capabilities["se:ieOptions"] = ieOptions
+ executor_kwargs = base_executor_kwargs(test_type, server_config,
+ cache_manager, **kwargs)
+ executor_kwargs["close_after_done"] = True
+ executor_kwargs["capabilities"] = capabilities
+ return executor_kwargs
+
+def env_extras(**kwargs):
+ return []
+
+def env_options():
+ return {"host": "web-platform.test",
+ "bind_hostname": "true",
+ "supports_debugger": False}
+
+class InternetExplorerBrowser(Browser):
+ used_ports = set()
+
+ def __init__(self, logger, webdriver_binary, webdriver_args=None):
+ Browser.__init__(self, logger)
+ self.server = InterentExplorerDriverServer(self.logger,
+ binary=webdriver_binary,
+ args=webdriver_args)
+ self.webdriver_host = "localhost"
+ self.webdriver_port = self.server.port
+
+ def start(self, **kwargs):
+ self.server.start()
+
+ def stop(self, force=False):
+ self.server.stop(force=force)
+
+ def pid(self):
+ return self.server.pid
+
+ def is_alive(self):
+ # TODO(ato): This only indicates the server is alive,
+ # and doesn't say anything about whether a browser session
+ # is active.
+ return self.server.is_alive()
+
+ def cleanup(self):
+ self.stop()
+
+ def executor_browser(self):
+ return ExecutorBrowser, {"webdriver_url": self.server.url}
diff --git a/tools/wptrunner/wptrunner/executors/base.py b/tools/wptrunner/wptrunner/executors/base.py
index c92493f..27be0c5 100644
--- a/tools/wptrunner/wptrunner/executors/base.py
+++ b/tools/wptrunner/wptrunner/executors/base.py
@@ -1,7 +1,9 @@
import hashlib
-import json
+import httplib
import os
+import threading
import traceback
+import socket
import urlparse
from abc import ABCMeta, abstractmethod
@@ -9,6 +11,10 @@
here = os.path.split(__file__)[0]
+# Extra timeout to use after internal test timeout at which the harness
+# should force a timeout
+extra_timeout = 5 # seconds
+
def executor_kwargs(test_type, server_config, cache_manager, **kwargs):
timeout_multiplier = kwargs["timeout_multiplier"]
@@ -22,6 +28,11 @@
if test_type == "reftest":
executor_kwargs["screenshot_cache"] = cache_manager.dict()
+ if test_type == "wdspec":
+ executor_kwargs["binary"] = kwargs.get("binary")
+ executor_kwargs["webdriver_binary"] = kwargs.get("webdriver_binary")
+ executor_kwargs["webdriver_args"] = kwargs.get("webdriver_args")
+
return executor_kwargs
@@ -310,6 +321,51 @@
class WdspecExecutor(TestExecutor):
convert_result = pytest_result_converter
+ protocol_cls = None
+
+ def __init__(self, browser, server_config, webdriver_binary,
+ webdriver_args, timeout_multiplier=1, capabilities=None,
+ debug_info=None, **kwargs):
+ self.do_delayed_imports()
+ TestExecutor.__init__(self, browser, server_config,
+ timeout_multiplier=timeout_multiplier,
+ debug_info=debug_info)
+ self.webdriver_binary = webdriver_binary
+ self.webdriver_args = webdriver_args
+ self.timeout_multiplier = timeout_multiplier
+ self.capabilities = capabilities
+ self.protocol = self.protocol_cls(self, browser)
+
+ def is_alive(self):
+ return self.protocol.is_alive
+
+ def on_environment_change(self, new_environment):
+ pass
+
+ def do_test(self, test):
+ timeout = test.timeout * self.timeout_multiplier + extra_timeout
+
+ success, data = WdspecRun(self.do_wdspec,
+ self.protocol.session_config,
+ test.abs_path,
+ timeout).run()
+
+ if success:
+ return self.convert_result(test, data)
+
+ return (test.result_cls(*data), [])
+
+ def do_wdspec(self, session_config, path, timeout):
+ harness_result = ("OK", None)
+ subtest_results = pytestrunner.run(path,
+ self.server_config,
+ session_config,
+ timeout=timeout)
+ return (harness_result, subtest_results)
+
+ def do_delayed_imports(self):
+ global pytestrunner
+ from . import pytestrunner
class Protocol(object):
@@ -329,3 +385,95 @@
def wait(self):
pass
+
+
+class WdspecRun(object):
+ def __init__(self, func, session, path, timeout):
+ self.func = func
+ self.result = (None, None)
+ self.session = session
+ self.path = path
+ self.timeout = timeout
+ self.result_flag = threading.Event()
+
+ def run(self):
+ """Runs function in a thread and interrupts it if it exceeds the
+ given timeout. Returns (True, (Result, [SubtestResult ...])) in
+ case of success, or (False, (status, extra information)) in the
+ event of failure.
+ """
+
+ executor = threading.Thread(target=self._run)
+ executor.start()
+
+ flag = self.result_flag.wait(self.timeout)
+ if self.result[1] is None:
+ self.result = False, ("EXTERNAL-TIMEOUT", None)
+
+ return self.result
+
+ def _run(self):
+ try:
+ self.result = True, self.func(self.session, self.path, self.timeout)
+ except (socket.timeout, IOError):
+ self.result = False, ("CRASH", None)
+ except Exception as e:
+ message = getattr(e, "message")
+ if message:
+ message += "\n"
+ message += traceback.format_exc(e)
+ self.result = False, ("ERROR", message)
+ finally:
+ self.result_flag.set()
+
+
+class WebDriverProtocol(Protocol):
+ server_cls = None
+
+ def __init__(self, executor, browser):
+ Protocol.__init__(self, executor, browser)
+ self.webdriver_binary = executor.webdriver_binary
+ self.webdriver_args = executor.webdriver_args
+ self.capabilities = self.executor.capabilities
+ self.session_config = None
+ self.server = None
+
+ def setup(self, runner):
+ """Connect to browser via the HTTP server."""
+ try:
+ self.server = self.server_cls(
+ self.logger,
+ binary=self.webdriver_binary,
+ args=self.webdriver_args)
+ self.server.start(block=False)
+ self.logger.info(
+ "WebDriver HTTP server listening at %s" % self.server.url)
+ self.session_config = {"host": self.server.host,
+ "port": self.server.port,
+ "capabilities": self.capabilities}
+ except Exception:
+ self.logger.error(traceback.format_exc())
+ self.executor.runner.send_message("init_failed")
+ else:
+ self.executor.runner.send_message("init_succeeded")
+
+ def teardown(self):
+ if self.server is not None and self.server.is_alive:
+ self.server.stop()
+
+ @property
+ def is_alive(self):
+ """Test that the connection is still alive.
+
+ Because the remote communication happens over HTTP we need to
+ make an explicit request to the remote. It is allowed for
+ WebDriver spec tests to not have a WebDriver session, since this
+ may be what is tested.
+
+ An HTTP request to an invalid path that results in a 404 is
+ proof enough to us that the server is alive and kicking.
+ """
+ conn = httplib.HTTPConnection(self.server.host, self.server.port)
+ conn.request("HEAD", self.server.base_path + "invalid")
+ res = conn.getresponse()
+ return res.status == 404
diff --git a/tools/wptrunner/wptrunner/executors/executorchrome.py b/tools/wptrunner/wptrunner/executors/executorchrome.py
new file mode 100644
index 0000000..3e0400d
--- /dev/null
+++ b/tools/wptrunner/wptrunner/executors/executorchrome.py
@@ -0,0 +1,10 @@
+from ..webdriver_server import ChromeDriverServer
+from .base import WdspecExecutor, WebDriverProtocol
+
+
+class ChromeDriverProtocol(WebDriverProtocol):
+ server_cls = ChromeDriverServer
+
+
+class ChromeDriverWdspecExecutor(WdspecExecutor):
+ protocol_cls = ChromeDriverProtocol
diff --git a/tools/wptrunner/wptrunner/executors/executorinternetexplorer.py b/tools/wptrunner/wptrunner/executors/executorinternetexplorer.py
new file mode 100644
index 0000000..898ff14
--- /dev/null
+++ b/tools/wptrunner/wptrunner/executors/executorinternetexplorer.py
@@ -0,0 +1,10 @@
+from ..webdriver_server import InternetExplorerDriverServer
+from .base import WdspecExecutor, WebDriverProtocol
+
+
+class InternetExplorerDriverProtocol(WebDriverProtocol):
+ server_cls = InternetExplorerDriverServer
+
+
+class InternetExplorerDriverWdspecExecutor(WdspecExecutor):
+ protocol_cls = InternetExplorerDriverProtocol
diff --git a/tools/wptrunner/wptrunner/executors/executormarionette.py b/tools/wptrunner/wptrunner/executors/executormarionette.py
index 1c588d3..3e6fb28 100644
--- a/tools/wptrunner/wptrunner/executors/executormarionette.py
+++ b/tools/wptrunner/wptrunner/executors/executormarionette.py
@@ -1,15 +1,9 @@
-import hashlib
-import httplib
import os
import socket
import threading
-import time
import traceback
import urlparse
import uuid
-from collections import defaultdict
-
-from ..wpttest import WdspecResult, WdspecSubtestResult
errors = None
marionette = None
@@ -23,16 +17,17 @@
RefTestImplementation,
TestExecutor,
TestharnessExecutor,
+ WdspecExecutor,
+ WdspecRun,
+ WebDriverProtocol,
+ extra_timeout,
testharness_result_converter,
reftest_result_converter,
- strip_server,
- WdspecExecutor)
+ strip_server)
+
from ..testrunner import Stop
from ..webdriver_server import GeckoDriverServer
-# Extra timeout to use after internal test timeout at which the harness
-# should force a timeout
-extra_timeout = 5 # seconds
def do_delayed_imports():
@@ -284,57 +279,6 @@
self.marionette.execute_script(script)
-class RemoteMarionetteProtocol(Protocol):
- def __init__(self, executor, browser):
- do_delayed_imports()
- Protocol.__init__(self, executor, browser)
- self.webdriver_binary = executor.webdriver_binary
- self.webdriver_args = executor.webdriver_args
- self.capabilities = self.executor.capabilities
- self.session_config = None
- self.server = None
-
- def setup(self, runner):
- """Connect to browser via the Marionette HTTP server."""
- try:
- self.server = GeckoDriverServer(
- self.logger,
- binary=self.webdriver_binary,
- args=self.webdriver_args)
- self.server.start(block=False)
- self.logger.info(
- "WebDriver HTTP server listening at %s" % self.server.url)
- self.session_config = {"host": self.server.host,
- "port": self.server.port,
- "capabilities": self.capabilities}
- except Exception:
- self.logger.error(traceback.format_exc())
- self.executor.runner.send_message("init_failed")
- else:
- self.executor.runner.send_message("init_succeeded")
-
- def teardown(self):
- if self.server is not None and self.server.is_alive:
- self.server.stop()
-
- @property
- def is_alive(self):
- """Test that the Marionette connection is still alive.
-
- Because the remote communication happens over HTTP we need to
- make an explicit request to the remote. It is allowed for
- WebDriver spec tests to not have a WebDriver session, since this
- may be what is tested.
-
- An HTTP request to an invalid path that results in a 404 is
- proof enough to us that the server is alive and kicking.
- """
- conn = httplib.HTTPConnection(self.server.host, self.server.port)
- conn.request("HEAD", self.server.base_path + "invalid")
- res = conn.getresponse()
- return res.status == 404
-
-
class ExecuteAsyncScriptRun(object):
def __init__(self, logger, func, protocol, url, timeout):
self.logger = logger
@@ -606,86 +550,10 @@
except socket.error:
pass
-class WdspecRun(object):
- def __init__(self, func, session, path, timeout):
- self.func = func
- self.result = (None, None)
- self.session = session
- self.path = path
- self.timeout = timeout
- self.result_flag = threading.Event()
- def run(self):
- """Runs function in a thread and interrupts it if it exceeds the
- given timeout. Returns (True, (Result, [SubtestResult ...])) in
- case of success, or (False, (status, extra information)) in the
- event of failure.
- """
-
- executor = threading.Thread(target=self._run)
- executor.start()
-
- flag = self.result_flag.wait(self.timeout)
- if self.result[1] is None:
- self.result = False, ("EXTERNAL-TIMEOUT", None)
-
- return self.result
-
- def _run(self):
- try:
- self.result = True, self.func(self.session, self.path, self.timeout)
- except (socket.timeout, IOError):
- self.result = False, ("CRASH", None)
- except Exception as e:
- message = getattr(e, "message")
- if message:
- message += "\n"
- message += traceback.format_exc(e)
- self.result = False, ("ERROR", message)
- finally:
- self.result_flag.set()
+class GeckoDriverProtocol(WebDriverProtocol):
+ server_cls = GeckoDriverServer
class MarionetteWdspecExecutor(WdspecExecutor):
- def __init__(self, browser, server_config, webdriver_binary,
- timeout_multiplier=1, close_after_done=True, debug_info=None,
- capabilities=None, webdriver_args=None, binary=None, **kwargs):
- self.do_delayed_imports()
- WdspecExecutor.__init__(self, browser, server_config,
- timeout_multiplier=timeout_multiplier,
- debug_info=debug_info)
- self.webdriver_binary = webdriver_binary
- self.webdriver_args = webdriver_args + ["--binary", binary]
- self.capabilities = capabilities
- self.protocol = RemoteMarionetteProtocol(self, browser)
-
- def is_alive(self):
- return self.protocol.is_alive
-
- def on_environment_change(self, new_environment):
- pass
-
- def do_test(self, test):
- timeout = test.timeout * self.timeout_multiplier + extra_timeout
-
- success, data = WdspecRun(self.do_wdspec,
- self.protocol.session_config,
- test.abs_path,
- timeout).run()
-
- if success:
- return self.convert_result(test, data)
-
- return (test.result_cls(*data), [])
-
- def do_wdspec(self, session_config, path, timeout):
- harness_result = ("OK", None)
- subtest_results = pytestrunner.run(path,
- self.server_config,
- session_config,
- timeout=timeout)
- return (harness_result, subtest_results)
-
- def do_delayed_imports(self):
- global pytestrunner
- from . import pytestrunner
+ protocol_cls = GeckoDriverProtocol
diff --git a/tools/wptrunner/wptrunner/executors/executorselenium.py b/tools/wptrunner/wptrunner/executors/executorselenium.py
index ef898f9..728d3c6 100644
--- a/tools/wptrunner/wptrunner/executors/executorselenium.py
+++ b/tools/wptrunner/wptrunner/executors/executorselenium.py
@@ -7,14 +7,11 @@
import urlparse
import uuid
-from .base import (ExecutorException,
- Protocol,
+from .base import (Protocol,
RefTestExecutor,
RefTestImplementation,
- TestExecutor,
TestharnessExecutor,
- testharness_result_converter,
- reftest_result_converter,
+ extra_timeout,
strip_server)
from ..testrunner import Stop
@@ -24,7 +21,6 @@
exceptions = None
RemoteConnection = None
-extra_timeout = 5
def do_delayed_imports():
global webdriver
@@ -34,6 +30,7 @@
from selenium.common import exceptions
from selenium.webdriver.remote.remote_connection import RemoteConnection
+
class SeleniumProtocol(Protocol):
def __init__(self, executor, browser, capabilities, **kwargs):
do_delayed_imports()
@@ -203,6 +200,7 @@
"timeout_multiplier": self.timeout_multiplier,
"timeout": timeout * 1000})
+
class SeleniumRefTestExecutor(RefTestExecutor):
def __init__(self, browser, server_config, timeout_multiplier=1,
screenshot_cache=None, close_after_done=True,
diff --git a/tools/wptrunner/wptrunner/executors/pytestrunner/runner.py b/tools/wptrunner/wptrunner/executors/pytestrunner/runner.py
index bc37384..043fccb 100644
--- a/tools/wptrunner/wptrunner/executors/pytestrunner/runner.py
+++ b/tools/wptrunner/wptrunner/executors/pytestrunner/runner.py
@@ -56,7 +56,8 @@
"--verbose", # show each individual subtest
"--capture", "no", # enable stdout/stderr from tests
"--basetemp", cache, # temporary directory
- "-p", "no:mozlog",
+ "-p", "no:mozlog", # use the WPT result recorder
+ "-p", "no:cacheprovider", # disable state preservation across invocations
path],
plugins=plugins)
diff --git a/tools/wptrunner/wptrunner/webdriver_server.py b/tools/wptrunner/wptrunner/webdriver_server.py
index 49d9c73..f78593e 100644
--- a/tools/wptrunner/wptrunner/webdriver_server.py
+++ b/tools/wptrunner/wptrunner/webdriver_server.py
@@ -12,8 +12,8 @@
__all__ = ["SeleniumServer", "ChromeDriverServer",
- "GeckoDriverServer", "ServoDriverServer",
- "WebDriverServer"]
+ "GeckoDriverServer", "InternetExplorerDriverServer",
+ "ServoDriverServer", "WebDriverServer"]
class WebDriverServer(object):
@@ -125,7 +125,7 @@
class ChromeDriverServer(WebDriverServer):
- default_base_path = "/wd/hub"
+ default_base_path = "/"
def __init__(self, logger, binary="chromedriver", port=None,
base_path="", args=None):
@@ -149,6 +149,17 @@
"--port=%s" % str(self.port)] + self._args
+class InternetExplorerDriverServer(WebDriverServer):
+ def __init__(self, logger, binary="IEDriverServer.exe", port=None,
+ base_path="", host="localhost", args=None):
+ WebDriverServer.__init__(
+ self, logger, binary, host=host, port=port, args=args)
+
+ def make_command(self):
+ return [self.binary,
+ "--port=%s" % str(self.port)] + self._args
+
+
class GeckoDriverServer(WebDriverServer):
def __init__(self, logger, marionette_port=2828, binary="geckodriver",
host="127.0.0.1", port=None, args=None):
diff --git a/tools/wptrunner/wptrunner/wpttest.py b/tools/wptrunner/wptrunner/wpttest.py
index 9c2604d..86320f8 100644
--- a/tools/wptrunner/wptrunner/wpttest.py
+++ b/tools/wptrunner/wptrunner/wpttest.py
@@ -77,8 +77,10 @@
self["debug"] = False
if product == "firefox" and "stylo" not in self:
self["stylo"] = False
- if 'STYLO_FORCE_ENABLED' in os.environ:
+ if "STYLO_FORCE_ENABLED" in os.environ:
self["stylo"] = True
+ if "STYLO_FORCE_DISABLED" in os.environ:
+ self["stylo"] = False
if extras is not None:
self.update(extras)
diff --git a/uievents/constructors/inputevent-constructor.html b/uievents/constructors/inputevent-constructor.html
index f1f5641..3876abc 100644
--- a/uievents/constructors/inputevent-constructor.html
+++ b/uievents/constructors/inputevent-constructor.html
@@ -5,21 +5,21 @@
<script>
test(function() {
var e = new InputEvent('type');
- assert_equals(e.data, null);
- assert_false(e.isComposing);
+ assert_equals(e.data, null, '.data');
+ assert_false(e.isComposing, '.isComposing');
}, 'InputEvent constructor without InputEventInit.');
test(function() {
var e = new InputEvent('type', { data: null, isComposing: true });
- assert_equals(e.data, null);
- assert_true(e.isComposing);
+ assert_equals(e.data, null, '.data');
+ assert_true(e.isComposing, '.isComposing');
}, 'InputEvent construtor with InputEventInit where data is null');
test(function() {
- assert_equals(new InputEvent('type', { data: ''}).data, '');
+ assert_equals(new InputEvent('type', { data: ''}).data, '', '.data');
}, 'InputEvent construtor with InputEventInit where data is empty string');
test(function() {
- assert_equals(new InputEvent('type', { data: 'data' }).data, 'data');
+ assert_equals(new InputEvent('type', { data: 'data' }).data, 'data', '.data');
}, 'InputEvent construtor with InputEventInit where data is non empty string');
</script>
diff --git a/viewport/OWNERS b/viewport/OWNERS
deleted file mode 100644
index c0dd4aa..0000000
--- a/viewport/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# TEAM: input-dev@chromium.org
-# COMPONENT: Blink>Input
diff --git a/wasm/many-memories.window.js b/wasm/many-memories.window.js
new file mode 100644
index 0000000..0613a00
--- /dev/null
+++ b/wasm/many-memories.window.js
@@ -0,0 +1,19 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This test makes sure browsers behave reasonably when asked to allocate a
+// large number of WebAssembly.Memory objects at once.
+test(function() {
+ let memories = [];
+ try {
+ for (let i = 0; i < 600; i++) {
+ memories.push(new WebAssembly.Memory({initial: 1}));
+ }
+ } catch (e) {
+ if (e instanceof RangeError) {
+ return;
+ }
+ throw e;
+ }
+}, "WebAssembly#CreateManyMemories");
diff --git a/web-animations/animation-model/animation-types/property-types.js b/web-animations/animation-model/animation-types/property-types.js
index bbc2411..0ae3277 100644
--- a/web-animations/animation-model/animation-types/property-types.js
+++ b/web-animations/animation-model/animation-types/property-types.js
@@ -1540,6 +1540,18 @@
[{ time: 500, expected: 'rgb(150, 150, 150) 15px 15px 15px, '
+ 'rgba(100, 100, 100, 0.5) 5px 5px 5px' }]);
}, property + ': mismatched list length (from shorter to longer)');
+
+ test(function(t) {
+ var idlName = propertyToIDL(property);
+ var target = createTestElement(t, setup);
+ target.style.color = 'rgb(0, 255, 0)';
+ var animation =
+ target.animate({ [idlName]: [ 'currentcolor 0px 0px 0px',
+ 'currentcolor 10px 10px 10px'] },
+ { duration: 1000, fill: 'both' });
+ testAnimationSamples(animation, idlName,
+ [{ time: 500, expected: 'rgb(0, 255, 0) 5px 5px 5px' }]);
+ }, property + ': with currentcolor');
},
testAddition: function(property, setup) {
@@ -1649,6 +1661,18 @@
[{ time: 500, expected: 'rgb(150, 150, 150) 15px 15px 15px 10px, '
+ 'rgba(100, 100, 100, 0.5) 5px 5px 5px 0px' }]);
}, property + ': mismatched list length (from longer to shorter)');
+
+ test(function(t) {
+ var idlName = propertyToIDL(property);
+ var target = createTestElement(t, setup);
+ target.style.color = 'rgb(0, 255, 0)';
+ var animation =
+ target.animate({ [idlName]: [ 'currentcolor 0px 0px 0px 0px',
+ 'currentcolor 10px 10px 10px 10px'] },
+ { duration: 1000, fill: 'both' });
+ testAnimationSamples(animation, idlName,
+ [{ time: 500, expected: 'rgb(0, 255, 0) 5px 5px 5px 5px' }]);
+ }, property + ': with currentcolor');
},
testAddition: function(property, setup) {
diff --git a/webdriver/tests/contexts/maximize_window.py b/webdriver/tests/contexts/maximize_window.py
index 91b1383..a47630b 100644
--- a/webdriver/tests/contexts/maximize_window.py
+++ b/webdriver/tests/contexts/maximize_window.py
@@ -1,22 +1,30 @@
from tests.support.inline import inline
from tests.support.asserts import assert_error, assert_success
+
alert_doc = inline("<script>window.alert()</script>")
+
+def maximize(session):
+ return session.transport.send("POST", "session/%s/window/maximize" % session.session_id)
+
+
# 10.7.3 Maximize Window
-def test_maximize_no_browsing_context(session, create_window):
- # Step 1
+
+
+def test_no_browsing_context(session, create_window):
+ # step 1
session.window_handle = create_window()
session.close()
- result = session.transport.send("POST", "session/%s/window/maximize" % session.session_id)
- assert_error(result, "no such window")
+ response = maximize(session)
+ assert_error(response, "no such window")
def test_handle_user_prompt(session):
- # Step 2
+ # step 2
session.url = alert_doc
- result = session.transport.send("POST", "session/%s/window/maximize" % session.session_id)
- assert_error(result, "unexpected alert open")
+ response = maximize(session)
+ assert_error(response, "unexpected alert open")
def test_maximize(session):
@@ -24,8 +32,8 @@
assert session.window.state == "normal"
# step 4
- result = session.transport.send("POST", "session/%s/window/maximize" % session.session_id)
- assert_success(result)
+ response = maximize(session)
+ assert_success(response)
assert before_size != session.window.size
assert session.window.state == "maximized"
@@ -35,28 +43,40 @@
before_size = session.window.size
assert session.window.state == "normal"
- result = session.transport.send("POST", "session/%s/window/maximize" % session.session_id)
+ response = maximize(session)
# step 5
- assert result.status == 200
- assert isinstance(result.body["value"], dict)
+ assert response.status == 200
+ assert isinstance(response.body["value"], dict)
- rect = result.body["value"]
- assert "width" in rect
- assert "height" in rect
- assert "x" in rect
- assert "y" in rect
- assert "state" in rect
- assert isinstance(rect["width"], (int, float))
- assert isinstance(rect["height"], (int, float))
- assert isinstance(rect["x"], (int, float))
- assert isinstance(rect["y"], (int, float))
- assert isinstance(rect["state"], basestring)
+ value = response.body["value"]
+ assert "width" in value
+ assert "height" in value
+ assert "x" in value
+ assert "y" in value
+ assert "state" in value
+ assert isinstance(value["width"], (int, float))
+ assert isinstance(value["height"], (int, float))
+ assert isinstance(value["x"], (int, float))
+ assert isinstance(value["y"], (int, float))
+ assert isinstance(value["state"], basestring)
assert before_size != session.window.size
assert session.window.state == "maximized"
+def test_maximize_twice_is_idempotent(session):
+ assert session.window.state == "normal"
+
+ first_response = maximize(session)
+ assert_success(first_response)
+ assert session.window.state == "maximized"
+
+ second_response = maximize(session)
+ assert_success(second_response)
+ assert session.window.state == "maximized"
+
+
def test_maximize_when_resized_to_max_size(session):
# Determine the largest available window size by first maximising
# the window and getting the window rect dimensions.
diff --git a/webdriver/tests/fullscreen_window.py b/webdriver/tests/fullscreen_window.py
new file mode 100644
index 0000000..99b144d
--- /dev/null
+++ b/webdriver/tests/fullscreen_window.py
@@ -0,0 +1,200 @@
+# META: timeout=long
+
+from tests.support.inline import inline
+from tests.support.asserts import assert_error, assert_success, assert_dialog_handled
+from tests.support.fixtures import create_dialog
+
+
+alert_doc = inline("<script>window.alert()</script>")
+
+
+def read_global(session, name):
+ return session.execute_script("return %s;" % name)
+
+
+def fullscreen(session):
+ return session.transport.send("POST", "session/%s/window/fullscreen" % session.session_id)
+
+
+# 10.7.5 Fullscreen Window
+
+
+# 1. If the current top-level browsing context is no longer open, return error
+# with error code no such window.
+def test_no_browsing_context(session, create_window):
+ # step 1
+ session.window_handle = create_window()
+ session.close()
+ response = fullscreen(session)
+ assert_error(response, "no such window")
+
+
+# [...]
+# 2. Handle any user prompts and return its value if it is an error.
+# [...]
+# In order to handle any user prompts a remote end must take the following
+# steps:
+# 2. Run the substeps of the first matching user prompt handler:
+#
+# [...]
+# - accept state
+# 1. Accept the current user prompt.
+# [...]
+#
+# 3. Return success.
+def test_handle_prompt_accept(new_session):
+ _, session = new_session({"alwaysMatch": {"unhandledPromptBehavior": "accept"}})
+ session.url = inline("<title>WD doc title</title>")
+ create_dialog(session)("alert", text="accept #1", result_var="accept1")
+
+ expected_title = read_global(session, "document.title")
+ response = fullscreen(session)
+
+ assert_success(response, expected_title)
+ assert_dialog_handled(session, "accept #1")
+ assert read_global(session, "accept1") == None
+
+ expected_title = read_global(session, "document.title")
+ create_dialog(session)("confirm", text="accept #2", result_var="accept2")
+
+ response = fullscreen(session)
+
+ assert_success(response, expected_title)
+ assert_dialog_handled(session, "accept #2")
+ assert read_global(session, "accept2"), True
+
+ expected_title = read_global(session, "document.title")
+ create_dialog(session)("prompt", text="accept #3", result_var="accept3")
+
+ response = fullscreen(session)
+
+ assert_success(response, expected_title)
+ assert_dialog_handled(session, "accept #3")
+ assert read_global(session, "accept3") == ""
+
+
+# [...]
+# 2. Handle any user prompts and return its value if it is an error.
+# [...]
+# In order to handle any user prompts a remote end must take the following
+# steps:
+# 2. Run the substeps of the first matching user prompt handler:
+#
+# [...]
+# - missing value default state
+# - not in the table of simple dialogs
+# 1. Dismiss the current user prompt.
+# 2. Return error with error code unexpected alert open.
+def test_handle_prompt_missing_value(session, create_dialog):
+ session.url = inline("<title>WD doc title</title>")
+ create_dialog("alert", text="dismiss #1", result_var="dismiss1")
+
+ response = fullscreen(session)
+
+ assert_error(response, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #1")
+ assert read_global(session, "accept1") == None
+
+ create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
+
+ response = fullscreen(session)
+
+ assert_error(response, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #2")
+ assert read_global(session, "dismiss2") == False
+
+ create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
+
+ response = fullscreen(session)
+
+ assert_error(response, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #3")
+ assert read_global(session, "dismiss3") == None
+
+
+# 4. Call fullscreen an element with the current top-level browsing
+# context's active document's document element.
+def test_fullscreen(session):
+ # step 4
+ response = fullscreen(session)
+ assert_success(response)
+ assert session.execute_script("return window.fullScreen") == True
+
+
+# 5. Return success with the JSON serialization of the current top-level
+# browsing context's window rect.
+#
+# [...]
+#
+# A top-level browsing context's window rect is defined as a
+# dictionary of the screenX, screenY, width and height attributes of the
+# WindowProxy. Its JSON representation is the following:
+#
+# x
+# WindowProxy's screenX attribute.
+#
+# y
+# WindowProxy's screenY attribute.
+#
+# width
+# Width of the top-level browsing context's outer dimensions,
+# including any browser chrome and externally drawn window
+# decorations in CSS reference pixels.
+#
+# height
+# Height of the top-level browsing context's outer dimensions,
+# including any browser chrome and externally drawn window decorations
+# in CSS reference pixels.
+#
+# state
+# The top-level browsing context's window state.
+#
+# [...]
+#
+# The top-level browsing context has an associated window state which
+# describes what visibility state its OS widget window is in. It can be
+# in one of the following states:
+#
+# "maximized"
+# The window is maximized.
+#
+# "minimized"
+# The window is iconified.
+#
+# "normal"
+# The window is shown normally.
+#
+# "fullscreen"
+# The window is in full screen mode.
+#
+# If for whatever reason the top-level browsing context's OS window
+# cannot enter either of the window states, or if this concept is not
+# applicable on the current system, the default state must be normal.
+def test_payload(session):
+ response = fullscreen(session)
+
+ # step 5
+ assert response.status == 200
+ assert isinstance(response.body["value"], dict)
+
+ rect = response.body["value"]
+ assert "width" in rect
+ assert "height" in rect
+ assert "x" in rect
+ assert "y" in rect
+ assert isinstance(rect["width"], (int, float))
+ assert isinstance(rect["height"], (int, float))
+ assert isinstance(rect["x"], (int, float))
+ assert isinstance(rect["y"], (int, float))
+
+
+def test_fullscreen_twice_is_idempotent(session):
+ assert session.execute_script("return window.fullScreen") is False
+
+ first_response = fullscreen(session)
+ assert_success(first_response)
+ assert session.execute_script("return window.fullScreen") is True
+
+ second_response = fullscreen(session)
+ assert_success(second_response)
+ assert session.execute_script("return window.fullScreen") is True
diff --git a/webdriver/tests/interaction/send_keys_content_editable.py b/webdriver/tests/interaction/send_keys_content_editable.py
new file mode 100644
index 0000000..a4bba8e
--- /dev/null
+++ b/webdriver/tests/interaction/send_keys_content_editable.py
@@ -0,0 +1,23 @@
+import pytest
+
+from tests.support.inline import inline
+
+
+def test_sets_insertion_point_to_end(session):
+ session.url = inline('<div contenteditable=true>Hello,</div>')
+ input = session.find.css("div", all=False)
+ input.send_keys(' world!')
+ text = session.execute_script('return arguments[0].innerText', args=[input])
+ assert "Hello, world!" == text.strip()
+
+
+# 12. Let current text length be the element’s length.
+#
+# 13. Set the text insertion caret using set selection range using current
+# text length for both the start and end parameters.
+def test_sets_insertion_point_to_after_last_text_node(session):
+ session.url = inline('<div contenteditable=true>Hel<span>lo</span>,</div>')
+ input = session.find.css("div", all=False)
+ input.send_keys(" world!")
+ text = session.execute_script("return arguments[0].innerText", args=[input])
+ assert "Hello, world!" == text.strip()
diff --git a/webdriver/tests/minimize_window.py b/webdriver/tests/minimize_window.py
new file mode 100644
index 0000000..3c7bd00
--- /dev/null
+++ b/webdriver/tests/minimize_window.py
@@ -0,0 +1,73 @@
+from tests.support.inline import inline
+from tests.support.asserts import assert_error, assert_success
+
+
+alert_doc = inline("<script>window.alert()</script>")
+
+
+def minimize(session):
+ return session.transport.send("POST", "session/%s/window/minimize" % session.session_id)
+
+
+# 10.7.4 Minimize Window
+
+
+def test_minimize_no_browsing_context(session, create_window):
+ # step 1
+ session.window_handle = create_window()
+ session.close()
+ response = minimize(session)
+ assert_error(response, "no such window")
+
+
+def test_handle_user_prompt(session):
+ # step 2
+ session.url = alert_doc
+ response = minimize(session)
+ assert_error(response, "unexpected alert open")
+
+
+def test_minimize(session):
+ assert session.window.state == "normal"
+
+ # step 4
+ response = minimize(session)
+ assert_success(response)
+
+ assert session.window.state == "minimized"
+
+
+def test_payload(session):
+ assert session.window.state == "normal"
+
+ response = minimize(session)
+
+ # step 5
+ assert response.status == 200
+ assert isinstance(response.body["value"], dict)
+
+ value = response.body["value"]
+ assert "width" in value
+ assert "height" in value
+ assert "x" in value
+ assert "y" in value
+ assert "state" in value
+ assert isinstance(value["width"], (int, float))
+ assert isinstance(value["height"], (int, float))
+ assert isinstance(value["x"], (int, float))
+ assert isinstance(value["y"], (int, float))
+ assert isinstance(value["state"], basestring)
+
+ assert session.window.state == "minimized"
+
+
+def test_minimize_twice_is_idempotent(session):
+ assert session.execute_script("return document.hidden") is False
+
+ first_response = minimize(session)
+ assert_success(first_response)
+ assert session.execute_script("return document.hidden") is True
+
+ second_response = minimize(session)
+ assert_success(second_response)
+ assert session.execute_script("return document.hidden") is True
diff --git a/webdriver/tests/sessions/new_session/conftest.py b/webdriver/tests/sessions/new_session/conftest.py
new file mode 100644
index 0000000..d3ee199
--- /dev/null
+++ b/webdriver/tests/sessions/new_session/conftest.py
@@ -0,0 +1,22 @@
+import pytest
+import sys
+
+import webdriver
+
+
+def product(a, b):
+ return [(a, item) for item in b]
+
+
+def flatten(l):
+ return [item for x in l for item in x]
+
+@pytest.fixture(scope="session")
+def platform_name():
+ return {
+ "linux2": "linux",
+ "win32": "windows",
+ "cygwin": "windows",
+ "darwin": "mac"
+ }.get(sys.platform)
+
diff --git a/webdriver/tests/sessions/new_session/create.py b/webdriver/tests/sessions/new_session/create.py
new file mode 100644
index 0000000..5766dbd
--- /dev/null
+++ b/webdriver/tests/sessions/new_session/create.py
@@ -0,0 +1,27 @@
+#META: timeout=long
+
+import pytest
+
+from conftest import product, flatten
+
+
+# Note that we can only test things here all implementations must support
+valid_data = [
+ ("acceptInsecureCerts", [False, None]),
+ ("browserName", [None]),
+ ("browserVersion", [None]),
+ ("platformName", [None]),
+ ("pageLoadStrategy", ["none", "eager", "normal", None]),
+ ("proxy", [None]),
+ ("unhandledPromptBehavior", ["dismiss", "accept", None]),
+ ("test:extension", [True, "abc", 123, [], {"key": "value"}, None]),
+]
+
+
+@pytest.mark.parametrize("body", [lambda key, value: {"alwaysMatch": {key: value}},
+ lambda key, value: {"firstMatch": [{key: value}]}])
+@pytest.mark.parametrize("key,value", flatten(product(*item) for item in valid_data))
+def test_valid(new_session, body, key, value):
+ resp = new_session({"capabilities": body(key, value)})
+
+# Continued in create-1.py to avoid timeouts
diff --git a/webdriver/tests/sessions/new_session/default_values.py b/webdriver/tests/sessions/new_session/default_values.py
new file mode 100644
index 0000000..bfe4144
--- /dev/null
+++ b/webdriver/tests/sessions/new_session/default_values.py
@@ -0,0 +1,48 @@
+# META: timeout=long
+
+import uuid
+
+import pytest
+
+from webdriver import error
+
+
+def test_basic(new_session):
+ resp, _ = new_session({"capabilities": {}})
+ assert set(resp.keys()) == {"sessionId", "capabilities"}
+
+
+def test_repeat_new_session(new_session):
+ resp, _ = new_session({"capabilities": {}})
+ with pytest.raises(error.SessionNotCreatedException):
+ new_session({"capabilities": {}})
+
+
+def test_no_capabilites(new_session):
+ with pytest.raises(error.InvalidArgumentException):
+ new_session({})
+
+
+def test_missing_first_match(new_session):
+ resp, _ = new_session({"capabilities": {"alwaysMatch": {}}})
+
+
+def test_missing_always_match(new_session):
+ resp, _ = new_session({"capabilities": {"firstMatch": [{}]}})
+
+
+def test_desired(new_session):
+ with pytest.raises(error.InvalidArgumentException):
+ resp, _ = new_session({"desiredCapbilities": {}})
+
+
+def test_ignore_non_spec_fields_in_capabilities(new_session):
+ resp, _ = new_session({"capabilities": {"desiredCapbilities": {"pageLoadStrategy": "eager"}}})
+ assert resp["capabilities"]["pageLoadStrategy"] == "normal"
+
+
+def test_valid_but_unmatchable_key(new_session):
+ resp, _ = new_session({"capabilities": {
+ "firstMatch": [{"pageLoadStrategy": "eager", "foo:unmatchable": True},
+ {"pageLoadStrategy": "none"}]}})
+ assert resp["capabilities"]["pageLoadStrategy"] == "none"
diff --git a/webdriver/tests/sessions/new_session/invalid_capabilities.py b/webdriver/tests/sessions/new_session/invalid_capabilities.py
new file mode 100644
index 0000000..325c9b2
--- /dev/null
+++ b/webdriver/tests/sessions/new_session/invalid_capabilities.py
@@ -0,0 +1,88 @@
+#META: timeout=long
+
+import pytest
+from webdriver import error
+
+from conftest import product, flatten
+
+
+@pytest.mark.parametrize("value", [None, 1, "{}", []])
+def test_invalid_capabilites(new_session, value):
+ with pytest.raises(error.InvalidArgumentException):
+ new_session({"capabilities": value})
+
+
+@pytest.mark.parametrize("value", [None, 1, "{}", []])
+def test_invalid_always_match(new_session, value):
+ with pytest.raises(error.InvalidArgumentException):
+ new_session({"capabilities": {"alwaysMatch": value}})
+
+
+@pytest.mark.parametrize("value", [None, 1, "[]", {}])
+def test_invalid_first_match(new_session, value):
+ with pytest.raises(error.InvalidArgumentException):
+ new_session({"capabilities": {"firstMatch": value}})
+
+
+invalid_data = [
+ ("acceptInsecureCerts", [1, [], {}, "false"]),
+ ("browserName", [1, [], {}, False]),
+ ("browserVersion", [1, [], {}, False]),
+ ("platformName", [1, [], {}, False]),
+ ("pageLoadStrategy", [1, [], {}, False, "invalid", "NONE", "Eager", "eagerblah", "interactive",
+ " eager", "eager "]),
+ ("proxy", [1, [], "{}", {"proxyType": "SYSTEM"}, {"proxyType": "systemSomething"},
+ {"proxy type": "pac"}, {"proxy-Type": "system"}, {"proxy_type": "system"},
+ {"proxytype": "system"}, {"PROXYTYPE": "system"}, {"proxyType": None},
+ {"proxyType": 1}, {"proxyType": []}, {"proxyType": {"value": "system"}},
+ {" proxyType": "system"}, {"proxyType ": "system"}, {"proxyType ": " system"},
+ {"proxyType": "system "}]),
+ ("timeouts", [1, [], "{}", {}, False, {"pageLOAD": 10}, {"page load": 10},
+ {"page load": 10}, {"pageLoad": "10"}, {"pageLoad": {"value": 10}},
+ {"invalid": 10}, {"pageLoad": -1}, {"pageLoad": 2**64},
+ {"pageLoad": None}, {"pageLoad": 1.1}, {"pageLoad": 10, "invalid": 10},
+ {" pageLoad": 10}, {"pageLoad ": 10}]),
+ ("unhandledPromptBehavior", [1, [], {}, False, "DISMISS", "dismissABC", "Accept",
+ " dismiss", "dismiss "])
+]
+
+@pytest.mark.parametrize("body", [lambda key, value: {"alwaysMatch": {key: value}},
+ lambda key, value: {"firstMatch": [{key: value}]}])
+@pytest.mark.parametrize("key,value", flatten(product(*item) for item in invalid_data))
+def test_invalid(new_session, body, key, value):
+ with pytest.raises(error.InvalidArgumentException):
+ resp = new_session({"capabilities": body(key, value)})
+
+
+invalid_extensions = [
+ "firefox",
+ "firefox_binary",
+ "firefoxOptions",
+ "chromeOptions",
+ "automaticInspection",
+ "automaticProfiling",
+ "platform",
+ "version",
+ "browser",
+ "platformVersion",
+ "javascriptEnabled",
+ "nativeEvents",
+ "seleniumProtocol",
+ "profile",
+ "trustAllSSLCertificates",
+ "initialBrowserUrl",
+ "requireWindowFocus",
+ "logFile",
+ "logLevel",
+ "safari.options",
+ "ensureCleanSession",
+]
+
+
+@pytest.mark.parametrize("body", [lambda key, value: {"alwaysMatch": {key: value}},
+ lambda key, value: {"firstMatch": [{key: value}]}])
+@pytest.mark.parametrize("key", invalid_extensions)
+def test_invalid(new_session, body, key):
+ with pytest.raises(error.InvalidArgumentException):
+ resp = new_session({"capabilities": body(key, {})})
+
diff --git a/webdriver/tests/sessions/new_session/merge.py b/webdriver/tests/sessions/new_session/merge.py
new file mode 100644
index 0000000..2e64620
--- /dev/null
+++ b/webdriver/tests/sessions/new_session/merge.py
@@ -0,0 +1,76 @@
+#META: timeout=long
+
+import pytest
+from webdriver import error
+
+from conftest import platform_name
+
+
+@pytest.mark.skipif(platform_name() is None, reason="Unsupported platform")
+@pytest.mark.parametrize("body", [lambda key, value: {"alwaysMatch": {key: value}},
+ lambda key, value: {"firstMatch": [{key: value}]}])
+def test_platform_name(new_session, platform_name, body):
+ resp, _ = new_session({"capabilities": body("platformName", platform_name)})
+ assert resp["capabilities"]["platformName"] == platform_name
+
+
+invalid_merge = [
+ ("acceptInsecureCerts", (True, True)),
+ ("unhandledPromptBehavior", ("accept", "accept")),
+ ("unhandledPromptBehavior", ("accept", "dismiss")),
+ ("timeouts", ({"script": 10}, {"script": 10})),
+ ("timeouts", ({"script": 10}, {"pageLoad": 10})),
+]
+
+
+@pytest.mark.parametrize("key,value", invalid_merge)
+def test_merge_invalid(new_session, key, value):
+ with pytest.raises(error.InvalidArgumentException):
+ new_session({"capabilities":
+ {"alwaysMatch": {key: value[0]},
+ "firstMatch": [{}, {key: value[1]}]}})
+
+
+@pytest.mark.skipif(platform_name() is None, reason="Unsupported platform")
+def test_merge_platformName(new_session, platform_name):
+ resp, _ = new_session({"capabilities":
+ {"alwaysMatch": {"timeouts": {"script": 10}}},
+ "firstMatch": [
+ {
+ "platformName": platform_name.upper(),
+ "pageLoadStrategy": "none"
+ },
+ {
+ "platformName": platform_name,
+ "pageLoadStrategy": "eager"
+ }
+ ]})
+
+ assert resp["capabilities"]["platformName"] == platform_name
+ assert resp["capabilities"]["pageLoadStrategy"] == "eager"
+
+
+def test_merge_browserName(new_session):
+ resp, session = new_session({})
+ browser_settings = {
+ "browserName": resp["capabilities"]["browserName"],
+ "browserVersion": resp["capabilities"]["browserVersion"],
+ "platformName": resp["capabilities"]["platformName"]
+ }
+ session.end()
+
+ resp, _ = new_session({"capabilities":
+ {"alwaysMatch": {"timeouts": {"script": 10}}},
+ "firstMatch": [
+ {
+ "browserName": browser_settings["browserName"] + "invalid",
+ "pageLoadStrategy": "none"
+ },
+ {
+ "browserName": browser_settings["browserName"],
+ "pageLoadStrategy": "eager"
+ }
+ ]})
+
+ assert resp["capabilities"]["browserName"] == browser_settings['browserName']
+ assert resp["capabilities"]["pageLoadStrategy"] == "eager"
diff --git a/webdriver/tests/sessions/new_session/response.py b/webdriver/tests/sessions/new_session/response.py
new file mode 100644
index 0000000..6669573
--- /dev/null
+++ b/webdriver/tests/sessions/new_session/response.py
@@ -0,0 +1,54 @@
+# META: timeout=long
+
+import uuid
+
+def test_resp_sessionid(new_session):
+ resp, _ = new_session({"capabilities": {}})
+ assert isinstance(resp["sessionId"], unicode)
+ uuid.UUID(hex=resp["sessionId"])
+
+
+def test_resp_capabilites(new_session):
+ resp, _ = new_session({"capabilities": {}})
+ assert isinstance(resp["sessionId"], unicode)
+ assert isinstance(resp["capabilities"], dict)
+ assert {"browserName",
+ "browserVersion",
+ "platformName",
+ "acceptInsecureCerts",
+ "setWindowRect",
+ "timeouts",
+ "proxy",
+ "pageLoadStrategy"}.issubset(
+ set(resp["capabilities"].keys()))
+
+
+def test_resp_data(new_session, platform_name):
+ resp, _ = new_session({"capabilities": {}})
+
+ assert isinstance(resp["capabilities"]["browserName"], unicode)
+ assert isinstance(resp["capabilities"]["browserVersion"], unicode)
+ if platform_name:
+ assert resp["capabilities"]["platformName"] == platform_name
+ else:
+ assert "platformName" in resp["capabilities"]
+ assert resp["capabilities"]["acceptInsecureCerts"] is False
+ assert isinstance(resp["capabilities"]["setWindowRect"], bool)
+ assert resp["capabilities"]["timeouts"]["implicit"] == 0
+ assert resp["capabilities"]["timeouts"]["pageLoad"] == 300000
+ assert resp["capabilities"]["timeouts"]["script"] == 30000
+ assert resp["capabilities"]["proxy"] == {}
+ assert resp["capabilities"]["pageLoadStrategy"] == "normal"
+
+
+def test_timeouts(new_session, platform_name):
+ resp, _ = new_session({"capabilities": {"alwaysMatch": {"timeouts": {"implicit": 1000}}}})
+ assert resp["capabilities"]["timeouts"] == {
+ "implicit": 1000,
+ "pageLoad": 300000,
+ "script": 30000
+ }
+
+def test_pageLoadStrategy(new_session, platform_name):
+ resp, _ = new_session({"capabilities": {"alwaysMatch": {"pageLoadStrategy": "eager"}}})
+ assert resp["capabilities"]["pageLoadStrategy"] == "eager"
diff --git a/webdriver/tests/set_window_rect.py b/webdriver/tests/set_window_rect.py
index 2b52a8e..d8df484 100644
--- a/webdriver/tests/set_window_rect.py
+++ b/webdriver/tests/set_window_rect.py
@@ -4,218 +4,439 @@
from support.fixtures import create_dialog
from support.asserts import assert_error, assert_dialog_handled, assert_success
+
alert_doc = inline("<script>window.alert()</script>")
+
+def set_window_rect(session, rect):
+ return session.transport.send("POST", "session/%s/window/rect" % session.session_id, rect)
+
+
# 10.7.2 Set Window Rect
-def test_set_window_rect_prompt_accept(new_session):
- # Step 2
- _, session = new_session({"alwaysMatch": {"unhandledPromptBehavior": "accept"}})
- session.url = inline("<title>WD doc title</title>")
- get_response = session.transport.send("GET", "session/%s/window/rect" % session.session_id)
- original = get_response.body["value"]
+def test_current_top_level_browsing_context_no_longer_open(session, create_window):
+ """
+ 1. If the current top-level browsing context is no longer open,
+ return error with error code no such window.
+ """
+ session.window_handle = create_window()
+ session.close()
+ response = set_window_rect(session, {})
+ assert_error(response, "no such window")
+
+
+def test_handle_prompt_dismiss():
+ """TODO"""
+
+
+def test_handle_prompt_accept(new_session):
+ """
+ 2. Handle any user prompts and return its value if it is an error.
+
+ [...]
+
+ In order to handle any user prompts a remote end must take the
+ following steps:
+
+ [...]
+
+ 2. Perform the following substeps based on the current session's
+ user prompt handler:
+
+ [...]
+
+ - accept state
+ Accept the current user prompt.
+
+ """
+
+ _, session = new_session(
+ {"alwaysMatch": {"unhandledPromptBehavior": "accept"}})
+ original = session.window.rect
+
+ # step 2
create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"x": int(original["y"]),
- "y": int(original["y"])})
+ result = set_window_rect(session, {"x": int(original["x"]),
+ "y": int(original["y"])})
assert result.status == 200
assert_dialog_handled(session, "dismiss #1")
create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"x": int(original["y"]),
- "y": int(original["y"])})
+ result = set_window_rect(session, {"x": int(original["x"]),
+ "y": int(original["y"])})
assert result.status == 200
assert_dialog_handled(session, "dismiss #2")
create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"x": int(original["y"]),
- "y": int(original["y"])})
- assert result.status == 200
+ result = set_window_rect(session, {"x": int(original["x"]),
+ "y": int(original["y"])})
+ assert_success(result)
assert_dialog_handled(session, "dismiss #3")
-def test_set_window_rect_handle_prompt_missing_value(session, create_dialog):
- # Step 2
- get_response = session.transport.send("GET",
- "session/%s/window/rect" % session.session_id)
- original = get_response.body["value"]
+def test_handle_prompt_dismiss_and_notify():
+ """TODO"""
- session.url = inline("<title>WD doc title</title>")
+
+def test_handle_prompt_accept_and_notify():
+ """TODO"""
+
+
+def test_handle_prompt_ignore():
+ """TODO"""
+
+
+def test_handle_prompt_missing_value(session, create_dialog):
+ """
+ 2. Handle any user prompts and return its value if it is an error.
+
+ [...]
+
+ In order to handle any user prompts a remote end must take the
+ following steps:
+
+ [...]
+
+ 2. Perform the following substeps based on the current session's
+ user prompt handler:
+
+ [...]
+
+ - missing value default state
+ 1. Dismiss the current user prompt.
+ 2. Return error with error code unexpected alert open.
+
+ """
+
+ original = session.window.rect
+
+ # step 2
create_dialog("alert", text="dismiss #1", result_var="dismiss1")
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"x": int(original["y"]),
- "y": int(original["y"])})
-
+ result = set_window_rect(session, {"x": int(original["x"]),
+ "y": int(original["y"])})
assert_error(result, "unexpected alert open")
assert_dialog_handled(session, "dismiss #1")
create_dialog("confirm", text="dismiss #2", result_var="dismiss2")
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"x": int(original["y"]),
- "y": int(original["y"])})
-
+ result = set_window_rect(session, {"x": int(original["x"]),
+ "y": int(original["y"])})
assert_error(result, "unexpected alert open")
assert_dialog_handled(session, "dismiss #2")
create_dialog("prompt", text="dismiss #3", result_var="dismiss3")
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"x": int(original["y"]),
- "y": int(original["y"])})
-
+ result = set_window_rect(session, {"x": int(original["x"]),
+ "y": int(original["y"])})
assert_error(result, "unexpected alert open")
assert_dialog_handled(session, "dismiss #3")
-@pytest.mark.parametrize("data", [
- {"height": None, "width": None, "x": "a", "y": "b"},
- {"height": "a", "width": "b", "x": None, "y": None},
- {"height": None, "width": None, "x": 10.1, "y": 10.1},
- {"height": 10.1, "width": 10.1, "x": None, "y": None},
- {"height": True, "width": False, "x": None, "y": None},
- {"height": None, "width": None, "x": True, "y": False},
- {"height": [], "width": [], "x": None, "y": None},
- {"height": None, "width": None, "x": [], "y": []},
- {"height": [], "width": [], "x": [], "y": []},
- {"height": {}, "width": {}, "x": None, "y": None},
- {"height": None, "width": None, "x": {}, "y": {}},
+@pytest.mark.parametrize("rect", [
+ {"width": "a"},
+ {"height": "b"},
+ {"width": "a", "height": "b"},
+ {"x": "a"},
+ {"y": "b"},
+ {"x": "a", "y": "b"},
+ {"width": "a", "height": "b", "x": "a", "y": "b"},
+
+ {"width": True},
+ {"height": False},
+ {"width": True, "height": False},
+ {"x": True},
+ {"y": False},
+ {"x": True, "y": False},
+ {"width": True, "height": False, "x": True, "y": False},
+
+ {"width": []},
+ {"height": []},
+ {"width": [], "height": []},
+ {"x": []},
+ {"y": []},
+ {"x": [], "y": []},
+ {"width": [], "height": [], "x": [], "y": []},
+
+ {"height": {}},
+ {"width": {}},
+ {"height": {}, "width": {}},
+ {"x": {}},
+ {"y": {}},
+ {"x": {}, "y": {}},
+ {"width": {}, "height": {}, "x": {}, "y": {}},
])
-def test_set_window_rect_invalid_params(session, data):
- # step 8-9
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- data)
+def test_invalid_types(session, rect):
+ """
+ 8. If width or height is neither null nor a Number from 0 to 2^64 -
+ 1, return error with error code invalid argument.
- assert_error(result, "invalid argument")
+ 9. If x or y is neither null nor a Number from -(263) to 263 - 1,
+ return error with error code invalid argument.
+ """
+ response = set_window_rect(session, rect)
+ assert_error(response, "invalid argument")
-def test_set_window_fullscreen(session):
+@pytest.mark.parametrize("rect", [
+ {"width": -1},
+ {"height": -2},
+ {"width": -1, "height": -2},
+])
+def test_out_of_bounds(session, rect):
+ """
+ 8. If width or height is neither null nor a Number from 0 to 2^64 -
+ 1, return error with error code invalid argument.
+
+ 9. If x or y is neither null nor a Number from -(263) to 263 - 1,
+ return error with error code invalid argument.
+ """
+ response = set_window_rect(session, rect)
+ assert_error(response, "invalid argument")
+
+
+def test_width_height_floats(session):
+ """
+ 8. If width or height is neither null nor a Number from 0 to 2^64 -
+ 1, return error with error code invalid argument.
+ """
+
+ response = set_window_rect(session, {"width": 200.5, "height": 400})
+ value = assert_success(response)
+ assert value["width"] == 200.2
+ assert value["height"] == 400
+
+ response = set_window_rect(session, {"width": 300, "height": 450.5})
+ value = assert_success(response)
+ assert value["width"] == 300
+ assert value["height"] == 450.5
+
+
+def test_x_y_floats(session):
+ """
+ 9. If x or y is neither null nor a Number from -(263) to 263 - 1,
+ return error with error code invalid argument.
+ """
+
+ response = set_window_rect(session, {"x": 200.5, "y": 400})
+ value = assert_success(response)
+ assert value["x"] == 200.2
+ assert value["y"] == 400
+
+ response = set_window_rect(session, {"x": 300, "y": 450.5})
+ value = assert_success(response)
+ assert value["x"] == 300
+ assert value["y"] == 450.5
+
+
+@pytest.mark.parametrize("rect", [
+ {},
+
+ {"width": None},
+ {"height": None},
+ {"width": None, "height": None},
+
+ {"x": None},
+ {"y": None},
+ {"x": None, "y": None},
+
+ {"width": None, "x": None},
+ {"width": None, "y": None},
+ {"height": None, "x": None},
+ {"height": None, "Y": None},
+
+ {"width": None, "height": None, "x": None, "y": None},
+
+ {"width": 200},
+ {"height": 200},
+ {"x": 200},
+ {"y": 200},
+ {"width": 200, "x": 200},
+ {"height": 200, "x": 200},
+ {"width": 200, "y": 200},
+ {"height": 200, "y": 200},
+])
+def test_no_change(session, rect):
+ """
+ 13. If width and height are not null:
+
+ [...]
+
+ 14. If x and y are not null:
+
+ [...]
+
+ 15. Return success with the JSON serialization of the current
+ top-level browsing context's window rect.
+ """
+
original = session.window.rect
+ response = set_window_rect(session, rect)
+ assert_success(response, original)
- # step 10
+
+def test_fully_exit_fullscreen(session):
+ """
+ 10. Fully exit fullscreen.
+
+ [...]
+
+ To fully exit fullscreen a document document, run these steps:
+
+ 1. If document's fullscreen element is null, terminate these steps.
+
+ 2. Unfullscreen elements whose fullscreen flag is set, within
+ document's top layer, except for document's fullscreen element.
+
+ 3. Exit fullscreen document.
+ """
session.window.fullscreen()
- assert session.window.state == "fullscreen"
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"width": 400, "height": 400})
- assert_success(result, {"x": original["x"],
- "y": original["y"],
- "width": 400.0,
- "height": 400.0,
- "state": "normal"})
+ assert session.execute_script("return window.fullScreen") is True
+
+ response = set_window_rect(session, {"width": 400, "height": 400})
+ value = assert_success(response)
+ assert value["width"] == 400
+ assert value["height"] == 400
+ assert value["state"] == "normal"
+
+ assert session.execute_script("return window.fullScreen") is False
-def test_set_window_rect_window_minimized(session):
- # step 11
+def test_restore_from_minimized(session):
+ """
+ 12. If the visibility state of the top-level browsing context's
+ active document is hidden, restore the window.
+
+ [...]
+
+ To restore the window, given an operating system level window with
+ an associated top-level browsing context, run implementation-specific
+ steps to restore or unhide the window to the visible screen. Do not
+ return from this operation until the visibility state of the top-level
+ browsing context's active document has reached the visible state,
+ or until the operation times out.
+ """
+
session.window.minimize()
- assert session.execute_script("return document.hidden")
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"width": 400, "height": 400})
- assert not session.execute_script("return document.hidden")
- assert_success(result, {"width": 400, "height": 400})
+ assert session.execute_script("return document.hidden") is True
+
+ response = set_window_rect(session, {"width": 450, "height": 450})
+ value = assert_success(response)
+ assert value["width"] == 450
+ assert value["height"] == 450
+ assert value["state"] == "normal"
+
+ assert session.execute_script("return document.hidden") is False
-def test_set_window_height_width(session):
+def test_restore_from_maximized(session):
+ """
+ 12. If the visibility state of the top-level browsing context's
+ active document is hidden, restore the window.
+
+ [...]
+
+ To restore the window, given an operating system level window with
+ an associated top-level browsing context, run implementation-specific
+ steps to restore or unhide the window to the visible screen. Do not
+ return from this operation until the visibility state of the top-level
+ browsing context's active document has reached the visible state,
+ or until the operation times out.
+ """
+
+ original_size = session.window.size
+ session.window.maximize()
+ assert session.window.size != original_size
+ assert session.window.state == "maximized"
+
+ response = set_window_rect(session, {"width": 400, "height": 400})
+ value = assert_success(response)
+ assert value["width"] == 400
+ assert value["height"] == 400
+ assert value["state"] == "normal"
+
+
+def test_height_width(session):
original = session.window.rect
-
- # step 12
max = session.execute_script("""
return {
width: window.screen.availWidth,
height: window.screen.availHeight,
}""")
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"width": max["width"] - 100,
- "height": max["height"] - 100})
+
+ # step 12
+ response = set_window_rect(session, {"width": max["width"] - 100,
+ "height": max["height"] - 100})
# step 14
- assert_success(result, {"x": original["x"],
- "y": original["y"],
- "width": max["width"] - 100,
- "height": max["height"] - 100,
- "state": "normal"})
+ assert_success(response, {"x": original["x"],
+ "y": original["y"],
+ "width": max["width"] - 100,
+ "height": max["height"] - 100,
+ "state": "normal"})
-def test_set_window_height_width_larger_than_max(session):
- # step 12
+def test_height_width_larger_than_max(session):
max = session.execute_script("""
return {
width: window.screen.availWidth,
height: window.screen.availHeight,
}""")
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"width": max["width"] + 100,
- "height": max["width"] + 100})
- # Step 14
- assert result.status == 200
- rect = result.body["value"]
+ # step 12
+ response = set_window_rect(session, {"width": max["width"] + 100,
+ "height": max["height"] + 100})
+
+ # step 14
+ rect = assert_success(response)
assert rect["width"] >= max["width"]
assert rect["height"] >= max["height"]
assert rect["state"] == "normal"
-def test_set_window_height_width_as_current(session):
+def test_height_width_as_current(session):
+ original = session.window.rect
+
# step 12
- original = session.window.rect
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"width": int(original["width"]),
- "height": int(original["height"])})
+ response = set_window_rect(session, {"width": int(original["width"]),
+ "height": int(original["height"])})
# step 14
- assert_success(result, {"x": original["x"],
- "y": original["y"],
- "width": original["width"],
- "height": original["height"],
- "state": original["state"]})
+ assert_success(response, {"x": original["x"],
+ "y": original["y"],
+ "width": original["width"],
+ "height": original["height"],
+ "state": original["state"]})
-def test_set_window_rect_x_y(session):
- # step 13
- original = session.window.rect
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"x": int(original["x"]) + 10,
- "y": int(original["y"]) + 10})
- # step 14
- assert_success(result, {"x": original["x"] + 10,
- "y": original["y"] + 10,
- "width": original["width"],
- "height": original["height"],
- "state": original["state"]})
-
-
-def test_set_window_rect_negative_x_y(session):
+def test_x_y(session):
original = session.window.rect
# step 13
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"x": - 8,
- "y": - 8})
+ response = set_window_rect(session, {"x": int(original["x"]) + 10,
+ "y": int(original["y"]) + 10})
# step 14
+ assert_success(response, {"x": original["x"] + 10,
+ "y": original["y"] + 10,
+ "width": original["width"],
+ "height": original["height"],
+ "state": original["state"]})
+
+def test_negative_x_y(session):
+ original = session.window.rect
+
+ # step 13
+ response = set_window_rect(session, {"x": - 8, "y": - 8})
+
+ # step 14
os = session.capabilities["platformName"]
# certain WMs prohibit windows from being moved off-screen
if os == "linux":
- rect = assert_success(result)
+ rect = assert_success(response)
assert rect["x"] <= 0
assert rect["y"] <= 0
assert rect["width"] == original["width"]
@@ -226,46 +447,65 @@
# horizontal axis. The system menu bar also blocks windows from
# being moved to (0,0).
elif os == "darwin":
- assert_success(result, {"x": -8,
- "y": 23,
- "width": original["width"],
- "height": original["height"],
- "state": original["state"]})
+ assert_success(response, {"x": -8,
+ "y": 23,
+ "width": original["width"],
+ "height": original["height"],
+ "state": original["state"]})
# It turns out that Windows is the only platform on which the
# window can be reliably positioned off-screen.
elif os == "windows_nt":
- assert_success(result, {"x": -8,
- "y": -8,
- "width": original["width"],
- "height": original["height"],
- "state": original["state"]})
+ assert_success(response, {"x": -8,
+ "y": -8,
+ "width": original["width"],
+ "height": original["height"],
+ "state": original["state"]})
-def test_set_window_x_y_as_current(session):
- # step 13
- original = session.window.rect
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"x": int(original["x"]),
- "y": int(original["y"])})
+def test_move_to_same_position(session):
+ original_position = session.window.position
+ position = session.window.position = (int(original_position[0]), int(original_position[1]))
+ assert position == original_position
+
+
+def test_move_to_same_x(session):
+ original_x = session.window.position[0]
+ position = session.window.position = (int(original_x), 345)
+ assert position == (original_x, 345)
+
+
+def test_move_to_same_y(session):
+ original_y = session.window.position[1]
+ position = session.window.position = (456, int(original_y))
+ assert position == (456, original_y)
+
+
+def test_resize_to_same_size(session):
+ original_size = session.window.size
+ size = session.window.size = (int(original_size[0]), int(original_size[1]))
+ assert size == original_size
+
+
+def test_resize_to_same_width(session):
+ original_width = session.window.size[0]
+ size = session.window.size = (int(original_width), 345)
+ assert size == (original_width, 345)
+
+
+def test_resize_to_same_height(session):
+ original_height = session.window.size[1]
+ size = session.window.size = (456, int(original_height))
+ assert size == (456, original_height)
+
+
+def test_payload(session):
# step 14
- assert_success(result, {"x": original["x"],
- "y": original["y"],
- "width": original["width"],
- "height": original["height"],
- "state": original["state"]})
+ response = set_window_rect(session, {"x": 400, "y": 400})
-def test_set_window_rect_payload(session):
- # step 14
- result = session.transport.send("POST",
- "session/%s/window/rect" % session.session_id,
- {"x": 400,
- "y": 400})
-
- assert result.status == 200
- assert isinstance(result.body["value"], dict)
- rect = result.body["value"]
+ assert response.status == 200
+ assert isinstance(response.body["value"], dict)
+ rect = response.body["value"]
assert "width" in rect
assert "height" in rect
assert "x" in rect
diff --git a/webdriver/tests/state/get_element_attribute.py b/webdriver/tests/state/get_element_attribute.py
new file mode 100644
index 0000000..f89147e
--- /dev/null
+++ b/webdriver/tests/state/get_element_attribute.py
@@ -0,0 +1,193 @@
+import pytest
+
+from tests.support.asserts import assert_error, assert_success, assert_dialog_handled
+from tests.support.fixtures import create_dialog
+from tests.support.inline import inline
+
+
+def get_attribute(session, element, attr):
+ return session.transport.send("GET", "session/{session_id}/element/{element_id}/attribute/{attr}"
+ .format(session_id=session.session_id,
+ element_id=element,
+ attr=attr))
+
+
+# 13.2 Get Element Attribute
+
+def test_no_browsing_context(session, create_window):
+ # 13.2 step 1
+ session.window_handle = create_window()
+ session.close()
+
+ result = get_attribute(session, "foo", "id")
+
+ assert_error(result, "no such window")
+
+
+def test_handle_prompt_dismiss(new_session):
+ # 13.2 step 2
+ _, session = new_session({"alwaysMatch": {"unhandledPromptBehavior": "dismiss"}})
+ session.url = inline("<input id=foo>")
+
+ create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+ result = get_attribute(session, "foo", "id")
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #1")
+
+ create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+ result = get_attribute(session, "foo", "id")
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #2")
+
+ create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+ result = get_attribute(session, "foo", "id")
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_accept(new_session):
+ # 13.2 step 2
+ _, session = new_session({"alwaysMatch": {"unhandledPromptBehavior": "accept"}})
+ session.url = inline("<input id=foo>")
+
+ create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+ result = get_attribute(session, "foo", "id")
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #1")
+
+ create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+ result = get_attribute(session, "foo", "id")
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #2")
+
+ create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+ result = get_attribute(session, "foo", "id")
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_missing_value(session, create_dialog):
+ # 13.2 step 2
+ session.url = inline("<input id=foo>")
+
+ create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+ result = get_attribute(session, "foo", "id")
+
+ assert_error(result, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #1")
+
+ create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+ result = get_attribute(session, "foo", "id")
+
+ assert_error(result, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #2")
+
+ create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+ result = get_attribute(session, "foo", "id")
+
+ assert_error(result, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #3")
+
+
+def test_element_not_found(session):
+ # 13.2 Step 3
+ result = get_attribute(session, "foo", "id")
+
+ assert_error(result, "no such element")
+
+
+def test_element_stale(session):
+ # 13.2 step 4
+ session.url = inline("<input id=foo>")
+ element = session.find.css("input", all=False)
+ session.refresh()
+ result = get_attribute(session, element.id, "id")
+
+ assert_error(result, "stale element reference")
+
+
+def test_normal(session):
+ # 13.2 Step 5
+ session.url = inline("<input type=checkbox>")
+ element = session.find.css("input", all=False)
+ result = get_attribute(session, element.id, "input")
+ assert_success(result, None)
+ assert False == session.execute_script("return document.querySelector('input').checked")
+
+ # Check we are not returning the property which will have a different value
+ element.click()
+ assert True == session.execute_script("return document.querySelector('input').checked")
+ result = get_attribute(session, element.id, "input")
+ assert_success(result, None)
+
+
+@pytest.mark.parametrize("tag,attrs", [
+ ("audio", ["autoplay", "controls", "loop", "muted"]),
+ ("button", ["autofocus", "disabled", "formnovalidate"]),
+ ("details", ["open"]),
+ ("dialog", ["open"]),
+ ("fieldset", ["disabled"]),
+ ("form", ["novalidate"]),
+ ("iframe", ["allowfullscreen"]),
+ ("img", ["ismap"]),
+ ("input", ["autofocus", "checked", "disabled", "formnovalidate", "multiple", "readonly", "required"]),
+ ("menuitem", ["checked", "default", "disabled"]),
+ ("object", ["typemustmatch"]),
+ ("ol", ["reversed"]),
+ ("optgroup", ["disabled"]),
+ ("option", ["disabled", "selected"]),
+ ("script", ["async", "defer"]),
+ ("select", ["autofocus", "disabled", "multiple", "required"]),
+ ("textarea", ["autofocus", "disabled", "readonly", "required"]),
+ ("track", ["default"]),
+ ("video", ["autoplay", "controls", "loop", "muted"])
+])
+def test_boolean_attribute(session, tag, attrs):
+ # 13.2 Step 5
+ for attr in attrs:
+ print("testing boolean attribute <{0} {1}>".format(tag, attr))
+ session.url = inline("<{0} {1}>".format(tag, attr))
+
+ element = session.find.css(tag, all=False)
+ result = result = get_attribute(session, element.id, attr)
+ assert_success(result, "true")
+
+
+def test_global_boolean_attributes(session):
+ # 13.2 Step 5
+ session.url = inline("<p hidden>foo")
+ element = session.find.css("p", all=False)
+ result = result = get_attribute(session, element.id, "hidden")
+
+ assert_success(result, "true")
+
+ session.url = inline("<p>foo")
+ element = session.find.css("p", all=False)
+ result = result = get_attribute(session, element.id, "hidden")
+ assert_success(result, None)
+
+ session.url = inline("<p itemscope>foo")
+ element = session.find.css("p", all=False)
+ result = result = get_attribute(session, element.id, "itemscope")
+
+ assert_success(result, "true")
+
+ session.url = inline("<p>foo")
+ element = session.find.css("p", all=False)
+ result = result = get_attribute(session, element.id, "itemscope")
+ assert_success(result, None)
diff --git a/webdriver/tests/state/get_element_property.py b/webdriver/tests/state/get_element_property.py
new file mode 100644
index 0000000..00a85af
--- /dev/null
+++ b/webdriver/tests/state/get_element_property.py
@@ -0,0 +1,164 @@
+from tests.support.asserts import assert_error, assert_dialog_handled, assert_success
+from tests.support.inline import inline
+from tests.support.fixtures import create_dialog
+
+_input = inline("<input id=i1>")
+
+
+# 13.3 Get Element Property
+
+def test_no_browsing_context(session, create_window):
+ # 13.3 step 1
+ session.window_handle = create_window()
+ session.close()
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "no such window")
+
+
+def test_handle_prompt_dismiss(new_session):
+ # 13.3 step 2
+ _, session = new_session({"alwaysMatch": {"unhandledPromptBehavior": "dismiss"}})
+ session.url = inline("<input id=foo>")
+
+ create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #1")
+
+ create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #2")
+
+ create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #3")
+
+
+
+def test_handle_prompt_accept(new_session):
+ # 13.3 step 2
+ _, session = new_session({"alwaysMatch": {"unhandledPromptBehavior": "accept"}})
+ session.url = inline("<input id=foo>")
+
+ create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #1")
+
+ create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #2")
+
+ create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_missing_value(session, create_dialog):
+ # 13.3 step 2
+ session.url = inline("<input id=foo>")
+
+ create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #1")
+
+ create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #2")
+
+ create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #3")
+
+def test_element_not_found(session):
+ # 13.3 Step 3
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "no such element")
+
+
+def test_element_stale(session):
+ # 13.3 step 4
+ session.url = _input
+ element = session.find.css("input", all=False)
+ session.refresh()
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id=element.id))
+
+ assert_error(result, "stale element reference")
+
+
+def test_element_non_existent(session):
+ # 13.3 step 5-7
+ session.url = _input
+ element = session.find.css("input", all=False)
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/foo"
+ .format(session_id=session.session_id,
+ element_id=element.id))
+
+ assert_success(result, None)
+ assert None == session.execute_script("return arguments[0].foo",
+ args=[element])
+
+
+def test_element(session):
+ # 13.3 step 5-7
+ session.url = inline("<input type=checkbox>")
+ element = session.find.css("input", all=False)
+ element.click()
+ assert None == session.execute_script("return arguments[0].hasAttribute('checked')",
+ args=[element])
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/property/id"
+ .format(session_id=session.session_id,
+ element_id=element.id))
+ assert_success(result, True)
diff --git a/webdriver/tests/state/get_element_tag_name.py b/webdriver/tests/state/get_element_tag_name.py
new file mode 100644
index 0000000..4100e9b
--- /dev/null
+++ b/webdriver/tests/state/get_element_tag_name.py
@@ -0,0 +1,146 @@
+from tests.support.asserts import assert_error, assert_dialog_handled, assert_success
+from tests.support.inline import inline
+from tests.support.fixtures import create_dialog
+
+
+# 13.6 Get Element Tag Name
+
+def test_no_browsing_context(session, create_window):
+ # 13.6 step 1
+ session.window_handle = create_window()
+ session.close()
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "no such window")
+
+
+def test_handle_prompt_dismiss(new_session):
+ # 13.6 step 2
+ _, session = new_session({"alwaysMatch": {"unhandledPromptBehavior": "dismiss"}})
+ session.url = inline("<input id=foo>")
+
+ create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #1")
+
+ create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #2")
+
+ create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_accept(new_session):
+ # 13.6 step 2
+ _, session = new_session({"alwaysMatch": {"unhandledPromptBehavior": "accept"}})
+ session.url = inline("<input id=foo>")
+
+ create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #1")
+
+ create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #2")
+
+ create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_missing_value(session, create_dialog):
+ # 13.6 step 2
+ session.url = inline("<input id=foo>")
+
+ create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #1")
+
+ create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #2")
+
+ create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #3")
+
+
+def test_element_not_found(session):
+ # 13.6 Step 3
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "no such element")
+
+
+def test_element_stale(session):
+ # 13.6 step 4
+ session.url = inline("<input id=foo>")
+ element = session.find.css("input", all=False)
+ session.refresh()
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id=element.id))
+
+ assert_error(result, "stale element reference")
+
+
+def test_get_element_tag_name(session):
+ # 13.6 step 6
+ session.url = inline("<input id=foo>")
+ element = session.find.css("input", all=False)
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/name"
+ .format(session_id=session.session_id,
+ element_id=element.id))
+ assert_success(result, "input")
diff --git a/webdriver/tests/state/is_element_selected.py b/webdriver/tests/state/is_element_selected.py
new file mode 100644
index 0000000..c82f26f
--- /dev/null
+++ b/webdriver/tests/state/is_element_selected.py
@@ -0,0 +1,180 @@
+from tests.support.asserts import assert_error, assert_dialog_handled, assert_success
+from tests.support.inline import inline
+from tests.support.fixtures import create_dialog
+
+
+alert_doc = inline("<script>window.alert()</script>")
+check_doc = inline("<input id=checked type=checkbox checked/><input id=notChecked type=checkbox/>")
+option_doc = inline("""<select>
+ <option id=notSelected>r-</option>
+ <option id=selected selected>r+</option>
+ </select>
+ """)
+
+
+# 13.1 Is Element Selected
+
+def test_no_browsing_context(session, create_window):
+ # 13.1 step 1
+ session.window_handle = create_window()
+ session.close()
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "no such window")
+
+
+def test_handle_prompt_dismiss(new_session):
+ # 13.1 step 2
+ _, session = new_session({"alwaysMatch": {"unhandledPromptBehavior": "dismiss"}})
+ session.url = inline("<input id=foo>")
+
+ create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #1")
+
+ create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #2")
+
+ create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_accept(new_session):
+ # 13.1 step 2
+ _, session = new_session({"alwaysMatch": {"unhandledPromptBehavior": "accept"}})
+ session.url = inline("<input id=foo>")
+
+ create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #1")
+
+ create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #2")
+
+ create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_success(result, "foo")
+ assert_dialog_handled(session, "dismiss #3")
+
+
+def test_handle_prompt_missing_value(session, create_dialog):
+ # 13.1 step 2
+ session.url = inline("<input id=foo>")
+
+ create_dialog(session)("alert", text="dismiss #1", result_var="dismiss1")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #1")
+
+ create_dialog(session)("confirm", text="dismiss #2", result_var="dismiss2")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #2")
+
+ create_dialog(session)("prompt", text="dismiss #3", result_var="dismiss3")
+
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id="foo"))
+
+ assert_error(result, "unexpected alert open")
+ assert_dialog_handled(session, "dismiss #3")
+
+
+def test_element_stale(session):
+ # 13.1 step 4
+ session.url = check_doc
+ element = session.find.css("#checked", all=False)
+ session.refresh()
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id=element.id))
+
+ assert_error(result, "stale element reference")
+
+
+def test_element_checked(session):
+ # 13.1 step 5
+ session.url = check_doc
+ element = session.find.css("#checked", all=False)
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id=element.id))
+
+ assert_success(result, True)
+
+
+def test_checkbox_not_selected(session):
+ # 13.1 step 5
+ session.url = check_doc
+ element = session.find.css("#notChecked", all=False)
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id=element.id))
+
+ assert_success(result, False)
+
+
+def test_element_selected(session):
+ # 13.1 step 5
+ session.url = option_doc
+ element = session.find.css("#selected", all=False)
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id=element.id))
+
+ assert_success(result, True)
+
+
+def test_element_not_selected(session):
+ # 13.1 step 5
+ session.url = option_doc
+ element = session.find.css("#notSelected", all=False)
+ result = session.transport.send("GET", "session/{session_id}/element/{element_id}/selected"
+ .format(session_id=session.session_id,
+ element_id=element.id))
+
+ assert_success(result, False)
diff --git a/webdriver/tests/support/fixtures.py b/webdriver/tests/support/fixtures.py
index 0265ddb..e16ac06 100644
--- a/webdriver/tests/support/fixtures.py
+++ b/webdriver/tests/support/fixtures.py
@@ -4,6 +4,7 @@
import re
import webdriver
+import mozlog
from tests.support.asserts import assert_error
from tests.support.http_request import HTTPRequest
@@ -12,6 +13,20 @@
default_host = "http://127.0.0.1"
default_port = "4444"
+logger = mozlog.get_default_logger()
+
+
+def ignore_exceptions(f):
+ def inner(*args, **kwargs):
+ try:
+ return f(*args, **kwargs)
+ except webdriver.error.WebDriverException as e:
+ logger.warning("Ignored exception %s" % e)
+ inner.__name__ = f.__name__
+ return inner
+
+
+@ignore_exceptions
def _ensure_valid_window(session):
"""If current window is not open anymore, ensure to have a valid
one selected.
@@ -23,6 +38,7 @@
session.window_handle = session.handles[0]
+@ignore_exceptions
def _dismiss_user_prompts(session):
"""Dismisses any open user prompts in windows."""
current_window = session.window_handle
@@ -37,20 +53,17 @@
session.window_handle = current_window
-def _restore_normal_window_state(session):
+@ignore_exceptions
+def _restore_window_state(session):
"""If the window is maximized, minimized, or fullscreened it will
be returned to normal state.
"""
- state = session.window.state
- if state == "maximized":
- session.window.maximize()
- elif state == "minimized":
- session.window.minimize()
- elif state == "fullscreen":
- session.window.fullscreen()
+ if session.window.state in ("maximized", "minimized", "fullscreen"):
+ session.window.size = (800, 600)
+@ignore_exceptions
def _restore_windows(session):
"""Closes superfluous windows opened by the test without ending
the session implicitly by closing the last window.
@@ -108,8 +121,8 @@
return create_window
-def http(session):
- return HTTPRequest(session.transport.host, session.transport.port)
+def http(configuration):
+ return HTTPRequest(configuration["host"], configuration["port"])
def server_config():
@@ -145,14 +158,14 @@
capabilities={"alwaysMatch": configuration["capabilities"]})
try:
_current_session.start()
- except webdriver.errors.SessionNotCreatedException:
+ except webdriver.error.SessionNotCreatedException:
if not _current_session.session_id:
raise
# finalisers are popped off a stack,
# making their ordering reverse
request.addfinalizer(lambda: _switch_to_top_level_browsing_context(_current_session))
- request.addfinalizer(lambda: _restore_normal_window_state(_current_session))
+ request.addfinalizer(lambda: _restore_window_state(_current_session))
request.addfinalizer(lambda: _restore_windows(_current_session))
request.addfinalizer(lambda: _dismiss_user_prompts(_current_session))
request.addfinalizer(lambda: _ensure_valid_window(_current_session))
@@ -197,6 +210,7 @@
host = "%s:%s" % (server_config["host"], port)
return urlparse.urlunsplit((protocol, host, path, query, fragment))
+ inner.__name__ = "url"
return inner
def create_dialog(session):
diff --git a/webdriver/tests/support/inline.py b/webdriver/tests/support/inline.py
index 2004bbc..99418d9 100644
--- a/webdriver/tests/support/inline.py
+++ b/webdriver/tests/support/inline.py
@@ -1,6 +1,10 @@
import urllib
-def inline(doc, doctype="html", mime="text/html;charset=utf-8"):
+
+def inline(doc, doctype="html", mime="text/html;charset=utf-8", protocol="http"):
+ from .fixtures import server_config, url
+ build_url = url(server_config())
+
if doctype == "html":
mime = "text/html;charset=utf-8"
elif doctype == "xhtml":
@@ -16,4 +20,21 @@
{}
</body>
</html>""".format(doc)
- return "data:{},{}".format(mime, urllib.quote(doc))
+
+ query = {"doc": doc}
+ if mime != "text/html;charset=utf8":
+ query["content-type"] = mime
+
+ return build_url("/webdriver/tests/support/inline.py",
+ query=urllib.urlencode(query),
+ protocol=protocol)
+
+
+def main(request, response):
+ doc = request.GET.first("doc", None)
+ content_type = request.GET.first("content-type", "text/html;charset=utf8")
+ if doc is None:
+ rv = 404, [("Content-Type", "text/plain")], "Missing doc parameter in query"
+ else:
+ rv = [("Content-Type", content_type)], doc
+ return rv
diff --git a/webrtc/RTCDTMFSender-helper.js b/webrtc/RTCDTMFSender-helper.js
new file mode 100644
index 0000000..865d0ea
--- /dev/null
+++ b/webrtc/RTCDTMFSender-helper.js
@@ -0,0 +1,114 @@
+'use strict';
+
+// Test is based on the following editor draft:
+// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
+
+// Code using this helper should also include RTCPeerConnection-helper.js
+// in the main HTML file
+
+// The following helper functions are called from RTCPeerConnection-helper.js:
+// getTrackFromUserMedia
+
+// Create a RTCDTMFSender using getUserMedia()
+function createDtmfSender(pc = new RTCPeerConnection()) {
+ return getTrackFromUserMedia('audio')
+ .then(([track, mediaStream]) => {
+ const sender = pc.addTrack(track, mediaStream);
+ const dtmfSender = sender.dtmf;
+
+ assert_true(dtmfSender instanceof RTCDTMFSender,
+ 'Expect audio sender.dtmf to be set to a RTCDTMFSender');
+
+ return dtmfSender;
+ });
+}
+
+/*
+ Create an RTCDTMFSender and test tonechange events on it.
+ testFunc
+ Test function that is going to manipulate the DTMFSender.
+ It will be called with:
+ t - the test object
+ sender - the created RTCDTMFSender
+ pc - the associated RTCPeerConnection as second argument.
+ toneChanges
+ Array of expected tonechange events fired. The elements
+ are array of 3 items:
+ expectedTone
+ The expected character in event.tone
+ expectedToneBuffer
+ The expected new value of dtmfSender.toneBuffer
+ expectedDuration
+ The rough time since beginning or last tonechange event
+ was fired.
+ desc
+ Test description.
+ */
+function test_tone_change_events(testFunc, toneChanges, desc) {
+ async_test(t => {
+ const pc = new RTCPeerConnection();
+
+ createDtmfSender(pc)
+ .then(dtmfSender => {
+ let lastEventTime = Date.now();
+
+ const onToneChange = t.step_func(ev => {
+ assert_true(ev instanceof RTCDTMFToneChangeEvent,
+ 'Expect tone change event object to be an RTCDTMFToneChangeEvent');
+
+ const { tone } = ev;
+ assert_equals(typeof tone, 'string',
+ 'Expect event.tone to be the tone string');
+
+ assert_greater_than(toneChanges.length, 0,
+ 'More tonechange event is fired than expected');
+
+ const [
+ expectedTone, expectedToneBuffer, expectedDuration
+ ] = toneChanges.shift();
+
+ assert_equals(tone, expectedTone,
+ `Expect current event.tone to be ${expectedTone}`);
+
+ assert_equals(dtmfSender.toneBuffer, expectedToneBuffer,
+ `Expect dtmfSender.toneBuffer to be updated to ${expectedToneBuffer}`);
+
+ const now = Date.now();
+ const duration = now - lastEventTime;
+
+ assert_approx_equals(duration, expectedDuration, 50,
+ `Expect tonechange event for "${tone}" to be fired approximately after ${expectedDuration} seconds`);
+
+ lastEventTime = now;
+
+ if(toneChanges.length === 0) {
+ // Wait for same duration as last expected duration + 100ms
+ // before passing test in case there are new tone events fired,
+ // in which case the test should fail.
+ t.step_timeout(
+ t.step_func(() => {
+ t.done();
+ pc.close();
+ }), expectedDuration + 100);
+ }
+ });
+
+ dtmfSender.addEventListener('tonechange', onToneChange);
+
+ testFunc(t, dtmfSender, pc);
+ })
+ .catch(t.step_func(err => {
+ assert_unreached(`Unexpected promise rejection: ${err}`);
+ }));
+ }, desc);
+}
+
+// Get the one and only tranceiver from pc.getTransceivers().
+// Assumes that there is only one tranceiver in pc.
+function getTransceiver(pc) {
+ const transceivers = pc.getTransceivers();
+ assert_equals(transceivers.length, 1,
+ 'Expect there to be only one tranceiver in pc');
+
+ return transceivers[0];
+}
diff --git a/webrtc/RTCDTMFSender-insertDTMF.html b/webrtc/RTCDTMFSender-insertDTMF.html
new file mode 100644
index 0000000..1f6a453
--- /dev/null
+++ b/webrtc/RTCDTMFSender-insertDTMF.html
@@ -0,0 +1,165 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCDTMFSender.prototype.insertDTMF</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script src="RTCDTMFSender-helper.js"></script>
+<script>
+ 'use strict';
+
+ // Test is based on the following editor draft:
+ // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
+
+ // The following helper functions are called from RTCPeerConnection-helper.js
+ // generateAnswer
+
+ // The following helper functions are called from RTCDTMFSender-helper.js
+ // createDtmfSender
+ // test_tone_change_events
+ // getTransceiver
+
+ /*
+ 7. Peer-to-peer DTMF
+ partial interface RTCRtpSender {
+ readonly attribute RTCDTMFSender? dtmf;
+ };
+
+ interface RTCDTMFSender : EventTarget {
+ void insertDTMF(DOMString tones,
+ optional unsigned long duration = 100,
+ optional unsigned long interToneGap = 70);
+ attribute EventHandler ontonechange;
+ readonly attribute DOMString toneBuffer;
+ };
+ */
+
+ /*
+ 7.2. insertDTMF
+ The tones parameter is treated as a series of characters.
+
+ The characters 0 through 9, A through D, #, and * generate the associated
+ DTMF tones.
+
+ The characters a to d MUST be normalized to uppercase on entry and are
+ equivalent to A to D.
+
+ As noted in [RTCWEB-AUDIO] Section 3, support for the characters 0 through 9,
+ A through D, #, and * are required.
+
+ The character ',' MUST be supported, and indicates a delay of 2 seconds
+ before processing the next character in the tones parameter.
+
+ All other characters (and only those other characters) MUST be considered
+ unrecognized.
+ */
+ promise_test(t => {
+ return createDtmfSender()
+ .then(dtmfSender => {
+ dtmfSender.insertDTMF('');
+ dtmfSender.insertDTMF('012345689');
+ dtmfSender.insertDTMF('ABCD');
+ dtmfSender.insertDTMF('abcd');
+ dtmfSender.insertDTMF('#*');
+ dtmfSender.insertDTMF(',');
+ dtmfSender.insertDTMF('0123456789ABCDabcd#*,');
+ });
+ }, 'insertDTMF() should succeed if tones contains valid DTMF characters');
+
+
+ /*
+ 7.2. insertDTMF
+ 6. If tones contains any unrecognized characters, throw an
+ InvalidCharacterError.
+ */
+ promise_test(t => {
+ return createDtmfSender()
+ .then(dtmfSender => {
+ assert_throws('InvalidCharacterError', () =>
+ // 'F' is invalid
+ dtmfSender.insertDTMF('123FFABC'));
+
+ assert_throws('InvalidCharacterError', () =>
+ // 'E' is invalid
+ dtmfSender.insertDTMF('E'));
+
+ assert_throws('InvalidCharacterError', () =>
+ // ' ' is invalid
+ dtmfSender.insertDTMF('# *'));
+ });
+ }, 'insertDTMF() should throw InvalidCharacterError if tones contains invalid DTMF characters');
+
+ /*
+ 7.2. insertDTMF
+ 3. If transceiver.stopped is true, throw an InvalidStateError.
+ */
+ test(t => {
+ const pc = new RTCPeerConnection();
+ const transceiver = pc.addTransceiver('audio');
+ const dtmfSender = transceiver.sender.dtmf;
+
+ transceiver.stop();
+ assert_throws('InvalidStateError', () => dtmfSender.insertDTMF(''));
+
+ }, 'insertDTMF() should throw InvalidStateError if transceiver is stopped');
+
+ /*
+ 7.2. insertDTMF
+ 4. If transceiver.currentDirection is recvonly or inactive, throw an InvalidStateError.
+ */
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+ const transceiver = pc.addTransceiver('audio', {
+ direction: 'recvonly'
+ });
+ const dtmfSender = transceiver.sender.dtmf;
+
+ return pc.createOffer()
+ .then(offer =>
+ pc.setLocalDescription(offer)
+ .then(() => generateAnswer(offer)))
+ .then(() => {
+ assert_equals(transceiver.currentDirection, 'inactive');
+ assert_throws('InvalidStateError', () => dtmfSender.insertDTMF(''));
+ });
+ }, 'insertDTMF() should throw InvalidStateError if transceiver.currentDirection is recvonly');
+
+ promise_test(t => {
+ const pc = new RTCPeerConnection();
+ const transceiver = pc.addTransceiver('audio', {
+ direction: 'inactive'
+ });
+ const dtmfSender = transceiver.sender.dtmf;
+
+ return pc.createOffer()
+ .then(offer =>
+ pc.setLocalDescription(offer)
+ .then(() => generateAnswer(offer)))
+ .then(() => {
+ assert_equals(transceiver.currentDirection, 'inactive');
+ assert_throws('InvalidStateError', () => dtmfSender.insertDTMF(''));
+ });
+ }, 'insertDTMF() should throw InvalidStateError if transceiver.currentDirection is inactive');
+
+ /*
+ 7.2. insertDTMF
+ The characters a to d MUST be normalized to uppercase on entry and are
+ equivalent to A to D.
+
+ 7. Set the object's toneBuffer attribute to tones.
+ */
+ promise_test(() => {
+ return createDtmfSender()
+ .then(dtmfSender => {
+ dtmfSender.insertDTMF('123');
+ assert_equals(dtmfSender.toneBuffer, '123');
+
+ dtmfSender.insertDTMF('ABC');
+ assert_equals(dtmfSender.toneBuffer, 'ABC');
+
+ dtmfSender.insertDTMF('bcd');
+ assert_equals(dtmfSender.toneBuffer, 'BCD');
+ });
+ }, 'insertDTMF() should set toneBuffer to provided tones normalized, with old tones overridden');
+
+</script>
diff --git a/webrtc/RTCDTMFSender-ontonechange-long.html b/webrtc/RTCDTMFSender-ontonechange-long.html
new file mode 100644
index 0000000..852194d
--- /dev/null
+++ b/webrtc/RTCDTMFSender-ontonechange-long.html
@@ -0,0 +1,50 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="timeout" content="long">
+<title>RTCDTMFSender.prototype.ontonechange (Long Timeout)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script src="RTCDTMFSender-helper.js"></script>
+<script>
+ 'use strict';
+
+ // Test is based on the following editor draft:
+ // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
+
+ // The following helper functions are called from RTCDTMFSender-helper.js
+ // test_tone_change_events
+
+ /*
+ 7. Peer-to-peer DTMF
+ partial interface RTCRtpSender {
+ readonly attribute RTCDTMFSender? dtmf;
+ };
+
+ interface RTCDTMFSender : EventTarget {
+ void insertDTMF(DOMString tones,
+ optional unsigned long duration = 100,
+ optional unsigned long interToneGap = 70);
+ attribute EventHandler ontonechange;
+ readonly attribute DOMString toneBuffer;
+ };
+
+ [Constructor(DOMString type, RTCDTMFToneChangeEventInit eventInitDict)]
+ interface RTCDTMFToneChangeEvent : Event {
+ readonly attribute DOMString tone;
+ };
+ */
+
+ /*
+ 7.2. insertDTMF
+ 8. If the value of the duration parameter is less than 40, set it to 40.
+ If, on the other hand, the value is greater than 6000, set it to 6000.
+ */
+ test_tone_change_events((t, dtmfSender) => {
+ dtmfSender.insertDTMF('A', 8000, 70);
+ }, [
+ ['A', '', 0],
+ ['', '', 6070]
+ ],'insertDTMF with duration greater than 6000 should be clamped to 6000');
+
+</script>
diff --git a/webrtc/RTCDTMFSender-ontonechange.html b/webrtc/RTCDTMFSender-ontonechange.html
new file mode 100644
index 0000000..fcaa509
--- /dev/null
+++ b/webrtc/RTCDTMFSender-ontonechange.html
@@ -0,0 +1,285 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>RTCDTMFSender.prototype.ontonechange</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="RTCPeerConnection-helper.js"></script>
+<script src="RTCDTMFSender-helper.js"></script>
+<script>
+ 'use strict';
+
+ // Test is based on the following editor draft:
+ // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
+
+ // The following helper functions are called from RTCPeerConnection-helper.js
+ // generateAnswer
+
+ // The following helper functions are called from RTCDTMFSender-helper.js
+ // test_tone_change_events
+ // getTransceiver
+
+ /*
+ 7. Peer-to-peer DTMF
+ partial interface RTCRtpSender {
+ readonly attribute RTCDTMFSender? dtmf;
+ };
+
+ interface RTCDTMFSender : EventTarget {
+ void insertDTMF(DOMString tones,
+ optional unsigned long duration = 100,
+ optional unsigned long interToneGap = 70);
+ attribute EventHandler ontonechange;
+ readonly attribute DOMString toneBuffer;
+ };
+
+ [Constructor(DOMString type, RTCDTMFToneChangeEventInit eventInitDict)]
+ interface RTCDTMFToneChangeEvent : Event {
+ readonly attribute DOMString tone;
+ };
+ */
+
+ /*
+ 7.2. insertDTMF
+ 11. If a Playout task is scheduled to be run; abort these steps; otherwise queue
+ a task that runs the following steps (Playout task):
+ 3. If toneBuffer is an empty string, fire an event named tonechange with an
+ empty string at the RTCDTMFSender object and abort these steps.
+ 4. Remove the first character from toneBuffer and let that character be tone.
+ 6. Queue a task to be executed in duration + interToneGap ms from now that
+ runs the steps labelled Playout task.
+ 7. Fire an event named tonechange with a string consisting of tone at the
+ RTCDTMFSender object.
+ */
+ test_tone_change_events(dtmfSender => {
+ dtmfSender.insertDTMF('123');
+ }, [
+ ['1', '23', 0],
+ ['2', '3', 170],
+ ['3', '', 170],
+ ['', '', 170]
+ ], 'insertDTMF() with default duration and intertoneGap should fire tonechange events at the expected time');
+
+ test_tone_change_events(dtmfSender => {
+ dtmfSender.insertDTMF('abc', 100, 70);
+ }, [
+ ['A', 'BC', 0],
+ ['B', 'C', 170],
+ ['C', '', 170],
+ ['', '', 170]
+ ], 'insertDTMF() with explicit duration and intertoneGap should fire tonechange events at the expected time');
+
+ /*
+ 7.2. insertDTMF
+ 10. If toneBuffer is an empty string, abort these steps.
+ */
+ async_test(t => {
+ createDtmfSender()
+ .then(dtmfSender => {
+ dtmfSender.addEventListener('tonechange',
+ t.unreached_func('Expect no tonechange event to be fired'));
+
+ dtmfSender.insertDTMF('', 100, 70);
+
+ t.step_timeout(t.step_func_done(), 300);
+ })
+ .catch(t.step_func(err => {
+ assert_unreached(`Unexpected promise rejection: ${err}`);
+ }));
+ }, `insertDTMF('') should not fire any tonechange event, including for '' tone`);
+
+ /*
+ 7.2. insertDTMF
+ 8. If the value of the duration parameter is less than 40, set it to 40.
+ If, on the other hand, the value is greater than 6000, set it to 6000.
+ */
+ test_tone_change_events((t, dtmfSender) => {
+ dtmfSender.insertDTMF('ABC', 10, 70);
+ }, [
+ ['A', 'BC', 0],
+ ['B', 'C', 110],
+ ['C', '', 110],
+ ['', '', 110]
+ ], 'insertDTMF() with duration less than 40 should be clamped to 40');
+
+ /*
+ 7.2. insertDTMF
+ 9. If the value of the interToneGap parameter is less than 30, set it to 30.
+ */
+ test_tone_change_events((t, dtmfSender) => {
+ dtmfSender.insertDTMF('ABC', 100, 10);
+ }, [
+ ['A', 'BC', 0],
+ ['B', 'C', 130],
+ ['C', '', 130],
+ ['', '', 130]
+ ],
+ 'insertDTMF() with interToneGap less than 30 should be clamped to 30');
+
+ /*
+ [w3c/webrtc-pc#1373]
+ This step is added to handle the "," character correctly. "," supposed to delay the next
+ tonechange event by 2000ms.
+
+ 7.2. insertDTMF
+ 11.5. If tone is "," delay sending tones for 2000 ms on the associated RTP media
+ stream, and queue a task to be executed in 2000 ms from now that runs the
+ steps labelled Playout task.
+ */
+ test_tone_change_events((t, dtmfSender) => {
+ dtmfSender.insertDTMF('A,B', 100, 70);
+
+ }, [
+ ['A', ',B', 0],
+ [',', 'B', 170],
+ ['B', '', 2000],
+ ['', '', 170]
+ ], 'insertDTMF with comma should delay next tonechange event for a constant 2000ms');
+
+ /*
+ 7.2. insertDTMF
+ 11.1. If transceiver.stopped is true, abort these steps.
+ */
+ test_tone_change_events((t, dtmfSender, pc) => {
+ const transceiver = getTransceiver(pc);
+ dtmfSender.addEventListener('tonechange', ev => {
+ if(ev.tone === 'B') {
+ transceiver.stop();
+ }
+ });
+
+ dtmfSender.insertDTMF('ABC', 100, 70);
+ }, [
+ ['A', 'BC', 0],
+ ['B', 'C', 170]
+ ], 'insertDTMF() with transceiver stopped in the middle should stop future tonechange events from firing');
+
+ /*
+ 7.2. insertDTMF
+ 3. If a Playout task is scheduled to be run, abort these steps;
+ otherwise queue a task that runs the following steps (Playout task):
+ */
+ test_tone_change_events((t, dtmfSender) => {
+ dtmfSender.addEventListener('tonechange', ev => {
+ if(ev.tone === 'B') {
+ // Set a timeout to make sure the toneBuffer
+ // is changed after the tonechange event listener
+ // by test_tone_change_events is called.
+ // This is to correctly test the expected toneBuffer.
+ t.step_timeout(() => {
+ dtmfSender.insertDTMF('12', 100, 70);
+ }, 10);
+ }
+ });
+
+ dtmfSender.insertDTMF('ABC', 100, 70);
+ }, [
+ ['A', 'BC', 0],
+ ['B', 'C', 170],
+ ['1', '2', 170],
+ ['2', '', 170],
+ ['', '', 170]
+ ], 'Calling insertDTMF() in the middle of tonechange events should cause future tonechanges to be updated to new tones');
+
+
+ /*
+ 7.2. insertDTMF
+ 3. If a Playout task is scheduled to be run, abort these steps;
+ otherwise queue a task that runs the following steps (Playout task):
+ */
+ test_tone_change_events((t, dtmfSender) => {
+ dtmfSender.addEventListener('tonechange', ev => {
+ if(ev.tone === 'B') {
+ t.step_timeout(() => {
+ dtmfSender.insertDTMF('12', 100, 70);
+ dtmfSender.insertDTMF('34', 100, 70);
+ }, 10);
+ }
+ });
+
+ dtmfSender.insertDTMF('ABC', 100, 70);
+ }, [
+ ['A', 'BC', 0],
+ ['B', 'C', 170],
+ ['3', '4', 170],
+ ['4', '', 170],
+ ['', '', 170]
+ ], 'Calling insertDTMF() multiple times in the middle of tonechange events should cause future tonechanges to be updated the last provided tones');
+
+ /*
+ 7.2. insertDTMF
+ 3. If a Playout task is scheduled to be run, abort these steps;
+ otherwise queue a task that runs the following steps (Playout task):
+ */
+ test_tone_change_events((t, dtmfSender) => {
+ dtmfSender.addEventListener('tonechange', ev => {
+ if(ev.tone === 'B') {
+ t.step_timeout(() => {
+ dtmfSender.insertDTMF('');
+ }, 10);
+ }
+ });
+
+ dtmfSender.insertDTMF('ABC', 100, 70);
+ }, [
+ ['A', 'BC', 0],
+ ['B', 'C', 170],
+ ['', '', 170]
+ ], `Calling insertDTMF('') in the middle of tonechange events should stop future tonechange events from firing`);
+
+ /*
+ 7.2. insertDTMF
+ 11.2. If transceiver.currentDirection is recvonly or inactive, abort these steps.
+ */
+ async_test(t => {
+ const pc = new RTCPeerConnection();
+ const transceiver = pc.addTransceiver('audio', { direction: 'sendrecv' });
+ const dtmfSender = transceiver.sender.dtmf;
+
+ // Since setRemoteDescription happens in parallel with tonechange event,
+ // We use a flag and allow tonechange events to be fired as long as
+ // the promise returned by setRemoteDescription is not yet resolved.
+ let remoteDescriptionIsSet = false;
+
+ // We only do basic tone verification and not check timing here
+ let expectedTones = ['A', 'B', 'C', 'D', ''];
+
+ const onToneChange = t.step_func(ev => {
+ assert_false(remoteDescriptionIsSet,
+ 'Expect no tonechange event to be fired after currentDirection is changed to recvonly');
+
+ const { tone } = ev;
+ const expectedTone = expectedTones.shift();
+ assert_equals(tone, expectedTone,
+ `Expect fired event.tone to be ${expectedTone}`);
+
+ // Only change transceiver.currentDirection after the first
+ // tonechange event, to make sure that tonechange is triggered
+ // then stopped
+ if(tone === 'A') {
+ transceiver.setDirection('recvonly');
+
+ pc.createOffer()
+ .then(offer =>
+ pc.setLocalDescription(offer)
+ .then(() => generateAnswer(offer)))
+ .then(answer => pc.setRemoteDescription(answer))
+ .then(() => {
+ assert_equals(transceiver.currentDirection, 'recvonly');
+ remoteDescriptionIsSet = true;
+
+ // Pass the test if no further tonechange event is
+ // fired in the next 300ms
+ t.step_timeout(t.step_func_done(), 300);
+ })
+ .catch(t.step_func(err => {
+ assert_unreached(`Unexpected promise rejection: ${err}`);
+ }));
+ }
+ });
+
+ dtmfSender.addEventListener('tonechange', onToneChange);
+ dtmfSender.insertDTMF('ABCD', 100, 70);
+
+ }, `Setting transceiver.currentDirection to recvonly in the middle of tonechange events should stop future tonechange events from firing`);
+
+</script>
diff --git a/webrtc/RTCPeerConnection-createOffer.html b/webrtc/RTCPeerConnection-createOffer.html
index 8711cb4..dfa4bdc 100644
--- a/webrtc/RTCPeerConnection-createOffer.html
+++ b/webrtc/RTCPeerConnection-createOffer.html
@@ -48,7 +48,7 @@
return pc.createOffer({ offerToReceiveAudio: true })
.then(offer =>
pc.setLocalDescription(offer)
- .then(offer => {
+ .then(() => {
assert_equals(pc.signalingState, 'have-local-offer');
assert_session_desc_equals(pc.localDescription, offer);
assert_session_desc_equals(pc.pendingLocalDescription, offer);
diff --git a/webrtc/RTCPeerConnection-helper.js b/webrtc/RTCPeerConnection-helper.js
index 8d086e1..1963df1 100644
--- a/webrtc/RTCPeerConnection-helper.js
+++ b/webrtc/RTCPeerConnection-helper.js
@@ -69,7 +69,7 @@
assert_true(typeof(sessionDesc.type) === 'string',
'Expect sessionDescription.type to be a string');
- assert_true(typeof(sessionDesc.type) === 'string',
+ assert_true(typeof(sessionDesc.sdp) === 'string',
'Expect sessionDescription.sdp to be a string');
}
diff --git a/webrtc/RTCPeerConnection-onnegotiationneeded.html b/webrtc/RTCPeerConnection-onnegotiationneeded.html
index 2acaa32..a71625c 100644
--- a/webrtc/RTCPeerConnection-onnegotiationneeded.html
+++ b/webrtc/RTCPeerConnection-onnegotiationneeded.html
@@ -82,25 +82,18 @@
*/
promise_test(t => {
const pc = new RTCPeerConnection();
- const promise = awaitNegotiation(pc);
- pc.createDataChannel('test');
- return promise;
- }, 'Creating first data channel should fire negotiationneeded event');
+ const negotiated = awaitNegotiation(pc);
- promise_test(t => {
- const pc = new RTCPeerConnection();
pc.createDataChannel('test');
- // Attaching the event handler after the negotiation-needed steps
- // are performed should still receive the event, because the event
- // firing is queued as a task
- return awaitNegotiation(pc);
- }, 'task for negotiationneeded event should be enqueued for next tick');
+ return negotiated;
+ }, 'Creating first data channel should fire negotiationneeded event');
test_never_resolve(t => {
const pc = new RTCPeerConnection();
- pc.createDataChannel('foo');
+ const negotiated = awaitNegotiation(pc);
- return awaitNegotiation(pc)
+ pc.createDataChannel('foo');
+ return negotiated
.then(({nextPromise}) => {
pc.createDataChannel('bar');
return nextPromise;
@@ -121,8 +114,10 @@
*/
promise_test(t => {
const pc = new RTCPeerConnection();
+ const negotiated = awaitNegotiation(pc);
+
pc.addTransceiver('audio');
- return awaitNegotiation(pc);
+ return negotiated;
}, 'addTransceiver() should fire negotiationneeded event');
/*
@@ -132,8 +127,10 @@
*/
test_never_resolve(t => {
const pc = new RTCPeerConnection();
+ const negotiated = awaitNegotiation(pc);
+
pc.addTransceiver('audio');
- return awaitNegotiation(pc)
+ return negotiated
.then(({nextPromise}) => {
pc.addTransceiver('video');
return nextPromise;
@@ -147,8 +144,10 @@
*/
test_never_resolve(t => {
const pc = new RTCPeerConnection();
+ const negotiated = awaitNegotiation(pc);
+
pc.createDataChannel('test');
- return awaitNegotiation(pc)
+ return negotiated
.then(({nextPromise}) => {
pc.addTransceiver('video');
return nextPromise;
@@ -162,19 +161,20 @@
*/
test_never_resolve(t => {
const pc = new RTCPeerConnection();
- const promise = awaitNegotiation(pc);
+ const negotiated = awaitNegotiation(pc);
- return pc.createOffer()
+ return pc.createOffer({ offerToReceiveAudio: true })
.then(offer => pc.setLocalDescription(offer))
- .then(() => {
+ .then(() => negotiated)
+ .then(({nextPromise}) => {
assert_equals(pc.signalingState, 'have-local-offer');
pc.createDataChannel('test');
- return promise;
+ return nextPromise;
});
}, 'negotiationneeded event should not fire if signaling state is not stable');
/*
- 4.3.1.6. Set the RTCSessionSessionDescription
+ 4.4.1.6. Set the RTCSessionSessionDescription
2.2.10. If connection's signaling state is now stable, update the negotiation-needed
flag. If connection's [[NegotiationNeeded]] slot was true both before and after
this update, queue a task that runs the following steps:
@@ -186,7 +186,7 @@
return assert_first_promise_fulfill_after_second(
awaitNegotiation(pc),
- pc.createOffer()
+ pc.createOffer({ offerToReceiveAudio: true })
.then(offer =>
pc.setLocalDescription(offer)
.then(() => {
diff --git a/webrtc/RTCPeerConnection-setRemoteDescription-offer.html b/webrtc/RTCPeerConnection-setRemoteDescription-offer.html
index 65f4730..7dbcb3a 100644
--- a/webrtc/RTCPeerConnection-setRemoteDescription-offer.html
+++ b/webrtc/RTCPeerConnection-setRemoteDescription-offer.html
@@ -58,7 +58,7 @@
return generateOffer({ data: true })
.then(offer =>
pc.setRemoteDescription(offer)
- .then(offer => {
+ .then(() => {
assert_equals(pc.signalingState, 'have-remote-offer');
assert_session_desc_equals(pc.remoteDescription, offer);
assert_session_desc_equals(pc.pendingRemoteDescription, offer);
@@ -127,9 +127,9 @@
return pc.createOffer()
.then(offer => pc.setLocalDescription(offer))
.then(() => generateOffer())
- .then(offer =>
+ .then(offer2 =>
promise_rejects(t, 'InvalidStateError',
- pc.setRemoteDescription(offer)));
+ pc.setRemoteDescription(offer2)));
}, 'Calling setRemoteDescription(offer) from have-local-offer state should reject with InvalidStateError');
</script>
diff --git a/webrtc/RTCPeerConnection-setRemoteDescription.html b/webrtc/RTCPeerConnection-setRemoteDescription.html
index 94e10de..f01fe08 100644
--- a/webrtc/RTCPeerConnection-setRemoteDescription.html
+++ b/webrtc/RTCPeerConnection-setRemoteDescription.html
@@ -57,7 +57,7 @@
type: 'bogus',
sdp: 'bogus'
}));
- }, 'setRemoteDescription with invalid type and invalid SDP should reject with TypeError')
+ }, 'setRemoteDescription with invalid type and invalid SDP should reject with TypeError');
promise_test(t => {
const pc = new RTCPeerConnection();
@@ -68,7 +68,7 @@
type: 'answer',
sdp: 'invalid'
}));
- }, 'setRemoteDescription() with invalid SDP and stable state should reject with InvalidStateError')
+ }, 'setRemoteDescription() with invalid SDP and stable state should reject with InvalidStateError');
/* Operations after returning to stable state */
@@ -79,21 +79,22 @@
test_state_change_event(t, pc,
['have-remote-offer', 'stable', 'have-remote-offer']);
- return generateOffer({ audio: true })
+ return pc2.createOffer({ offerToReceiveAudio: true })
.then(offer1 =>
pc.setRemoteDescription(offer1)
.then(() => pc.createAnswer())
.then(answer => pc.setLocalDescription(answer))
- .then(() => generateOffer({ data: true }))
- .then(offer2 =>
- pc.setRemoteDescription(offer2)
+ .then(() => pc2.createOffer({ offerToReceiveVideo: true }))
+ .then(offer2 => {
+ return pc.setRemoteDescription(offer2)
.then(() => {
assert_equals(pc.signalingState, 'have-remote-offer');
assert_session_desc_not_equals(offer1, offer2);
assert_session_desc_equals(pc.remoteDescription, offer2);
assert_session_desc_equals(pc.currentRemoteDescription, offer1);
assert_session_desc_equals(pc.pendingRemoteDescription, offer2);
- })));
+ });
+ }));
}, 'Calling setRemoteDescription() again after one round of remote-offer/local-answer should succeed');
promise_test(t => {
diff --git a/webrtc/RTCTrackEvent-constructor.html b/webrtc/RTCTrackEvent-constructor.html
index 7948442..d5c5f62 100644
--- a/webrtc/RTCTrackEvent-constructor.html
+++ b/webrtc/RTCTrackEvent-constructor.html
@@ -43,9 +43,9 @@
assert_array_equals(trackEvent.streams, []);
assert_equals(trackEvent.transceiver, transceiver);
- assert_equals(event.type, 'track');
- assert_false(event.bubbles);
- assert_false(event.cancelable);
+ assert_equals(trackEvent.type, 'track');
+ assert_false(trackEvent.bubbles);
+ assert_false(trackEvent.cancelable);
}, `new RTCTrackEvent() with valid receiver, track, transceiver should succeed`);
diff --git a/webrtc/coverage/RTCDTMFSender.txt b/webrtc/coverage/RTCDTMFSender.txt
new file mode 100644
index 0000000..aa30021
--- /dev/null
+++ b/webrtc/coverage/RTCDTMFSender.txt
@@ -0,0 +1,122 @@
+Coverage is based on the following editor draft:
+https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html
+
+7. insertDTMF
+
+ [Trivial]
+ - The tones parameter is treated as a series of characters.
+
+ [RTCDTMFSender-insertDTMF]
+ - The characters 0 through 9, A through D, #, and * generate the associated
+ DTMF tones.
+
+ [RTCDTMFSender-insertDTMF]
+ - The characters a to d MUST be normalized to uppercase on entry and are equivalent
+ to A to D.
+
+ [RTCDTMFSender-insertDTMF]
+ - As noted in [RTCWEB-AUDIO] Section 3, support for the characters 0 through 9,
+ A through D, #, and * are required.
+
+ [RTCDTMFSender-insertDTMF]
+ - The character ',' MUST be supported, and indicates a delay of 2 seconds before
+ processing the next character in the tones parameter.
+
+ [RTCDTMFSender-insertDTMF]
+ - All other characters (and only those other characters) MUST
+ be considered unrecognized.
+
+ [Trivial]
+ - The duration parameter indicates the duration in ms to use for each character passed
+ in the tones parameters.
+
+ [RTCDTMFSender-ontonechange]
+ - The duration cannot be more than 6000 ms or less than 40 ms.
+
+ [RTCDTMFSender-ontonechange]
+ - The default duration is 100 ms for each tone.
+
+ [RTCDTMFSender-ontonechange]
+ - The interToneGap parameter indicates the gap between tones in ms. The user agent
+ clamps it to at least 30 ms. The default value is 70 ms.
+
+ [Untestable]
+ - The browser MAY increase the duration and interToneGap times to cause the times
+ that DTMF start and stop to align with the boundaries of RTP packets but it MUST
+ not increase either of them by more than the duration of a single RTP audio packet.
+
+ [Trivial]
+ When the insertDTMF() method is invoked, the user agent MUST run the following steps:
+
+ [Trivial]
+ 1. let sender be the RTCRtpSender used to send DTMF.
+
+ [Trivial]
+ 2. Let transceiver be the RTCRtpTransceiver object associated with sender.
+
+ [RTCDTMFSender-insertDTMF]
+ 3. If transceiver.stopped is true, throw an InvalidStateError.
+
+ [RTCDTMFSender-insertDTMF]
+ 4. If transceiver.currentDirection is recvonly or inactive, throw an
+ InvalidStateError.
+
+ [Trivial]
+ 5. Let tones be the method's first argument.
+
+ [RTCDTMFSender-insertDTMF]
+ 6. If tones contains any unrecognized characters, throw an InvalidCharacterError.
+
+ [RTCDTMFSender-insertDTMF]
+ 7. Set the object's toneBuffer attribute to tones.
+
+ [RTCDTMFSender-ontonechange]
+ 8. If the value of the duration parameter is less than 40, set it to 40.
+
+ [RTCDTMFSender-ontonechange-long]
+ If, on the other hand, the value is greater than 6000, set it to 6000.
+
+ [RTCDTMFSender-ontonechange]
+ 9. If the value of the interToneGap parameter is less than 30, set it to 30.
+
+ [RTCDTMFSender-ontonechange]
+ 10. If toneBuffer is an empty string, abort these steps.
+
+ [RTCDTMFSender-ontonechange]
+ 11. If a Playout task is scheduled to be run; abort these steps;
+
+ [RTCDTMFSender-ontonechange]
+ otherwise queue a task that runs the following steps (Playout task):
+
+ [RTCDTMFSender-ontonechange]
+ 1. If transceiver.stopped is true, abort these steps.
+
+ [RTCDTMFSender-ontonechange]
+ 2. If transceiver.currentDirection is recvonly or inactive, abort these steps.
+
+ [RTCDTMFSender-ontonechange]
+ 3. If toneBuffer is an empty string, fire an event named tonechange with an
+ empty string at the RTCDTMFSender object and abort these steps.
+
+ [RTCDTMFSender-ontonechange]
+ 4. Remove the first character from toneBuffer and let that character be tone.
+
+ [Untestable]
+ 5. Start playout of tone for duration ms on the associated RTP media stream,
+ using the appropriate codec.
+
+ [RTCDTMFSender-ontonechange]
+ 6. Queue a task to be executed in duration + interToneGap ms from now that
+ runs the steps labelled Playout task.
+
+ [RTCDTMFSender-ontonechange]
+ 7. Fire an event named tonechange with a string consisting of tone at the
+ RTCDTMFSender object.
+
+Coverage Report
+
+ Tested 31
+ Not Tested 0
+ Untestable 1
+
+ Total 32
diff --git a/webstorage/resources/storage_session_window_noopener_second.html b/webstorage/resources/storage_session_window_noopener_second.html
new file mode 100644
index 0000000..7e47737
--- /dev/null
+++ b/webstorage/resources/storage_session_window_noopener_second.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>WebStorage Test: sessionStorage - second page</title>
+</head>
+<body>
+<script>
+
+var storage = window.sessionStorage;
+
+var assertions = [];
+
+assertions.push({
+ actual: storage.getItem("FOO"),
+ expected: null,
+ message: "storage.getItem('FOO')"
+});
+
+storage.setItem("FOO", "BAR-NEWWINDOW");
+
+assertions.push({
+ actual: storage.getItem("FOO"),
+ expected: "BAR-NEWWINDOW",
+ message: "value for FOO after changing"
+});
+
+let channel = new BroadcastChannel('storage_session_window_noopener');
+channel.postMessage(assertions, '*');
+
+window.close();
+
+</script>
+</body>
+</html>
diff --git a/webstorage/storage_session_window_noopener.html b/webstorage/storage_session_window_noopener.html
new file mode 100644
index 0000000..034402f
--- /dev/null
+++ b/webstorage/storage_session_window_noopener.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>WebStorage Test: sessionStorage - open a new window with noopener</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+async_test(function(t) {
+
+ var storage = window.sessionStorage;
+ storage.clear();
+
+ storage.setItem("FOO", "BAR");
+
+ let channel = new BroadcastChannel("storage_session_window_noopener");
+ channel.addEventListener("message", t.step_func(function(e) {
+ e.data.forEach(t.step_func(function(assertion) {
+ assert_equals(assertion.actual, assertion.expected, assertion.message);
+ }));
+ assert_equals(storage.getItem("FOO"), "BAR", "value for FOO in original window");
+ t.done();
+ }));
+
+ var win = window.open("resources/storage_session_window_noopener_second.html",
+ "_blank",
+ "noopener");
+
+}, "A new noopener window to make sure there is a not copy of the previous window's sessionStorage");
+
+</script>
+</body>
+</html>
diff --git a/webusb/usb-allowed-by-feature-policy.https.sub.html.headers b/webusb/usb-allowed-by-feature-policy.https.sub.html.headers
index f10a282..5c7eac0 100644
--- a/webusb/usb-allowed-by-feature-policy.https.sub.html.headers
+++ b/webusb/usb-allowed-by-feature-policy.https.sub.html.headers
@@ -1 +1 @@
-Feature-Policy: {"usb": ["*"]}
+Feature-Policy: usb *
diff --git a/webusb/usb-disabled-by-feature-policy.https.sub.html.headers b/webusb/usb-disabled-by-feature-policy.https.sub.html.headers
index 24332c7..4fd1e26 100644
--- a/webusb/usb-disabled-by-feature-policy.https.sub.html.headers
+++ b/webusb/usb-disabled-by-feature-policy.https.sub.html.headers
@@ -1 +1 @@
-Feature-Policy: {"usb": []}
+Feature-Policy: usb 'none'
diff --git a/webvr/webvr-disabled-by-feature-policy.https.sub.html b/webvr/webvr-disabled-by-feature-policy.https.sub.html
new file mode 100644
index 0000000..567499c
--- /dev/null
+++ b/webvr/webvr-disabled-by-feature-policy.https.sub.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+
+ <script>
+ 'use strict';
+ var same_origin_src = '/feature-policy/resources/feature-policy-webvr.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var header = 'Feature-Policy header vr "none"';
+
+ promise_test(() => {
+ return navigator.getVRDisplays().then(() => {
+ assert_unreached('expected promise to reject');
+ }, error => {
+ });
+ }, header + ' disallows the top-level document.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'navigator.getVRDisplays()', t, same_origin_src,
+ expect_feature_unavailable_default);
+ }, header + ' disallows same-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'navigator.getVRDisplays()', t, cross_origin_src,
+ expect_feature_unavailable_default);
+ }, header + ' disallows cross-origin iframes.');
+ </script>
+</body>
diff --git a/webvr/webvr-disabled-by-feature-policy.https.sub.html.headers b/webvr/webvr-disabled-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000..d021af7
--- /dev/null
+++ b/webvr/webvr-disabled-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: vr 'none'
diff --git a/webvr/webvr-enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html b/webvr/webvr-enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html
new file mode 100644
index 0000000..da01daf
--- /dev/null
+++ b/webvr/webvr-enabled-by-feature-policy-attribute-redirect-on-load.https.sub.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script>
+ 'use strict';
+ var relative_path = '/feature-policy/resources/feature-policy-webvr.html';
+ var base_src = '/feature-policy/resources/redirect-on-load.html#';
+ var same_origin_src = base_src + relative_path;
+ var cross_origin_src = base_src + 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ relative_path;
+ var header = 'Feature-Policy allow="vr" attribute';
+
+ async_test(t => {
+ test_feature_availability(
+ 'navigator.getVRDisplays()', t, same_origin_src,
+ expect_feature_available_default, 'vr');
+ }, header + ' allows same-origin relocation');
+
+ async_test(t => {
+ test_feature_availability(
+ 'navigator.getVRDisplays()', t, cross_origin_src,
+ expect_feature_unavailable_default, 'vr');
+ }, header + ' disallows cross-origin relocation');
+ </script>
+</body>
diff --git a/webvr/webvr-enabled-by-feature-policy-attribute.https.sub.html b/webvr/webvr-enabled-by-feature-policy-attribute.https.sub.html
new file mode 100644
index 0000000..d715f90
--- /dev/null
+++ b/webvr/webvr-enabled-by-feature-policy-attribute.https.sub.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script>
+ 'use strict';
+ var same_origin_src = '/feature-policy/resources/feature-policy-webvr.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var header = 'Feature-Policy allow="vr" attribute';
+
+ async_test(t => {
+ test_feature_availability(
+ 'navigator.getVRDisplays()', t, same_origin_src,
+ expect_feature_available_default, 'vr');
+ }, header + ' allows same-origin iframe');
+
+ async_test(t => {
+ test_feature_availability(
+ 'navigator.getVRDisplays()', t, cross_origin_src,
+ expect_feature_available_default, 'vr');
+ }, header + ' allows cross-origin iframe');
+ </script>
+</body>
diff --git a/webvr/webvr-enabled-by-feature-policy.https.sub.html b/webvr/webvr-enabled-by-feature-policy.https.sub.html
new file mode 100644
index 0000000..ee02566
--- /dev/null
+++ b/webvr/webvr-enabled-by-feature-policy.https.sub.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+
+ <script>
+ 'use strict';
+ var same_origin_src = '/feature-policy/resources/feature-policy-webvr.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var header = 'Feature-Policy header vr *';
+
+ promise_test(
+ () => navigator.getVRDisplays(),
+ header + ' allows the top-level document.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'navigator.getVRDisplays()', t, same_origin_src,
+ expect_feature_available_default);
+ }, header + ' allows same-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'navigator.getVRDisplays()', t, cross_origin_src,
+ expect_feature_available_default);
+ }, header + ' allows cross-origin iframes.');
+ </script>
+</body>
diff --git a/webvr/webvr-enabled-by-feature-policy.https.sub.html.headers b/webvr/webvr-enabled-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000..e7427ee
--- /dev/null
+++ b/webvr/webvr-enabled-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: vr *
diff --git a/webvr/webvr-enabled-on-self-origin-by-feature-policy.https.sub.html b/webvr/webvr-enabled-on-self-origin-by-feature-policy.https.sub.html
new file mode 100644
index 0000000..bd7e82f
--- /dev/null
+++ b/webvr/webvr-enabled-on-self-origin-by-feature-policy.https.sub.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+
+ <script>
+ 'use strict';
+ var same_origin_src = '/feature-policy/resources/feature-policy-webvr.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var header = 'Feature-Policy header vr "self"';
+
+ promise_test(
+ () => navigator.getVRDisplays(),
+ header + ' allows the top-level document.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'navigator.getVRDisplays()', t, same_origin_src,
+ expect_feature_available_default);
+ }, header + ' allows same-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'navigator.getVRDisplays()', t, cross_origin_src,
+ expect_feature_unavailable_default);
+ }, header + ' disallows cross-origin iframes.');
+ </script>
+</body>
diff --git a/webvr/webvr-enabled-on-self-origin-by-feature-policy.https.sub.html.headers b/webvr/webvr-enabled-on-self-origin-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000..87d343d
--- /dev/null
+++ b/webvr/webvr-enabled-on-self-origin-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: vr 'self'
diff --git a/webvtt/api/VTTCue/align.html b/webvtt/api/VTTCue/align.html
index f06a172..e3a920a 100644
--- a/webvtt/api/VTTCue/align.html
+++ b/webvtt/api/VTTCue/align.html
@@ -3,6 +3,7 @@
<link rel="help" href="https://w3c.github.io/webvtt/#dom-vttcue-align">
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
+<script src=common.js></script>
<div id=log></div>
<script>
test(function(){
@@ -56,10 +57,10 @@
t.onerror = this.step_func(function() {
assert_unreached('got error event');
});
- t.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest\n\n'+
- '00:00:00.000 --> 00:00:00.001 align:start\ntest\n\n'+
- '00:00:00.000 --> 00:00:00.001 align:center\ntest\n\n'+
- '00:00:00.000 --> 00:00:00.001 align:end\ntest');
+ t.src = make_vtt_track('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest\n\n'+
+ '00:00:00.000 --> 00:00:00.001 align:start\ntest\n\n'+
+ '00:00:00.000 --> 00:00:00.001 align:center\ntest\n\n'+
+ '00:00:00.000 --> 00:00:00.001 align:end\ntest', this);
t.track.mode = 'showing';
video.appendChild(t);
});
diff --git a/webvtt/api/VTTCue/common.js b/webvtt/api/VTTCue/common.js
new file mode 100644
index 0000000..2c39352
--- /dev/null
+++ b/webvtt/api/VTTCue/common.js
@@ -0,0 +1,8 @@
+function make_vtt_track(contents, test) {
+ var track_blob = new Blob([contents], { type: 'text/vtt' });
+ var track_url = URL.createObjectURL(track_blob);
+ test.add_cleanup(function() {
+ URL.revokeObjectURL(track_url);
+ });
+ return track_url;
+}
diff --git a/webvtt/api/VTTCue/line.html b/webvtt/api/VTTCue/line.html
index e0c4ddc..dcf46db 100644
--- a/webvtt/api/VTTCue/line.html
+++ b/webvtt/api/VTTCue/line.html
@@ -3,6 +3,7 @@
<link rel="help" href="https://w3c.github.io/webvtt/#dom-vttcue-line">
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
+<script src=common.js></script>
<div id=log></div>
<script>
test(function(){
@@ -55,9 +56,9 @@
t.onerror = this.step_func(function() {
assert_unreached('got error event');
});
- t.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest\n\n'+
- '00:00:00.000 --> 00:00:00.001 line:0\ntest\n\n'+
- '00:00:00.000 --> 00:00:00.001 line:0%\ntest');
+ t.src = make_vtt_track('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest\n\n'+
+ '00:00:00.000 --> 00:00:00.001 line:0\ntest\n\n'+
+ '00:00:00.000 --> 00:00:00.001 line:0%\ntest', this);
t.track.mode = 'showing';
video.appendChild(t);
});
diff --git a/webvtt/api/VTTCue/snapToLines.html b/webvtt/api/VTTCue/snapToLines.html
index ac9f843..b3f9f34 100644
--- a/webvtt/api/VTTCue/snapToLines.html
+++ b/webvtt/api/VTTCue/snapToLines.html
@@ -3,6 +3,7 @@
<link rel="help" href="https://w3c.github.io/webvtt/#dom-vttcue-snaptolines">
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
+<script src=common.js></script>
<div id=log></div>
<script>
setup(function(){
@@ -90,9 +91,9 @@
t.onerror = this.step_func(function() {
assert_unreached('got error event');
});
- t.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest\n\n'+
- '00:00:00.000 --> 00:00:00.001 line:0\ntest\n\n'+
- '00:00:00.000 --> 00:00:00.001 line:0%\ntest');
+ t.src = make_vtt_track('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest\n\n'+
+ '00:00:00.000 --> 00:00:00.001 line:0\ntest\n\n'+
+ '00:00:00.000 --> 00:00:00.001 line:0%\ntest', this);
t.track.mode = 'showing';
video.appendChild(t);
});
diff --git a/webvtt/api/VTTCue/text.html b/webvtt/api/VTTCue/text.html
index 8ceb3a6..a61c600 100644
--- a/webvtt/api/VTTCue/text.html
+++ b/webvtt/api/VTTCue/text.html
@@ -3,6 +3,7 @@
<link rel="help" href="https://w3c.github.io/webvtt/#dom-vttcue-text">
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
+<script src=common.js></script>
<div id=log></div>
<script>
setup(function(){
@@ -32,8 +33,8 @@
t.onerror = this.step_func(function() {
assert_unreached('got error event');
});
- t.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\n'+
- '\n\nfoobar\n00:00:00.000 --> 00:00:00.001\ntest');
+ t.src = make_vtt_track('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\n'+
+ '\n\nfoobar\n00:00:00.000 --> 00:00:00.001\ntest', this);
t.track.mode = 'showing';
video.appendChild(t);
});
diff --git a/webvtt/api/VTTCue/vertical.html b/webvtt/api/VTTCue/vertical.html
index 63ca161..8b93f6b 100644
--- a/webvtt/api/VTTCue/vertical.html
+++ b/webvtt/api/VTTCue/vertical.html
@@ -3,6 +3,7 @@
<link rel="help" href="https://w3c.github.io/webvtt/#dom-vttcue-vertical">
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
+<script src=common.js></script>
<div id=log></div>
<script>
setup(function(){
@@ -47,9 +48,9 @@
t.onerror = this.step_func(function() {
assert_unreached('got error event');
});
- t.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest\n\n'+
- '00:00:00.000 --> 00:00:00.001 vertical:rl\ntest\n\n'+
- '00:00:00.000 --> 00:00:00.001 vertical:lr\ntest');
+ t.src = make_vtt_track('WEBVTT\n\n00:00:00.000 --> 00:00:00.001\ntest\n\n'+
+ '00:00:00.000 --> 00:00:00.001 vertical:rl\ntest\n\n'+
+ '00:00:00.000 --> 00:00:00.001 vertical:lr\ntest', this);
t.track.mode = 'showing';
video.appendChild(t);
});
diff --git a/webvtt/api/historical.html b/webvtt/api/historical.html
new file mode 100644
index 0000000..8f6c09b
--- /dev/null
+++ b/webvtt/api/historical.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<title>Historical WebVTT APIs must not be supported</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+// Also see /html/semantics/embedded-content/media-elements/historical.html
+
+[
+ // https://github.com/w3c/webvtt/pull/31
+ ['VTTCue', 'regionId'],
+ ['TextTrack', 'regions'],
+ ['TextTrack', 'addRegion'],
+ ['TextTrack', 'removeRegion'],
+ ['VTTRegion', 'track'],
+ // id re-introduced in https://github.com/w3c/webvtt/pull/349/files
+
+].forEach(function(feature) {
+ var interf = feature[0];
+ var member = feature[1];
+ test(function() {
+ assert_true(interf in window, interf + ' is not supported');
+ assert_false(member in window[interf].prototype);
+ }, interf + ' ' + member + ' member must be nuked');
+});
+
+[
+ // https://github.com/w3c/webvtt/pull/31
+ 'VTTRegionList',
+
+].forEach(function(interf) {
+ test(function() {
+ assert_false(interf in window);
+ }, interf + ' interface must be nuked');
+});
+</script>
diff --git a/webvtt/api/interfaces.html b/webvtt/api/interfaces.html
index 5fb5662..5e112da 100644
--- a/webvtt/api/interfaces.html
+++ b/webvtt/api/interfaces.html
@@ -123,7 +123,8 @@
enum LineAlignSetting { "start", "center", "end" };
enum PositionAlignSetting { "line-left", "center", "line-right", "auto" };
enum AlignSetting { "start", "center", "end", "left", "right" };
-[Constructor(double startTime, double endTime, DOMString text)]
+[Exposed=Window,
+ Constructor(double startTime, double endTime, DOMString text)]
interface VTTCue : TextTrackCue {
attribute VTTRegion? region;
attribute DirectionSetting vertical;
@@ -139,7 +140,8 @@
};
enum ScrollSetting { "" /* none */, "up" };
-[Constructor]
+[Exposed=Window,
+ Constructor]
interface VTTRegion {
attribute DOMString id;
attribute double width;
diff --git a/webvtt/parsing/cue-text-parsing/common.js b/webvtt/parsing/cue-text-parsing/common.js
index 543fe46..c72bdc0 100644
--- a/webvtt/parsing/cue-text-parsing/common.js
+++ b/webvtt/parsing/cue-text-parsing/common.js
@@ -154,7 +154,14 @@
t.test_id = test.name;
t.url_encoded_input = test.input;
t.expected = expected;
- track.src = 'data:text/vtt,'+encodeURIComponent('WEBVTT\n\n00:00.000 --> 00:01.000\n')+test.input;
+ var track_blob = new Blob(['WEBVTT\n\n00:00.000 --> 00:01.000\n',
+ decodeURIComponent(test.input)],
+ { type: 'text/vtt' });
+ var track_url = URL.createObjectURL(track_blob);;
+ track.src = track_url;
+ t.add_cleanup(function() {
+ URL.revokeObjectURL(track_url);
+ });
track['default'] = true;
track.kind = 'subtitles';
track.onload = t.step_func(trackLoaded);
diff --git a/workers/semantics/interface-objects/002.worker.js b/workers/semantics/interface-objects/002.worker.js
index 8fc0b6a..2c5f5a5 100644
--- a/workers/semantics/interface-objects/002.worker.js
+++ b/workers/semantics/interface-objects/002.worker.js
@@ -41,6 +41,9 @@
"InputEvent",
"KeyboardEvent",
"CompositionEvent",
+ // https://w3c.github.io/webvtt/
+ "VTTCue",
+ "VTTRegion",
];
for (var i = 0; i < unexpected.length; ++i) {
test(function () {