Merge pull request #9414 from w3c/gwhit-css-1959

Test for font-variation-settings
diff --git a/2dcontext/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html b/2dcontext/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html
index 5f1c7fa..1621c03 100644
--- a/2dcontext/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html
+++ b/2dcontext/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html
@@ -8,34 +8,29 @@
 <body class="show_output">
 
 <h1>2d.drawImage.zerocanvas</h1>
-<p class="desc"></p>
+<p class="desc">drawImage with zero-sized canvas as the source shoud throw exception</p>
 
 
 <p class="output">Actual output:</p>
 <canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
-<p class="output expectedtext">Expected output:<p><img src="/images/green-100x50.png" class="output expected" id="expected" alt="">
+
 <ul id="d"></ul>
 <script>
-var t = async_test("");
+var t = async_test("drawImage with zero-sized canvas as the source shoud throw exception");
 _addTest(function(canvas, ctx) {
 
-ctx.fillStyle = '#0f0';
-ctx.fillRect(0, 0, 100, 50);
-
 var canvas2 = document.createElement('canvas');
 canvas2.width = 0;
-canvas2.height = 10;
-ctx.drawImage(canvas2, 0, 0);
+canvas2.height = 50;
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(canvas2, 0, 0); });
 
-canvas2.width = 10;
+canvas2.width = 50;
 canvas2.height = 0;
-ctx.drawImage(canvas2, 0, 0);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(canvas2, 0, 0); });
 
 canvas2.width = 0;
 canvas2.height = 0;
-ctx.drawImage(canvas2, 0, 0);
-
-_assertPixelApprox(canvas, 50,25, 0,255,0,255, "50,25", "0,255,0,255", 2);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(canvas2, 0, 0); });
 
 
 });
diff --git a/2dcontext/tools/spec.yaml b/2dcontext/tools/spec.yaml
index 278dc95..e57bbc8 100644
--- a/2dcontext/tools/spec.yaml
+++ b/2dcontext/tools/spec.yaml
@@ -586,7 +586,7 @@
     text: "If the image argument is <...> an HTMLVideoElement object whose readyState attribute is either HAVE_NOTHING or HAVE_METADATA<^>, then the implementation *must* return without drawing anything."
   - id: 2d.drawImage.zerocanvas
     previously: [ 10, "dw and dh" ]
-    text: "If the image argument is an HTMLCanvasElement object with either a horizontal dimension or a vertical dimension equal to zero, then the implementation *must* return without drawing anything<^>."
+    text: "If the image argument is an HTMLCanvasElement or an OffscreenCanvas object with either a horizontal dimension or a vertical dimension equal to zero, then the implementation *must* throw an INVALID_STATE_ERR exception<^>."
   - id: 2d.drawImage.zerosource
     text: "If one of the sw or sh arguments is zero<^>, the implementation *must* return without drawing anything."
   - id: 2d.drawImage.paint
diff --git a/2dcontext/tools/tests2d.yaml b/2dcontext/tools/tests2d.yaml
index 7da08f9..f7b858a 100644
--- a/2dcontext/tools/tests2d.yaml
+++ b/2dcontext/tools/tests2d.yaml
@@ -9002,27 +9002,22 @@
   expected: green
 
 - name: 2d.drawImage.zerocanvas
+  desc: drawImage with zero-sized canvas as the source shoud throw exception
   testing:
     - 2d.drawImage.zerocanvas
   code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
-
     var canvas2 = document.createElement('canvas');
     canvas2.width = 0;
-    canvas2.height = 10;
-    ctx.drawImage(canvas2, 0, 0);
+    canvas2.height = 50;
+    @assert throws INVALID_STATE_ERR ctx.drawImage(canvas2, 0, 0);
 
-    canvas2.width = 10;
+    canvas2.width = 50;
     canvas2.height = 0;
-    ctx.drawImage(canvas2, 0, 0);
+    @assert throws INVALID_STATE_ERR ctx.drawImage(canvas2, 0, 0);
 
     canvas2.width = 0;
     canvas2.height = 0;
-    ctx.drawImage(canvas2, 0, 0);
-
-    @assert pixel 50,25 ==~ 0,255,0,255;
-  expected: green
+    @assert throws INVALID_STATE_ERR ctx.drawImage(canvas2, 0, 0);
 
 - name: 2d.drawImage.svg
   desc: drawImage() of an SVG image
diff --git a/IndexedDB/interleaved-cursors.html b/IndexedDB/interleaved-cursors-common.js
similarity index 93%
rename from IndexedDB/interleaved-cursors.html
rename to IndexedDB/interleaved-cursors-common.js
index 3112690..a76ec52 100644
--- a/IndexedDB/interleaved-cursors.html
+++ b/IndexedDB/interleaved-cursors-common.js
@@ -1,12 +1,5 @@
-<!doctype html>
-<meta charset="utf-8">
-<meta name="timeout" content="long">
-<title>IndexedDB: Interleaved iteration of multiple cursors</title>
-<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="support-promises.js"></script>
-<script>
+// Infrastructure shared by interleaved-cursors-{small,large}.html
+
 // Number of objects that each iterator goes over.
 const itemCount = 10;
 
@@ -169,7 +162,7 @@
   });
 }
 
-for (let cursorCount of [1, 10, 100, 500]) {
+function cursorTest(cursorCount) {
   promise_test(testCase => {
     return createDatabase(testCase, (database, transaction) => {
       const store = database.createObjectStore('cache',
@@ -193,4 +186,3 @@
     });
   }, `${cursorCount} cursors`);
 }
-</script>
diff --git a/IndexedDB/interleaved-cursors-large.html b/IndexedDB/interleaved-cursors-large.html
new file mode 100644
index 0000000..6f4e440
--- /dev/null
+++ b/IndexedDB/interleaved-cursors-large.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>IndexedDB: Interleaved iteration of multiple cursors</title>
+<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support-promises.js"></script>
+<script src="interleaved-cursors-common.js"></script>
+<script>
+cursorTest(250);
+</script>
diff --git a/IndexedDB/interleaved-cursors-small.html b/IndexedDB/interleaved-cursors-small.html
new file mode 100644
index 0000000..a4c4777
--- /dev/null
+++ b/IndexedDB/interleaved-cursors-small.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>IndexedDB: Interleaved iteration of multiple cursors</title>
+<link rel="author" href="pwnall@chromium.org" title="Victor Costan">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support-promises.js"></script>
+<script src="interleaved-cursors-common.js"></script>
+<script>
+cursorTest(1);
+cursorTest(10);
+cursorTest(100);
+</script>
diff --git a/README.md b/README.md
index 0247e8c..90f1f47 100644
--- a/README.md
+++ b/README.md
@@ -32,17 +32,22 @@
 and read the [Windows Notes](#windows-notes) section below.
 
 To get the tests running, you need to set up the test domains in your
-[`hosts` file](http://en.wikipedia.org/wiki/Hosts_%28file%29%23Location_in_the_file_system). The
-following entries are required:
+[`hosts` file](http://en.wikipedia.org/wiki/Hosts_%28file%29%23Location_in_the_file_system).
 
+The necessary content can be generated with `./wpt make-hosts-file`; on
+Windows, you will need to preceed the prior command with `python` or
+the path to the Python binary (`python wpt make-hosts-file`).
+
+For example, on most UNIX-like systems, you can setup the hosts file with:
+
+```bash
+./wpt make-hosts-file | sudo tee -a /etc/hosts
 ```
-127.0.0.1   web-platform.test
-127.0.0.1   www.web-platform.test
-127.0.0.1   www1.web-platform.test
-127.0.0.1   www2.web-platform.test
-127.0.0.1   xn--n8j6ds53lwwkrqhv28a.web-platform.test
-127.0.0.1   xn--lve-6lad.web-platform.test
-0.0.0.0     nonexistent-origin.web-platform.test
+
+And on Windows (note this requires an Administrator privileged shell):
+
+```bash
+python wpt make-hosts-file >> %SystemRoot%\System32\drivers\etc\hosts
 ```
 
 If you are behind a proxy, you also need to make sure the domains above are
diff --git a/common/css-paint-tests.js b/common/worklet-reftest.js
similarity index 71%
rename from common/css-paint-tests.js
rename to common/worklet-reftest.js
index cd57332..e060835 100644
--- a/common/css-paint-tests.js
+++ b/common/worklet-reftest.js
@@ -1,12 +1,12 @@
 // To make sure that we take the snapshot at the right time, we do double
 // requestAnimationFrame. In the second frame, we take a screenshot, that makes
 // sure that we already have a full frame.
-function importPaintWorkletAndTerminateTestAfterAsyncPaint(code) {
-    if (typeof CSS.paintWorklet == "undefined") {
+function importWorkletAndTerminateTestAfterAsyncPaint(worklet, code) {
+    if (typeof worklet == "undefined") {
         takeScreenshot();
     } else {
         var blob = new Blob([code], {type: 'text/javascript'});
-        CSS.paintWorklet.addModule(URL.createObjectURL(blob)).then(function() {
+        worklet.addModule(URL.createObjectURL(blob)).then(function() {
             requestAnimationFrame(function() {
                 requestAnimationFrame(function() {
                     takeScreenshot();
diff --git a/compat/webkit-appearance.tentative.html b/compat/webkit-appearance.tentative.html
new file mode 100644
index 0000000..8c0273d
--- /dev/null
+++ b/compat/webkit-appearance.tentative.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<title>-webkit-appearance support</title>
+<!-- There is no spec for -webkit-appearance. It is supported in Opera, Safari,
+     Chrome, and Edge. Firefox has expressed intent to support it. -->
+<link rel="help" href="https://github.com/whatwg/compat/issues/6">
+<meta name="assert" content="This test checks for support of the -webkit-appearance CSS attribute." />
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="tester"></div>
+
+<script>
+const WEBKIT_APPEARANCE_VALUES = [
+  'none',
+  'checkbox',
+  'radio',
+  'push-button',
+  'square-button',
+  'button',
+  'button-bevel',
+  'inner-spin-button',
+  'listbox',
+  'listitem',
+  'media-enter-fullscreen-button',
+  'media-exit-fullscreen-button',
+  'media-mute-button',
+  'media-play-button',
+  'media-overlay-play-button',
+  'media-toggle-closed-captions-button',
+  'media-slider',
+  'media-sliderthumb',
+  'media-volume-slider-container',
+  'media-volume-slider',
+  'media-volume-sliderthumb',
+  'media-controls-background',
+  'media-controls-fullscreen-background',
+  'media-current-time-display',
+  'media-time-remaining-display',
+  'menulist',
+  'menulist-button',
+  'menulist-text',
+  'menulist-textfield',
+  'meter',
+  'progress-bar',
+  'progress-bar-value',
+  'slider-horizontal',
+  'slider-vertical',
+  'sliderthumb-horizontal',
+  'sliderthumb-vertical',
+  'caret',
+  'searchfield',
+  'searchfield-cancel-button',
+  'textfield',
+  'textarea',
+];
+
+for (const appearance_value of WEBKIT_APPEARANCE_VALUES) {
+  test(() => {
+    const div = document.getElementById('tester');
+    div.style = `-webkit-appearance: ${appearance_value}`;
+    assert_equals(getComputedStyle(div).webkitAppearance, appearance_value);
+  });
+}
+</script>
diff --git a/css/CSS2/normal-flow/margin-collapse-through-zero-height-block.html b/css/CSS2/normal-flow/margin-collapse-through-zero-height-block.html
new file mode 100644
index 0000000..471a4c7
--- /dev/null
+++ b/css/CSS2/normal-flow/margin-collapse-through-zero-height-block.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Collapse bottom margin from previous sibling through zero height block to next sibling</title>
+<link rel="author" title="Morten Stenshorne" href="mstensho@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/CSS22/box.html#collapsing-margins" title="8.3.1 Collapsing margins">
+<link rel="match" href="../../reference/ref-filled-green-200px-square.html">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="overflow:hidden; width:200px; height:400px; background:green;">
+  <div style="margin-bottom:200px;"></div>
+  <div style="height:0;"></div>
+  <div style="height:200px; margin-top:100px; background:white;"></div>
+  <div style="height:200px; background:red;"></div>
+</div>
diff --git a/css/css-align/content-distribution/place-content-shorthand-004.html b/css/css-align/content-distribution/place-content-shorthand-004.html
index 30f45b6..3f9bd88 100644
--- a/css/css-align/content-distribution/place-content-shorthand-004.html
+++ b/css/css-align/content-distribution/place-content-shorthand-004.html
@@ -48,6 +48,12 @@
     }, "Verify 'auto' values are invalid");
 
     test(function() {
+        checkInvalidValues("self-start")
+        checkInvalidValues("center self-end")
+        checkInvalidValues("self-end start")
+    }, "Verify self-position values are invalid");
+
+    test(function() {
         checkInvalidValues("")
     }, "Verify empty declaration is invalid");
 </script>
diff --git a/css/css-align/default-alignment/parse-justify-items-002.html b/css/css-align/default-alignment/parse-justify-items-002.html
index 6ba2c7c..1806fa2 100644
--- a/css/css-align/default-alignment/parse-justify-items-002.html
+++ b/css/css-align/default-alignment/parse-justify-items-002.html
@@ -19,39 +19,39 @@
 test(function() {
     element = document.createElement("div");
     document.body.appendChild(element);
-    checkValues(element, "justifyItems", "justify-items", "", "legacy");
+    checkValues(element, "justifyItems", "justify-items", "", "normal");
 }, "Test 'initial' value when nothing is specified");
 
 test(function() {
     container.style.display = "";
-    checkInitialValues(element, "justifyItems", "justify-items", "center", "legacy");
+    checkInitialValues(element, "justifyItems", "justify-items", "center", "normal");
 }, "Test justify-items: 'initial'");
 
 test(function() {
     container.style.display = "grid";
-    checkInitialValues(element, "justifyItems", "justify-items", "safe start", "legacy");
+    checkInitialValues(element, "justifyItems", "justify-items", "safe start", "normal");
 }, "Test grid items justify-items: 'initial'");
 
 test(function() {
     container.style.display = "flex";
-    checkInitialValues(element, "justifyItems", "justify-items", "unsafe end", "legacy");
+    checkInitialValues(element, "justifyItems", "justify-items", "unsafe end", "normal");
 }, "Test flex items justify-items: 'initial'");
 
 test(function() {
     container.style.display = "";
     element.style.position = "absolute";
-    checkInitialValues(element, "justifyItems", "justify-items", "start", "legacy");
+    checkInitialValues(element, "justifyItems", "justify-items", "start", "normal");
 }, "Test absolute positioned elements justify-items: 'initial'");
 
 test(function() {
     container.style.display = "grid";
     element.style.position = "absolute";
-    checkInitialValues(element, "justifyItems", "justify-items", "end", "legacy");
+    checkInitialValues(element, "justifyItems", "justify-items", "end", "normal");
 }, "Test absolute positioned grid items justify-items: 'initial'");
 
 test(function() {
     container.style.display = "flex";
     element.style.position = "absolute";
-    checkInitialValues(element, "justifyItems", "justify-items", "end", "legacy");
+    checkInitialValues(element, "justifyItems", "justify-items", "end", "normal");
 }, "Test absolute positioned flex items justify-items: 'initial'");
 </script>
diff --git a/css/css-align/default-alignment/place-items-shorthand-004.html b/css/css-align/default-alignment/place-items-shorthand-004.html
index c5d9472..aa3ff30 100644
--- a/css/css-align/default-alignment/place-items-shorthand-004.html
+++ b/css/css-align/default-alignment/place-items-shorthand-004.html
@@ -37,7 +37,18 @@
        checkInvalidValues("auto")
        checkInvalidValues("auto right")
        checkInvalidValues("auto auto")
-    }, "Verify 'auto' value is invalid as first longhand value.");
+       checkInvalidValues("center auto")
+    }, "Verify 'auto' value is invalid.");
+
+    test(function() {
+       checkInvalidValues("legacy")
+       checkInvalidValues("legacy start")
+       checkInvalidValues("end legacy")
+       checkInvalidValues("legacy left")
+       checkInvalidValues("center legacy")
+       checkInvalidValues("start legacy center")
+    }, "Verify 'legacy' value is invalid.");
+
 
     test(function() {
         checkInvalidValues("")
diff --git a/css/css-color/color-function-parsing.html b/css/css-color/color-function-parsing.html
new file mode 100644
index 0000000..7f483bb
--- /dev/null
+++ b/css/css-color/color-function-parsing.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 4: color() parsing</title>
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#color-function">
+<meta name="assert" content="Tests basic parsing of the color function">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="test"></div>
+<script>
+    const div = document.querySelector("#test");
+    function testColorFunction(description, rule, expectedValue) {
+        test(function() {
+            div.style.color = "black";
+            div.style.color = rule;
+            assert_equals(getComputedStyle(div).color, expectedValue);
+        }, description);
+    }
+
+    testColorFunction("Basic sRGB white", "color(srgb 1 1 1)", "color(srgb 1 1 1)");
+    testColorFunction("White with lots of space", "color(    srgb         1      1 1       )", "color(srgb 1 1 1)");
+    testColorFunction("sRGB color", "color(srgb 0.25 0.5 0.75)", "color(srgb 0.25 0.5 0.75)");
+    testColorFunction("Different case for sRGB", "color(SrGb 0.25 0.5 0.75)", "color(srgb 0.25 0.5 0.75)");
+    testColorFunction("sRGB color with unnecessary decimals", "color(srgb 1.00000 0.500000 0.20)", "color(srgb 1 0.5 0.2)");
+    testColorFunction("sRGB white with 0.5 alpha", "color(srgb 1 1 1 / 0.5)", "color(srgb 1 1 1 / 0.5)");
+    testColorFunction("sRGB white with 0 alpha", "color(srgb 1 1 1 / 0)", "color(srgb 1 1 1 / 0)");
+    testColorFunction("sRGB white with 50% alpha", "color(srgb 1 1 1 / 50%)", "color(srgb 1 1 1 / 0.5)");
+    testColorFunction("sRGB white with 0% alpha", "color(srgb 1 1 1 / 0%)", "color(srgb 1 1 1 / 0)");
+    testColorFunction("One missing component is 0", "color(srgb 1 1)", "color(srgb 1 1 0)");
+    testColorFunction("Two missing components are 0", "color(srgb 1)", "color(srgb 1 0 0)");
+    testColorFunction("All components missing", "color(srgb)", "color(srgb 0 0 0)");
+    testColorFunction("Display P3 color", "color(display-p3 0.6 0.7 0.8)", "color(display-p3 0.6 0.7 0.8)");
+    testColorFunction("Different case for Display P3", "color(dIspLaY-P3 0.6 0.7 0.8)", "color(display-p3 0.6 0.7 0.8)");
+
+    testColorFunction("Unknown color space should fallback", "color(unknown 1 2 3, red)", "color(unknown 1 2 3, red)");
+
+    testColorFunction("sRGB color with negative component should clamp to 0", "color(srgb -0.25 0.5 0.75)", "color(srgb 0 0.5 0.75)");
+    testColorFunction("sRGB color with component > 1 should clamp", "color(srgb 0.25 1.5 0.75)", "color(srgb 0.25 1 0.75)");
+    testColorFunction("Display P3 color with negative component should clamp to 0", "color(display-p3 0.5 -199 0.75)", "color(display-p3 0.5 0 0.75)");
+    testColorFunction("Display P3 color with component > 1 should clamp", "color(display-p3 184 1.00001 2347329746587)", "color(display-p3 1 1 1)");
+    testColorFunction("Alpha > 1 should clamp", "color(srgb 0.1 0.2 0.3 / 1.9)", "color(srgb 0.1 0.2 0.3)");
+    testColorFunction("Negative alpha should clamp", "color(srgb 1 1 1 / -0.2)", "color(srgb 1 1 1 / 0)");
+
+    // Invalid properties
+    testColorFunction("Empty", "color()", "rgb(0, 0, 0)");
+    testColorFunction("Bad color space", "color(banana 1 1 1)", "rgb(0, 0, 0)");
+    testColorFunction("Bad Display P3 color space", "color(displayp3 1 1 1)", "rgb(0, 0, 0)");
+    testColorFunction("No color space", "color(1 1 1)", "rgb(0, 0, 0)");
+    testColorFunction("Too many parameters", "color(srgb 1 1 1 1)", "rgb(0, 0, 0)");
+    testColorFunction("Way too many parameters", "color(srgb 1 1 1 1 1)", "rgb(0, 0, 0)");
+    testColorFunction("Bad parameters", "color(srgb 1 eggs 1)", "rgb(0, 0, 0)");
+    testColorFunction("Bad alpha", "color(srgb 1 1 1 / bacon)", "rgb(0, 0, 0)");
+    testColorFunction("Junk after alpha", "color(srgb 1 1 1 / 1 cucumber)", "rgb(0, 0, 0)");
+</script>
diff --git a/css/css-grid/grid-items/grid-minimum-size-grid-items-022.html b/css/css-grid/grid-items/grid-minimum-size-grid-items-022.html
index 66417a4..127ea8d 100644
--- a/css/css-grid/grid-items/grid-minimum-size-grid-items-022.html
+++ b/css/css-grid/grid-items/grid-minimum-size-grid-items-022.html
@@ -135,3 +135,217 @@
   <div data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
   <div data-expected-width="25"></div>
 </div>
+
+<h3>grid content writing-mode: vertical-lr;</h3>
+
+<pre>grid-template-columns: auto;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: auto;">
+  <div data-expected-height="100">XXXXXXXXXX</div>
+  <div data-expected-height="100"></div>
+</div>
+
+<pre>grid-template-columns: 0px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: 0px;">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: 25px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: 25px;">
+  <div data-expected-height="25">XXXXXXXXXX</div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px);</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px);</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="25">XXXXXXXXXX</div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item margin height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="0" style="margin: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item margin height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="15" style="margin: 5px 0px;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item padding height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="10" style="padding: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item padding height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="25" style="padding: 5px 0px;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item border height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="10" style="border: solid 5px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item border height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="25" style="border: solid 5px blue;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<h3>grid content writing-mode: vertical-rl;</h3>
+
+<pre>grid-template-columns: auto;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: auto;">
+  <div data-expected-height="100">XXXXXXXXXX</div>
+  <div data-expected-height="100"></div>
+</div>
+
+<pre>grid-template-columns: 0px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: 0px;">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: 25px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: 25px;">
+  <div data-expected-height="25">XXXXXXXXXX</div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px);</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px);</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="25">XXXXXXXXXX</div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item margin height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="0" style="margin: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item margin height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="15" style="margin: 5px 0px;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item padding height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="10" style="padding: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item padding height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="25" style="padding: 5px 0px;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item border height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="10" style="border: solid 5px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item border height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="25" style="border: solid 5px blue;"></div>
+  <div data-expected-height="25"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 25px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 25px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="25"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-minimum-size-grid-items-023.html b/css/css-grid/grid-items/grid-minimum-size-grid-items-023.html
index d821e5c..636a40f 100644
--- a/css/css-grid/grid-items/grid-minimum-size-grid-items-023.html
+++ b/css/css-grid/grid-items/grid-minimum-size-grid-items-023.html
@@ -32,7 +32,7 @@
 
 <div id="log"></div>
 
-<h3>writing-mode: vertical-lr;</h3>
+<h3>item writing-mode: vertical-lr;</h3>
 
 <pre>grid-template-rows: auto;</pre>
 
@@ -139,7 +139,7 @@
   <div data-expected-height="25"></div>
 </div>
 
-<h3>writing-mode: vertical-rl;</h3>
+<h3>item writing-mode: vertical-rl;</h3>
 
 <pre>grid-template-rows: auto;</pre>
 
@@ -246,3 +246,216 @@
   <div data-expected-height="25"></div>
 </div>
 
+<h3>grid container writing-mode: vertical-lr; &amp; item writing-mode: horizontal-tb;</h3>
+
+<pre>grid-template-rows: auto;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: auto;">
+  <div class="horizontalTB" data-expected-width="100">XXXXXXXXXX</div>
+  <div data-expected-width="100"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px);</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: 25px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: 25px;">
+  <div class="horizontalTB" data-expected-width="25">XXXXXXXXXX</div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px);</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px);</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="25">XXXXXXXXXX</div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item margin width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0" style="margin: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item margin width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="15" style="margin: 0px 5px;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item padding width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="padding: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item padding width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="25" style="padding: 0px 5px;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item border width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="border: solid 5px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item border width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="25" style="border: solid 5px blue;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<h3>grid container writing-mode: vertical-rl; &amp; item writing-mode: horizontal-tb;</h3>
+
+<pre>grid-template-rows: auto;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: auto;">
+  <div class="horizontalTB" data-expected-width="100">XXXXXXXXXX</div>
+  <div data-expected-width="100"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px);</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: 25px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: 25px;">
+  <div class="horizontalTB" data-expected-width="25">XXXXXXXXXX</div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px);</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px);</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="25">XXXXXXXXXX</div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item margin width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0" style="margin: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item margin width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="15" style="margin: 0px 5px;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item padding width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="padding: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item padding width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="25" style="padding: 0px 5px;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item border width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="border: solid 5px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item border width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="25" style="border: solid 5px blue;"></div>
+  <div data-expected-width="25"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 25px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 25px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="25"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-minimum-size-grid-items-024.html b/css/css-grid/grid-items/grid-minimum-size-grid-items-024.html
new file mode 100644
index 0000000..fb706bf
--- /dev/null
+++ b/css/css-grid/grid-items/grid-minimum-size-grid-items-024.html
@@ -0,0 +1,355 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Minimum size of grid items</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/css-grid-1/#min-size-auto" title="6.5. Implied Minimum Size of Grid Items">
+<meta name="assert" content="Checks that automatic minimum size is clamped with different column sizes and spaning items.">
+<link rel="stylesheet" href="../support/grid.css">
+<style>
+.grid {
+  border: solid thick;
+  font: 10px/1 Ahem;
+  width: 50px;
+  height: 50px;
+  grid-template-rows: 25px 25px;
+}
+
+.grid > div {
+  grid-column: span 2;
+}
+
+.grid > div:nth-child(1) {
+  color: blue;
+  background: cyan;
+}
+
+.grid > div:nth-child(2) {
+  background: magenta;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<pre>grid-template-columns: auto auto;</pre>
+
+<div class="grid" style="grid-template-columns: auto auto;">
+  <div data-expected-width="100">XXXXXXXXXX</div>
+  <div data-expected-width="100"></div>
+</div>
+
+<pre>grid-template-columns: 0px 0px;</pre>
+
+<div class="grid" style="grid-template-columns: 0px 0px;">
+  <div data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-columns: 20px 20px;</pre>
+
+<div class="grid" style="grid-template-columns: 20px 20px;">
+  <div data-expected-width="40">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-width="40">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item margin width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-width="0" style="margin: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item margin width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-width="30" style="margin: 0px 5px;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item padding width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-width="10" style="padding: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item padding width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-width="40" style="padding: 0px 5px;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item border width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-width="10" style="border: solid 5px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item border width: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-width="40" style="border: solid 5px blue;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<h3>grid content writing-mode: vertical-lr;</h3>
+
+<pre>grid-template-columns: auto auto;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: auto auto;">
+  <div data-expected-height="100">XXXXXXXXXX</div>
+  <div data-expected-height="100"></div>
+</div>
+
+<pre>grid-template-columns: 0px 0px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: 0px 0px;">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: 20px 20px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: 20px 20px;">
+  <div data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item margin height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="0" style="margin: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item margin height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="30" style="margin: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item padding height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="10" style="padding: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item padding height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="40" style="padding: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item border height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="10" style="border: solid 5px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item border height: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="40" style="border: solid 5px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<h3>grid content writing-mode: vertical-rl;</h3>
+
+<pre>grid-template-columns: auto auto;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: auto auto;">
+  <div data-expected-height="100">XXXXXXXXXX</div>
+  <div data-expected-height="100"></div>
+</div>
+
+<pre>grid-template-columns: 0px 0px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: 0px 0px;">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: 20px 20px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: 20px 20px;">
+  <div data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item margin height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="0" style="margin: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item margin height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="30" style="margin: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item padding height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="10" style="padding: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item padding height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="40" style="padding: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item border height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="10" style="border: solid 5px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item border height: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="40" style="border: solid 5px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 0px) minmax(auto, 0px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 0px) minmax(auto, 0px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-columns: minmax(auto, 20px) minmax(auto, 20px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-columns: minmax(auto, 20px) minmax(auto, 20px);">
+  <div data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
diff --git a/css/css-grid/grid-items/grid-minimum-size-grid-items-025.html b/css/css-grid/grid-items/grid-minimum-size-grid-items-025.html
new file mode 100644
index 0000000..722426e
--- /dev/null
+++ b/css/css-grid/grid-items/grid-minimum-size-grid-items-025.html
@@ -0,0 +1,465 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Minimum size of grid items</title>
+<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/css-grid-1/#min-size-auto" title="6.5. Implied Minimum Size of Grid Items">
+<meta name="assert" content="Checks that automatic minimum size is clamped with different row sizes and spaning items.">
+<link rel="stylesheet" href="../support/grid.css">
+<style>
+.grid {
+  border: solid thick;
+  font: 10px/1 Ahem;
+  width: 50px;
+  height: 50px;
+  grid-template-columns: 25px 25px;
+  margin: 50px 0px;
+}
+
+.grid > div {
+  grid-row: span 2;
+}
+
+.grid > div:nth-child(1) {
+  color: blue;
+  background: cyan;
+}
+
+.grid > div:nth-child(2) {
+  background: magenta;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.grid')">
+
+<div id="log"></div>
+
+<h3>item writing-mode: vertical-lr;</h3>
+
+<pre>grid-template-rows: auto auto;</pre>
+
+<div class="grid" style="grid-template-rows: auto auto;">
+  <div class="verticalLR" data-expected-height="100">XXXXXXXXXX</div>
+  <div data-expected-height="100"></div>
+</div>
+
+<pre>grid-template-rows: 0px 0px;</pre>
+
+<div class="grid" style="grid-template-rows: 0px 0px;">
+  <div class="verticalLR" data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-rows: 20px 20px;</pre>
+
+<div class="grid" style="grid-template-rows: 20px 20px;">
+  <div class="verticalLR" data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalLR" data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalLR" data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalLR" data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalLR" data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item margin height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalLR" data-expected-height="0" style="margin: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item margin height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalLR" data-expected-height="30" style="margin: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item padding height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalLR" data-expected-height="10" style="padding: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item padding height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalLR" data-expected-height="40" style="padding: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item border height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalLR" data-expected-height="10" style="border: solid 5px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item border height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalLR" data-expected-height="40" style="border: solid 5px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalLR" data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalLR" data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<h3>item writing-mode: vertical-rl;</h3>
+
+<pre>grid-template-rows: auto auto;</pre>
+
+<div class="grid" style="grid-template-rows: auto auto;">
+  <div class="verticalRL" data-expected-height="100">XXXXXXXXXX</div>
+  <div data-expected-height="100"></div>
+</div>
+
+<pre>grid-template-rows: 0px 0px;</pre>
+
+<div class="grid" style="grid-template-rows: 0px 0px;">
+  <div class="verticalRL" data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-rows: 20px 20px;</pre>
+
+<div class="grid" style="grid-template-rows: 20px 20px;">
+  <div class="verticalRL" data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalRL" data-expected-height="0">XXXXXXXXXX</div>
+  <div data-expected-height="0"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalRL" data-expected-height="40">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalRL" data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalRL" data-expected-height="10" style="height: 10px;">XXXXXXXXXX</div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item margin height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalRL" data-expected-height="0" style="margin: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item margin height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalRL" data-expected-height="30" style="margin: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item padding height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalRL" data-expected-height="10" style="padding: 5px 0px;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item padding height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalRL" data-expected-height="40" style="padding: 5px 0px;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item border height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalRL" data-expected-height="10" style="border: solid 5px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item border height: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalRL" data-expected-height="40" style="border: solid 5px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="verticalRL" data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item height + margin + border + padding: 10px;</pre>
+
+<div class="grid" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="verticalRL" data-expected-height="8" style="height: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-height="40"></div>
+</div>
+
+<h3>grid container writing-mode: vertical-lr; &amp; item writing-mode: horizontal-tb;</h3>
+
+<pre>grid-template-rows: auto auto;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: auto auto;">
+  <div class="horizontalTB" data-expected-width="100">XXXXXXXXXX</div>
+  <div data-expected-width="100"></div>
+</div>
+
+<pre>grid-template-rows: 0px 0px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: 0px 0px;">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: 20px 20px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: 20px 20px;">
+  <div class="horizontalTB" data-expected-width="40">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="40">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item margin width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0" style="margin: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item margin width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="30" style="margin: 0px 5px;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item padding width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="padding: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item padding width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="40" style="padding: 0px 5px;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item border width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="border: solid 5px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item border width: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="40" style="border: solid 5px blue;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalLR" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<h3>grid container writing-mode: vertical-rl; &amp; item writing-mode: horizontal-tb;</h3>
+
+<pre>grid-template-rows: auto auto;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: auto auto;">
+  <div class="horizontalTB" data-expected-width="100">XXXXXXXXXX</div>
+  <div data-expected-width="100"></div>
+</div>
+
+<pre>grid-template-rows: 0px 0px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: 0px 0px;">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: 20px 20px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: 20px 20px;">
+  <div class="horizontalTB" data-expected-width="40">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0">XXXXXXXXXX</div>
+  <div data-expected-width="0"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="40">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="10" style="width: 10px;">XXXXXXXXXX</div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item margin width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="0" style="margin: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item margin width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="30" style="margin: 0px 5px;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item padding width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="padding: 0px 5px;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item padding width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="40" style="padding: 0px 5px;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item border width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="10" style="border: solid 5px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item border width: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="40" style="border: solid 5px blue;"></div>
+  <div data-expected-width="40"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 0px) minmax(auto, 0px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 0px) minmax(auto, 0px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="10"></div>
+</div>
+
+<pre>grid-template-rows: minmax(auto, 20px) minmax(auto, 20px); item width + margin + border + padding: 10px;</pre>
+
+<div class="grid verticalRL" style="grid-template-rows: minmax(auto, 20px) minmax(auto, 20px);">
+  <div class="horizontalTB" data-expected-width="8" style="width: 4px; margin: 1px; padding: 1px; border: solid 1px blue;"></div>
+  <div data-expected-width="40"></div>
+</div>
diff --git a/css/css-paint-api/background-image-alpha.https.html b/css/css-paint-api/background-image-alpha.https.html
index de8a0d7..53d869d 100644
--- a/css/css-paint-api/background-image-alpha.https.html
+++ b/css/css-paint-api/background-image-alpha.https.html
@@ -21,7 +21,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="background">
   <div id="canvas-opaque" class="container"></div>
@@ -49,7 +49,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/background-image-multiple.https.html b/css/css-paint-api/background-image-multiple.https.html
index 7fb5751..79ff883 100644
--- a/css/css-paint-api/background-image-multiple.https.html
+++ b/css/css-paint-api/background-image-multiple.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -27,7 +27,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/background-image-tiled.https.html b/css/css-paint-api/background-image-tiled.https.html
index 0497acf..8498c82 100644
--- a/css/css-paint-api/background-image-tiled.https.html
+++ b/css/css-paint-api/background-image-tiled.https.html
@@ -21,7 +21,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="one"></div>
 <div id="two"></div>
@@ -38,7 +38,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-background-image-001.https.html b/css/css-paint-api/geometry-background-image-001.https.html
index d1207e0..601d418 100644
--- a/css/css-paint-api/geometry-background-image-001.https.html
+++ b/css/css-paint-api/geometry-background-image-001.https.html
@@ -13,7 +13,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -28,7 +28,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-background-image-002.https.html b/css/css-paint-api/geometry-background-image-002.https.html
index 47455ba..1d57073 100644
--- a/css/css-paint-api/geometry-background-image-002.https.html
+++ b/css/css-paint-api/geometry-background-image-002.https.html
@@ -13,7 +13,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -28,7 +28,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-background-image-tiled-001.https.html b/css/css-paint-api/geometry-background-image-tiled-001.https.html
index 5cf8eb7..8e28b54 100644
--- a/css/css-paint-api/geometry-background-image-tiled-001.https.html
+++ b/css/css-paint-api/geometry-background-image-tiled-001.https.html
@@ -14,7 +14,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -30,7 +30,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-background-image-tiled-002.https.html b/css/css-paint-api/geometry-background-image-tiled-002.https.html
index 491abd1..9248e38 100644
--- a/css/css-paint-api/geometry-background-image-tiled-002.https.html
+++ b/css/css-paint-api/geometry-background-image-tiled-002.https.html
@@ -14,7 +14,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -29,7 +29,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-background-image-tiled-003.https.html b/css/css-paint-api/geometry-background-image-tiled-003.https.html
index 9a29c30..5b6b6c7 100644
--- a/css/css-paint-api/geometry-background-image-tiled-003.https.html
+++ b/css/css-paint-api/geometry-background-image-tiled-003.https.html
@@ -14,7 +14,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -29,7 +29,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-border-image-001.https.html b/css/css-paint-api/geometry-border-image-001.https.html
index 54249d3..3fb4643 100644
--- a/css/css-paint-api/geometry-border-image-001.https.html
+++ b/css/css-paint-api/geometry-border-image-001.https.html
@@ -15,7 +15,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -34,7 +34,7 @@
 
 <script>
     document.getElementById('canvas-geometry').style.borderWidth = '10px';
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-border-image-002.https.html b/css/css-paint-api/geometry-border-image-002.https.html
index 4759886..26e24bb 100644
--- a/css/css-paint-api/geometry-border-image-002.https.html
+++ b/css/css-paint-api/geometry-border-image-002.https.html
@@ -15,7 +15,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -34,7 +34,7 @@
 
 <script>
     document.getElementById('canvas-geometry').style.borderImageOutset = '20px';
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-border-image-003.https.html b/css/css-paint-api/geometry-border-image-003.https.html
index 1ce6ac9..a26f2b7 100644
--- a/css/css-paint-api/geometry-border-image-003.https.html
+++ b/css/css-paint-api/geometry-border-image-003.https.html
@@ -15,7 +15,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -34,7 +34,7 @@
 
 <script>
     document.getElementById('canvas-geometry').style.borderImageOutset = '10px';
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-border-image-004.https.html b/css/css-paint-api/geometry-border-image-004.https.html
index b15b66d..60db7ff 100644
--- a/css/css-paint-api/geometry-border-image-004.https.html
+++ b/css/css-paint-api/geometry-border-image-004.https.html
@@ -15,7 +15,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -35,7 +35,7 @@
 <script>
     document.getElementById('canvas-geometry').style.borderWidth = '10px';
     document.getElementById('canvas-geometry').style.borderImageOutset = '10px';
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/geometry-with-float-size.https.html b/css/css-paint-api/geometry-with-float-size.https.html
index 6cd3eca..65477e4 100644
--- a/css/css-paint-api/geometry-with-float-size.https.html
+++ b/css/css-paint-api/geometry-with-float-size.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -29,7 +29,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/hidpi/device-pixel-ratio.https.html b/css/css-paint-api/hidpi/device-pixel-ratio.https.html
index fb62c23..46a9aa2 100644
--- a/css/css-paint-api/hidpi/device-pixel-ratio.https.html
+++ b/css/css-paint-api/hidpi/device-pixel-ratio.https.html
@@ -13,7 +13,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <p>This test ensures that the PaintWorkletGlobalScope.devicePixelRatio returns
 the correct value, which should be identical as window.devicePixelRatio. To
@@ -33,7 +33,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/invalid-image-constructor-error.https.html b/css/css-paint-api/invalid-image-constructor-error.https.html
index 022b915..439ff8b 100644
--- a/css/css-paint-api/invalid-image-constructor-error.https.html
+++ b/css/css-paint-api/invalid-image-constructor-error.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -33,7 +33,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 
 </body>
diff --git a/css/css-paint-api/invalid-image-paint-error.https.html b/css/css-paint-api/invalid-image-paint-error.https.html
index c185d89..2806ad9 100644
--- a/css/css-paint-api/invalid-image-paint-error.https.html
+++ b/css/css-paint-api/invalid-image-paint-error.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -30,7 +30,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/invalid-image-pending-script.https.html b/css/css-paint-api/invalid-image-pending-script.https.html
index e19fa41..4347a00 100644
--- a/css/css-paint-api/invalid-image-pending-script.https.html
+++ b/css/css-paint-api/invalid-image-pending-script.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -26,7 +26,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/overdraw.https.html b/css/css-paint-api/overdraw.https.html
index f95eeb5..5be26f7 100644
--- a/css/css-paint-api/overdraw.https.html
+++ b/css/css-paint-api/overdraw.https.html
@@ -10,7 +10,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -24,7 +24,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint-arguments.https.html b/css/css-paint-api/paint-arguments.https.html
index f4ae452..abfb2a6 100644
--- a/css/css-paint-api/paint-arguments.https.html
+++ b/css/css-paint-api/paint-arguments.https.html
@@ -21,7 +21,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 
 <div id="background">
@@ -43,7 +43,7 @@
 </script>
 
 <script>
-  importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+  importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint-function-arguments.https.html b/css/css-paint-api/paint-function-arguments.https.html
index d49f3f2..d87b0b8 100644
--- a/css/css-paint-api/paint-function-arguments.https.html
+++ b/css/css-paint-api/paint-function-arguments.https.html
@@ -21,7 +21,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 
 <div id="background">
@@ -43,7 +43,7 @@
 </script>
 
 <script>
-  importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+  importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-composite.https.html b/css/css-paint-api/paint2d-composite.https.html
index ba88f92..80e94e4 100644
--- a/css/css-paint-api/paint2d-composite.https.html
+++ b/css/css-paint-api/paint2d-composite.https.html
@@ -19,7 +19,7 @@
     #xor { background-image: paint(xor); }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="source-over"></div>
 <div id="source-in"></div>
@@ -67,7 +67,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-filter.https.html b/css/css-paint-api/paint2d-filter.https.html
index 560b1b2..d0c4539 100644
--- a/css/css-paint-api/paint2d-filter.https.html
+++ b/css/css-paint-api/paint2d-filter.https.html
@@ -24,7 +24,7 @@
     #filter-url { background-image: paint(filter-url); }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="filter-none"></div>
 <div id="filter-blur-10px"></div>
@@ -100,7 +100,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-gradient.https.html b/css/css-paint-api/paint2d-gradient.https.html
index b936430..892a791 100644
--- a/css/css-paint-api/paint2d-gradient.https.html
+++ b/css/css-paint-api/paint2d-gradient.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -34,7 +34,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-image.https.html b/css/css-paint-api/paint2d-image.https.html
index ba89430..6fce4b6 100644
--- a/css/css-paint-api/paint2d-image.https.html
+++ b/css/css-paint-api/paint2d-image.https.html
@@ -10,7 +10,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <div id="output"></div>
 
 <script id="code" type="text/worklet">
@@ -25,6 +25,6 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </html>
diff --git a/css/css-paint-api/paint2d-paths.https.html b/css/css-paint-api/paint2d-paths.https.html
index 55a01b4..091f548 100644
--- a/css/css-paint-api/paint2d-paths.https.html
+++ b/css/css-paint-api/paint2d-paths.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-rects.https.html b/css/css-paint-api/paint2d-rects.https.html
index 24247da..2494272 100644
--- a/css/css-paint-api/paint2d-rects.https.html
+++ b/css/css-paint-api/paint2d-rects.https.html
@@ -10,7 +10,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -30,7 +30,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-shadows.https.html b/css/css-paint-api/paint2d-shadows.https.html
index ad0a1aa..98dcfbc 100644
--- a/css/css-paint-api/paint2d-shadows.https.html
+++ b/css/css-paint-api/paint2d-shadows.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -33,7 +33,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/paint2d-transform.https.html b/css/css-paint-api/paint2d-transform.https.html
index f5b6aa8..c91b500 100644
--- a/css/css-paint-api/paint2d-transform.https.html
+++ b/css/css-paint-api/paint2d-transform.https.html
@@ -9,7 +9,7 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="output"></div>
 
@@ -32,7 +32,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-001.https.html b/css/css-paint-api/parse-input-arguments-001.https.html
index 931a55a..4356ce6 100644
--- a/css/css-paint-api/parse-input-arguments-001.https.html
+++ b/css/css-paint-api/parse-input-arguments-001.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-002.https.html b/css/css-paint-api/parse-input-arguments-002.https.html
index c07c0c0..6fc16c7 100644
--- a/css/css-paint-api/parse-input-arguments-002.https.html
+++ b/css/css-paint-api/parse-input-arguments-002.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-003.https.html b/css/css-paint-api/parse-input-arguments-003.https.html
index ff388de..44fd850 100644
--- a/css/css-paint-api/parse-input-arguments-003.https.html
+++ b/css/css-paint-api/parse-input-arguments-003.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-004.https.html b/css/css-paint-api/parse-input-arguments-004.https.html
index 8d5b8d7..138a790 100644
--- a/css/css-paint-api/parse-input-arguments-004.https.html
+++ b/css/css-paint-api/parse-input-arguments-004.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -43,7 +43,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-005.https.html b/css/css-paint-api/parse-input-arguments-005.https.html
index b726c22..c12e00c 100644
--- a/css/css-paint-api/parse-input-arguments-005.https.html
+++ b/css/css-paint-api/parse-input-arguments-005.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -39,7 +39,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-006.https.html b/css/css-paint-api/parse-input-arguments-006.https.html
index 7d8504c..664b7a8 100644
--- a/css/css-paint-api/parse-input-arguments-006.https.html
+++ b/css/css-paint-api/parse-input-arguments-006.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -38,7 +38,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-007.https.html b/css/css-paint-api/parse-input-arguments-007.https.html
index a59ac93..53f245b 100644
--- a/css/css-paint-api/parse-input-arguments-007.https.html
+++ b/css/css-paint-api/parse-input-arguments-007.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-008.https.html b/css/css-paint-api/parse-input-arguments-008.https.html
index 2d6df23..1914e3c 100644
--- a/css/css-paint-api/parse-input-arguments-008.https.html
+++ b/css/css-paint-api/parse-input-arguments-008.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-009.https.html b/css/css-paint-api/parse-input-arguments-009.https.html
index 03c7c69..21d004d 100644
--- a/css/css-paint-api/parse-input-arguments-009.https.html
+++ b/css/css-paint-api/parse-input-arguments-009.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -40,7 +40,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-010.https.html b/css/css-paint-api/parse-input-arguments-010.https.html
index 0bd6768..d923479 100644
--- a/css/css-paint-api/parse-input-arguments-010.https.html
+++ b/css/css-paint-api/parse-input-arguments-010.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -40,7 +40,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-011.https.html b/css/css-paint-api/parse-input-arguments-011.https.html
index e772a50..6cea438 100644
--- a/css/css-paint-api/parse-input-arguments-011.https.html
+++ b/css/css-paint-api/parse-input-arguments-011.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -38,7 +38,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-012.https.html b/css/css-paint-api/parse-input-arguments-012.https.html
index 422b45f..938150a 100644
--- a/css/css-paint-api/parse-input-arguments-012.https.html
+++ b/css/css-paint-api/parse-input-arguments-012.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-013.https.html b/css/css-paint-api/parse-input-arguments-013.https.html
index cd62879..37e3eb435 100644
--- a/css/css-paint-api/parse-input-arguments-013.https.html
+++ b/css/css-paint-api/parse-input-arguments-013.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -38,7 +38,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-014.https.html b/css/css-paint-api/parse-input-arguments-014.https.html
index 39aee6a..690e488 100644
--- a/css/css-paint-api/parse-input-arguments-014.https.html
+++ b/css/css-paint-api/parse-input-arguments-014.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -40,7 +40,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-015.https.html b/css/css-paint-api/parse-input-arguments-015.https.html
index c2e1844..fba7671 100644
--- a/css/css-paint-api/parse-input-arguments-015.https.html
+++ b/css/css-paint-api/parse-input-arguments-015.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -43,7 +43,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-016.https.html b/css/css-paint-api/parse-input-arguments-016.https.html
index 4bea1b4..8c9d05d 100644
--- a/css/css-paint-api/parse-input-arguments-016.https.html
+++ b/css/css-paint-api/parse-input-arguments-016.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -42,7 +42,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-017.https.html b/css/css-paint-api/parse-input-arguments-017.https.html
index 38f9f43..0d14fe74 100644
--- a/css/css-paint-api/parse-input-arguments-017.https.html
+++ b/css/css-paint-api/parse-input-arguments-017.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -41,7 +41,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-018.https.html b/css/css-paint-api/parse-input-arguments-018.https.html
index 9d03a8d..541332a 100644
--- a/css/css-paint-api/parse-input-arguments-018.https.html
+++ b/css/css-paint-api/parse-input-arguments-018.https.html
@@ -13,7 +13,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <p>This test result should show a rect with black border, where the rect is
 filled with green on the lower right corner. The registerPaint('failureIndicator')
@@ -63,7 +63,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-019.https.html b/css/css-paint-api/parse-input-arguments-019.https.html
index 76627fb..707a0c6 100644
--- a/css/css-paint-api/parse-input-arguments-019.https.html
+++ b/css/css-paint-api/parse-input-arguments-019.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -41,7 +41,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-020.https.html b/css/css-paint-api/parse-input-arguments-020.https.html
index b582555..fe8dbad 100644
--- a/css/css-paint-api/parse-input-arguments-020.https.html
+++ b/css/css-paint-api/parse-input-arguments-020.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -40,7 +40,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-021.https.html b/css/css-paint-api/parse-input-arguments-021.https.html
index 666d4c4..0c3a596 100644
--- a/css/css-paint-api/parse-input-arguments-021.https.html
+++ b/css/css-paint-api/parse-input-arguments-021.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -43,7 +43,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/parse-input-arguments-022.https.html b/css/css-paint-api/parse-input-arguments-022.https.html
index 3c5d9f9..50aaa6b 100644
--- a/css/css-paint-api/parse-input-arguments-022.https.html
+++ b/css/css-paint-api/parse-input-arguments-022.https.html
@@ -12,7 +12,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -41,7 +41,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/registered-properties-in-custom-paint.https.html b/css/css-paint-api/registered-properties-in-custom-paint.https.html
index c446347..00b0368 100644
--- a/css/css-paint-api/registered-properties-in-custom-paint.https.html
+++ b/css/css-paint-api/registered-properties-in-custom-paint.https.html
@@ -14,7 +14,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -56,7 +56,7 @@
     CSS.registerProperty({name: '--length', syntax: '<length>', initialValue: '0px'});
     CSS.registerProperty({name: '--length-initial', syntax: '<length>', initialValue: '20px'});
     CSS.registerProperty({name: '--number', syntax: '<number>', initialValue: '0'});
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/style-background-image.https.html b/css/css-paint-api/style-background-image.https.html
index 7853607..6eeaf3c 100644
--- a/css/css-paint-api/style-background-image.https.html
+++ b/css/css-paint-api/style-background-image.https.html
@@ -14,7 +14,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body>
 <div id="canvas-geometry" class="container"></div>
 
@@ -56,7 +56,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/style-before-pseudo.https.html b/css/css-paint-api/style-before-pseudo.https.html
index 5249f58..bf3f69f 100644
--- a/css/css-paint-api/style-before-pseudo.https.html
+++ b/css/css-paint-api/style-before-pseudo.https.html
@@ -18,7 +18,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body style="font: 10px/1 Ahem;">
 <div></div>
 
@@ -57,7 +57,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/style-first-letter-pseudo.https.html b/css/css-paint-api/style-first-letter-pseudo.https.html
index 3cdffb4..b8a22ce 100644
--- a/css/css-paint-api/style-first-letter-pseudo.https.html
+++ b/css/css-paint-api/style-first-letter-pseudo.https.html
@@ -15,7 +15,7 @@
 }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
+<script src="/common/worklet-reftest.js"></script>
 <body style="font: 10px/1 Ahem;">
 <div>ppp</div>
 
@@ -51,7 +51,7 @@
 </script>
 
 <script>
-    importPaintWorkletAndTerminateTestAfterAsyncPaint(document.getElementById('code').textContent);
+    importWorkletAndTerminateTestAfterAsyncPaint(CSS.paintWorklet, document.getElementById('code').textContent);
 </script>
 </body>
 </html>
diff --git a/css/css-paint-api/valid-image-after-load.https.html b/css/css-paint-api/valid-image-after-load.https.html
index 73557d2..44612f9 100644
--- a/css/css-paint-api/valid-image-after-load.https.html
+++ b/css/css-paint-api/valid-image-after-load.https.html
@@ -9,7 +9,6 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
 <body>
 <div id="output"></div>
 
diff --git a/css/css-paint-api/valid-image-before-load.https.html b/css/css-paint-api/valid-image-before-load.https.html
index 483d16c..7738d9d 100644
--- a/css/css-paint-api/valid-image-before-load.https.html
+++ b/css/css-paint-api/valid-image-before-load.https.html
@@ -9,7 +9,6 @@
     }
 </style>
 <script src="/common/reftest-wait.js"></script>
-<script src="/common/css-paint-tests.js"></script>
 <body>
 <div id="output"></div>
 
diff --git a/css/css-pseudo/first-letter-property-whitelist.html b/css/css-pseudo/first-letter-property-whitelist.html
new file mode 100644
index 0000000..073e554
--- /dev/null
+++ b/css/css-pseudo/first-letter-property-whitelist.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<title>CSS Test: Properties allowed on ::first-letter pseudo elements</title>
+<link rel="author" title="Chris Nardi" href="mailto:cnardi@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-pseudo-4/#first-letter-styling">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#target::first-letter {}
+#target { visibility: hidden; }
+</style>
+<div id="target">text</div>
+<script>
+'use strict';
+var style;
+const target = document.querySelector("#target");
+var defaultComputedStyle = getComputedStyle(target);
+
+test(function() {
+  var styleRule = document.styleSheets[0].cssRules[0];
+  assert_equals(styleRule.selectorText, '#target::first-letter', 'make sure we have the correct style rule');
+  style = styleRule.style;
+}, 'pre test setup');
+
+var validProperties = {
+  backgroundAttachment: 'fixed',
+  backgroundBlendMode: 'hue',
+  backgroundClip: 'padding-box',
+  backgroundColor: 'rgb(10, 20, 30)',
+  backgroundImage: 'linear-gradient(black, white)',
+  backgroundOrigin: 'border-box',
+  backgroundPosition: 'left 10px top 20px',
+  backgroundRepeat: 'no-repeat',
+  backgroundSize: '10px 20px',
+  border: '40px dotted rgb(10, 20, 30)',
+  borderImage: 'linear-gradient(black, white) 10% / 20 / 30px repeat',
+  borderRadius: '10px 20px',
+  boxShadow: 'rgb(10, 20, 30) 10px 20px 30px 40px inset',
+  color: 'rgba(10, 20, 30, 0.4)',
+  float: 'right',
+  font: 'italic small-caps 900 normal 10px / 20px sans-serif',
+  fontFeatureSettings: '"vert" 2',
+  fontSizeAdjust: '0.5',
+  fontKerning: 'none',
+  fontVariationSettings: '"XHGT" 0.7',
+  letterSpacing: '12px',
+  margin: '10px 20px 30px 40px',
+  padding: '10px 20px 30px 40px',
+  opacity: '0.5',
+  textDecoration: 'overline wavy rgb(10, 20, 30)',
+  textJustify: 'inter-word',
+  textShadow: 'rgb(10, 20, 30) 10px 20px 30px',
+  textTransform: 'capitalize',
+  textUnderlinePosition: 'under',
+  verticalAlign: '12%',
+  wordSpacing: '12px'
+};
+
+var invalidProperties = {
+  transition: 'transform 1s',
+  transform: 'rotate(45deg)',
+  wordBreak: 'break-all'
+};
+
+function testFirstLetterProperty(property, rule, expected, reason) {
+  test(function() {
+    style[property] = "";
+    style[property] = rule;
+    assert_equals(getComputedStyle(target, '::first-letter')[property], expected);
+    style[property] = "";
+  }, reason);
+}
+
+for (var property in validProperties) {
+  testFirstLetterProperty(property, validProperties[property], validProperties[property],
+                          "Whitelisted property " + property + " should be applied to first-letter pseudo elements.");
+}
+
+for (var property in invalidProperties) {
+  testFirstLetterProperty(property, invalidProperties[property], defaultComputedStyle[property],
+                          "Non-whitelisted property " + property + " should not be applied to first-letter pseudo elements.");
+}
+</script>
diff --git a/css/css-typed-om/resources/testhelper.js b/css/css-typed-om/resources/testhelper.js
index 0099da2..91af64c 100644
--- a/css/css-typed-om/resources/testhelper.js
+++ b/css/css-typed-om/resources/testhelper.js
@@ -57,6 +57,9 @@
     case 'CSSSkewX':
       assert_style_value_equals(a.ax, b.ax);
       break;
+    case 'CSSSkewY':
+      assert_style_value_equals(a.ay, b.ay);
+      break;
     case 'CSSPerspective':
       assert_style_value_equals(a.length, b.length);
       break;
diff --git a/css/css-typed-om/stylevalue-normalization/normalize-tokens.tentative.html b/css/css-typed-om/stylevalue-normalization/normalize-tokens.tentative.html
index 3311380..5e58bea 100644
--- a/css/css-typed-om/stylevalue-normalization/normalize-tokens.tentative.html
+++ b/css/css-typed-om/stylevalue-normalization/normalize-tokens.tentative.html
@@ -29,22 +29,22 @@
   {
     value: 'var(--A, 1em)',
     expectedResult: [
-      new CSSVariableReferenceValue('--A', new CSSUnparsedValue(' 1em')),
+      new CSSVariableReferenceValue('--A', new CSSUnparsedValue([' 1em'])),
     ]
   },
   {
     value: 'var(--A, var(--B))',
     expectedResult: [
-      new CSSVariableReferenceValue('--A', new CSSUnparsedValue(' ', new CSSVariableReferenceValue('--B'))),
+      new CSSVariableReferenceValue('--A', new CSSUnparsedValue([' ', new CSSVariableReferenceValue('--B')])),
     ]
   },
   {
     value: 'calc(42px + var(--foo, 15em) + var(--bar, var(--far) + 15px))',
     expectedResult: [
       'calc(42px + ',
-      new CSSVariableReferenceValue('--foo', new CSSUnparsedValue(' 15em')),
+      new CSSVariableReferenceValue('--foo', new CSSUnparsedValue([' 15em'])),
       ' + ',
-      new CSSVariableReferenceValue('--bar', new CSSUnparsedValue(' ', new CSSVariableReferenceValue('--far'), ' + 15px')),
+      new CSSVariableReferenceValue('--bar', new CSSUnparsedValue([' ', new CSSVariableReferenceValue('--far'), ' + 15px'])),
       ')',
     ]
   },
@@ -52,11 +52,11 @@
 
 for (const {value, expectedResult} of gTestCases) {
   test(t => {
-    assert_string_normalizes_to(t, 'color', value, new CSSUnparsedValue(...expectedResult));
+    assert_string_normalizes_to(t, 'color', value, new CSSUnparsedValue(expectedResult));
   }, 'Normalizing "' + value + '" on a CSS property returns correct CSSUnparsedValue');
 
   test(t => {
-    assert_string_normalizes_to(t, '--X', value, new CSSUnparsedValue(...expectedResult));
+    assert_string_normalizes_to(t, '--X', value, new CSSUnparsedValue(expectedResult));
   }, 'Normalizing "' + value + '" on a custom property returns correct CSSUnparsedValue');
 }
 
diff --git a/css/css-typed-om/stylevalue-normalization/transformvalue-normalization.tentative.html b/css/css-typed-om/stylevalue-normalization/transformvalue-normalization.tentative.html
index 626c4bb..9157ae8 100644
--- a/css/css-typed-om/stylevalue-normalization/transformvalue-normalization.tentative.html
+++ b/css/css-typed-om/stylevalue-normalization/transformvalue-normalization.tentative.html
@@ -140,7 +140,7 @@
   },
   {
     cssText: 'skewY(90deg)',
-    expected: new CSSSkew(CSS.deg(0), CSS.deg(90)),
+    expected: new CSSSkewY(CSS.deg(90)),
     desc: 'skewY()'
   },
   {
@@ -158,13 +158,14 @@
 
 test(t => {
   test_transform_normalization(t,
-      'translate(1px) rotateX(90deg) perspective(1px) skew(90deg) skewX(20deg) scale3d(1, 2, 3)',
+      'translate(1px) rotateX(90deg) perspective(1px) skew(90deg) skewX(20deg) skewY(30deg) scale3d(1, 2, 3)',
       new CSSTransformValue([
         new CSSTranslate(CSS.px(1), CSS.px(0)),
         new CSSRotate(CSS.number(1), CSS.number(0), CSS.number(0), CSS.deg(90)),
         new CSSPerspective(CSS.px(1)),
         new CSSSkew(CSS.deg(90), CSS.deg(0)),
         new CSSSkewX(CSS.deg(20)),
+        new CSSSkewY(CSS.deg(30)),
         new CSSScale(CSS.number(1), CSS.number(2), CSS.number(3)),
       ]));
 }, 'Normalizing a <transform-list> returns a CSSTransformValue containing all the transforms');
diff --git a/css/css-typed-om/stylevalue-serialization/cssTransformValue.tentative.html b/css/css-typed-om/stylevalue-serialization/cssTransformValue.tentative.html
index 48926ad..dc87e81 100644
--- a/css/css-typed-om/stylevalue-serialization/cssTransformValue.tentative.html
+++ b/css/css-typed-om/stylevalue-serialization/cssTransformValue.tentative.html
@@ -55,6 +55,11 @@
     desc: 'CSSSkewX'
   },
   {
+    value: new CSSSkewY(CSS.deg(90)),
+    cssText: 'skewY(90deg)',
+    desc: 'CSSSkewY'
+  },
+  {
     value: new CSSPerspective(CSS.px(1)),
     cssText: 'perspective(1px)',
     desc: 'CSSPerspective'
diff --git a/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html b/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html
index 8b7a868..6fc2e02 100644
--- a/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html
+++ b/css/css-typed-om/stylevalue-serialization/cssUnparsedValue.html
@@ -11,25 +11,25 @@
 'use strict';
 
 test(() => {
-  assert_equals(new CSSUnparsedValue('lem', 'on', 'ade').toString(), 'lemonade');
+  assert_equals(new CSSUnparsedValue(['lem', 'on', 'ade']).toString(), 'lemonade');
 }, 'CSSUnparsedValue containing strings serializes to its concatenated contents');
 
 test(() => {
-  assert_equals(new CSSUnparsedValue(
+  assert_equals(new CSSUnparsedValue([
       new CSSVariableReferenceValue('--A',
-        new CSSUnparsedValue(new CSSVariableReferenceValue('--B'))),
-      new CSSVariableReferenceValue('--C')).toString(),
+        new CSSUnparsedValue([new CSSVariableReferenceValue('--B')])),
+      new CSSVariableReferenceValue('--C')]).toString(),
     'var(--A,var(--B))var(--C)');
 }, 'CSSUnparsedValue containing variable references serializes its ' +
    'concatenated contents');
 
 test(() => {
-  assert_equals(new CSSUnparsedValue('foo', 'bar ',
+  assert_equals(new CSSUnparsedValue(['foo', 'bar ',
       new CSSVariableReferenceValue('--A',
-        new CSSUnparsedValue('baz ',
-          new CSSVariableReferenceValue('--B'), 'lemon')),
+        new CSSUnparsedValue(['baz ',
+          new CSSVariableReferenceValue('--B'), 'lemon'])),
       new CSSVariableReferenceValue('--C',
-        new CSSUnparsedValue('ade'))).toString(),
+        new CSSUnparsedValue(['ade']))]).toString(),
     'foobar var(--A,baz var(--B)lemon)var(--C,ade)');
 }, 'CSSUnparsedValue containing mix of strings and variable references ' +
    'serializes to its concatenated contents');
diff --git a/css/css-typed-om/stylevalue-subclasses/cssSkewY.tentative.html b/css/css-typed-om/stylevalue-subclasses/cssSkewY.tentative.html
new file mode 100644
index 0000000..643d2f6
--- /dev/null
+++ b/css/css-typed-om/stylevalue-subclasses/cssSkewY.tentative.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSSSkewY tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#cssskewy">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/testhelper.js"></script>
+<script>
+'use strict';
+
+const gInvalidTestCases = [
+  { value: 'auto', desc: 'a keyword'},
+  { value: 3.14, desc: 'a double'},
+  { value: 0, desc: 'a unitless zero'},
+  { value: '10deg', desc: 'a string angle'},
+  { value: CSS.number(10), desc: 'a number CSSUnitValue'},
+  { value: CSS.s(10), desc: 'a time dimension CSSUnitValue'},
+  { value: new CSSMathSum(CSS.px(1)), desc: 'a CSSMathValue of length type' },
+];
+
+for (const {value, desc} of gInvalidTestCases) {
+  test(() => {
+    assert_throws(new TypeError(), () => new CSSSkewY(value));
+  }, 'Constructing a CSSSkewY with ' + desc + ' throws a TypeError');
+}
+
+for (const {value, desc} of gInvalidTestCases) {
+  test(() => {
+    let skewY = new CSSSkewY(CSS.deg(0));
+    assert_throws(new TypeError(), () => skewY.ay = value);
+    assert_style_value_equals(skewY.ay, CSS.deg(0));
+  }, 'Updating CSSSkewY.ay with ' + desc + ' throws a TypeError');
+}
+
+const gValidTestCases = [
+  { value: CSS.deg(-3.14), desc: 'an angle CSSUnitValue' },
+  { value: new CSSMathSum(CSS.deg(1)), desc: 'a CSSMathValue of angle type' },
+];
+
+for (const {value: ay, desc: ayDesc} of gValidTestCases) {
+  test(() => {
+    const skewY = new CSSSkewY(ay);
+    assert_equals(skewY.ay, ay);
+    assert_true(skewY.is2D);
+  }, 'CSSSkewY can be constructed from ' + ayDesc);
+}
+
+for (const {value, desc} of gValidTestCases) {
+  test(() => {
+    let skewY = new CSSSkewY(CSS.deg(0));
+    skewY.ay = value;
+    assert_style_value_equals(skewY.ay, value);
+  }, 'CSSSkewY.ay can be updated to ' + desc);
+}
+
+test(() => {
+  let skewY = new CSSSkewY(CSS.deg(0));
+  skewY.is2D = false;
+  assert_true(skewY.is2D);
+}, 'Modifying skewY.is2D is a no-op');
+
+</script>
diff --git a/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue.html b/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue.html
index 3a994dd..8af27e1 100644
--- a/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue.html
+++ b/css/css-typed-om/stylevalue-subclasses/cssUnparsedValue.html
@@ -37,7 +37,7 @@
 
 for (const {args, description} of gTestArguments) {
   test(() => {
-    const result = new CSSUnparsedValue(...args);
+    const result = new CSSUnparsedValue(args);
 
     assert_not_equals(result, null,
         'A CSSUnparsedValue should be created');
diff --git a/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue.html b/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue.html
index 24082eb..e729acf 100644
--- a/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue.html
+++ b/css/css-typed-om/stylevalue-subclasses/cssVariableReferenceValue.html
@@ -22,7 +22,7 @@
 
 test(() => {
   const result = new CSSVariableReferenceValue('--foo',
-      new CSSUnparsedValue('lemon'));
+      new CSSUnparsedValue(['lemon']));
 
   assert_not_equals(result, null,
       'A CSSVariableReferenceValue should be created');
@@ -30,7 +30,7 @@
       'Variable member should be same as passed in the constructor');
   assert_not_equals(result.fallback, null,
       'Fallback member should not be null');
-  assert_style_value_equals(result.fallback, new CSSUnparsedValue('lemon'),
+  assert_style_value_equals(result.fallback, new CSSUnparsedValue(['lemon']),
       'Fallback member should be as same as passed in the constructor');
 }, 'CSSVariableReferenceValue can be constructed with fallback');
 
diff --git a/css/css-typed-om/the-stylepropertymap/computed/computed.tentative.html b/css/css-typed-om/the-stylepropertymap/computed/computed.tentative.html
index d2a68e7..edadefa 100644
--- a/css/css-typed-om/the-stylepropertymap/computed/computed.tentative.html
+++ b/css/css-typed-om/the-stylepropertymap/computed/computed.tentative.html
@@ -38,7 +38,7 @@
 
 test(() => {
   const result = styleMap.get('--foo');
-  assert_style_value_equals(result, new CSSUnparsedValue(' auto'));
+  assert_style_value_equals(result, new CSSUnparsedValue([' auto']));
 }, 'Computed StylePropertyMap contains custom property declarations in style rules');
 
 test(() => {
@@ -48,7 +48,7 @@
 
 test(() => {
   const result = styleMap.get('--bar');
-  assert_style_value_equals(result, new CSSUnparsedValue(' 5'));
+  assert_style_value_equals(result, new CSSUnparsedValue([' 5']));
 }, 'Computed StylePropertyMap contains custom property declarations in inline rules');
 
 test(() => {
diff --git a/css/css-typed-om/the-stylepropertymap/computed/get.html b/css/css-typed-om/the-stylepropertymap/computed/get.html
index c11ca73..3e061e1 100644
--- a/css/css-typed-om/the-stylepropertymap/computed/get.html
+++ b/css/css-typed-om/the-stylepropertymap/computed/get.html
@@ -24,7 +24,7 @@
 test(t => {
   const styleMap = createComputedStyleMap(t, '--foo: auto; --bar: 10px');
   assert_style_value_equals(styleMap.get('--foo'),
-      new CSSUnparsedValue(' auto'));
+      new CSSUnparsedValue([' auto']));
 }, 'Getting a valid custom property from computed style returns the ' +
    'correct entry');
 
diff --git a/css/css-typed-om/the-stylepropertymap/computed/getAll.tentative.html b/css/css-typed-om/the-stylepropertymap/computed/getAll.tentative.html
index cd8dcd0..ca7d3b6 100644
--- a/css/css-typed-om/the-stylepropertymap/computed/getAll.tentative.html
+++ b/css/css-typed-om/the-stylepropertymap/computed/getAll.tentative.html
@@ -31,7 +31,7 @@
 
 test(t => {
   const styleMap = createComputedStyleMap(t, '--foo: auto; --bar: 10px');
-  assert_style_value_array_equals(styleMap.getAll('--foo'), [new CSSUnparsedValue(' auto')]);
+  assert_style_value_array_equals(styleMap.getAll('--foo'), [new CSSUnparsedValue([' auto'])]);
 }, 'Calling StylePropertyMap.getAll with a valid custom property returns a single element list with the correct entry');
 
 test(t => {
diff --git a/css/css-typed-om/the-stylepropertymap/computed/iterable.tentative.html b/css/css-typed-om/the-stylepropertymap/computed/iterable.tentative.html
index 07d59d8..4006b20 100644
--- a/css/css-typed-om/the-stylepropertymap/computed/iterable.tentative.html
+++ b/css/css-typed-om/the-stylepropertymap/computed/iterable.tentative.html
@@ -34,9 +34,9 @@
 
 test(t => {
   const styleMap = createComputedStyleMap(t, '--A: A; --C: C; color: red; --B: B;');
-  assert_style_value_equals(findInStyleMap(styleMap, '--A'), new CSSUnparsedValue(' A'));
-  assert_style_value_equals(findInStyleMap(styleMap, '--B'), new CSSUnparsedValue(' B'));
-  assert_style_value_equals(findInStyleMap(styleMap, '--C'), new CSSUnparsedValue(' C'));
+  assert_style_value_equals(findInStyleMap(styleMap, '--A'), new CSSUnparsedValue([' A']));
+  assert_style_value_equals(findInStyleMap(styleMap, '--B'), new CSSUnparsedValue([' B']));
+  assert_style_value_equals(findInStyleMap(styleMap, '--C'), new CSSUnparsedValue([' C']));
 }, 'StylePropertyMap iterator returns custom properties with the correct CSSStyleValue');
 
 </script>
diff --git a/css/css-typed-om/the-stylepropertymap/declared/declared.tentative.html b/css/css-typed-om/the-stylepropertymap/declared/declared.tentative.html
index 355a509..b1d7ce6 100644
--- a/css/css-typed-om/the-stylepropertymap/declared/declared.tentative.html
+++ b/css/css-typed-om/the-stylepropertymap/declared/declared.tentative.html
@@ -45,7 +45,7 @@
 }, 'Declared StylePropertyMap does not contain inline styles');
 
 test(() => {
-  assert_style_value_equals(styleMap.get('--foo'), new CSSUnparsedValue(' auto'));
+  assert_style_value_equals(styleMap.get('--foo'), new CSSUnparsedValue([' auto']));
 }, 'Declared StylePropertyMap contains custom property declarations');
 
 test(() => {
diff --git a/css/css-typed-om/the-stylepropertymap/declared/get.html b/css/css-typed-om/the-stylepropertymap/declared/get.html
index aa649df..c4215cb 100644
--- a/css/css-typed-om/the-stylepropertymap/declared/get.html
+++ b/css/css-typed-om/the-stylepropertymap/declared/get.html
@@ -29,7 +29,7 @@
 test(t => {
   const styleMap = createDeclaredStyleMap(t, '--foo: auto; --bar: 10px');
   assert_style_value_equals(styleMap.get('--foo'),
-      new CSSUnparsedValue(' auto'));
+      new CSSUnparsedValue([' auto']));
 }, 'Getting a valid custom property from CSS rule returns the ' +
    'correct entry');
 
diff --git a/css/css-typed-om/the-stylepropertymap/declared/getAll.tentative.html b/css/css-typed-om/the-stylepropertymap/declared/getAll.tentative.html
index 5fe635c..dd4e90b 100644
--- a/css/css-typed-om/the-stylepropertymap/declared/getAll.tentative.html
+++ b/css/css-typed-om/the-stylepropertymap/declared/getAll.tentative.html
@@ -36,7 +36,7 @@
 
 test(t => {
   const styleMap = createDeclaredStyleMap(t, '--foo: auto; --bar: 10px');
-  assert_style_value_array_equals(styleMap.getAll('--foo'), [new CSSUnparsedValue(' auto')]);
+  assert_style_value_array_equals(styleMap.getAll('--foo'), [new CSSUnparsedValue([' auto'])]);
 }, 'Calling StylePropertyMap.getAll with a valid custom property returns a single element list with the correct entry');
 
 test(t => {
diff --git a/css/css-typed-om/the-stylepropertymap/declared/iterable.tentative.html b/css/css-typed-om/the-stylepropertymap/declared/iterable.tentative.html
index be61e25..0609010 100644
--- a/css/css-typed-om/the-stylepropertymap/declared/iterable.tentative.html
+++ b/css/css-typed-om/the-stylepropertymap/declared/iterable.tentative.html
@@ -42,9 +42,9 @@
 
   assert_array_equals(keys, ['--A', '--B', '--C']);
   assert_style_value_array_equals(values, [
-    new CSSUnparsedValue(' A'),
-    new CSSUnparsedValue(' B'),
-    new CSSUnparsedValue(' C'),
+    new CSSUnparsedValue([' A']),
+    new CSSUnparsedValue([' B']),
+    new CSSUnparsedValue([' C']),
   ])
 }, 'StylePropertyMap iterator returns custom properties with the correct CSSStyleValue');
 
diff --git a/css/css-typed-om/the-stylepropertymap/declared/set.tentative.html b/css/css-typed-om/the-stylepropertymap/declared/set.tentative.html
index fe4cced..232c7d3 100644
--- a/css/css-typed-om/the-stylepropertymap/declared/set.tentative.html
+++ b/css/css-typed-om/the-stylepropertymap/declared/set.tentative.html
@@ -63,11 +63,11 @@
 test(t => {
   let styleMap = createDeclaredStyleMap(t, '');
 
-  styleMap.set('--foo', new CSSUnparsedValue('auto'));
-  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue('auto'));
+  styleMap.set('--foo', new CSSUnparsedValue(['auto']));
+  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue(['auto']));
 
   styleMap.set('--foo', '20px');
-  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue('20px'));
+  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue(['20px']));
 }, 'Setting a custom property with CSSStyleValue or String updates its value');
 
 test(t => {
diff --git a/css/css-typed-om/the-stylepropertymap/declared/update.tentative.html b/css/css-typed-om/the-stylepropertymap/declared/update.tentative.html
index a5ee95e..8602e2c 100644
--- a/css/css-typed-om/the-stylepropertymap/declared/update.tentative.html
+++ b/css/css-typed-om/the-stylepropertymap/declared/update.tentative.html
@@ -46,11 +46,11 @@
 test(t => {
   let styleMap = createDeclaredStyleMap(t, '');
 
-  styleMap.update('--foo', () => new CSSUnparsedValue('auto'));
-  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue('auto'));
+  styleMap.update('--foo', () => new CSSUnparsedValue(['auto']));
+  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue(['auto']));
 
-  styleMap.update('--foo', () => new CSSUnparsedValue('20px'));
-  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue('20px'));
+  styleMap.update('--foo', () => new CSSUnparsedValue(['20px']));
+  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue(['20px']));
 }, 'Updating a custom property with CSSStyleValue updates its value');
 
 test(t => {
diff --git a/css/css-typed-om/the-stylepropertymap/inline/get.html b/css/css-typed-om/the-stylepropertymap/inline/get.html
index ac7c6e4..8588e6e 100644
--- a/css/css-typed-om/the-stylepropertymap/inline/get.html
+++ b/css/css-typed-om/the-stylepropertymap/inline/get.html
@@ -29,7 +29,7 @@
 test(t => {
   const styleMap = createInlineStyleMap(t, '--foo: auto; --bar: 10px');
   assert_style_value_equals(styleMap.get('--foo'),
-      new CSSUnparsedValue(' auto'));
+      new CSSUnparsedValue([' auto']));
 }, 'Getting a valid custom property from inline style returns the ' +
    'correct entry');
 
diff --git a/css/css-typed-om/the-stylepropertymap/inline/getAll.tentative.html b/css/css-typed-om/the-stylepropertymap/inline/getAll.tentative.html
index 62cbec3..c7e3700 100644
--- a/css/css-typed-om/the-stylepropertymap/inline/getAll.tentative.html
+++ b/css/css-typed-om/the-stylepropertymap/inline/getAll.tentative.html
@@ -36,7 +36,7 @@
 
 test(t => {
   const styleMap = createInlineStyleMap(t, '--foo: auto; --bar: 10px');
-  assert_style_value_array_equals(styleMap.getAll('--foo'), [new CSSUnparsedValue(' auto')]);
+  assert_style_value_array_equals(styleMap.getAll('--foo'), [new CSSUnparsedValue([' auto'])]);
 }, 'Calling StylePropertyMap.getAll with a valid custom property returns a single element list with the correct entry');
 
 test(t => {
diff --git a/css/css-typed-om/the-stylepropertymap/inline/iterable.tentative.html b/css/css-typed-om/the-stylepropertymap/inline/iterable.tentative.html
index cbf76d8..ebd1df7 100644
--- a/css/css-typed-om/the-stylepropertymap/inline/iterable.tentative.html
+++ b/css/css-typed-om/the-stylepropertymap/inline/iterable.tentative.html
@@ -42,9 +42,9 @@
 
   assert_array_equals(keys, ['--A', '--B', '--C']);
   assert_style_value_array_equals(values, [
-    new CSSUnparsedValue(' A'),
-    new CSSUnparsedValue(' B'),
-    new CSSUnparsedValue(' C'),
+    new CSSUnparsedValue([' A']),
+    new CSSUnparsedValue([' B']),
+    new CSSUnparsedValue([' C']),
   ])
 }, 'StylePropertyMap iterator returns custom properties with the correct CSSStyleValue');
 
diff --git a/css/css-typed-om/the-stylepropertymap/inline/set.tentative.html b/css/css-typed-om/the-stylepropertymap/inline/set.tentative.html
index dc8ccc5..67d2426d 100644
--- a/css/css-typed-om/the-stylepropertymap/inline/set.tentative.html
+++ b/css/css-typed-om/the-stylepropertymap/inline/set.tentative.html
@@ -63,11 +63,11 @@
 test(t => {
   let styleMap = createInlineStyleMap(t, '');
 
-  styleMap.set('--foo', new CSSUnparsedValue('auto'));
-  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue('auto'));
+  styleMap.set('--foo', new CSSUnparsedValue(['auto']));
+  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue(['auto']));
 
   styleMap.set('--foo', '20px');
-  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue('20px'));
+  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue(['20px']));
 }, 'Setting a custom property with CSSStyleValue or String updates its value');
 
 test(t => {
diff --git a/css/css-typed-om/the-stylepropertymap/inline/update.tentative.html b/css/css-typed-om/the-stylepropertymap/inline/update.tentative.html
index ac2d834..77f2c10 100644
--- a/css/css-typed-om/the-stylepropertymap/inline/update.tentative.html
+++ b/css/css-typed-om/the-stylepropertymap/inline/update.tentative.html
@@ -46,11 +46,11 @@
 test(t => {
   let styleMap = createInlineStyleMap(t, '');
 
-  styleMap.update('--foo', () => new CSSUnparsedValue('auto'));
-  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue('auto'));
+  styleMap.update('--foo', () => new CSSUnparsedValue(['auto']));
+  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue(['auto']));
 
-  styleMap.update('--foo', () => new CSSUnparsedValue('20px'));
-  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue('20px'));
+  styleMap.update('--foo', () => new CSSUnparsedValue(['20px']));
+  assert_style_value_array_equals(styleMap.get('--foo'), new CSSUnparsedValue(['20px']));
 }, 'Updating a custom property with CSSStyleValue updates its value');
 
 test(t => {
diff --git a/css/css-writing-modes/available-size-011.html b/css/css-writing-modes/available-size-011.html
new file mode 100644
index 0000000..38aa583
--- /dev/null
+++ b/css/css-writing-modes/available-size-011.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>orthogonal flow parent with max-height</title>
+<meta charset=utf-8>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<meta name="assert" content="If an orthogonal flow's parent doesn't have a definite block size but does have a max block size, use that as the available size">
+<link rel="match" href="reference/available-size-011-ref.html">
+<meta name="flags" content="">
+<style>
+main {
+  max-height: 1em;
+  line-height: 1em;
+}
+div {
+  writing-mode: vertical-rl;
+}
+</style>
+
+<p>This test passes if the word “PASS” (without the quotation marks) appears below, written horizontally from left to right.
+<main><div>S S A P</div></main>
diff --git a/css/css-writing-modes/available-size-012.html b/css/css-writing-modes/available-size-012.html
new file mode 100644
index 0000000..937129e
--- /dev/null
+++ b/css/css-writing-modes/available-size-012.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / max-height + min-height / content height</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-001-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size and the nearest ancestor scroller does not have a fixed height but does have a fixed max-height, use that, increased by min-height if it exists and is larger. (same as -001, with min-height)">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  max-height: 4ch; /* **max**-height does not give the element a definite block size */
+  min-height: 8ch;
+  overflow: hidden;
+  color: transparent;
+  position: relative; /* to act as a container of #red */
+  padding: 1ch 0;
+}
+
+div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  left: 0; top: 1ch;
+  writing-mode: vertical-rl;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div>0 0 0 0 <span>0</span> 0 0 0</div> <!-- If this div take its height from
+  the min-height of its parent, it should wrap just right for the green 0 to
+  overlap with the red one. -->
+</div>
diff --git a/css/css-writing-modes/available-size-013.html b/css/css-writing-modes/available-size-013.html
new file mode 100644
index 0000000..1ffd656
--- /dev/null
+++ b/css/css-writing-modes/available-size-013.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / height + min-height/ content height</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-001-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size and the nearest ancestor scroller does have a fixed height, use that, increased by the min-height if it is set and is larger (same as -003, with min-height).">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  height: 4ch;
+  min-height: 8ch;
+  width: 300px; /* Shrinkwrapping this div is not what we're interested in testing here, so give it a width. See nested-orthogonal-001.html for that. */
+  overflow: hidden;
+  color: transparent;
+  position: relative; /* to act as a container of #red */
+  writing-mode: vertical-lr;
+  padding: 1ch 0;
+}
+
+div > div { padding-bottom: 2ch; } /* so that the content height of the parent and of the fixed size scrolling ancestor are different */
+div > div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  left: 0; top: 1ch;
+  writing-mode: vertical-rl;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div><div>0 0 0 0 <span>0</span> 0 0 0</div></div> <!-- If this div take its height from
+  the height of its scrollable ancestor, it should wrap just right for the green 0 to
+  overlap with the red one. -->
+</div>
diff --git a/css/css-writing-modes/available-size-014.html b/css/css-writing-modes/available-size-014.html
new file mode 100644
index 0000000..e0e41fb
--- /dev/null
+++ b/css/css-writing-modes/available-size-014.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / height + min-height / not remaining size</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-001-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size and the nearest ancestor scroller does a have fixed height, use that whole height increased by min-height if that's larger, even if some other content already consumes some of it (same as -005, with min-height).">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  height: 4ch;
+  min-height: 8ch;
+  width: 300px; /* Shrinkwrapping this div is not what we're interested in testing here, so give it a width. See nested-orthogonal-001.html for that. */
+  overflow: hidden;
+  color: transparent;
+  position: relative; /* to act as a container of #red */
+}
+
+div > div { padding-bottom: 2ch; } /* so that the content height of the parent and of the fixed size scrolling ancestor are different */
+div > div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  left: 0;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  top: 1ch;
+}
+#spacer { /* shrinks the remaining space of the parent div. */
+  height: 1ch;
+  width: 100%;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div><aside id="spacer"></aside><div>0 0 0 0 <span>0</span> 0 0 0</div></div>
+  <!-- If the inner div take its height from the height of its scrollable
+  ancestor, it should wrap just right for the green 0 to overlap with the red
+  one. If instead it takes it size from the remaining height after having
+  removed #spacer, or does some other calculation that takes #spacer into
+  account, it won't line up with #red.-->
+</div>
diff --git a/css/css-writing-modes/available-size-015.html b/css/css-writing-modes/available-size-015.html
new file mode 100644
index 0000000..f7cb13b
--- /dev/null
+++ b/css/css-writing-modes/available-size-015.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / ICB / tall max-height + min-height scroller</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size, and there's a scroller with max-height and min-height, and max-height is smaller than the ICB but min-height is larger than the ICB, use the ICB as the available space (same as -008, with min-height).">
+<meta name="flags" content="">
+<style>
+body { margin-top: 0; margin-bottom: 0; } /* Shouldn't matter, but in some browsers does. -007 tests this aspect specifically. */
+div {
+  writing-mode: vertical-rl;
+  font-family: monospace;
+  font-size: 20px;
+  position: relative; /* to be a container for #red*/
+}
+.spacer { /* using 5 spacers of 20vh each instead of a single large one, so
+             that the line would wrap between spacers if it ends up being
+             shorter thatn 100vh*/
+  display: inline-block;
+  height: calc(20vh - 0.1px); /*Using this instead of 20vh, to account for accumulation of rounding errors, that might make 5*20vh taller than 100vh in some browsers*/
+}
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+  color: transparent;
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  font-family: monospace;
+  font-size: 20px;
+  left: 0; top: 0;
+}
+
+section {
+  overflow: hidden;
+  max-height: 40vh;
+  min-height: 120vh;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<section>
+<div><aside id="red">0</aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside> <span>0</span></div>
+</section>
diff --git a/css/css-writing-modes/available-size-016.html b/css/css-writing-modes/available-size-016.html
new file mode 100644
index 0000000..c3c388e
--- /dev/null
+++ b/css/css-writing-modes/available-size-016.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / ICB / tall height + min-height scroller</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent doesn't have a definite block size, and there's a scroller with height and min-height, and height is smaller than the ICB but min-height is larger than the ICB, use the ICB as the available space (same as -009, with min-height).">
+<meta name="flags" content="">
+<style>
+body { margin-top: 0; margin-bottom: 0; } /* Shouldn't matter, but in some browsers does. -007 tests this aspect specifically. */
+div {
+  writing-mode: vertical-rl;
+  font-family: monospace;
+  font-size: 20px;
+  position: relative; /* to be a container for #red*/
+}
+.spacer { /* using 5 spacers of 20vh each instead of a single large one, so
+             that the line would wrap between spacers if it ends up being
+             shorter thatn 100vh*/
+  display: inline-block;
+  height: calc(20vh - 0.1px); /*Using this instead of 20vh, to account for accumulation of rounding errors, that might make 5*20vh taller than 100vh in some browsers*/
+}
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+  color: transparent;
+}
+
+#red { /* Not necessary when when comparing to the reference, but makes human judgement easier */
+  position: absolute;
+  background: red;
+  writing-mode: vertical-rl;
+  z-index: -1;
+  font-family: monospace;
+  font-size: 20px;
+  left: 0; top: 0;
+}
+
+section {
+  overflow: hidden;
+  writing-mode: vertical-rl;
+  height: 40vh;
+  min-height: 120vh;
+}
+section > section {
+  writing-mode: horizontal-tb;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<section>
+<div><aside id="red">0</aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside><aside class="spacer"></aside> <span>0</span></div>
+</section>
diff --git a/css/css-writing-modes/available-size-017.html b/css/css-writing-modes/available-size-017.html
new file mode 100644
index 0000000..79f1b85
--- /dev/null
+++ b/css/css-writing-modes/available-size-017.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / height + min-height parent</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent has a height and a min-height larger than the height, use min-height as the available size.">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  height: 4ch;
+  min-height: 8ch;
+  color: transparent;
+  position: relative; /* to act as a container of #green */
+}
+
+div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red {
+  position: absolute;
+  background: red;
+  left: 0;
+  writing-mode: vertical-rl;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div>0 0 0 0 <span>0</span> 0 0 0</div> <!-- If this div takes its height from
+  the min-height of its parent (which it should)
+  it should wrap just right for the green 0 to
+  overlap with the red one. -->
+</div>
diff --git a/css/css-writing-modes/available-size-018.html b/css/css-writing-modes/available-size-018.html
new file mode 100644
index 0000000..4e86db7
--- /dev/null
+++ b/css/css-writing-modes/available-size-018.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Testing Available Space in Orthogonal Flows / max-height + min-height parent</title>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<link rel="help" href="https://www.w3.org/TR/css-writing-modes-3/#orthogonal-auto">
+<link rel="match" href="reference/available-size-002-ref.html">
+<meta name="assert" content="When an orthogonal flow's parent has a max-height and a min-height larger than the height, use min-height as the available size.">
+<meta name="flags" content="">
+<style>
+body > div {
+  font-family: monospace; /* to be able to reliably measure things in ch*/
+  font-size: 20px;
+  max-height: 4ch;
+  min-height: 8ch;
+  color: transparent;
+  position: relative; /* to act as a container of #green */
+}
+
+div > div { writing-mode: vertical-rl; }
+
+span {
+  background: green;
+  display: inline-block; /* This should not change it's size or position, but makes the size of the background predictable*/
+}
+
+#red {
+  position: absolute;
+  background: red;
+  left: 0;
+  writing-mode: vertical-rl;
+  z-index: -1;
+}
+</style>
+
+<p>Test passes if there is a <strong>green rectangle</strong> below and <strong>no red</strong>.
+
+<div>
+  <aside id="red">0</aside>
+  <div>0 0 0 0 <span>0</span> 0 0 0</div> <!-- If this div takes its height from
+  the min-height of its parent (which it should)
+  it should wrap just right for the green 0 to
+  overlap with the red one. -->
+</div>
diff --git a/css/css-writing-modes/reference/available-size-011-ref.html b/css/css-writing-modes/reference/available-size-011-ref.html
new file mode 100644
index 0000000..ef66b4e
--- /dev/null
+++ b/css/css-writing-modes/reference/available-size-011-ref.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<title>CSS writing mode test reference</title>
+<meta charset=utf-8>
+<link rel="author" title="Florian Rivoal" href="https://florian.rivoal.net/">
+<style>
+div {
+  line-height: 1em;
+  height: 1em;
+  writing-mode: vertical-rl;
+}
+</style>
+
+<p>This test passes if the word “PASS” (without the quotation marks) appears below, written horizontally from left to right.
+<div>S S A P</div>
diff --git a/css/cssom/selectorText-modification-restyle-002.html b/css/cssom/selectorText-modification-restyle-002.html
new file mode 100644
index 0000000..a6b37c2
--- /dev/null
+++ b/css/cssom/selectorText-modification-restyle-002.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>CSSOM: Modify selectorText in a shadow tree stylesheet</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/cssom/#dom-cssstylerule-selectortext">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #container { color: red }
+  .subtree * { color: pink }
+</style>
+<div id="container">
+  <div id="host"></div>
+</div>
+<script>
+  const root = host.attachShadow({mode:"open"});
+  root.innerHTML = "<style>nomatch { color: green }</style><div>Green</div>";
+  const div = root.querySelector("div");
+
+  test(() => {
+    assert_equals(getComputedStyle(div).color, "rgb(255, 0, 0)", "Color should initial be red.");
+  }, "Check initial color.");
+
+  test(() => {
+    // The combination of the .subtree and CSSOM selector style invalidations
+    // caused the Blink implementation to fail an assertion.
+    container.className = "subtree";
+    root.styleSheets[0].cssRules[0].selectorText = "div";
+    assert_equals(getComputedStyle(div).color, "rgb(0, 128, 0)", "Color should be green after stylesheet change.");
+  }, "Check that color changes correctly after shadow stylesheet selector and #container class is changed.");
+</script>
diff --git a/css/selectors/matches-nested.html b/css/selectors/matches-nested.html
new file mode 100644
index 0000000..b82d43d
--- /dev/null
+++ b/css/selectors/matches-nested.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Selectors: :matches()</title>
+    <link rel="author" title="Victoria Su" href="mailto:victoriaytsu@google.com">
+    <link rel="help" href="https://drafts.csswg.org/selectors-4/#matches">
+    <meta name="assert" content="This tests that the :matches() selector is effective when nested">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+      /* Testing that highest specificity is chosen for class outside of :matches() */
+      .a+.b+.c>.e+.d {
+        color: black;
+        font-size: 10px;
+        width: 10px;
+      }
+      .a+:matches(.b+.f, .b+:matches(*, .c>.e, .g, *))+.d {
+        color: red;
+        font-size: 20px;
+      }
+      .a+.b+.c>.e+.d {
+        color: yellow;
+      }
+      /* Testing specificty of a class within :matches() */
+      .a+.c>.e {
+        color: black;
+      }
+      .a+:matches(.b+.f, :matches(.c>.e, .g)) {
+        color: red;
+      }
+      .c>.e {
+        color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="a">
+    </div>
+    <div class="b" id="b2">
+    </div>
+    <div class="c" id="c2">
+      <div class="e">
+      </div>
+      <div class="d" id="d1">
+        Yellow
+      </div>
+    </div>
+    <div class="a">
+    </div>
+    <div class="c" id="c2">
+      <div class="e" id="e1">
+        Red
+      </div>
+    </div>
+    <script>
+
+      var red = "rgb(255, 0, 0)";
+      var yellow = "rgb(255, 255, 0)";
+
+      test(() => {
+        assert_equals(getComputedStyle(d1).color, yellow);
+        assert_equals(getComputedStyle(d1).fontSize, "20px");
+        assert_equals(getComputedStyle(d1).width, "10px");
+      }, "Test nested :matches() chooses highest specificity for class outside :matches().");
+
+      test(() => {
+        assert_equals(getComputedStyle(e1).color, red);
+      }, "Test nested :matches() specificity for class within arguments.");
+
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/css/selectors/matches-specificity.html b/css/selectors/matches-specificity.html
new file mode 100644
index 0000000..41d7251
--- /dev/null
+++ b/css/selectors/matches-specificity.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>CSS Selectors: :matches()</title>
+    <link rel="author" title="Victoria Su" href="mailto:victoriaytsu@google.com">
+    <link rel="help" href="https://drafts.csswg.org/selectors-4/#matches">
+    <meta name="assert" content="This tests that the :matches() selector chooses the correct specificity">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <style>
+      .b.c + .d + .q.r + .s + #target {
+        font-size: 10px;
+        height: 10px;
+        width: 10px;
+      }
+      :matches(.a, .b.c + .d, .e) + :matches(* + .p, .q.r + .s, * + .t) + #target {
+        height: 20px;
+        width: 20px;
+      }
+      .b.c + .d + .q.r + .s + #target {
+        width: 30px;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="b c"></div>
+    <div class="a d e"></div>
+    <div class="q r"></div>
+    <div class="p s t"></div>
+    <div id="target"></div>
+    <script>
+
+      test(() => {
+        assert_equals(getComputedStyle(target).width, "30px");
+        assert_equals(getComputedStyle(target).height, "20px");
+        assert_equals(getComputedStyle(target).fontSize, "10px");
+      }, "Test :matches() uses highest possible specificity");
+
+    </script>
+  </body>
+</html>
\ No newline at end of file
diff --git a/custom-elements/Document-createElement.html b/custom-elements/Document-createElement.html
index 97a5659..3b89130 100644
--- a/custom-elements/Document-createElement.html
+++ b/custom-elements/Document-createElement.html
@@ -36,12 +36,18 @@
     customElements.define('autonomous-custom-element', AutonomousCustomElement);
     customElements.define('is-custom-element', IsCustomElement);
 
-    var instance = document.createElement('autonomous-custom-element', { is: "is-custom-element"});
+    var instance = document.createElement('autonomous-custom-element', { is: 'is-custom-element'});
 
     assert_true(instance instanceof AutonomousCustomElement);
     assert_equals(instance.localName, 'autonomous-custom-element');
     assert_equals(instance.namespaceURI, 'http://www.w3.org/1999/xhtml', 'A custom element HTML must use HTML namespace');
 
+    var instance2 = document.createElement('undefined-element', { is: 'is-custom-element'});
+    assert_false(instance2.matches(':defined'));
+    class DefinedLater extends HTMLElement {}
+    customElements.define('undefined-element', DefinedLater);
+    document.body.appendChild(instance2);
+    assert_true(instance2 instanceof DefinedLater);
 }, 'document.createElement must create an instance of autonomous custom elements when it has is attribute');
 
 function assert_reports(expected, testFunction, message) {
diff --git a/custom-elements/Document-createElementNS.html b/custom-elements/Document-createElementNS.html
index 5711a46..ed30d0d 100644
--- a/custom-elements/Document-createElementNS.html
+++ b/custom-elements/Document-createElementNS.html
@@ -24,6 +24,14 @@
 }, 'autonomous: document.createElementNS should check namespaces.');
 
 test(() => {
+  const xhtmlNS = 'http://www.w3.org/1999/xhtml';
+  assert_false(document.createElementNS(xhtmlNS, 'x-foo') instanceof HTMLUnknownElement);
+  assert_false(document.createElementNS(xhtmlNS, 'x-foo', {}) instanceof HTMLUnknownElement);
+  assert_false((new Document()).createElementNS(xhtmlNS, 'x-foo') instanceof HTMLUnknownElement);
+  assert_false((new Document()).createElementNS(xhtmlNS, 'x-foo', {}) instanceof HTMLUnknownElement);
+}, 'autonomous: document.createElementNS should not create HTMLUnknownElement for a valid custom element name');
+
+test(() => {
   class MyBuiltinElement extends HTMLElement {};
 
   customElements.define('my-builtin', MyBuiltinElement, { extends: 'address' });
diff --git a/docs/introduction.md b/docs/introduction.md
index c718efd..8833d86 100644
--- a/docs/introduction.md
+++ b/docs/introduction.md
@@ -102,17 +102,22 @@
 and read the [Windows Notes](#windows-notes) section below.
 
 To get the tests running, you need to set up the test domains in your
-[`hosts` file](http://en.wikipedia.org/wiki/Hosts_%28file%29%23Location_in_the_file_system). The
-following entries are required:
+[`hosts` file](http://en.wikipedia.org/wiki/Hosts_%28file%29%23Location_in_the_file_system).
 
+The necessary content can be generated with `./wpt make-hosts-file`; on
+Windows, you will need to preceed the prior command with `python` or
+the path to the Python binary (`python wpt make-hosts-file`).
+
+For example, on most UNIX-like systems, you can setup the hosts file with:
+
+```bash
+./wpt make-hosts-file | sudo tee -a /etc/hosts
 ```
-127.0.0.1   web-platform.test
-127.0.0.1   www.web-platform.test
-127.0.0.1   www1.web-platform.test
-127.0.0.1   www2.web-platform.test
-127.0.0.1   xn--n8j6ds53lwwkrqhv28a.web-platform.test
-127.0.0.1   xn--lve-6lad.web-platform.test
-0.0.0.0     nonexistent-origin.web-platform.test
+
+And on Windows (note this requires an Administrator privileged shell):
+
+```bash
+python wpt make-hosts-file >> %SystemRoot%\System32\drivers\etc\hosts
 ```
 
 If you are behind a proxy, you also need to make sure the domains above are
diff --git a/html/editing/editing-0/contenteditable/contentEditable-slotted-inherit.html b/html/editing/editing-0/contenteditable/contentEditable-slotted-inherit.html
new file mode 100644
index 0000000..42da515
--- /dev/null
+++ b/html/editing/editing-0/contenteditable/contentEditable-slotted-inherit.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>contentEditable inherit from light tree parent</title>
+<link rel="author" title="Rune Lillesveen" href="mailto:futhark@chromium.org">
+<link rel=help href="https://html.spec.whatwg.org/multipage/#contenteditable">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<p>You should see the word PASS two times below and no FAIL.</p>
+<div id="host1" contenteditable><div>FAILPASS</div></div>
+<div id="host2" contenteditable><div>FAILPASS</div></div>
+<script>
+  test(() => {
+    const root = host1.attachShadow({mode:"open"});
+    root.innerHTML = "<slot></slot>";
+    const text = host1.firstChild.firstChild;
+    const selection = window.getSelection();
+    selection.collapse(text, 0);
+    selection.extend(text, 4);
+    host1.focus();
+    document.execCommand("delete");
+    host1.blur();
+    assert_equals(text.data, "PASS", "Text should be PASS after FAIL is deleted");
+  }, "Slotted child of contenteditable host should be editable - slot direct child of shadow root");
+
+  test(() => {
+    const root = host2.attachShadow({mode:"open"});
+    root.innerHTML = "<div><slot></slot></div>";
+    const text = host2.firstChild.firstChild;
+    const selection = window.getSelection();
+    selection.collapse(text, 0);
+    selection.extend(text, 4);
+    host2.focus();
+    document.execCommand("delete");
+    host2.blur();
+    assert_equals(text.data, "PASS", "Text should be PASS after FAIL is deleted");
+  }, "Slotted child of contenteditable host should be editable - slot wrapped in shadow tree ancestor");
+</script>
diff --git a/html/semantics/forms/textfieldselection/setSelectionRange.html b/html/semantics/forms/textfieldselection/setSelectionRange.html
new file mode 100644
index 0000000..bdf52a7
--- /dev/null
+++ b/html/semantics/forms/textfieldselection/setSelectionRange.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset="utf-8">
+<title></title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<textarea>
+
+</textarea>
+<script>
+test(function() {
+    let textarea = document.querySelector('textarea');
+    assert_equals(textarea.selectionStart, 0);
+    assert_equals(textarea.selectionEnd, 0);
+    textarea.setSelectionRange(0, 1);
+    assert_equals(textarea.selectionStart, 0);
+    assert_equals(textarea.selectionEnd, 1);
+}, "setSelectionRange on line boundaries");
+</script>
diff --git a/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py b/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py
new file mode 100644
index 0000000..5df883c
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py
@@ -0,0 +1,4 @@
+def main(request, response):
+    headers = [("Content-Type", "text/javascript"), ("Cache-control", "public, max-age=100")]
+    body = "throw('fox');"
+    return 200, headers, body
diff --git a/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html b/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html
new file mode 100644
index 0000000..255e79e
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<script src="cacheable-script-throw.py?iframe"></script>
diff --git a/html/semantics/scripting-1/the-script-element/muted-errors.sub.html b/html/semantics/scripting-1/the-script-element/muted-errors.sub.html
new file mode 100644
index 0000000..a42179d
--- /dev/null
+++ b/html/semantics/scripting-1/the-script-element/muted-errors.sub.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Muted Errors</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// https://html.spec.whatwg.org/#report-the-error
+// If script's muted errors is true, then set message to "Script error.",
+// urlString to the empty string, line and col to 0, and errorValue to null.
+    setup({allow_uncaught_exception: true});
+
+    window.log = [];
+    window.addEventListener("error", ev => log.push(ev));
+
+    function check(shouldBeMuted) {
+      assert_equals(log.length, 1);
+      var ev = log[0];
+      log = [];
+      if (shouldBeMuted) {
+        assert_equals(ev.message, "Script error.");
+        assert_equals(ev.error, null, 'error');
+        assert_equals(ev.filename, "", 'filename');
+        assert_equals(ev.lineno, 0, 'lineno');
+        assert_equals(ev.colno, 0, 'colno');
+      } else {
+        assert_not_equals(ev.message, "Script error.");
+        assert_not_equals(ev.error, null);
+      }
+    }
+
+    var test1 = async_test("Errors for same-origin script shouldn't be muted");
+    var check1 = test1.step_func_done(() => check(false));
+
+    var test2 = async_test("Errors for cross-origin script should be muted");
+    var check2 = test2.step_func_done(() => check(true));
+
+    var test3 = async_test("Errors for cross-origin script should be muted " +
+                           "even if the script is once loaded as same-origin");
+    function step3() {
+      var script = document.createElement('script');
+      script.setAttribute('src', "//{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py?iframe");
+      script.onerror = test3.unreached_func();
+      script.onload = test3.step_func_done(() => check(true));
+      document.body.appendChild(script);
+    }
+    function unreachable() { log.push("unexpected"); }
+</script>
+<script src="cacheable-script-throw.py" onerror="test1.unreached_func()()" onload="check1()"></script>
+<script src="//{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/cacheable-script-throw.py"
+    onerror="test2.unreached_func()()" onload="check2()"></script>
+<iframe src="//{{domains[www2]}}:{{ports[http][0]}}/html/semantics/scripting-1/the-script-element/muted-errors-iframe.html"
+    onerror="test3.unreached_func()()" onload="step3()"></iframe>
diff --git a/interfaces/accelerometer.idl b/interfaces/accelerometer.idl
index df78885..7e5509d 100644
--- a/interfaces/accelerometer.idl
+++ b/interfaces/accelerometer.idl
@@ -1,11 +1,17 @@
-[Constructor(optional SensorOptions options), SecureContext, Exposed=Window]
+enum LocalCoordinateSystem { "device", "screen" };
+
+dictionary SpatialSensorOptions : SensorOptions {
+  LocalCoordinateSystem referenceFrame = "device";
+};
+
+[Constructor(optional SpatialSensorOptions options), SecureContext, Exposed=Window]
 interface Accelerometer : Sensor {
   readonly attribute double? x;
   readonly attribute double? y;
   readonly attribute double? z;
 };
 
-[Constructor(optional SensorOptions options), SecureContext, Exposed=Window]
+[Constructor(optional SpatialSensorOptions options), SecureContext, Exposed=Window]
 interface LinearAccelerationSensor : Accelerometer {
 };
 
diff --git a/lint.whitelist b/lint.whitelist
index 2f9b44d..95f82ea 100644
--- a/lint.whitelist
+++ b/lint.whitelist
@@ -225,6 +225,7 @@
 SET TIMEOUT: user-timing/*
 SET TIMEOUT: webaudio/js/lodash.js
 SET TIMEOUT: webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html
+SET TIMEOUT: webauthn/*timeout.https.html
 SET TIMEOUT: webdriver/*
 SET TIMEOUT: webmessaging/*
 SET TIMEOUT: websockets/*
diff --git a/media/foo-no-cors.vtt b/media/foo-no-cors.vtt
new file mode 100644
index 0000000..b533895
--- /dev/null
+++ b/media/foo-no-cors.vtt
@@ -0,0 +1,4 @@
+WEBVTT
+
+00:00:00.000 --> 00:00:05.000
+Foo
diff --git a/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html b/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html
index 57210ef..ef66d5e 100644
--- a/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html
+++ b/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.html
@@ -16,17 +16,16 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.fillStyle = '#0f0';
-ctx.fillRect(0, 0, 100, 50);
 var offscreenCanvas2 = new OffscreenCanvas(0, 10);
-ctx.drawImage(offscreenCanvas2, 0, 0);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(offscreenCanvas2, 0, 0); });
+
 offscreenCanvas2.width = 10;
 offscreenCanvas2.height = 0;
-ctx.drawImage(offscreenCanvas2, 0, 0);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(offscreenCanvas2, 0, 0); });
+
 offscreenCanvas2.width = 0;
 offscreenCanvas2.height = 0;
-ctx.drawImage(offscreenCanvas2, 0, 0);
-_assertPixelApprox(offscreenCanvas, 50,25, 0,255,0,255, "50,25", "0,255,0,255", 2);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(offscreenCanvas2, 0, 0); });
 
 t.done();
 
diff --git a/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.worker.js b/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.worker.js
index 45dfff6..3a17cfe 100644
--- a/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.worker.js
+++ b/offscreen-canvas/drawing-images-to-the-canvas/2d.drawImage.zerocanvas.worker.js
@@ -12,17 +12,16 @@
 var offscreenCanvas = new OffscreenCanvas(100, 50);
 var ctx = offscreenCanvas.getContext('2d');
 
-ctx.fillStyle = '#0f0';
-ctx.fillRect(0, 0, 100, 50);
 var offscreenCanvas2 = new OffscreenCanvas(0, 10);
-ctx.drawImage(offscreenCanvas2, 0, 0);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(offscreenCanvas2, 0, 0); });
+
 offscreenCanvas2.width = 10;
 offscreenCanvas2.height = 0;
-ctx.drawImage(offscreenCanvas2, 0, 0);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(offscreenCanvas2, 0, 0); });
+
 offscreenCanvas2.width = 0;
 offscreenCanvas2.height = 0;
-ctx.drawImage(offscreenCanvas2, 0, 0);
-_assertPixelApprox(offscreenCanvas, 50,25, 0,255,0,255, "50,25", "0,255,0,255", 2);
+assert_throws("INVALID_STATE_ERR", function() { ctx.drawImage(offscreenCanvas2, 0, 0); });
 
 t.done();
 
diff --git a/offscreen-canvas/tools/tests2d.yaml b/offscreen-canvas/tools/tests2d.yaml
index fc3b158..f542d3c 100644
--- a/offscreen-canvas/tools/tests2d.yaml
+++ b/offscreen-canvas/tools/tests2d.yaml
@@ -4417,17 +4417,16 @@
   testing:
     - 2d.drawImage.zerocanvas
   code: |
-    ctx.fillStyle = '#0f0';
-    ctx.fillRect(0, 0, 100, 50);
     var offscreenCanvas2 = new OffscreenCanvas(0, 10);
-    ctx.drawImage(offscreenCanvas2, 0, 0);
+    @assert throws INVALID_STATE_ERR ctx.drawImage(offscreenCanvas2, 0, 0);
+
     offscreenCanvas2.width = 10;
     offscreenCanvas2.height = 0;
-    ctx.drawImage(offscreenCanvas2, 0, 0);
+    @assert throws INVALID_STATE_ERR ctx.drawImage(offscreenCanvas2, 0, 0);
+
     offscreenCanvas2.width = 0;
     offscreenCanvas2.height = 0;
-    ctx.drawImage(offscreenCanvas2, 0, 0);
-    @assert pixel 50,25 ==~ 0,255,0,255;
+    @assert throws INVALID_STATE_ERR ctx.drawImage(offscreenCanvas2, 0, 0);
 
 - name: 2d.drawImage.wrongtype
   desc: Incorrect image types in drawImage do not match any defined overloads, so WebIDL throws a TypeError
diff --git a/picture-in-picture/not-processing-user-gesture.html b/picture-in-picture/not-processing-user-gesture.html
deleted file mode 100644
index 0075894..0000000
--- a/picture-in-picture/not-processing-user-gesture.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<title>Test request Picture-in-Picture requires a user gesture</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<body></body>
-<script>
-promise_test(t => {
-  return promise_rejects(t, 'NotAllowedError',
-      document.createElement('video').requestPictureInPicture());
-}, );
-</script>
diff --git a/picture-in-picture/request-picture-in-picture.html b/picture-in-picture/request-picture-in-picture.html
index eaabcb7..32bbfc2 100644
--- a/picture-in-picture/request-picture-in-picture.html
+++ b/picture-in-picture/request-picture-in-picture.html
@@ -9,5 +9,21 @@
 <script>
 promise_test(t => {
   return requestPictureInPictureWithTrustedClick(document.createElement('video'));
-});
+}, 'request Picture-in-Picture resolves on user click');
+
+promise_test(t => {
+  return promise_rejects(t, 'NotAllowedError',
+      document.createElement('video').requestPictureInPicture());
+}, 'request Picture-in-Picture requires a user gesture');
+
+promise_test(t => {
+  return callWithTrustedClick(() => {
+    const first = document.createElement('video').requestPictureInPicture();
+    const second = document.createElement('video').requestPictureInPicture();
+    return Promise.all([
+      first,
+      promise_rejects(t, 'NotAllowedError', second)
+    ]);
+  });
+}, 'request Picture-in-Picture consumes user gesture');
 </script>
diff --git a/service-workers/service-worker/http-to-https-redirect-and-register.https.html b/service-workers/service-worker/http-to-https-redirect-and-register.https.html
index d78b23a..d1c6678 100644
--- a/service-workers/service-worker/http-to-https-redirect-and-register.https.html
+++ b/service-workers/service-worker/http-to-https-redirect-and-register.https.html
@@ -8,11 +8,13 @@
 <script>
 'use strict';
 
+var host_info = get_host_info();
+
 // Loads a non-secure url in a new window, which redirects to |target_url|.
 // That page then registers a service worker, and messages back with the result.
 // Returns a promise that resolves with the result.
 function redirect_and_register(target_url) {
-  var redirect_url = get_host_info()['UNAUTHENTICATED_ORIGIN'] + base_path() +
+  var redirect_url = host_info.HTTP_REMOTE_ORIGIN + base_path() +
     'resources/redirect.py?Redirect=';
   var child = window.open(redirect_url + encodeURIComponent(target_url));
   return new Promise(resolve => {
@@ -35,7 +37,7 @@
   }, 'register on a secure page after redirect from an non-secure url');
 
 promise_test(function(t) {
-    var target_url = get_host_info()['UNAUTHENTICATED_ORIGIN'] + base_path() +
+    var target_url = host_info.HTTP_REMOTE_ORIGIN + base_path() +
       'resources/http-to-https-redirect-and-register-iframe.html';
 
     return redirect_and_register(target_url)
diff --git a/service-workers/service-worker/resources/vtt-frame.html b/service-workers/service-worker/resources/vtt-frame.html
new file mode 100644
index 0000000..c3ac803
--- /dev/null
+++ b/service-workers/service-worker/resources/vtt-frame.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Page Title</title>
+<video>
+  <track>
+</video>
diff --git a/service-workers/service-worker/update-bytecheck.https.html b/service-workers/service-worker/update-bytecheck.https.html
index 6e4c6ec..ec3d15a 100644
--- a/service-workers/service-worker/update-bytecheck.https.html
+++ b/service-workers/service-worker/update-bytecheck.https.html
@@ -5,6 +5,7 @@
 <script src="resources/testharness-helpers.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/test-helpers.sub.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
 <script>
 
 /*
@@ -26,11 +27,12 @@
                   {cors: true,  main: 'time',    imported: 'default'},
                   {cors: true,  main: 'time',    imported: 'time'   }];
 
+const host_info = get_host_info();
 settings.reduce((p, s) => {
   return p.then(promise_test(function(t) {
     var path = !s.cors ? ''
-                       : 'https://www1.web-platform.test:8443/' +
-                         'service-workers/service-worker/resources/';
+                       : host_info.HTTPS_REMOTE_ORIGIN +
+                         '/service-workers/service-worker/resources/';
     var script = 'resources/bytecheck-worker.py' +
                  '?main=' + s.main +
                  '&imported=' + s.imported +
diff --git a/service-workers/service-worker/webvtt-cross-origin.https.html b/service-workers/service-worker/webvtt-cross-origin.https.html
new file mode 100644
index 0000000..0f98060
--- /dev/null
+++ b/service-workers/service-worker/webvtt-cross-origin.https.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>cross-origin webvtt returned by service worker is detected</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/test-helpers.sub.js?pipe=sub"></script>
+<body>
+<script>
+// This file tests opaque responses for WebVTT text track. It creates
+// an iframe with a <track> element, controlled by a service worker.
+// Each test tries to load a text track, the service worker intercepts the
+// requests and responds with opaque or non-opaque responses. The opaque
+// responses should result in load errors.
+
+const host_info = get_host_info();
+const kScript = 'resources/fetch-rewrite-worker.js';
+// Add '?ignore' so the service worker falls back for the navigation.
+const kScope = 'resources/vtt-frame.html?ignore';
+let frame;
+
+function load_track(url) {
+  const track = frame.contentDocument.querySelector('track');
+  const result = new Promise((resolve, reject) => {
+      track.onload = (e => {
+          resolve('load event');
+        });
+      track.onerror = (e => {
+          resolve('error event');
+        });
+    });
+
+  track.src = url;
+  // Setting mode to hidden seems needed, or else the text track requests don't
+  // occur.
+  track.track.mode = 'hidden';
+  return result;
+}
+
+promise_test(t => {
+    return service_worker_unregister_and_register(t, kScript, kScope)
+      .then(registration => {
+          promise_test(() => {
+              frame.remove();
+              return registration.unregister();
+            }, 'restore global state');
+
+          return wait_for_state(t, registration.installing, 'activated');
+        })
+      .then(() => {
+          return with_iframe(kScope);
+        })
+      .then(f => {
+          frame = f;
+        })
+  }, 'initialize global state');
+
+promise_test(t => {
+    let url = '/media/foo.vtt';
+    // Add '?url' and tell the service worker to return a same-origin URL.
+    url += '?url=' + host_info.HTTPS_ORIGIN + '/media/foo.vtt';
+    return load_track(url)
+      .then(result => {
+          assert_equals(result, 'load event');
+        });
+  }, 'same-origin text track should load');
+
+promise_test(t => {
+    let url = '/media/foo.vtt';
+    // Add '?url' and tell the service worker to return a cross-origin URL.
+    url += '?url=' + get_host_info().HTTPS_REMOTE_ORIGIN + '/media/foo.vtt';
+    return load_track(url)
+      .then(result => {
+          assert_equals(result, 'error event');
+        });
+  }, 'cross-origin text track with no-cors request should not load');
+
+promise_test(t => {
+    let url = '/media/foo.vtt';
+    // Add '?url' and tell the service worker to return a cross-origin URL that
+    // doesn't support CORS.
+    url += '?url=' + get_host_info().HTTPS_REMOTE_ORIGIN +
+        '/media/foo-no-cors.vtt';
+    // Add '&mode' to tell the service worker to do a CORS request.
+    url += '&mode=cors';
+    return load_track(url)
+      .then(result => {
+          assert_equals(result, 'error event');
+        });
+  }, 'cross-origin text track with rejected cors request should not load');
+
+promise_test(t => {
+    let url = '/media/foo.vtt';
+    // Add '?url' and tell the service worker to return a cross-origin URL.
+    url += '?url=' + get_host_info().HTTPS_REMOTE_ORIGIN + '/media/foo.vtt';
+    // Add '&mode' to tell the service worker to do a CORS request.
+    url += '&mode=cors';
+    // Add '&credentials=anonymous' to allow Access-Control-Allow-Origin=*.
+    url += '&credentials=anonymous';
+    return load_track(url)
+      .then(result => {
+          assert_equals(result, 'load event');
+        });
+  }, 'cross-origin text track with approved cors request should load');
+</script>
+</body>
diff --git a/streams/readable-byte-streams/properties.js b/streams/readable-byte-streams/properties.js
index 02d0294..975fba7 100644
--- a/streams/readable-byte-streams/properties.js
+++ b/streams/readable-byte-streams/properties.js
@@ -88,7 +88,7 @@
         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.constructor.length, 0, 'constructor has 0 parameters');
         assert_equals(byobRequest.respond.length, 1, 'respond has 1 parameter');
         assert_equals(byobRequest.respondWithNewView.length, 1, 'releaseLock has 1 parameter');
 
diff --git a/tools/ci/commands.json b/tools/ci/commands.json
index d682d2a..a8db428 100644
--- a/tools/ci/commands.json
+++ b/tools/ci/commands.json
@@ -2,5 +2,6 @@
     "test-jobs": {"path": "jobs.py", "script": "run", "parser": "create_parser", "help": "List test jobs that should run for a set of commits",
                   "virtualenv": false},
     "check-stability": {"path": "check_stability.py", "script": "run", "parser": "get_parser", "parse_known": true, "help": "Check test stability",
-                        "virtualenv": true, "install": ["requests"], "requirements": ["../wptrunner/requirements.txt"]}
+                        "virtualenv": true, "install": ["requests"], "requirements": ["../wptrunner/requirements.txt"]},
+  "make-hosts-file": {"path": "make_hosts_file.py", "script": "run", "parser": "create_parser", "help": "Output a hosts file to stdout", "virtualenv": false}
 }
diff --git a/tools/ci/lib.sh b/tools/ci/lib.sh
index db49ec6..2273793 100644
--- a/tools/ci/lib.sh
+++ b/tools/ci/lib.sh
@@ -6,15 +6,7 @@
     echo "## /etc/hosts ##"
     cat /etc/hosts
     sudo sed -i 's/^::1\s*localhost/::1/' /etc/hosts
-    sudo sh -c 'echo "
-127.0.0.1 web-platform.test
-127.0.0.1 www.web-platform.test
-127.0.0.1 www1.web-platform.test
-127.0.0.1 www2.web-platform.test
-127.0.0.1 xn--n8j6ds53lwwkrqhv28a.web-platform.test
-127.0.0.1 xn--lve-6lad.web-platform.test
-0.0.0.0 nonexistent-origin.web-platform.test
-" >> /etc/hosts'
+    ./wpt make-hosts-file | sudo tee -a /etc/hosts
     echo "== /etc/hosts =="
     cat /etc/hosts
     echo "----------------"
diff --git a/tools/ci/make_hosts_file.py b/tools/ci/make_hosts_file.py
new file mode 100644
index 0000000..b3c7758
--- /dev/null
+++ b/tools/ci/make_hosts_file.py
@@ -0,0 +1,19 @@
+import argparse
+import os
+
+from ..localpaths import repo_root
+
+from ..serve.serve import load_config, normalise_config, make_hosts_file
+
+def create_parser():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("address", default="127.0.0.1", nargs="?", help="Address that hosts should point at")
+    return parser
+
+def run(**kwargs):
+    config = load_config(os.path.join(repo_root, "config.default.json"),
+                         os.path.join(repo_root, "config.json"))
+
+    config = normalise_config(config, {})
+
+    print(make_hosts_file(config, kwargs["address"]))
diff --git a/tools/conftest.py b/tools/conftest.py
index 894fe62..4ce5e74 100644
--- a/tools/conftest.py
+++ b/tools/conftest.py
@@ -1,5 +1,6 @@
 import platform
 import os
+import sys
 
 from hypothesis import settings, HealthCheck
 
@@ -11,3 +12,11 @@
 
 settings.load_profile(os.getenv("HYPOTHESIS_PROFILE",
                                 "default" if impl != "PyPy" else "pypy"))
+
+# serve can't even be imported on Py3, so totally ignore it even from collection
+collect_ignore = []
+if sys.version_info[0] >= 3:
+    serve = os.path.join(os.path.dirname(__file__), "serve")
+    collect_ignore.extend([os.path.join(root, f)
+                           for root, _, files in os.walk(serve)
+                           for f in files])
diff --git a/tools/serve/serve.py b/tools/serve/serve.py
index c77badc..9c5e7ca 100644
--- a/tools/serve/serve.py
+++ b/tools/serve/serve.py
@@ -203,6 +203,8 @@
               u"天気の良い日",
               u"élève"]
 
+not_subdomains = [u"nonexistent-origin"]
+
 class RoutesBuilder(object):
     def __init__(self):
         self.forbidden_override = [("GET", "/tools/runner/*", handlers.file_handler),
@@ -468,6 +470,24 @@
             for subdomain in subdomains}
 
 
+def get_not_subdomains(host):
+    #This assumes that the tld is ascii-only or already in punycode
+    return {subdomain: (subdomain.encode("idna"), host)
+            for subdomain in not_subdomains}
+
+
+def make_hosts_file(config, host):
+    rv = []
+
+    for domain in config["domains"].values():
+        rv.append("%s\t%s\n" % (host, domain))
+
+    for not_domain in config.get("not_domains", {}).values():
+        rv.append("0.0.0.0\t%s\n" % not_domain)
+
+    return "".join(rv)
+
+
 def start_servers(host, ports, paths, routes, bind_hostname, config, ssl_config,
                   **kwargs):
     servers = defaultdict(list)
@@ -626,6 +646,7 @@
 def normalise_config(config, ports):
     host = config["external_host"] if config["external_host"] else config["host"]
     domains = get_subdomains(host)
+    not_domains = get_not_subdomains(host)
     ports_ = {}
     for scheme, ports_used in ports.iteritems():
         ports_[scheme] = ports_used
@@ -633,6 +654,9 @@
     for key, value in domains.iteritems():
         domains[key] = ".".join(value)
 
+    for key, value in not_domains.iteritems():
+        not_domains[key] = ".".join(value)
+
     domains[""] = host
 
     ports_ = {}
@@ -644,6 +668,7 @@
     config_ = config.copy()
     config_["host"] = host
     config_["domains"] = domains
+    config_["not_domains"] = not_domains
     config_["ports"] = ports_
     return config_
 
diff --git a/tools/serve/test_serve.py b/tools/serve/test_serve.py
new file mode 100644
index 0000000..3337601
--- /dev/null
+++ b/tools/serve/test_serve.py
@@ -0,0 +1,12 @@
+from . import serve
+
+def test_make_hosts_file():
+    hosts = serve.make_hosts_file({
+        "domains": {"www": "www.foo.bar.test", "www1": "www1.foo.bar.test"},
+        "not_domains": {"aaa": "aaa.foo.bar.test", "bbb": "bbb.foo.bar.test"}
+    }, "127.1.1.1")
+    lines = hosts.split("\n")
+    assert "127.1.1.1\twww.foo.bar.test" in lines
+    assert "127.1.1.1\twww1.foo.bar.test" in lines
+    assert "0.0.0.0\taaa.foo.bar.test" in lines
+    assert "0.0.0.0\tbbb.foo.bar.test" in lines
diff --git a/tools/wpt/run.py b/tools/wpt/run.py
index f3fbcbd..236299e 100644
--- a/tools/wpt/run.py
+++ b/tools/wpt/run.py
@@ -11,6 +11,8 @@
 sys.path.insert(0, os.path.abspath(os.path.join(wpt_root, "tools")))
 
 from . import browser, utils, virtualenv
+from ..serve import serve
+
 logger = None
 
 
@@ -94,13 +96,10 @@
 
 def check_environ(product):
     if product not in ("firefox", "servo"):
-        expected_hosts = ["web-platform.test",
-                          "www.web-platform.test",
-                          "www1.web-platform.test",
-                          "www2.web-platform.test",
-                          "xn--n8j6ds53lwwkrqhv28a.web-platform.test",
-                          "xn--lve-6lad.web-platform.test",
-                          "nonexistent-origin.web-platform.test"]
+        expected_hosts = {".".join(x)
+                          for x in serve.get_subdomains("web-platform.test").values()}
+        expected_hosts |= {".".join(x)
+                           for x in serve.get_not_subdomains("web-platform.test").values()}
         missing_hosts = set(expected_hosts)
         if platform.uname()[0] != "Windows":
             hosts_path = "/etc/hosts"
@@ -114,13 +113,17 @@
                 for host in hosts:
                     missing_hosts.discard(host)
             if missing_hosts:
-                raise WptrunError("""Missing hosts file configuration. Expected entries like:
+                if platform.uname()[0] != "Windows":
+                    message = """Missing hosts file configuration. Run
 
-%s
+python wpt make-hosts-file >> %s
 
-See README.md for more details.""" % "\n".join("%s\t%s" %
-                                               ("127.0.0.1" if "nonexistent" not in host else "0.0.0.0", host)
-                                               for host in expected_hosts))
+from a shell with Administrator privileges.""" % hosts_path
+                else:
+                    message = """Missing hosts file configuration. Run
+
+./wpt make-hosts-file | sudo tee -a %s""" % hosts_path
+                raise WptrunError(message)
 
 
 class BrowserSetup(object):
diff --git a/tools/wptrunner/MANIFEST.in b/tools/wptrunner/MANIFEST.in
index 7083923..d3b530a 100644
--- a/tools/wptrunner/MANIFEST.in
+++ b/tools/wptrunner/MANIFEST.in
@@ -5,4 +5,3 @@
 include wptrunner/*.js
 include wptrunner/executors/*.js
 include wptrunner/config.json
-include wptrunner/browsers/server-locations.txt
\ No newline at end of file
diff --git a/tools/wptrunner/setup.py b/tools/wptrunner/setup.py
index 7da5141..148a1d2 100644
--- a/tools/wptrunner/setup.py
+++ b/tools/wptrunner/setup.py
@@ -52,7 +52,6 @@
                                   "testharness_runner.html",
                                   "config.json",
                                   "wptrunner.default.ini",
-                                  "browsers/server-locations.txt",
                                   "browsers/sauce_setup/*",
                                   "prefs/*"]},
       include_package_data=True,
diff --git a/tools/wptrunner/wptrunner/browsers/chrome.py b/tools/wptrunner/wptrunner/browsers/chrome.py
index 215fccf..7f9a21c 100644
--- a/tools/wptrunner/wptrunner/browsers/chrome.py
+++ b/tools/wptrunner/wptrunner/browsers/chrome.py
@@ -60,8 +60,7 @@
 
 
 def env_options():
-    return {"host": "web-platform.test",
-            "bind_hostname": "true"}
+    return {"bind_hostname": "true"}
 
 
 class ChromeBrowser(Browser):
diff --git a/tools/wptrunner/wptrunner/browsers/chrome_android.py b/tools/wptrunner/wptrunner/browsers/chrome_android.py
index 47090be..3e832cb 100644
--- a/tools/wptrunner/wptrunner/browsers/chrome_android.py
+++ b/tools/wptrunner/wptrunner/browsers/chrome_android.py
@@ -68,8 +68,7 @@
 
 
 def env_options():
-    return {"host": "web-platform.test",
-            "bind_hostname": "true"}
+    return {"bind_hostname": "true"}
 
 
 class ChromeAndroidBrowser(Browser):
diff --git a/tools/wptrunner/wptrunner/browsers/edge.py b/tools/wptrunner/wptrunner/browsers/edge.py
index db4ae00..094405c 100644
--- a/tools/wptrunner/wptrunner/browsers/edge.py
+++ b/tools/wptrunner/wptrunner/browsers/edge.py
@@ -38,8 +38,7 @@
     return []
 
 def env_options():
-    return {"host": "web-platform.test",
-            "bind_hostname": "true",
+    return {"bind_hostname": "true",
             "supports_debugger": False}
 
 class EdgeBrowser(Browser):
diff --git a/tools/wptrunner/wptrunner/browsers/firefox.py b/tools/wptrunner/wptrunner/browsers/firefox.py
index 6d1f58d..57e0527 100644
--- a/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -3,12 +3,12 @@
 import signal
 import subprocess
 import sys
+import tempfile
 
 import mozinfo
 import mozleak
 from mozprocess import ProcessHandler
 from mozprofile import FirefoxProfile, Preferences
-from mozprofile.permissions import ServerLocations
 from mozrunner import FirefoxRunner
 from mozrunner.utils import get_stack_fixer_function
 from mozcrash import mozcrash
@@ -23,7 +23,6 @@
 from ..executors.executormarionette import (MarionetteTestharnessExecutor,
                                             MarionetteRefTestExecutor,
                                             MarionetteWdspecExecutor)
-from ..environment import hostnames
 
 
 here = os.path.join(os.path.split(__file__)[0])
@@ -80,7 +79,8 @@
                                                          **kwargs),
             "leak_check": kwargs["leak_check"],
             "stylo_threads": kwargs["stylo_threads"],
-            "chaos_mode_flags": kwargs["chaos_mode_flags"]}
+            "chaos_mode_flags": kwargs["chaos_mode_flags"],
+            "config": kwargs["config"]}
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
@@ -102,7 +102,7 @@
         if kwargs["binary_args"]:
             options["args"] = kwargs["binary_args"]
         options["prefs"] = {
-            "network.dns.localDomains": ",".join(hostnames)
+            "network.dns.localDomains": ",".join(server_config['domains'].values())
         }
         capabilities["moz:firefoxOptions"] = options
     if kwargs["certutil_binary"] is None:
@@ -143,7 +143,7 @@
                  symbols_path=None, stackwalk_binary=None, certutil_binary=None,
                  ca_certificate_path=None, e10s=False, stackfix_dir=None,
                  binary_args=None, timeout_multiplier=None, leak_check=False, stylo_threads=1,
-                 chaos_mode_flags=None):
+                 chaos_mode_flags=None, config=None):
         Browser.__init__(self, logger)
         self.binary = binary
         self.prefs_root = prefs_root
@@ -159,6 +159,7 @@
         self.certutil_binary = certutil_binary
         self.e10s = e10s
         self.binary_args = binary_args
+        self.config = config
         if stackfix_dir:
             self.stack_fixer = get_stack_fixer_function(stackfix_dir,
                                                         self.symbols_path)
@@ -189,15 +190,12 @@
         if self.chaos_mode_flags is not None:
             env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags)
 
-        locations = ServerLocations(filename=os.path.join(here, "server-locations.txt"))
-
         preferences = self.load_prefs()
 
-        self.profile = FirefoxProfile(locations=locations,
-                                      preferences=preferences)
+        self.profile = FirefoxProfile(preferences=preferences)
         self.profile.set_preferences({"marionette.port": self.marionette_port,
                                       "dom.disable_open_during_load": False,
-                                      "network.dns.localDomains": ",".join(hostnames),
+                                      "network.dns.localDomains": ",".join(self.config['domains'].values()),
                                       "network.proxy.type": 0,
                                       "places.history.enabled": False,
                                       "dom.send_after_paint_to_content": True,
diff --git a/tools/wptrunner/wptrunner/browsers/ie.py b/tools/wptrunner/wptrunner/browsers/ie.py
index 13f5827..9c0007e 100644
--- a/tools/wptrunner/wptrunner/browsers/ie.py
+++ b/tools/wptrunner/wptrunner/browsers/ie.py
@@ -42,8 +42,7 @@
     return []
 
 def env_options():
-    return {"host": "web-platform.test",
-            "bind_hostname": "true",
+    return {"bind_hostname": "true",
             "supports_debugger": False}
 
 class InternetExplorerBrowser(Browser):
diff --git a/tools/wptrunner/wptrunner/browsers/opera.py b/tools/wptrunner/wptrunner/browsers/opera.py
index 57edfa0..693a19a 100644
--- a/tools/wptrunner/wptrunner/browsers/opera.py
+++ b/tools/wptrunner/wptrunner/browsers/opera.py
@@ -60,8 +60,7 @@
 
 
 def env_options():
-    return {"host": "web-platform.test",
-            "bind_hostname": "true"}
+    return {"bind_hostname": "true"}
 
 
 class OperaBrowser(Browser):
diff --git a/tools/wptrunner/wptrunner/browsers/sauce.py b/tools/wptrunner/wptrunner/browsers/sauce.py
index 4c1be1c..42c83c5 100644
--- a/tools/wptrunner/wptrunner/browsers/sauce.py
+++ b/tools/wptrunner/wptrunner/browsers/sauce.py
@@ -113,8 +113,7 @@
 
 
 def env_options():
-    return {"host": "web-platform.test",
-            "bind_hostname": "true",
+    return {"bind_hostname": "true",
             "supports_debugger": False}
 
 
@@ -128,6 +127,7 @@
 class SauceConnect():
 
     def __init__(self, **kwargs):
+        self.config = kwargs["config"]
         self.sauce_user = kwargs["sauce_user"]
         self.sauce_key = kwargs["sauce_key"]
         self.sauce_tunnel_id = kwargs["sauce_tunnel_id"]
@@ -153,8 +153,7 @@
             "--metrics-address=0.0.0.0:9876",
             "--readyfile=./sauce_is_ready",
             "--tunnel-domains",
-            "web-platform.test",
-            "*.web-platform.test"
+            ",".join(self.config['domains'].values())
         ])
 
         # Timeout config vars
diff --git a/tools/wptrunner/wptrunner/browsers/server-locations.txt b/tools/wptrunner/wptrunner/browsers/server-locations.txt
deleted file mode 100644
index 5dcaf4b..0000000
--- a/tools/wptrunner/wptrunner/browsers/server-locations.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-http://localhost:8000    primary
-
-http://web-platform.test:8000
-http://www.web-platform.test:8000
-http://www1.web-platform.test:8000
-http://www2.web-platform.test:8000
-http://xn--n8j6ds53lwwkrqhv28a.web-platform.test:8000
-http://xn--lve-6lad.web-platform.test:8000
-
-http://web-platform.test:8001
-http://www.web-platform.test:8001
-http://www1.web-platform.test:8001
-http://www2.web-platform.test:8001
-http://xn--n8j6ds53lwwkrqhv28a.web-platform.test:8001
-http://xn--lve-6lad.web-platform.test:8001
-
-https://web-platform.test:8443
-https://www.web-platform.test:8443
-https://www1.web-platform.test:8443
-https://www2.web-platform.test:8443
-https://xn--n8j6ds53lwwkrqhv28a.web-platform.test:8443
-https://xn--lve-6lad.web-platform.test:8443
-
-# These are actually ws servers, but until mozprofile is
-# fixed we have to pretend that they are http servers
-http://web-platform.test:8888
-http://www.web-platform.test:8888
-http://www1.web-platform.test:8888
-http://www2.web-platform.test:8888
-http://xn--n8j6ds53lwwkrqhv28a.web-platform.test:8888
-http://xn--lve-6lad.web-platform.test:8888
diff --git a/tools/wptrunner/wptrunner/browsers/servodriver.py b/tools/wptrunner/wptrunner/browsers/servodriver.py
index c251de8..0986450 100644
--- a/tools/wptrunner/wptrunner/browsers/servodriver.py
+++ b/tools/wptrunner/wptrunner/browsers/servodriver.py
@@ -1,9 +1,12 @@
 import os
+import shutil
 import subprocess
 import tempfile
 
 from mozprocess import ProcessHandler
 
+from serve.serve import make_hosts_file
+
 from .base import Browser, require_arg, get_free_port, browser_command, ExecutorBrowser
 from ..executors import executor_kwargs as base_executor_kwargs
 from ..executors.executorservodriver import (ServoWebDriverTestharnessExecutor,
@@ -26,14 +29,6 @@
     "update_properties": "update_properties",
 }
 
-hosts_text = """127.0.0.1 web-platform.test
-127.0.0.1 www.web-platform.test
-127.0.0.1 www1.web-platform.test
-127.0.0.1 www2.web-platform.test
-127.0.0.1 xn--n8j6ds53lwwkrqhv28a.web-platform.test
-127.0.0.1 xn--lve-6lad.web-platform.test
-"""
-
 
 def check_args(**kwargs):
     require_arg(kwargs, "binary")
@@ -69,10 +64,10 @@
     return ["debug", "os", "version", "processor", "bits"], None
 
 
-def make_hosts_file():
+def write_hosts_file(config):
     hosts_fd, hosts_path = tempfile.mkstemp()
     with os.fdopen(hosts_fd, "w") as f:
-        f.write(hosts_text)
+        f.write(make_hosts_file(config, "127.0.0.1"))
     return hosts_path
 
 
@@ -87,7 +82,7 @@
         self.webdriver_port = None
         self.proc = None
         self.debug_info = debug_info
-        self.hosts_path = make_hosts_file()
+        self.hosts_path = write_hosts_file()
         self.command = None
         self.user_stylesheets = user_stylesheets if user_stylesheets else []
 
@@ -158,6 +153,7 @@
 
     def cleanup(self):
         self.stop()
+        shutil.rmtree(os.path.dirname(self.hosts_file))
 
     def executor_browser(self):
         assert self.webdriver_port is not None
diff --git a/tools/wptrunner/wptrunner/environment.py b/tools/wptrunner/wptrunner/environment.py
index 7115341..22b02a9 100644
--- a/tools/wptrunner/wptrunner/environment.py
+++ b/tools/wptrunner/wptrunner/environment.py
@@ -18,14 +18,6 @@
 sslutils = None
 
 
-hostnames = ["web-platform.test",
-             "www.web-platform.test",
-             "www1.web-platform.test",
-             "www2.web-platform.test",
-             "xn--n8j6ds53lwwkrqhv28a.web-platform.test",
-             "xn--lve-6lad.web-platform.test"]
-
-
 def do_delayed_imports(logger, test_paths):
     global serve, sslutils
 
diff --git a/tools/wptrunner/wptrunner/executors/executorservo.py b/tools/wptrunner/wptrunner/executors/executorservo.py
index ca8ec8a..ac8e92e 100644
--- a/tools/wptrunner/wptrunner/executors/executorservo.py
+++ b/tools/wptrunner/wptrunner/executors/executorservo.py
@@ -13,6 +13,8 @@
 
 from mozprocess import ProcessHandler
 
+from serve.serve import make_hosts_file
+
 from .base import (ExecutorException,
                    Protocol,
                    RefTestImplementation,
@@ -30,18 +32,10 @@
 
 extra_timeout = 5  # seconds
 
-hosts_text = """127.0.0.1 web-platform.test
-127.0.0.1 www.web-platform.test
-127.0.0.1 www1.web-platform.test
-127.0.0.1 www2.web-platform.test
-127.0.0.1 xn--n8j6ds53lwwkrqhv28a.web-platform.test
-127.0.0.1 xn--lve-6lad.web-platform.test
-"""
-
-def make_hosts_file():
+def write_hosts_file(config):
     hosts_fd, hosts_path = tempfile.mkstemp()
     with os.fdopen(hosts_fd, "w") as f:
-        f.write(hosts_text)
+        f.write(make_hosts_file(config, "127.0.0.1"))
     return hosts_path
 
 
@@ -57,7 +51,7 @@
         self.result_data = None
         self.result_flag = None
         self.protocol = Protocol(self, browser)
-        self.hosts_path = make_hosts_file()
+        self.hosts_path = write_hosts_file(server_config)
 
     def teardown(self):
         try:
diff --git a/tools/wptrunner/wptrunner/hosts.py b/tools/wptrunner/wptrunner/hosts.py
deleted file mode 100644
index 915c17f..0000000
--- a/tools/wptrunner/wptrunner/hosts.py
+++ /dev/null
@@ -1,100 +0,0 @@
-from __future__ import unicode_literals
-
-
-class HostsLine(object):
-    def __init__(self, ip_address, canonical_hostname, aliases=None, comment=None):
-        self.ip_address = ip_address
-        self.canonical_hostname = canonical_hostname
-        self.aliases = aliases if aliases is not None else []
-        self.comment = comment
-        if self.ip_address is None:
-            assert self.canonical_hostname is None
-            assert not self.aliases
-            assert self.comment is not None
-
-    @classmethod
-    def from_string(cls, line):
-        if not line.strip():
-            return
-
-        line = line.strip()
-
-        ip_address = None
-        canonical_hostname = None
-        aliases = []
-        comment = None
-
-        comment_parts = line.split("#", 1)
-        if len(comment_parts) > 1:
-            comment = comment_parts[1]
-
-        data = comment_parts[0].strip()
-
-        if data:
-            fields = data.split()
-            if len(fields) < 2:
-                raise ValueError("Invalid hosts line")
-
-            ip_address = fields[0]
-            canonical_hostname = fields[1]
-            aliases = fields[2:]
-
-        return cls(ip_address, canonical_hostname, aliases, comment)
-
-
-class HostsFile(object):
-    def __init__(self):
-        self.data = []
-        self.by_hostname = {}
-
-    def set_host(self, host):
-        if host.canonical_hostname is None:
-            self.data.append(host)
-        elif host.canonical_hostname in self.by_hostname:
-            old_host = self.by_hostname[host.canonical_hostname]
-            old_host.ip_address = host.ip_address
-            old_host.aliases = host.aliases
-            old_host.comment = host.comment
-        else:
-            self.data.append(host)
-            self.by_hostname[host.canonical_hostname] = host
-
-    @classmethod
-    def from_file(cls, f):
-        rv = cls()
-        for line in f:
-            host = HostsLine.from_string(line)
-            if host is not None:
-                rv.set_host(host)
-        return rv
-
-    def to_string(self):
-        field_widths = [0, 0]
-        for line in self.data:
-            if line.ip_address is not None:
-                field_widths[0] = max(field_widths[0], len(line.ip_address))
-                field_widths[1] = max(field_widths[1], len(line.canonical_hostname))
-
-        lines = []
-
-        for host in self.data:
-            line = ""
-            if host.ip_address is not None:
-                ip_string = host.ip_address.ljust(field_widths[0])
-                hostname_str = host.canonical_hostname
-                if host.aliases:
-                    hostname_str = "%s %s" % (hostname_str.ljust(field_widths[1]),
-                                              " ".join(host.aliases))
-                line = "%s %s" % (ip_string, hostname_str)
-            if host.comment:
-                if line:
-                    line += " "
-                line += "#%s" % host.comment
-            lines.append(line)
-
-        lines.append("")
-
-        return "\n".join(lines)
-
-    def to_file(self, f):
-        f.write(self.to_string().encode("utf8"))
diff --git a/tools/wptrunner/wptrunner/tests/browsers/__init__.py b/tools/wptrunner/wptrunner/tests/browsers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/wptrunner/wptrunner/tests/browsers/__init__.py
diff --git a/tools/wptrunner/wptrunner/tests/browsers/test_sauce.py b/tools/wptrunner/wptrunner/tests/browsers/test_sauce.py
index bf3e487..019b538 100644
--- a/tools/wptrunner/wptrunner/tests/browsers/test_sauce.py
+++ b/tools/wptrunner/wptrunner/tests/browsers/test_sauce.py
@@ -20,6 +20,9 @@
         exists.return_value = True
 
         sauce_connect = sauce.SauceConnect(
+            config={
+                "domains": {"": "example.net"}
+            },
             sauce_user="aaa",
             sauce_key="bbb",
             sauce_tunnel_id="ccc",
@@ -46,6 +49,9 @@
         exists.return_value = readyfile
 
         sauce_connect = sauce.SauceConnect(
+            config={
+                "domains": {"": "example.net"}
+            },
             sauce_user="aaa",
             sauce_key="bbb",
             sauce_tunnel_id="ccc",
@@ -68,6 +74,9 @@
         exists.return_value = False
 
         sauce_connect = sauce.SauceConnect(
+            config={
+                "domains": {"": "example.net"}
+            },
             sauce_user="aaa",
             sauce_key="bbb",
             sauce_tunnel_id="ccc",
@@ -82,3 +91,34 @@
         # Check we actually kill it after termination fails
         Popen.return_value.terminate.assert_called()
         Popen.return_value.kill.assert_called()
+
+
+def test_sauceconnect_tunnel_domains():
+    with mock.patch.object(sauce.SauceConnect, "upload_prerun_exec"),\
+            mock.patch.object(sauce.subprocess, "Popen") as Popen,\
+            mock.patch.object(sauce.os.path, "exists") as exists:
+        Popen.return_value.poll.return_value = None
+        Popen.return_value.returncode = None
+        exists.return_value = True
+
+        sauce_connect = sauce.SauceConnect(
+            config={
+                "domains": {"foo": "foo.bar.example.com", "": "example.net"}
+            },
+            sauce_user="aaa",
+            sauce_key="bbb",
+            sauce_tunnel_id="ccc",
+            sauce_connect_binary="ddd")
+
+        sauce_connect.__enter__(None)
+
+        Popen.assert_called_once()
+        args, kwargs = Popen.call_args
+        cmd = args[0]
+        assert "--tunnel-domains" in cmd
+        i = cmd.index("--tunnel-domains")
+        rest = cmd[i+1:]
+        assert len(rest) >= 1
+        if len(rest) > 1:
+            assert rest[1].startswith("-"), "--tunnel-domains takes a comma separated list (not a space separated list)"
+        assert set(rest[0].split(",")) == {"foo.bar.example.com", "example.net"}
diff --git a/tools/wptrunner/wptrunner/tests/test_hosts.py b/tools/wptrunner/wptrunner/tests/test_hosts.py
deleted file mode 100644
index e7d41f3..0000000
--- a/tools/wptrunner/wptrunner/tests/test_hosts.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import unittest
-import sys
-from os.path import join, dirname
-from cStringIO import StringIO
-
-sys.path.insert(0, join(dirname(__file__), "..", ".."))
-
-from wptrunner import hosts
-
-
-class HostsTest(unittest.TestCase):
-    def do_test(self, input, expected):
-        host_file = hosts.HostsFile.from_file(StringIO(input))
-        self.assertEquals(host_file.to_string(), expected)
-
-    def test_simple(self):
-        self.do_test("""127.0.0.1    \tlocalhost  alias # comment
-# Another comment""",
-                     """127.0.0.1 localhost alias # comment
-# Another comment
-""")
-
-    def test_blank_lines(self):
-        self.do_test("""127.0.0.1    \tlocalhost  alias # comment
-
-\r
-    \t
-# Another comment""",
-                     """127.0.0.1 localhost alias # comment
-# Another comment
-""")
-
-    def test_whitespace(self):
-        self.do_test("""    \t127.0.0.1    \tlocalhost  alias # comment     \r
-    \t# Another comment""",
-                     """127.0.0.1 localhost alias # comment
-# Another comment
-""")
-
-    def test_alignment(self):
-        self.do_test("""127.0.0.1    \tlocalhost  alias
-192.168.1.1 another_host    another_alias
-""","""127.0.0.1   localhost    alias
-192.168.1.1 another_host another_alias
-""")
-
-    def test_multiple_same_name(self):
-        # The semantics are that we overwrite earlier entries with the same name
-        self.do_test("""127.0.0.1    \tlocalhost  alias
-192.168.1.1 localhost    another_alias""","""192.168.1.1 localhost another_alias
-""")
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tools/wptrunner/wptrunner/wptrunner.py b/tools/wptrunner/wptrunner/wptrunner.py
index 6677902..d3d9d07 100644
--- a/tools/wptrunner/wptrunner/wptrunner.py
+++ b/tools/wptrunner/wptrunner/wptrunner.py
@@ -218,6 +218,7 @@
                     browser_kwargs = get_browser_kwargs(test_type,
                                                         run_info,
                                                         ssl_env=ssl_env,
+                                                        config=test_environment.config,
                                                         **kwargs)
 
                     executor_cls = executor_classes.get(test_type)
diff --git a/tools/wptserve/wptserve/pipes.py b/tools/wptserve/wptserve/pipes.py
index b71c8af..534afed 100644
--- a/tools/wptserve/wptserve/pipes.py
+++ b/tools/wptserve/wptserve/pipes.py
@@ -392,6 +392,12 @@
             value = request.headers
         elif field == "GET":
             value = FirstWrapper(request.GET)
+        elif field == "domains":
+            if ('not_domains' in request.server.config and
+                    tokens[1][1] in request.server.config['not_domains']):
+                value = request.server.config['not_domains']
+            else:
+                value = request.server.config['domains']
         elif field in request.server.config:
             value = request.server.config[tokens[0][1]]
         elif field == "location":
diff --git a/webauthn/createcredential-badargs-attestation.https.html b/webauthn/createcredential-badargs-attestation.https.html
new file mode 100644
index 0000000..a56f4f0
--- /dev/null
+++ b/webauthn/createcredential-badargs-attestation.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() attestation parameter Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // attestation bad values
+    new CreateCredentialsTest("options.publicKey.attestation", {}).runTest("Bad attestation parameter: attestation is empty object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.attestation", []).runTest("Bad attestation parameter: attestation is empty array", new TypeError());
+    new CreateCredentialsTest("options.publicKey.attestation", null).runTest("Bad attestation parameter: attestation is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.attestation", "noneofyourbusiness").runTest("Bad attestation parameter: attestation is \"noneofyourbusiness\"", new TypeError());
+    new CreateCredentialsTest("options.publicKey.attestation", "").runTest("Bad attestation parameter: attestation is empty string", new TypeError());
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-badargs-authnrselection.https.html b/webauthn/createcredential-badargs-authnrselection.https.html
new file mode 100644
index 0000000..2c42135
--- /dev/null
+++ b/webauthn/createcredential-badargs-authnrselection.https.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() authenticator selection Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var defaultAuthnrSel = {
+        authenticatorAttachment: "cross-platform",
+        requireResidentKey: false,
+        userVerification: "preferred"
+    };
+    // attachment
+    var authnrSelAttachPlatform = cloneObject(defaultAuthnrSel);
+    authnrSelAttachPlatform.authenticatorAttachment = "platform";
+    var authnrSelBadAttachEmptyStr = cloneObject(defaultAuthnrSel);
+    authnrSelBadAttachEmptyStr.authenticatorAttachment = "";
+    var authnrSelBadAttachEmptyObj = cloneObject(defaultAuthnrSel);
+    authnrSelBadAttachEmptyObj.authenticatorAttachment = {};
+    var authnrSelBadAttachNull = cloneObject(defaultAuthnrSel);
+    authnrSelBadAttachNull.authenticatorAttachment = null;
+    // resident key
+    var authnrSelRkTrue = cloneObject(defaultAuthnrSel);
+    authnrSelRkTrue.requireResidentKey = true;
+    var authnrSelRkBadString = cloneObject(defaultAuthnrSel);
+    authnrSelRkBadString.requireResidentKey = "foo";
+    // user verification
+    var authnrSelUvRequired = cloneObject(defaultAuthnrSel);
+    authnrSelUvRequired.userVerification = "required";
+    var authnrSelBadUvEmptyStr = cloneObject(defaultAuthnrSel);
+    authnrSelBadUvEmptyStr.userVerification = "";
+    var authnrSelBadUvEmptyObj = cloneObject(defaultAuthnrSel);
+    authnrSelBadUvEmptyObj.userVerification = {};
+    var authnrSelBadUvStr = cloneObject(defaultAuthnrSel);
+    authnrSelBadUvStr.userVerification = "requiredshirtshoestshirt";
+    var authnrSelBadUvNull = cloneObject(defaultAuthnrSel);
+    authnrSelBadUvNull.userVerification = null;
+
+    // authenticatorSelection bad values
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", []).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection is empty array", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", null).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", "").runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection is empty string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", "none").runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection is string", new TypeError());
+
+    // authenticatorSelection bad attachment values
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadAttachEmptyStr).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection attachment is empty string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadAttachEmptyObj).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection attachment is empty object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadAttachNull).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection attachment is null", new TypeError());
+    // XXX: assumes authnr is behaving like most U2F authnrs; really depends on the authnr or mock configuration
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelAttachPlatform).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection attachment platform", "NotAllowedError");
+
+    // authenticatorSelection bad requireResidentKey values
+   // XXX: assumes authnr is behaving like most U2F authnrs; really depends on the authnr or mock configuration
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelRkTrue).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection residentKey true", "NotAllowedError");
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelRkBadString).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection residentKey is string", new TypeError());
+    // TODO: not sure if rk is "boolean" or "truthy"; add test cases if it should only accept boolean values
+
+    // authenticatorSelection bad userVerification values
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadUvEmptyStr).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection userVerification empty string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadUvEmptyObj).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection userVerification empty object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadUvStr).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection userVerification bad value", new TypeError());
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelBadUvNull).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection userVerification null", new TypeError());
+    // XXX: assumes this is a mock authenticator the properly reports that it is not doing userVerfication
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelUvRequired).runTest("Bad AuthenticatorSelectionCriteria: authenticatorSelection userVerification required", "NotAllowedError");
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest, cloneObject */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-badargs-challenge.https.html b/webauthn/createcredential-badargs-challenge.https.html
new file mode 100644
index 0000000..0ad67f2
--- /dev/null
+++ b/webauthn/createcredential-badargs-challenge.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() challenge Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // bad challenge values
+    new CreateCredentialsTest({path: "options.publicKey.challenge", value: undefined}).runTest("Bad challenge: challenge missing", new TypeError());
+    new CreateCredentialsTest("options.publicKey.challenge", "hi mom").runTest("Bad challenge: challenge is string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.challenge", null).runTest("Bad challenge: challenge is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.challenge", {}).runTest("Bad challenge: challenge is empty object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.challenge", new Array()).runTest("Bad challenge: challenge is empty Array", new TypeError());
+    new CreateCredentialsTest("options.publicKey.challenge", new ArrayBuffer(0)).runTest("Bad challenge: challenge is empty ArrayBuffer", new TypeError());
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-badargs-rp.https.html b/webauthn/createcredential-badargs-rp.https.html
index f06e02c..9958b2c 100644
--- a/webauthn/createcredential-badargs-rp.https.html
+++ b/webauthn/createcredential-badargs-rp.https.html
@@ -12,29 +12,29 @@
     "use strict";
 
     // rp bad values
-    new CreateCredentialsTest({path: "options.publicKey.rp", value: undefined}).testBadArgs("rp missing");
-    new CreateCredentialsTest("options.publicKey.rp", "hi mom").testBadArgs("rp is string");
-    // new CreateCredentialsTest("options.publicKey.rp", {}).testBadArgs("rp is empty object");
+    new CreateCredentialsTest({path: "options.publicKey.rp", value: undefined}).runTest("Bad rp: rp missing", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp", "hi mom").runTest("Bad rp: rp is string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp", {}).runTest("Bad rp: rp is empty object", new TypeError());
 
-    // rp.id
-    // new CreateCredentialsTest({path: "options.publicKey.rp.id", value: undefined}).testBadArgs("rp missing id");
-    new CreateCredentialsTest("options.publicKey.rp.id", {}).testBadArgs("Bad rp: id is object");
-    new CreateCredentialsTest("options.publicKey.rp.id", null).testBadArgs("Bad rp: id is null");
-    new CreateCredentialsTest("options.publicKey.rp.id", "").testBadArgs("Bad rp: id is empty String");
+    // // rp.id
+    new CreateCredentialsTest("options.publicKey.rp.id", {}).runTest("Bad rp: id is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp.id", null).runTest("Bad rp: id is null", "SecurityError");
+    new CreateCredentialsTest("options.publicKey.rp.id", "").runTest("Bad rp: id is empty String", "SecurityError");
+    new CreateCredentialsTest("options.publicKey.rp.id", "invalid domain.com").runTest("Bad rp: id is invalid domain (has space)", "SecurityError");
+    new CreateCredentialsTest("options.publicKey.rp.id", "-invaliddomain.com").runTest("Bad rp: id is invalid domain (starts with dash)", "SecurityError");
+    new CreateCredentialsTest("options.publicKey.rp.id", "0invaliddomain.com").runTest("Bad rp: id is invalid domain (starts with number)", "SecurityError");
 
-    // rp.name
-    // new CreateCredentialsTest({path: "options.publicKey.rp.name", value: undefined}).testBadArgs("rp missing name");
-    new CreateCredentialsTest("options.publicKey.rp.name", {}).testBadArgs("Bad rp: name is object");
-    new CreateCredentialsTest("options.publicKey.rp.name", null).testBadArgs("Bad rp: name is null");
-    new CreateCredentialsTest("options.publicKey.rp.name", "").testBadArgs("Bad rp: name is empty String");
+    // // rp.name
+    new CreateCredentialsTest({path: "options.publicKey.rp.name", value: undefined}).runTest("rp missing name", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp.name", {}).runTest("Bad rp: name is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp.name", null).runTest("Bad rp: name is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp.name", "").runTest("Bad rp: name is empty String", new TypeError());
 
-    // rp.icon
-    // new CreateCredentialsTest({path: "options.publicKey.rp.icon", value: undefined}).testBadArgs("rp missing icon");
-    new CreateCredentialsTest("options.publicKey.rp.icon", {}).testBadArgs("Bad rp: icon is object");
-    new CreateCredentialsTest("options.publicKey.rp.icon", null).testBadArgs("Bad rp: icon is null");
-    new CreateCredentialsTest("options.publicKey.rp.icon", "").testBadArgs("Bad rp: icon is empty String");
-    // TODO: see https://github.com/w3c/webauthn/issues/587 for the 'undefined' tests that are commented out above
-    // TODO: unicode tests for icon URL (see also: USVString)
+    // // rp.icon
+    new CreateCredentialsTest("options.publicKey.rp.icon", {}).runTest("Bad rp: icon is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp.icon", null).runTest("Bad rp: icon is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.rp.icon", "").runTest("Bad rp: icon is empty String", new TypeError());
+    // // TODO: unicode tests for icon URL (see also: USVString)
 });
 
 /* JSHINT */
diff --git a/webauthn/createcredential-badargs-user.https.html b/webauthn/createcredential-badargs-user.https.html
new file mode 100644
index 0000000..dd1870a
--- /dev/null
+++ b/webauthn/createcredential-badargs-user.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() user Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // user bad values
+    new CreateCredentialsTest({path: "options.publicKey.user", value: undefined}).runTest("Bad user: user missing", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user", "hi mom").runTest("Bad user: user is string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user", {}).runTest("Bad user: user is empty object", new TypeError());
+
+    // // user.id
+    new CreateCredentialsTest({path: "options.publicKey.user.id", value: undefined}).runTest("Bad user: id is undefined", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", {}).runTest("Bad user: id is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", null).runTest("Bad user: id is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", "").runTest("Bad user: id is empty String", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new Array()).runTest("Bad user: id is empty Array", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new ArrayBuffer(0)).runTest("Bad user: id is empty ArrayBuffer", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new ArrayBuffer(65)).runTest("Bad user: ArrayBuffer id is too long (65 bytes)", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new Int16Array(33)).runTest("Bad user: Int16Array id is too long (66 bytes)", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new Int32Array(17)).runTest("Bad user: Int32Array id is too long (68 bytes)", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new Float32Array(17)).runTest("Bad user: Float32Array id is too long (68 bytes)", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.id", new Float64Array(9)).runTest("Bad user: Float64Array id is too long (72 bytes)", new TypeError());
+    var buf = new ArrayBuffer(65);
+    new CreateCredentialsTest("options.publicKey.user.id", new DataView(buf)).runTest("Bad user: id is too long (65 bytes)", new TypeError());
+
+    // // user.name
+    new CreateCredentialsTest({path: "options.publicKey.user.name", value: undefined}).runTest("user missing name", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.name", {}).runTest("Bad user: name is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.name", null).runTest("Bad user: name is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.name", "").runTest("Bad user: name is empty String", new TypeError());
+
+    // // user.icon
+    new CreateCredentialsTest("options.publicKey.user.icon", {}).runTest("Bad user: icon is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.icon", null).runTest("Bad user: icon is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.icon", "").runTest("Bad user: icon is empty String", new TypeError());
+    // // TODO: unicode tests for icon URL (see also: USVString)
+
+    // // user.displayName
+    new CreateCredentialsTest({path: "options.publicKey.user.displayName", value: undefined}).runTest("Bad user: displayName is undefined", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.displayName", {}).runTest("Bad user: displayName is object", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.displayName", null).runTest("Bad user: displayName is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.user.displayName", "").runTest("Bad user: displayName is empty String", new TypeError());
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-excludecredentials.https.html b/webauthn/createcredential-excludecredentials.https.html
new file mode 100644
index 0000000..a4cfb0a
--- /dev/null
+++ b/webauthn/createcredential-excludecredentials.https.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() excludeCredentials Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // bad excludeCredentials values
+    new CreateCredentialsTest("options.publicKey.excludeCredentials", "hi mom").runTest("Bad excludeCredentials: string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.excludeCredentials", {}).runTest("Bad excludeCredentials: empty object", new TypeError());
+    // TODO: bad excludeCredentials with [{.type}] or [{.id}] or [{.transports}] wrong
+
+    // good excludeCredentials values
+    new CreateCredentialsTest({path: "options.publicKey.excludeCredentials", value: undefined}).runTest("excludeCredentials missing");
+    new CreateCredentialsTest("options.publicKey.excludeCredentials", []).runTest("excludeCredentials empty array");
+
+    // proper excludeCredentials behavior
+    // should error on excluding existing credential
+    promise_test((t) => {
+        var cred1;
+        return Promise.resolve()
+            .then(() => {
+                return createCredential();
+            })
+            .then((cred) => {
+                cred1 = cred;
+                var excludeCred = {
+                    id: cred.rawId,
+                    type: "public-key"
+                };
+                var args = {
+                    options: {
+                        publicKey: {
+                            excludeCredentials: [excludeCred]
+                        }
+                    }
+                };
+                var p = createCredential(args);
+                return promise_rejects (t, "NotAllowedError", p, "expected to fail on excluded credenetial");
+            });
+    }, "exclude existing credential");
+
+    // should not error on excluding random credential
+    promise_test(() => {
+        return Promise.resolve()
+            .then(() => {
+                return createCredential();
+            })
+            .then(() => {
+                var randomCredId = new Uint8Array(162);
+                window.crypto.getRandomValues(randomCredId);
+
+                var excludeCred = {
+                    id: randomCredId,
+                    type: "public-key"
+                };
+                var args = {
+                    options: {
+                        publicKey: {
+                            excludeCredentials: [excludeCred]
+                        }
+                    }
+                };
+                return createCredential(args);
+            });
+    }, "exclude random (non-existing) credential");
+
+    // TODO: exclude including transport type (USB, BLE, NFC)
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest, createCredential, promise_test, promise_rejects */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-extensions.https.html b/webauthn/createcredential-extensions.https.html
new file mode 100644
index 0000000..3b3c9bf
--- /dev/null
+++ b/webauthn/createcredential-extensions.https.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() extensions Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var dummyExtension = {
+        foo: true,
+        bar: "yup"
+    };
+
+    // bad extension values
+    new CreateCredentialsTest("options.publicKey.extensions", "hi mom").runTest("Bad extensions: extensions is string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.extensions", null).runTest("Bad extensions: extensions is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.extensions", []).runTest("Bad extensions: extensions is empty Array", new TypeError());
+    new CreateCredentialsTest("options.publicKey.extensions", new ArrayBuffer(0)).runTest("Bad extensions: extensions is empty ArrayBuffer", new TypeError());
+    var badJson = '{"foo": true, "bar: "yup"}'; // missing quote after "bar"
+    new CreateCredentialsTest("options.publicKey.extensions", {foo: badJson}).runTest("Bad extensions: malformatted JSON", new TypeError());
+    new CreateCredentialsTest("options.publicKey.extensions", {foo: dummyExtension}).runTest("Bad extensions: JavaScript object", new TypeError());
+    var badExtId = {};
+    badExtId[createRandomString(65)] = dummyExtension;
+    new CreateCredentialsTest("options.publicKey.extensions", {badExtId: dummyExtension}).runTest("Bad extensions: extension ID too long", new TypeError());
+
+    // phony extensions
+    // TODO: not sure if this should pass or fail
+    // should be clarified as part of https://github.com/w3c/webauthn/pull/765
+    var randomExtId = {};
+    randomExtId[createRandomString(64)] = dummyExtension;
+    new CreateCredentialsTest("options.publicKey.extensions", {foo: JSON.stringify(randomExtId)}).runTest("extensions is a nonsensical JSON string");
+
+    // TODO
+    // defined extensions:
+    // * appid
+    // * txAuthSimple
+    // * txAuthGeneric
+    // * authnSel
+    // * exts
+    // * uvi
+    // * loc
+    // * uvm
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest, createRandomString */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-passing.https.html b/webauthn/createcredential-passing.https.html
index a66d3c1..25dba1d 100644
--- a/webauthn/createcredential-passing.https.html
+++ b/webauthn/createcredential-passing.https.html
@@ -12,9 +12,108 @@
     "use strict";
 
     // CreateCredentialTest passing tests
-    new CreateCredentialsTest().test();
+
+    // default arguments
+    new CreateCredentialsTest().runTest("passing credentials.create() with default arguments");
+
+    // rp
+    new CreateCredentialsTest({path: "options.publicKey.rp.id", value: window.location.host}).runTest("passing credentials.create() with rpId (host and port)");
+    new CreateCredentialsTest({path: "options.publicKey.rp.id", value: window.location.hostname}).runTest("passing credentials.create() with rpId (hostname)");
+    new CreateCredentialsTest({path: "options.publicKey.rp.icon", value: undefined}).runTest("passing credentials.create() without rp.icon");
+
+    // user
+    new CreateCredentialsTest("options.publicKey.user.id", new ArrayBuffer(1)).runTest("very short user id");
+    new CreateCredentialsTest("options.publicKey.user.id", new ArrayBuffer(64)).runTest("max length user id");
+    new CreateCredentialsTest("options.publicKey.user.id", new Uint8Array(64)).runTest("Uint8Array user id");
+    new CreateCredentialsTest("options.publicKey.user.id", new Int8Array(64)).runTest("Int8Array user id");
+    new CreateCredentialsTest("options.publicKey.user.id", new Int16Array(32)).runTest("Int16Array user id");
+    new CreateCredentialsTest("options.publicKey.user.id", new Int32Array(16)).runTest("Int32Array user id");
+    new CreateCredentialsTest("options.publicKey.user.id", new Float32Array(16)).runTest("Float32Array user id");
+    var dvBuf1 = new ArrayBuffer(16);
+    new CreateCredentialsTest("options.publicKey.user.id", new DataView(dvBuf1)).runTest("DataView user id");
+    new CreateCredentialsTest({path: "options.publicKey.user.icon", value: undefined}).runTest("passing credentials.create() without user.icon");
+
+    // good challenge values
+    // all these challenges are zero-filled buffers... think anyone will complain?
+    new CreateCredentialsTest("options.publicKey.challenge", new Int16Array(33)).runTest("Int16Array challenge");
+    new CreateCredentialsTest("options.publicKey.challenge", new Int32Array(17)).runTest("Int32Array challenge");
+    new CreateCredentialsTest("options.publicKey.challenge", new Float32Array(17)).runTest("Float32Array challenge");
+    new CreateCredentialsTest("options.publicKey.challenge", new Float64Array(9)).runTest("Float64Array challenge");
+    var dvBuf2 = new ArrayBuffer(65);
+    new CreateCredentialsTest("options.publicKey.challenge", new DataView(dvBuf2)).runTest("DataView challenge");
+    new CreateCredentialsTest("options.publicKey.challenge", new ArrayBuffer(8192)).runTest("Absurdly large challenge");
+
+    // good pubKeyCredParams values
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", []).runTest("Bad pubKeyCredParams: pubKeyCredParams is empty Array");
+    const pkParamEC256 = {
+        type: "public-key",
+        alg: cose_alg_ECDSA_w_SHA256
+    };
+    const pkParamEC512 = {
+        type: "public-key",
+        alg: cose_alg_ECDSA_w_SHA512
+    };
+    // XXX: presumes all mock authenticators support EC256
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [pkParamEC256]).runTest("EC256 pubKeyCredParams");
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [pkParamEC512, pkParamEC256])
+        .runTest("SelectEC256 pubKeyCredParams from a list");
+    // TODO: currently most browsers are mocking FIDO U2F, which is EC256 only
+    // new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [pkParamEC512]).runTest("EC512 pubKeyCredParams");
+
+    // NOTE: excludeCredentials parameter -- see also: createcredential-excludecredentials.https.html
+
+    // timeout
+    new CreateCredentialsTest({path: "options.publicKey.timeout", value: undefined}).runTest("passing credentials.create() with no timeout");
+
+    // valid authenticatorSelection values
+    var defaultAuthnrSel = {
+        authenticatorAttachment: "cross-platform",
+        requireResidentKey: false,
+        userVerification: "preferred"
+    };
+    // attachment
+    var authnrSelAttachUndef = cloneObject(defaultAuthnrSel);
+    authnrSelAttachUndef.authenticatorAttachment = undefined;
+    // resident key
+    var authnrSelRkUndef = cloneObject(defaultAuthnrSel);
+    authnrSelRkUndef.requireResidentKey = undefined;
+    var authnrSelRkFalse = cloneObject(defaultAuthnrSel);
+    authnrSelRkFalse.requireResidentKey = false;
+    // user verification
+    var authnrSelUvUndef = cloneObject(defaultAuthnrSel);
+    authnrSelUvUndef.userVerification = undefined;
+    var authnrSelUvDiscouraged = cloneObject(defaultAuthnrSel);
+    authnrSelUvDiscouraged.userVerification = "discouraged";
+    new CreateCredentialsTest({path: "options.publicKey.authenticatorSelection", value: undefined}).runTest("authenticatorSelection is undefined");
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", {}).runTest("authenticatorSelection is empty object");
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", cloneObject(defaultAuthnrSel)).runTest("authenticatorSelection default values");
+
+    // authnr selection attachment
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelAttachUndef).runTest("authenticatorSelection attachment undefined");
+
+    // authnr selection resident key
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelRkUndef).runTest("authenticatorSelection residentKey undefined");
+    // XXX: assumes authnr is behaving like most U2F authnrs; really depends on the authnr or mock configuration
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelRkFalse).runTest("authenticatorSelection residentKey false");
+
+    // authnr selection user verification
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelUvUndef).runTest("authenticatorSelection userVerification undefined");
+    new CreateCredentialsTest("options.publicKey.authenticatorSelection", authnrSelUvDiscouraged).runTest("authenticatorSelection userVerification discouraged");
+
+
+    // good attestation values
+    new CreateCredentialsTest("options.publicKey.attestation", "none").runTest("attestation parameter: attestation is \"none\"");
+    new CreateCredentialsTest("options.publicKey.attestation", "indirect").runTest("attestation parameter: attestation is \"indirect\"");
+    new CreateCredentialsTest("options.publicKey.attestation", "direct").runTest("attestation parameter: attestation is \"direct\"");
+    new CreateCredentialsTest({path: "options.publicKey.attestation", value: undefined}).runTest("attestation parameter: attestation is undefined");
+    // TODO: test this with multiple mock authenticators to make sure that the right options are chosen when available?
+
+    // good extension values
+    new CreateCredentialsTest({path: "options.publicKey.extensions", value: undefined}).runTest("extensions undefined");
+    new CreateCredentialsTest("options.publicKey.extensions", {}).runTest("extensions are empty object");
+    new CreateCredentialsTest("options.publicKey.extensions", {foo: "", bar: "", bat: ""}).runTest("extensions are dict of empty strings");
 });
 
 /* JSHINT */
-/* globals standardSetup, CreateCredentialsTest */
+/* globals standardSetup, CreateCredentialsTest, cose_alg_ECDSA_w_SHA256, cose_alg_ECDSA_w_SHA512, cloneObject */
 </script>
\ No newline at end of file
diff --git a/webauthn/createcredential-pubkeycredparams.https.html b/webauthn/createcredential-pubkeycredparams.https.html
new file mode 100644
index 0000000..325191c
--- /dev/null
+++ b/webauthn/createcredential-pubkeycredparams.https.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() pubKeyCredParams Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var badType = {
+        type: "something-else",
+        alg: cose_alg_ECDSA_w_SHA512
+    };
+    var badTypeEmptyString = cloneObject(badType);
+    badTypeEmptyString.type = "";
+    var badTypeNull = cloneObject(badType);
+    badTypeNull.type = null;
+    var badTypeEmptyObj = cloneObject(badType);
+    badTypeEmptyObj.type = {};
+
+    var badAlg = {
+        type: "public-key",
+        alg: 42
+    };
+    var badAlgZero = cloneObject(badAlg);
+    badAlgZero.alg = 0;
+
+    // bad pubKeyCredParams values
+    new CreateCredentialsTest({path: "options.publicKey.pubKeyCredParams", value: undefined}).runTest("Bad pubKeyCredParams: pubKeyCredParams is undefined", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", "hi mom").runTest("Bad pubKeyCredParams: pubKeyCredParams is string", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", null).runTest("Bad pubKeyCredParams: pubKeyCredParams is null", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [badType]).runTest("Bad pubKeyCredParams: first param has bad type (\"something-else\")", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [badTypeEmptyString]).runTest("Bad pubKeyCredParams: first param has bad type (\"\")", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [badTypeNull]).runTest("Bad pubKeyCredParams: first param has bad type (null)", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [badTypeEmptyObj]).runTest("Bad pubKeyCredParams: first param has bad type (empty object)", new TypeError());
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [badAlg]).runTest("Bad pubKeyCredParams: first param has bad alg (42)", "NotSupportedError");
+    new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [badAlgZero]).runTest("Bad pubKeyCredParams: first param has bad alg (0)", "NotSupportedError");
+
+    // TODO: come back to this when mock authenticators support multiple cryptos so that we can test the preference ranking
+    // function verifyEC256(res) {
+    //     debug ("verifyEC256 got", res);
+    //     debug ("client data JSON", ab2str(res.response.clientDataJSON));
+    //     parseAuthenticatorData(res.response.attestationObject);
+    // }
+    // new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [pkParamEC256, pkParamEC512])
+    //     .afterTest(verifyEC256)
+    //     .runTest("EC256, EC512 pubKeyCredParams");
+    // function verifyEC512(res) {
+    //     debug ("verifyEC512 got", res);
+    //     debug ("client data JSON", ab2str(res.response.clientDataJSON));
+    //     // parseAuthenticatorData(res.response.attestationObject);
+    //     printHex ("clientDataJSON", res.response.clientDataJSON);
+    //     printHex ("attestationObject", res.response.attestationObject);
+    // }
+    // new CreateCredentialsTest("options.publicKey.pubKeyCredParams", [pkParamEC512, pkParamEC256])
+    //     .afterTest(verifyEC512)
+    //     .runTest("EC512, EC256 pubKeyCredParams");
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest, cose_alg_ECDSA_w_SHA512, cloneObject */
+</script>
\ No newline at end of file
diff --git a/webauthn/createcredential-timeout.https.html b/webauthn/createcredential-timeout.https.html
new file mode 100644
index 0000000..b94ae58
--- /dev/null
+++ b/webauthn/createcredential-timeout.https.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.create() timeout Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    // bad timeout values
+    // TODO: there is some debate as to whether MAX_UNSIGNED_LONG + 1 and / or -1 should be disallowed since they get converted to valid values internally
+    // new CreateCredentialsTest({path: "options.publicKey.timeout", value: -1}).runTest("Bad timeout: negative", new TypeError());
+    // new CreateCredentialsTest({path: "options.publicKey.timeout", value: 4294967295 + 1}).runTest("Bad timeout: too big", new TypeError());
+
+    // timeout test
+    // XXX: this probably always passes with most mock authenticators unless
+    // some setup happens right here to make sure they don't return a credential
+    // right away. So... uhh... I guess test this with a real authenticator if you
+    // want to see if it really works.
+    promise_test(() => {
+        return new Promise((resolve, reject) => {
+            var args = {
+                options: {
+                    publicKey: {
+                        timeout: 1
+                    }
+                }
+            };
+
+            setTimeout(() => {
+                reject(new Error ("timed out"));
+            }, 1000);
+
+            createCredential(args).then((res) => {
+                resolve(res);
+            });
+        });
+    }, "ensure create credential times out");
+    // TODO: createCredential.timeout > 1s && setTimeout < 1s
+    // TODO: createCredential.timeout < 5s && setTimeout > 5s
+});
+
+/* JSHINT */
+/* globals standardSetup, CreateCredentialsTest, createCredential, promise_test */
+</script>
\ No newline at end of file
diff --git a/webauthn/getcredential-badargs-rpid.https.html b/webauthn/getcredential-badargs-rpid.https.html
new file mode 100644
index 0000000..9e8da4d
--- /dev/null
+++ b/webauthn/getcredential-badargs-rpid.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn credential.get() rpId Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var credPromise = createCredential();
+
+    new GetCredentialsTest("options.publicKey.rpId", "")
+        .addCredential(credPromise)
+        .runTest("Bad rpId: empty string", "SecurityError");
+    new GetCredentialsTest("options.publicKey.rpId", null)
+        .addCredential(credPromise)
+        .runTest("Bad rpId: null", "SecurityError");
+    new GetCredentialsTest("options.publicKey.rpId", "invalid domain.com")
+        .addCredential(credPromise)
+        .runTest("Bad rpId: invalid domain (has space)", "SecurityError");
+    new GetCredentialsTest("options.publicKey.rpId", "-invaliddomain.com")
+        .addCredential(credPromise)
+        .runTest("Bad rpId: invalid domain (starts with dash)", "SecurityError");
+    new GetCredentialsTest("options.publicKey.rpId", "0invaliddomain.com")
+        .addCredential(credPromise)
+        .runTest("Bad rpId: invalid domain (starts with number)", "SecurityError");
+});
+
+/* JSHINT */
+/* globals standardSetup, GetCredentialsTest, createCredential */
+</script>
\ No newline at end of file
diff --git a/webauthn/getcredential-badargs-userverification.https.html b/webauthn/getcredential-badargs-userverification.https.html
new file mode 100644
index 0000000..6101540
--- /dev/null
+++ b/webauthn/getcredential-badargs-userverification.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.get() user verification Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var credPromise = createCredential();
+
+    // authenticatorSelection bad userVerification values
+    new GetCredentialsTest("options.publicKey.userVerification", "")
+        .addCredential(credPromise)
+        .runTest("Bad userVerification: empty string", new TypeError());
+    new GetCredentialsTest("options.publicKey.userVerification", {})
+        .addCredential(credPromise)
+        .runTest("Bad userVerification: empty object", new TypeError());
+    new GetCredentialsTest("options.publicKey.userVerification", "requiredshirtshoestshirt")
+        .addCredential(credPromise)
+        .runTest("Bad userVerification: bad value", new TypeError());
+    new GetCredentialsTest("options.publicKey.userVerification", null)
+        .addCredential(credPromise)
+        .runTest("Bad userVerification: null", new TypeError());
+    // XXX: assumes this is a mock authenticator the properly reports that it is not doing userVerfication
+    new GetCredentialsTest("options.publicKey.userVerification", "required")
+        .addCredential(credPromise)
+        .runTest("Bad userVerification: \"required\"", "NotAllowedError");
+});
+
+/* JSHINT */
+/* globals standardSetup, GetCredentialsTest, createCredential */
+</script>
\ No newline at end of file
diff --git a/webauthn/getcredential-extensions.https.html b/webauthn/getcredential-extensions.https.html
new file mode 100644
index 0000000..bad5ce4
--- /dev/null
+++ b/webauthn/getcredential-extensions.https.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.get() extensions Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var dummyExtension = {
+        foo: true,
+        bar: "yup"
+    };
+    var credPromise = createCredential();
+
+    // bad extension values
+    new GetCredentialsTest("options.publicKey.extensions", "hi mom")
+        .addCredential(credPromise)
+        .runTest("Bad extensions: extensions is string", new TypeError());
+    new GetCredentialsTest("options.publicKey.extensions", null)
+        .addCredential(credPromise)
+        .runTest("Bad extensions: extensions is null", new TypeError());
+    new GetCredentialsTest("options.publicKey.extensions", [])
+        .addCredential(credPromise)
+        .runTest("Bad extensions: extensions is empty Array", new TypeError());
+    new GetCredentialsTest("options.publicKey.extensions", new ArrayBuffer(0))
+        .addCredential(credPromise)
+        .runTest("Bad extensions: extensions is empty ArrayBuffer", new TypeError());
+    var badJson = '{"foo": true, "bar: "yup"}'; // missing quote after "bar"
+    new GetCredentialsTest("options.publicKey.extensions", {foo: badJson})
+        .addCredential(credPromise)
+        .runTest("Bad extensions: malformatted JSON", new TypeError());
+    new GetCredentialsTest("options.publicKey.extensions", {foo: dummyExtension})
+        .addCredential(credPromise)
+        .runTest("Bad extensions: JavaScript object", new TypeError());
+    var badExtId = {};
+    badExtId[createRandomString(65)] = dummyExtension;
+    new GetCredentialsTest("options.publicKey.extensions", {badExtId: dummyExtension})
+        .addCredential(credPromise)
+        .runTest("Bad extensions: extension ID too long", new TypeError());
+
+    // phony extensions
+    // TODO: not sure if this should pass or fail
+    // should be clarified as part of https://github.com/w3c/webauthn/pull/765
+    var randomExtId = {};
+    randomExtId[createRandomString(64)] = dummyExtension;
+    new GetCredentialsTest("options.publicKey.extensions", {foo: JSON.stringify(randomExtId)})
+        .addCredential(credPromise)
+        .runTest("extensions is a nonsensical JSON string");
+
+    // TODO
+    // defined extensions:
+    // * appid
+    // * txAuthSimple
+    // * txAuthGeneric
+    // * authnSel
+    // * exts
+    // * uvi
+    // * loc
+    // * uvm
+});
+
+/* JSHINT */
+/* globals standardSetup, GetCredentialsTest, createRandomString, createCredential */
+</script>
\ No newline at end of file
diff --git a/webauthn/getcredential-passing.https.html b/webauthn/getcredential-passing.https.html
index c7cf794..58b085f 100644
--- a/webauthn/getcredential-passing.https.html
+++ b/webauthn/getcredential-passing.https.html
@@ -11,11 +11,56 @@
 standardSetup(function() {
     "use strict";
 
-    // GetCredentialsTest passing tests
-    // new GetCredentialsTest().addCredential();
-    new GetCredentialsTest().addCredential().test();
+    var credPromise = createCredential();
+
+    // GetCredentialsTest with default args
+    new GetCredentialsTest()
+        .addCredential(credPromise)
+        .runTest("passing credentials.get() with default args");
+
+    // timeout
+    new GetCredentialsTest({path: "options.publicKey.timeout", value: undefined})
+        .addCredential(credPromise)
+        .runTest("passing credentials.create() with no timeout");
+
+    // rpId
+    new GetCredentialsTest({path: "options.publicKey.rpId", value: undefined})
+        .addCredential(credPromise)
+        .runTest("rpId undefined");
+    new GetCredentialsTest({path: "options.publicKey.rpId", value: window.location.host})
+        .addCredential(credPromise)
+        .runTest("passing credentials.get() with rpId (host and port)");
+    new GetCredentialsTest({path: "options.publicKey.rpId", value: window.location.hostname})
+        .addCredential(credPromise)
+        .runTest("passing credentials.get() with rpId (hostname)");
+
+    // allowCredentials
+    new GetCredentialsTest({path: "options.publicKey.allowCredentials", value: undefined})
+        .runTest("no credential specified");
+
+    // authnr selection user verification
+    new GetCredentialsTest({path: "options.publicKey.userVerification", value: undefined})
+        .addCredential(credPromise)
+        .runTest("authenticatorSelection userVerification undefined");
+    new GetCredentialsTest("options.publicKey.userVerification", "preferred")
+        .addCredential(credPromise)
+        .runTest("authenticatorSelection userVerification preferred");
+    new GetCredentialsTest("options.publicKey.userVerification", "discouraged")
+        .addCredential(credPromise)
+        .runTest("authenticatorSelection userVerification discouraged");
+
+    // good extension values
+    new GetCredentialsTest({path: "options.publicKey.extensions", value: undefined})
+        .addCredential(credPromise)
+        .runTest("extensions undefined");
+    new GetCredentialsTest("options.publicKey.extensions", {})
+        .addCredential(credPromise)
+        .runTest("extensions are empty object");
+    new GetCredentialsTest("options.publicKey.extensions", {foo: "", bar: "", bat: ""})
+        .addCredential(credPromise)
+        .runTest("extensions are dict of empty strings");
 });
 
 /* JSHINT */
-/* globals standardSetup, GetCredentialsTest */
+/* globals standardSetup, GetCredentialsTest, createCredential */
 </script>
\ No newline at end of file
diff --git a/webauthn/getcredential-timeout.https.html b/webauthn/getcredential-timeout.https.html
new file mode 100644
index 0000000..8f5c2f3
--- /dev/null
+++ b/webauthn/getcredential-timeout.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>WebAuthn navigator.credentials.get() timeout Tests</title>
+<link rel="author" title="Adam Powers" href="mailto:adam@fidoalliance.org">
+<link rel="help" href="https://w3c.github.io/webauthn/#iface-credential">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src=helpers.js></script>
+<body></body>
+<script>
+standardSetup(function() {
+    "use strict";
+
+    var credPromise = createCredential();
+
+    // bad timeout values
+    // TODO: there is some debate as to whether MAX_UNSIGNED_LONG + 1 and / or -1 should be disallowed since they get converted to valid values internally
+    // new GetCredentialsTest({path: "options.publicKey.timeout", value: -1})
+    //     .addCredential(credPromise)
+    //     .runTest("Bad timeout: negative", new TypeError());
+    // new GetCredentialsTest({path: "options.publicKey.timeout", value: 4294967295 + 1})
+    //     .addCredential(credPromise)
+    //     .runTest("Bad timeout: too big", new TypeError());
+
+    // timeout test
+    // XXX: this probably always passes with most mock authenticators unless
+    // some setup happens right here to make sure they don't return a credential
+    // right away. So... uhh... I guess test this with a real authenticator if you
+    // want to see if it really works.
+    var timer;
+    function startTimer() {
+        timer = setTimeout(() => {
+            throw new Error("Timer went off before timeout");
+        }, 1000);
+    }
+    function stopTimer() {
+        clearTimeout(timer);
+    }
+    new GetCredentialsTest({path: "options.publicKey.timeout", value: 1})
+        .addCredential(credPromise)
+        .beforeTest(startTimer)
+        .afterTest(stopTimer)
+        .runTest("ensure create credential times out");
+    // TODO: createCredential.timeout > 1s && setTimeout < 1s
+    // TODO: createCredential.timeout < 5s && setTimeout > 5s
+});
+
+/* JSHINT */
+/* globals standardSetup, GetCredentialsTest, createCredential */
+</script>
\ No newline at end of file
diff --git a/webauthn/helpers.js b/webauthn/helpers.js
index 11d0aee..568d3e2 100644
--- a/webauthn/helpers.js
+++ b/webauthn/helpers.js
@@ -1,5 +1,4 @@
-
-/* Useful constants for working with COSE key objects */
+// Useful constants for working with COSE key objects
 const cose_kty = 1;
 const cose_kty_ec2 = 2;
 const cose_alg = 3;
@@ -11,6 +10,117 @@
 const cose_crv_y = -3;
 
 /**
+ * These are the default arguments that will be passed to navigator.credentials.create()
+ * unless modified by a specific test case
+ */
+var createCredentialDefaultArgs = {
+    options: {
+        publicKey: {
+            // Relying Party:
+            rp: {
+                name: "Acme",
+                icon: "https://www.w3.org/StyleSheets/TR/2016/logos/W3C"
+            },
+
+            // User:
+            user: {
+                id: new Uint8Array(16), // Won't survive the copy, must be rebuilt
+                name: "john.p.smith@example.com",
+                displayName: "John P. Smith",
+                icon: "https://pics.acme.com/00/p/aBjjjpqPb.png"
+            },
+
+            pubKeyCredParams: [{
+                type: "public-key",
+                alg: cose_alg_ECDSA_w_SHA256,
+            }],
+
+            timeout: 60000, // 1 minute
+            excludeCredentials: [] // No excludeList
+        }
+    }
+};
+
+/**
+ * These are the default arguments that will be passed to navigator.credentials.get()
+ * unless modified by a specific test case
+ */
+var getCredentialDefaultArgs = {
+    options: {
+        publicKey: {
+            timeout: 60000
+            // allowCredentials: [newCredential]
+        }
+    }
+};
+
+function createCredential(opts) {
+    opts = opts || {};
+
+    // set the default options
+    var createArgs = cloneObject(createCredentialDefaultArgs);
+    let challengeBytes = new Uint8Array(16);
+    window.crypto.getRandomValues(challengeBytes);
+    createArgs.options.publicKey.challenge = challengeBytes;
+    createArgs.options.publicKey.user.id = new Uint8Array(16);
+
+    // change the defaults with any options that were passed in
+    extendObject (createArgs, opts);
+
+    // create the credential, return the Promise
+    return navigator.credentials.create(createArgs.options);
+}
+
+function createRandomString(len) {
+    var text = "";
+    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+    for(var i = 0; i < len; i++) {
+        text += possible.charAt(Math.floor(Math.random() * possible.length));
+    }
+    return text;
+}
+
+
+function ab2str(buf) {
+    return String.fromCharCode.apply(null, new Uint8Array(buf));
+}
+
+// Useful constants for working with attestation data
+const authenticator_data_user_present = 0x01;
+const authenticator_data_user_verified = 0x04;
+const authenticator_data_attested_cred_data = 0x40;
+const authenticator_data_extension_data = 0x80;
+
+function parseAuthenticatorData(buf) {
+    if (buf.byteLength < 37) {
+        throw new TypeError ("parseAuthenticatorData: buffer must be at least 37 bytes");
+    }
+
+    printHex ("authnrData", buf);
+
+    var authnrData = new DataView(buf);
+    var authnrDataObj = {};
+    authnrDataObj.length = buf.byteLength;
+
+    authnrDataObj.rpIdHash = new Uint8Array (buf.slice (0,32));
+    authnrDataObj.rawFlags = authnrData.getUint8(32);
+    authnrDataObj.counter = authnrData.getUint32(33, false);
+    authnrDataObj.rawCounter = [];
+    authnrDataObj.rawCounter[0] = authnrData.getUint8(33);
+    authnrDataObj.rawCounter[1] = authnrData.getUint8(34);
+    authnrDataObj.rawCounter[2] = authnrData.getUint8(35);
+    authnrDataObj.rawCounter[3] = authnrData.getUint8(36);
+    authnrDataObj.flags = {};
+
+    authnrDataObj.flags.userPresent = (authnrDataObj.rawFlags&authenticator_data_user_present)?true:false;
+    authnrDataObj.flags.userVerified = (authnrDataObj.rawFlags&authenticator_data_user_verified)?true:false;
+    authnrDataObj.flags.attestedCredentialData = (authnrDataObj.rawFlags&authenticator_data_attested_cred_data)?true:false;
+    authnrDataObj.flags.extensionData = (authnrDataObj.rawFlags&authenticator_data_extension_data)?true:false;
+
+    return authnrDataObj;
+}
+
+/**
  * TestCase
  *
  * A generic template for test cases
@@ -118,66 +228,127 @@
 
     /**
      * run the test function with the top-level properties of the test object applied as arguments
+     * expects the test to pass, and then validates the results
      */
-    test(desc) {
-        promise_test(() => {
-            return this.doIt()
-                .then((ret) => {
-                    // check the result
-                    this.validateRet(ret);
-                    return ret;
-                });
-        }, desc);
+    testPasses(desc) {
+        return this.doIt()
+            .then((ret) => {
+                // check the result
+                this.validateRet(ret);
+                return ret;
+            });
+    }
+
+    /**
+     * run the test function with the top-level properties of the test object applied as arguments
+     * expects the test to fail
+     */
+    testFails(t, testDesc, expectedErr) {
+        return promise_rejects(t, expectedErr, this.doIt(), "Expected bad parameters to fail");
+    }
+
+    /**
+     * Runs the test that's implemented by the class by calling the doIt() function
+     * @param  {String} desc                A description of the test being run
+     * @param  [Error|String] expectedErr   A string matching an error type, such as "SecurityError" or an object with a .name value that is an error type string
+     */
+    runTest(desc, expectedErr) {
+        promise_test((t) => {
+            return Promise.resolve().then(() => {
+                return this.testSetup();
+            }).then(() => {
+                if (expectedErr === undefined) {
+                    return this.testPasses(desc);
+                } else {
+                    return this.testFails(t, desc, expectedErr);
+                }
+            }).then((res) => {
+                return this.testTeardown(res);
+            })
+        }, desc)
+    }
+
+    /**
+     * called before runTest
+     * virtual method expected to be overridden by child class if needed
+     */
+    testSetup() {
+        if (this.beforeTestFn) {
+            this.beforeTestFn.call(this);
+        }
+
+        return Promise.resolve();
+    }
+
+    /**
+     * Adds a callback function that gets called in the TestCase context
+     * and within the testing process.
+     */
+    beforeTest(fn) {
+        if (typeof fn !== "function") {
+            throw new Error ("Tried to call non-function before test");
+        }
+
+        this.beforeTestFn = fn;
+
+        return this;
+    }
+
+    /**
+     * called after runTest
+     * virtual method expected to be overridden by child class if needed
+     */
+    testTeardown(res) {
+        if (this.afterTestFn) {
+            this.afterTestFn.call(this, res);
+        }
+
+        return Promise.resolve();
+    }
+
+    /**
+     * Adds a callback function that gets called in the TestCase context
+     * and within the testing process. Good for validating results.
+     */
+    afterTest(fn) {
+        if (typeof fn !== "function") {
+            throw new Error ("Tried to call non-function after test");
+        }
+
+        this.afterTestFn = fn;
+
+        return this;
     }
 
     /**
      * validates the value returned from the test function
+     * virtual method expected to be overridden by child class
      */
     validateRet() {
         throw new Error("Not implemented");
     }
-
-    /**
-     * calls doIt() with testObject() and expects it to fail with a TypeError()
-     */
-    testBadArgs(testDesc) {
-        promise_test(function(t) {
-            return promise_rejects(t, new TypeError(), this.doIt(), "Expected bad parameters to fail");
-        }.bind(this), testDesc);
-    }
 }
 
-var createCredentialDefaultArgs = {
-    options: {
-        publicKey: {
-            // Relying Party:
-            rp: {
-                name: "Acme"
-            },
-
-            // User:
-            user: {
-                id: new Uint8Array(), // Won't survive the copy, must be rebuilt
-                name: "john.p.smith@example.com",
-                displayName: "John P. Smith",
-                icon: "https://pics.acme.com/00/p/aBjjjpqPb.png"
-            },
-
-            pubKeyCredParams: [{
-                type: "public-key",
-                alg: cose_alg_ECDSA_w_SHA256,
-            }],
-
-            timeout: 60000, // 1 minute
-            excludeCredentials: [] // No excludeList
-        }
-    }
-};
-
 function cloneObject(o) {
     return JSON.parse(JSON.stringify(o));
 }
 
+function extendObject(dst, src) {
+    Object.keys(src).forEach(function(key) {
+        if (isSimpleObject(src[key])) {
+            extendObject (dst[key], src[key]);
+        } else {
+            dst[key] = src[key];
+        }
+    });
+}
+
+function isSimpleObject(o) {
+    return (typeof o === "object" &&
+        !Array.isArray(o) &&
+        !(o instanceof ArrayBuffer));
+}
+
 /**
  * CreateCredentialTest
  *
@@ -198,7 +369,7 @@
         window.crypto.getRandomValues(challengeBytes);
         this.testObject = cloneObject(createCredentialDefaultArgs);
         // cloneObject can't clone the BufferSource in user.id, so let's recreate it.
-        this.testObject.options.publicKey.user.id = new Uint8Array();
+        this.testObject.options.publicKey.user.id = new Uint8Array(16);
         this.testObject.options.publicKey.challenge = challengeBytes;
 
         // how to order the properties of testObject when passing them to makeCredential
@@ -235,15 +406,8 @@
         // default arguments
         let challengeBytes = new Uint8Array(16);
         window.crypto.getRandomValues(challengeBytes);
-        this.testObject = {
-            options: {
-                publicKey: {
-                    challenge: challengeBytes,
-                    // timeout: 60000,
-                    // allowCredentials: [newCredential]
-                }
-            }
-        };
+        this.testObject = cloneObject(getCredentialDefaultArgs);
+        this.testObject.options.publicKey.challenge = challengeBytes;
 
         // how to order the properties of testObject when passing them to makeCredential
         this.argOrder = [
@@ -266,33 +430,28 @@
         // if a Promise was passed in, add it to the list
         if (arg instanceof Promise) {
             this.credentialPromiseList.push(arg);
-            return;
+            return this;
         }
 
         // if a credential object was passed in, convert it to a Promise for consistency
         if (typeof arg === "object") {
             this.credentialPromiseList.push(Promise.resolve(arg));
-            return;
+            return this;
         }
 
-        // if a credential wasn't passed in, create one
-        let challengeBytes = new Uint8Array(16);
-        window.crypto.getRandomValues(challengeBytes);
-        var createArgs = cloneObject(createCredentialDefaultArgs);
-        createArgs.options.publicKey.challenge = challengeBytes;
-        createArgs.options.publicKey.user.id = new Uint8Array();
-        var p = navigator.credentials.create(createArgs.options);
+        // if no credential specified then create one
+        var p = createCredential();
         this.credentialPromiseList.push(p);
 
         return this;
     }
 
-    test() {
+    testSetup(desc) {
         if (!this.credentialPromiseList.length) {
             throw new Error("Attempting list without defining credential to test");
         }
 
-        Promise.all(this.credentialPromiseList)
+        return Promise.all(this.credentialPromiseList)
             .then((credList) => {
                 var idList = credList.map((cred) => {
                     return {
@@ -302,12 +461,15 @@
                     };
                 });
                 this.testObject.options.publicKey.allowCredentials = idList;
-                return super.test();
+                // return super.test(desc);
+            })
+            .catch((err) => {
+                throw Error(err);
             });
     }
 
     validateRet(ret) {
-        validatePublicKeyCredential (ret);
+        validatePublicKeyCredential(ret);
         validateAuthenticatorAssertionResponse(ret.response);
     }
 }
@@ -335,12 +497,16 @@
 function validateAuthenticatorAttestationResponse(attr) {
     // class
     assert_class_string(attr, "AuthenticatorAttestationResponse", "Expected credentials.create() to return instance of 'AuthenticatorAttestationResponse' class");
+
     // clientDataJSON
     assert_idl_attribute(attr, "clientDataJSON", "credentials.create() should return AuthenticatorAttestationResponse with clientDataJSON attribute");
     assert_readonly(attr, "clientDataJSON", "credentials.create() should return AuthenticatorAttestationResponse with readonly clientDataJSON attribute");
+    // TODO: clientDataJSON() and make sure fields are correct
+
     // attestationObject
     assert_idl_attribute(attr, "attestationObject", "credentials.create() should return AuthenticatorAttestationResponse with attestationObject attribute");
     assert_readonly(attr, "attestationObject", "credentials.create() should return AuthenticatorAttestationResponse with readonly attestationObject attribute");
+    // TODO: parseAuthenticatorData() and make sure flags are correct
 }
 
 /**
@@ -349,15 +515,20 @@
 function validateAuthenticatorAssertionResponse(assert) {
     // class
     assert_class_string(assert, "AuthenticatorAssertionResponse", "Expected credentials.create() to return instance of 'AuthenticatorAssertionResponse' class");
+
     // clientDataJSON
     assert_idl_attribute(assert, "clientDataJSON", "credentials.get() should return AuthenticatorAssertionResponse with clientDataJSON attribute");
     assert_readonly(assert, "clientDataJSON", "credentials.get() should return AuthenticatorAssertionResponse with readonly clientDataJSON attribute");
+    // TODO: clientDataJSON() and make sure fields are correct
+
     // signature
     assert_idl_attribute(assert, "signature", "credentials.get() should return AuthenticatorAssertionResponse with signature attribute");
     assert_readonly(assert, "signature", "credentials.get() should return AuthenticatorAssertionResponse with readonly signature attribute");
+
     // authenticatorData
     assert_idl_attribute(assert, "authenticatorData", "credentials.get() should return AuthenticatorAssertionResponse with authenticatorData attribute");
     assert_readonly(assert, "authenticatorData", "credentials.get() should return AuthenticatorAssertionResponse with readonly authenticatorData attribute");
+    // TODO: parseAuthenticatorData() and make sure flags are correct
 }
 
 //************* BEGIN DELETE AFTER 1/1/2018 *************** //
@@ -370,8 +541,8 @@
 // note that the polyfill only gets loaded if navigator.credentials create doesn't exist
 // AND if the polyfill script is found at the right path (i.e. - the polyfill is opt-in)
 function ensureInterface() {
-    if (typeof navigator.credentials.create !== "function") {
-        debug = console.log;
+    if (typeof navigator.credentials === "object" && typeof navigator.credentials.create !== "function") {
+        // debug = onsole.log;
 
         return loadJavaScript("/webauthn/webauthn-polyfill/webauthn-polyfill.js")
             .then(() => {
diff --git a/webauthn/securecontext.http.html b/webauthn/securecontext.http.html
index 82464bd..5278695 100644
--- a/webauthn/securecontext.http.html
+++ b/webauthn/securecontext.http.html
@@ -17,7 +17,7 @@
     // Example 1
     // http://example.com/ opened in a top-level browsing context is not a secure context, as it was not delivered over an authenticated and encrypted channel.
     test (() => {
-        assert_false (typeof navigator.credentials.create === "function");
+        assert_false (typeof navigator.credentials === "object" && typeof navigator.credentials.create === "function");
     }, "no navigator.credentials.create in non-secure context");
 
     // Example 4: TODO
diff --git a/webauthn/securecontext.https.html b/webauthn/securecontext.https.html
index 9810a7f..6c9aabd 100644
--- a/webauthn/securecontext.https.html
+++ b/webauthn/securecontext.https.html
@@ -17,7 +17,7 @@
     // Example 2
     // https://example.com/ opened in a top-level browsing context is a secure context, as it was delivered over an authenticated and encrypted channel.
     test (() => {
-        assert_true (typeof navigator.credentials.create === "function");
+        assert_true (typeof navigator.credentials === "object" && typeof navigator.credentials.create === "function");
     }, "navigator.credentials.create exists in secure context");
 
     // Example 3: TODO