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&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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` => `&#10;`, `<` => `&lt;`), and
+  // double-escape the "escaped" content.
+  var rawBrace = "&lt;";
+  var escapedBrace = "&amp;lt;";
+  var doubleEscapedBrace = "&amp;amp;lt;";
+  var rawNewline = "&#10;";
+  var escapedNewline = "&amp;#10;";
+  var doubleEscapedNewline = "&amp;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(&quot;loaded&quot;, &quot;*&quot;);'
+              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
+              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(&quot;loaded&quot;, &quot;*&quot;);'
+              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
+              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(&quot;loaded&quot;, &quot;*&quot;);'
+              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
+              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(&quot;loaded&quot;, &quot;*&quot;);'
+              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
+              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(&quot;loaded&quot;, &quot;*&quot;);'
+              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
+              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(&quot;loaded&quot;, &quot;*&quot;);'
+              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
+              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(&quot;loaded&quot;, &quot;*&quot;);'
+              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
+              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(&quot;loaded&quot;, &quot;*&quot;);'
+              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
+              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(&quot;loaded&quot;, &quot;*&quot;);'
+              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
+              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(&quot;loaded&quot;, &quot;*&quot;);'
+              onerror='window.parent.postMessage(&quot;error&quot;, &quot;*&quot;);'
+              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 () {