Bug 1454007 [wpt PR 10461] - Streams: Constructors property lookup order, a=testonly
authorAdam Rice <ricea@chromium.org>
Sun, 29 Apr 2018 20:50:21 +0000
changeset 472391 4d864d2b3e4610141cfdd0dd502b92759e5a9abf
parent 472390 496361c2ecc0bc345b447e9d6b13ed05e701e052
child 472392 a8cde567d6ad40c6f69c3468dd761cdc82cb8a3a
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1454007, 10461
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1454007 [wpt PR 10461] - Streams: Constructors property lookup order, a=testonly Automatic update from web-platform-testsStreams: Constructors property lookup order (#10461) ReadableStream, WritableStream and TransformStream look up and validate properties on their arguments. Add tests to verify that these lookups and validations happen in the order specified by the standard. https://github.com/whatwg/streams/pull/922 adjusts some of the ordering of these operations. These tests correspond to that change. A new utility file streams/resources/constructor-ordering.js contains functions to construct objects which track property lookups and validations and can cause them to fail. For each constructor the constructor.js file contains a list of the expected operations in order, and a test case to verify it. A test in streams/transform-streams/properties.js already detected the ordering of property accesses by the TransformStream constructor. writableStrategy is now accessed before readableStrategy to match the order of the constructor arguments, and so the expectations of this test are changed to match. -- wpt-commits: 85f9d70519e5259f01e8653a4916134227e0f178 wpt-pr: 10461
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/streams/readable-byte-streams/constructor.dedicatedworker.html
testing/web-platform/tests/streams/readable-byte-streams/constructor.html
testing/web-platform/tests/streams/readable-byte-streams/constructor.js
testing/web-platform/tests/streams/readable-byte-streams/constructor.serviceworker.https.html
testing/web-platform/tests/streams/readable-byte-streams/constructor.sharedworker.html
testing/web-platform/tests/streams/readable-streams/constructor.dedicatedworker.html
testing/web-platform/tests/streams/readable-streams/constructor.html
testing/web-platform/tests/streams/readable-streams/constructor.js
testing/web-platform/tests/streams/readable-streams/constructor.serviceworker.https.html
testing/web-platform/tests/streams/readable-streams/constructor.sharedworker.html
testing/web-platform/tests/streams/resources/constructor-ordering.js
testing/web-platform/tests/streams/transform-streams/constructor.dedicatedworker.html
testing/web-platform/tests/streams/transform-streams/constructor.html
testing/web-platform/tests/streams/transform-streams/constructor.js
testing/web-platform/tests/streams/transform-streams/constructor.serviceworker.https.html
testing/web-platform/tests/streams/transform-streams/constructor.sharedworker.html
testing/web-platform/tests/streams/transform-streams/properties.js
testing/web-platform/tests/streams/writable-streams/constructor.html
testing/web-platform/tests/streams/writable-streams/constructor.js
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -294870,16 +294870,21 @@
      {}
     ]
    ],
    "streams/readable-byte-streams/construct-byob-request.js": [
     [
      {}
     ]
    ],
+   "streams/readable-byte-streams/constructor.js": [
+    [
+     {}
+    ]
+   ],
    "streams/readable-byte-streams/detached-buffers.js": [
     [
      {}
     ]
    ],
    "streams/readable-byte-streams/general.js": [
     [
      {}
@@ -294905,16 +294910,21 @@
      {}
     ]
    ],
    "streams/readable-streams/cancel.js": [
     [
      {}
     ]
    ],
+   "streams/readable-streams/constructor.js": [
+    [
+     {}
+    ]
+   ],
    "streams/readable-streams/count-queuing-strategy-integration.js": [
     [
      {}
     ]
    ],
    "streams/readable-streams/default-reader.js": [
     [
      {}
@@ -294950,16 +294960,21 @@
      {}
     ]
    ],
    "streams/readable-streams/templated.js": [
     [
      {}
     ]
    ],
+   "streams/resources/constructor-ordering.js": [
+    [
+     {}
+    ]
+   ],
    "streams/resources/recording-streams.js": [
     [
      {}
     ]
    ],
    "streams/resources/rs-test-templates.js": [
     [
      {}
@@ -294980,16 +294995,21 @@
      {}
     ]
    ],
    "streams/transform-streams/brand-checks.js": [
     [
      {}
     ]
    ],
+   "streams/transform-streams/constructor.js": [
+    [
+     {}
+    ]
+   ],
    "streams/transform-streams/errors.js": [
     [
      {}
     ]
    ],
    "streams/transform-streams/flush.js": [
     [
      {}
@@ -365574,16 +365594,40 @@
     ]
    ],
    "streams/readable-byte-streams/construct-byob-request.sharedworker.html": [
     [
      "/streams/readable-byte-streams/construct-byob-request.sharedworker.html",
      {}
     ]
    ],
+   "streams/readable-byte-streams/constructor.dedicatedworker.html": [
+    [
+     "/streams/readable-byte-streams/constructor.dedicatedworker.html",
+     {}
+    ]
+   ],
+   "streams/readable-byte-streams/constructor.html": [
+    [
+     "/streams/readable-byte-streams/constructor.html",
+     {}
+    ]
+   ],
+   "streams/readable-byte-streams/constructor.serviceworker.https.html": [
+    [
+     "/streams/readable-byte-streams/constructor.serviceworker.https.html",
+     {}
+    ]
+   ],
+   "streams/readable-byte-streams/constructor.sharedworker.html": [
+    [
+     "/streams/readable-byte-streams/constructor.sharedworker.html",
+     {}
+    ]
+   ],
    "streams/readable-byte-streams/detached-buffers.dedicatedworker.html": [
     [
      "/streams/readable-byte-streams/detached-buffers.dedicatedworker.html",
      {}
     ]
    ],
    "streams/readable-byte-streams/detached-buffers.html": [
     [
@@ -365742,16 +365786,40 @@
     ]
    ],
    "streams/readable-streams/cancel.sharedworker.html": [
     [
      "/streams/readable-streams/cancel.sharedworker.html",
      {}
     ]
    ],
+   "streams/readable-streams/constructor.dedicatedworker.html": [
+    [
+     "/streams/readable-streams/constructor.dedicatedworker.html",
+     {}
+    ]
+   ],
+   "streams/readable-streams/constructor.html": [
+    [
+     "/streams/readable-streams/constructor.html",
+     {}
+    ]
+   ],
+   "streams/readable-streams/constructor.serviceworker.https.html": [
+    [
+     "/streams/readable-streams/constructor.serviceworker.https.html",
+     {}
+    ]
+   ],
+   "streams/readable-streams/constructor.sharedworker.html": [
+    [
+     "/streams/readable-streams/constructor.sharedworker.html",
+     {}
+    ]
+   ],
    "streams/readable-streams/count-queuing-strategy-integration.dedicatedworker.html": [
     [
      "/streams/readable-streams/count-queuing-strategy-integration.dedicatedworker.html",
      {}
     ]
    ],
    "streams/readable-streams/count-queuing-strategy-integration.html": [
     [
@@ -366006,16 +366074,40 @@
     ]
    ],
    "streams/transform-streams/brand-checks.sharedworker.html": [
     [
      "/streams/transform-streams/brand-checks.sharedworker.html",
      {}
     ]
    ],
+   "streams/transform-streams/constructor.dedicatedworker.html": [
+    [
+     "/streams/transform-streams/constructor.dedicatedworker.html",
+     {}
+    ]
+   ],
+   "streams/transform-streams/constructor.html": [
+    [
+     "/streams/transform-streams/constructor.html",
+     {}
+    ]
+   ],
+   "streams/transform-streams/constructor.serviceworker.https.html": [
+    [
+     "/streams/transform-streams/constructor.serviceworker.https.html",
+     {}
+    ]
+   ],
+   "streams/transform-streams/constructor.sharedworker.html": [
+    [
+     "/streams/transform-streams/constructor.sharedworker.html",
+     {}
+    ]
+   ],
    "streams/transform-streams/errors.dedicatedworker.html": [
     [
      "/streams/transform-streams/errors.dedicatedworker.html",
      {}
     ]
    ],
    "streams/transform-streams/errors.html": [
     [
@@ -600915,16 +601007,36 @@
   "streams/readable-byte-streams/construct-byob-request.serviceworker.https.html": [
    "79dbbe66a34ca531fc532e37f6c11483a917cf9e",
    "testharness"
   ],
   "streams/readable-byte-streams/construct-byob-request.sharedworker.html": [
    "2a8a870d82e13cccac5b59de0ece4b8e9ff2775f",
    "testharness"
   ],
+  "streams/readable-byte-streams/constructor.dedicatedworker.html": [
+   "6c954f0d265d21fe1dae9d85afbcc79e8d875797",
+   "testharness"
+  ],
+  "streams/readable-byte-streams/constructor.html": [
+   "e8fa572176af7fabf288991f6094bf8aaf50a943",
+   "testharness"
+  ],
+  "streams/readable-byte-streams/constructor.js": [
+   "07d1029b4a703289045f2b65dbfae0dc48b09950",
+   "support"
+  ],
+  "streams/readable-byte-streams/constructor.serviceworker.https.html": [
+   "025f934a714ce8c52df953ba570a527cbcb88399",
+   "testharness"
+  ],
+  "streams/readable-byte-streams/constructor.sharedworker.html": [
+   "f6ce1491f86ee048da0f6135440b7aa5359caa22",
+   "testharness"
+  ],
   "streams/readable-byte-streams/detached-buffers.dedicatedworker.html": [
    "8742d541924382fb06a3258d6723f22ddf299045",
    "testharness"
   ],
   "streams/readable-byte-streams/detached-buffers.html": [
    "9eb042e92c7592d9a5a233469d491d61619a9045",
    "testharness"
   ],
@@ -601055,16 +601167,36 @@
   "streams/readable-streams/cancel.serviceworker.https.html": [
    "815c9b14a88bb84526e3ea0b93ba8d74bb57838a",
    "testharness"
   ],
   "streams/readable-streams/cancel.sharedworker.html": [
    "52f22079742ab1a783b9a03dbf255a2f481c6d45",
    "testharness"
   ],
+  "streams/readable-streams/constructor.dedicatedworker.html": [
+   "6c954f0d265d21fe1dae9d85afbcc79e8d875797",
+   "testharness"
+  ],
+  "streams/readable-streams/constructor.html": [
+   "e8fa572176af7fabf288991f6094bf8aaf50a943",
+   "testharness"
+  ],
+  "streams/readable-streams/constructor.js": [
+   "0ad3f6a45f88d003b33b7385fb1b085890393364",
+   "support"
+  ],
+  "streams/readable-streams/constructor.serviceworker.https.html": [
+   "025f934a714ce8c52df953ba570a527cbcb88399",
+   "testharness"
+  ],
+  "streams/readable-streams/constructor.sharedworker.html": [
+   "f6ce1491f86ee048da0f6135440b7aa5359caa22",
+   "testharness"
+  ],
   "streams/readable-streams/count-queuing-strategy-integration.dedicatedworker.html": [
    "8e023ec682231d47d43549e26636758652c8f702",
    "testharness"
   ],
   "streams/readable-streams/count-queuing-strategy-integration.html": [
    "be512c75571757d8d64d110d1028abd20ce1a9ae",
    "testharness"
   ],
@@ -601235,16 +601367,20 @@
   "streams/readable-streams/templated.serviceworker.https.html": [
    "82dcb18ef90fb8a957cd2a991cafc598e918e35b",
    "testharness"
   ],
   "streams/readable-streams/templated.sharedworker.html": [
    "e21d57a0dd02a13b9b93ace6ab2f292f29bd4f93",
    "testharness"
   ],
+  "streams/resources/constructor-ordering.js": [
+   "0f43cd2dce2e1a31da1ae9628383c6395b0feecd",
+   "support"
+  ],
   "streams/resources/recording-streams.js": [
    "e249947d73a7ecd1ddc0a39cc23c7a53e71a049c",
    "support"
   ],
   "streams/resources/rs-test-templates.js": [
    "ba1d02757a258008a7f05e93206fb210adbbfc6a",
    "support"
   ],
@@ -601291,16 +601427,36 @@
   "streams/transform-streams/brand-checks.serviceworker.https.html": [
    "c8279e28f5cd80d454d884154ae883a8c965705f",
    "testharness"
   ],
   "streams/transform-streams/brand-checks.sharedworker.html": [
    "6f3911baf77e26af2d7e7d7472caae4df6d5a27e",
    "testharness"
   ],
+  "streams/transform-streams/constructor.dedicatedworker.html": [
+   "6c954f0d265d21fe1dae9d85afbcc79e8d875797",
+   "testharness"
+  ],
+  "streams/transform-streams/constructor.html": [
+   "e8fa572176af7fabf288991f6094bf8aaf50a943",
+   "testharness"
+  ],
+  "streams/transform-streams/constructor.js": [
+   "93845386cad513a39d8536f82fc536f61ecc1dc3",
+   "support"
+  ],
+  "streams/transform-streams/constructor.serviceworker.https.html": [
+   "025f934a714ce8c52df953ba570a527cbcb88399",
+   "testharness"
+  ],
+  "streams/transform-streams/constructor.sharedworker.html": [
+   "f6ce1491f86ee048da0f6135440b7aa5359caa22",
+   "testharness"
+  ],
   "streams/transform-streams/errors.dedicatedworker.html": [
    "ea56c89e303ed85d5f6acd423600b1d4ad73ac13",
    "testharness"
   ],
   "streams/transform-streams/errors.html": [
    "bb42fe4b43455e530f8fbb02d86f69fecdd994ef",
    "testharness"
   ],
@@ -601400,17 +601556,17 @@
    "0d766237560b16ddb1bfcd02e701089132f1b3ec",
    "testharness"
   ],
   "streams/transform-streams/properties.html": [
    "82a278a1e8599b5bbd1ab8abadfb13d85f45aa5e",
    "testharness"
   ],
   "streams/transform-streams/properties.js": [
-   "8bf21998fc6b9d26e79e1fdf541a2efb8f566d36",
+   "04ed8ef8fdaf1906c8a3cf0fe8e52943abe6084c",
    "support"
   ],
   "streams/transform-streams/properties.serviceworker.https.html": [
    "2ef8fc878249c429a89e0748e6a98fac47c1a99a",
    "testharness"
   ],
   "streams/transform-streams/properties.sharedworker.html": [
    "5c855e897d1143092ecc10b58268e6a576882184",
@@ -601596,21 +601752,21 @@
    "b2aeb127d5ae7087b42e3fc08faaa113c84e6fd8",
    "testharness"
   ],
   "streams/writable-streams/constructor.dedicatedworker.html": [
    "6c954f0d265d21fe1dae9d85afbcc79e8d875797",
    "testharness"
   ],
   "streams/writable-streams/constructor.html": [
-   "cedc1d744e01b63e27503f59791cd45834db039e",
+   "e8fa572176af7fabf288991f6094bf8aaf50a943",
    "testharness"
   ],
   "streams/writable-streams/constructor.js": [
-   "0f67e4c79f7a7d32c29b14b917e8921dbb3171c1",
+   "a116d92d0e20f463de6073ee6392e30c1d6927dc",
    "support"
   ],
   "streams/writable-streams/constructor.serviceworker.https.html": [
    "025f934a714ce8c52df953ba570a527cbcb88399",
    "testharness"
   ],
   "streams/writable-streams/constructor.sharedworker.html": [
    "f6ce1491f86ee048da0f6135440b7aa5359caa22",
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/constructor.dedicatedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js dedicated worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new Worker('constructor.js'));
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/constructor.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js browser context wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script src="../resources/constructor-ordering.js"></script>
+
+<script src="constructor.js"></script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/constructor.js
@@ -0,0 +1,53 @@
+'use strict';
+
+if (self.importScripts) {
+  self.importScripts('/resources/testharness.js');
+  self.importScripts('../resources/constructor-ordering.js');
+}
+
+const operations = [
+  op('get', 'size'),
+  op('get', 'highWaterMark'),
+  op('get', 'type'),
+  op('validate', 'type'),
+  op('validate', 'size'),
+  op('tonumber', 'highWaterMark'),
+  op('validate', 'highWaterMark'),
+  op('get', 'pull'),
+  op('validate', 'pull'),
+  op('get', 'cancel'),
+  op('validate', 'cancel'),
+  op('get', 'autoAllocateChunkSize'),
+  op('tonumber', 'autoAllocateChunkSize'),
+  op('validate', 'autoAllocateChunkSize'),
+  op('get', 'start'),
+  op('validate', 'start')
+];
+
+for (const failureOp of operations) {
+  test(() => {
+    const record = new OpRecorder(failureOp);
+    const underlyingSource = createRecordingObjectWithProperties(record, ['start', 'pull', 'cancel']);
+
+    // The valid value for "type" is "bytes", so set it separately.
+    defineCheckedProperty(record, underlyingSource, 'type', () => record.check('type') ? 'invalid' : 'bytes');
+
+    // autoAllocateChunkSize is a special case because it has a tonumber step.
+    defineCheckedProperty(record, underlyingSource, 'autoAllocateChunkSize',
+                          () => createRecordingNumberObject(record, 'autoAllocateChunkSize'));
+
+    const strategy = createRecordingStrategy(record);
+
+    try {
+      new ReadableStream(underlyingSource, strategy);
+      assert_unreached('constructor should throw');
+    } catch (e) {
+      assert_equals(typeof e, 'object', 'e should be an object');
+    }
+
+    assert_equals(record.actual(), expectedAsString(operations, failureOp),
+                  'operations should be performed in the right order');
+  }, `ReadableStream constructor should stop after ${failureOp} fails`);
+}
+
+done();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/constructor.serviceworker.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js service worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+
+<script>
+'use strict';
+service_worker_test('constructor.js', 'Service worker test setup');
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/constructor.sharedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js shared worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new SharedWorker('constructor.js'));
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-streams/constructor.dedicatedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js dedicated worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new Worker('constructor.js'));
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-streams/constructor.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js browser context wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script src="../resources/constructor-ordering.js"></script>
+
+<script src="constructor.js"></script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-streams/constructor.js
@@ -0,0 +1,42 @@
+'use strict';
+
+if (self.importScripts) {
+  self.importScripts('/resources/testharness.js');
+  self.importScripts('../resources/constructor-ordering.js');
+}
+
+const operations = [
+  op('get', 'size'),
+  op('get', 'highWaterMark'),
+  op('get', 'type'),
+  op('validate', 'type'),
+  op('validate', 'size'),
+  op('tonumber', 'highWaterMark'),
+  op('validate', 'highWaterMark'),
+  op('get', 'pull'),
+  op('validate', 'pull'),
+  op('get', 'cancel'),
+  op('validate', 'cancel'),
+  op('get', 'start'),
+  op('validate', 'start')
+];
+
+for (const failureOp of operations) {
+  test(() => {
+    const record = new OpRecorder(failureOp);
+    const underlyingSource = createRecordingObjectWithProperties(record, ['type', 'start', 'pull', 'cancel']);
+    const strategy = createRecordingStrategy(record);
+
+    try {
+      new ReadableStream(underlyingSource, strategy);
+      assert_unreached('constructor should throw');
+    } catch (e) {
+      assert_equals(typeof e, 'object', 'e should be an object');
+    }
+
+    assert_equals(record.actual(), expectedAsString(operations, failureOp),
+                  'operations should be performed in the right order');
+  }, `ReadableStream constructor should stop after ${failureOp} fails`);
+}
+
+done();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-streams/constructor.serviceworker.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js service worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+
+<script>
+'use strict';
+service_worker_test('constructor.js', 'Service worker test setup');
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-streams/constructor.sharedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js shared worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new SharedWorker('constructor.js'));
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/resources/constructor-ordering.js
@@ -0,0 +1,129 @@
+'use strict';
+
+// Helpers for tests that constructors perform getting and validation of properties in the standard order.
+// See ../readable-streams/constructor.js for an example of how to use them.
+
+// Describes an operation on a property. |type| is "get", "validate" or "tonumber". |name| is the name of the property
+// in question. |side| is usually undefined, but is used by TransformStream to distinguish between the readable and
+// writable strategies.
+class Op {
+  constructor(type, name, side) {
+    this.type = type;
+    this.name = name;
+    this.side = side;
+  }
+
+  toString() {
+    return this.side === undefined ? `${this.type} on ${this.name}` : `${this.type} on ${this.name} (${this.side})`;
+  }
+
+  equals(otherOp) {
+    return this.type === otherOp.type && this.name === otherOp.name && this.side === otherOp.side;
+  }
+}
+
+// Provides a concise syntax to create an Op object. |side| is used by TransformStream to distinguish between the two
+// strategies.
+function op(type, name, side = undefined) {
+  return new Op(type, name, side);
+}
+
+// Records a sequence of operations. Also checks each operation against |failureOp| to see if it should fail.
+class OpRecorder {
+  constructor(failureOp) {
+    this.ops = [];
+    this.failureOp = failureOp;
+    this.matched = false;
+  }
+
+  // Record an operation. Returns true if this operation should fail.
+  recordAndCheck(type, name, side = undefined) {
+    const recordedOp = op(type, name, side);
+    this.ops.push(recordedOp);
+    return this.failureOp.equals(recordedOp);
+  }
+
+  // Returns true if validation of this property should fail.
+  check(name, side = undefined) {
+    return this.failureOp.equals(op('validate', name, side));
+  }
+
+  // Returns the sequence of recorded operations as a string.
+  actual() {
+    return this.ops.toString();
+  }
+}
+
+// Creates an object with the list of properties named in |properties|. Every property access will be recorded in
+// |record|, which will also be used to determine whether a particular property access should fail, or whether it should
+// return an invalid value that will fail validation.
+function createRecordingObjectWithProperties(record, properties) {
+  const recordingObject = {};
+  for (const property of properties) {
+    defineCheckedProperty(record, recordingObject, property, () => record.check(property) ? 'invalid' : undefined);
+  }
+  return recordingObject;
+}
+
+// Add a getter to |object| named |property| which throws if op('get', property) should fail, and otherwise calls
+// getter() to get the return value.
+function defineCheckedProperty(record, object, property, getter) {
+  Object.defineProperty(object, property, {
+    get() {
+      if (record.recordAndCheck('get', property)) {
+        throw new Error(`intentional failure of get ${property}`);
+      }
+      return getter();
+    }
+  });
+}
+
+// Similar to createRecordingObjectWithProperties(), but with specific functionality for "highWaterMark" so that numeric
+// conversion can be recorded. Permits |side| to be specified so that TransformStream can distinguish between its two
+// strategies.
+function createRecordingStrategy(record, side = undefined) {
+  return {
+    get size() {
+      if (record.recordAndCheck('get', 'size', side)) {
+        throw new Error(`intentional failure of get size`);
+      }
+      return record.check('size', side) ? 'invalid' : undefined;
+    },
+    get highWaterMark() {
+      if (record.recordAndCheck('get', 'highWaterMark', side)) {
+        throw new Error(`intentional failure of get highWaterMark`);
+      }
+      return createRecordingNumberObject(record, 'highWaterMark', side);
+    }
+  };
+}
+
+// Creates an object which will record when it is converted to a number. It will assert if the conversion is to some
+// other type, and will fail if op('tonumber', property, side) is set as the failure step. The object will convert to -1
+// if 'validate' is set as the failure step, and 1 otherwise.
+function createRecordingNumberObject(record, property, side = undefined) {
+  return {
+    [Symbol.toPrimitive](hint) {
+      assert_equals(hint, 'number', `hint for ${property} should be 'number'`);
+      if (record.recordAndCheck('tonumber', property, side)) {
+        throw new Error(`intentional failure of ${op('tonumber', property, side)}`);
+      }
+      return record.check(property, side) ? -1 : 1;
+    }
+  };
+}
+
+// Creates a string from everything in |operations| up to and including |failureOp|. "validate" steps are excluded from
+// the output, as we cannot record them except by making them fail.
+function expectedAsString(operations, failureOp) {
+  const expected = [];
+  for (const step of operations) {
+    if (step.type !== 'validate') {
+      expected.push(step);
+    }
+    if (step.equals(failureOp)) {
+      break;
+    }
+  }
+  return expected.toString();
+}
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/transform-streams/constructor.dedicatedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js dedicated worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new Worker('constructor.js'));
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/transform-streams/constructor.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js browser context wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script src="../resources/constructor-ordering.js"></script>
+
+<script src="constructor.js"></script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/transform-streams/constructor.js
@@ -0,0 +1,51 @@
+'use strict';
+
+if (self.importScripts) {
+  self.importScripts('/resources/testharness.js');
+  self.importScripts('../resources/constructor-ordering.js');
+}
+
+const operations = [
+  op('get', 'size', 'writable'),
+  op('get', 'highWaterMark', 'writable'),
+  op('get', 'size', 'readable'),
+  op('get', 'highWaterMark', 'readable'),
+  op('get', 'writableType'),
+  op('validate', 'writableType'),
+  op('validate', 'size', 'writable'),
+  op('tonumber', 'highWaterMark', 'writable'),
+  op('validate', 'highWaterMark', 'writable'),
+  op('get', 'readableType'),
+  op('validate', 'readableType'),
+  op('validate', 'size', 'readable'),
+  op('tonumber', 'highWaterMark', 'readable'),
+  op('validate', 'highWaterMark', 'readable'),
+  op('get', 'transform'),
+  op('validate', 'transform'),
+  op('get', 'flush'),
+  op('validate', 'flush'),
+  op('get', 'start'),
+  op('validate', 'start')
+];
+
+for (const failureOp of operations) {
+  test(() => {
+    const record = new OpRecorder(failureOp);
+    const transformer = createRecordingObjectWithProperties(
+        record, ['readableType', 'writableType', 'start', 'transform', 'flush']);
+    const writableStrategy = createRecordingStrategy(record, 'writable');
+    const readableStrategy = createRecordingStrategy(record, 'readable');
+
+    try {
+      new TransformStream(transformer, writableStrategy, readableStrategy);
+      assert_unreached('constructor should throw');
+    } catch (e) {
+      assert_equals(typeof e, 'object', 'e should be an object');
+    }
+
+    assert_equals(record.actual(), expectedAsString(operations, failureOp),
+                  'operations should be performed in the right order');
+  }, `TransformStream constructor should stop after ${failureOp} fails`);
+}
+
+done();
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/transform-streams/constructor.serviceworker.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js service worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+
+<script>
+'use strict';
+service_worker_test('constructor.js', 'Service worker test setup');
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/streams/transform-streams/constructor.sharedworker.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>constructor.js shared worker wrapper file</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+'use strict';
+fetch_tests_from_worker(new SharedWorker('constructor.js'));
+</script>
--- a/testing/web-platform/tests/streams/transform-streams/properties.js
+++ b/testing/web-platform/tests/streams/transform-streams/properties.js
@@ -177,18 +177,18 @@ for (const method in transformerMethods)
         return () => Promise.resolve();
       })
     };
     for (const trap of unreachedTraps) {
       handler[trap] = t.unreached_func(`${trap} should not be trapped`);
     }
     const transformer = new Proxy({}, handler);
     const ts = new TransformStream(transformer, undefined, { highWaterMark: Infinity });
-    assert_array_equals(touchedProperties, ['readableType', 'writableType', 'transform', 'flush', 'start'],
+    assert_array_equals(touchedProperties, ['writableType', 'readableType', 'transform', 'flush', 'start'],
                         'expected properties should be got');
     return trigger(ts).then(() => {
-      assert_array_equals(touchedProperties, ['readableType', 'writableType', 'transform', 'flush', 'start'],
+      assert_array_equals(touchedProperties, ['writableType', 'readableType', 'transform', 'flush', 'start'],
                           'no properties should be accessed on method call');
     });
   }, `unexpected properties should not be accessed when calling transformer method ${method}`);
 }
 
 done();
--- a/testing/web-platform/tests/streams/writable-streams/constructor.html
+++ b/testing/web-platform/tests/streams/writable-streams/constructor.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>constructor.js browser context wrapper file</title>
 
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 
-
+<script src="../resources/constructor-ordering.js"></script>
 
 <script src="constructor.js"></script>
--- a/testing/web-platform/tests/streams/writable-streams/constructor.js
+++ b/testing/web-platform/tests/streams/writable-streams/constructor.js
@@ -1,12 +1,13 @@
 'use strict';
 
 if (self.importScripts) {
   self.importScripts('/resources/testharness.js');
+  self.importScripts('../resources/constructor-ordering.js');
 }
 
 const error1 = new Error('error1');
 error1.name = 'error1';
 
 promise_test(() => {
   let controller;
   const ws = new WritableStream({
@@ -114,17 +115,17 @@ test(() => {
   new WritableStream({
     start(c) {
       WritableStreamDefaultController = c.constructor;
     }
   });
 
   assert_throws(new TypeError(), () => new WritableStreamDefaultController({}),
                 'constructor should throw a TypeError exception');
-}, 'WritableStreamDefaultController constructor should throw unless passed a WritableStream');
+}, 'WritableStreamDefaultController constructor should throw');
 
 test(() => {
   let WritableStreamDefaultController;
   const stream = new WritableStream({
     start(c) {
       WritableStreamDefaultController = c.constructor;
     }
   });
@@ -145,9 +146,45 @@ test(() => {
 test(() => {
   const stream = new WritableStream();
   const writer = stream.getWriter();
   const WritableStreamDefaultWriter = writer.constructor;
   assert_throws(new TypeError(), () => new WritableStreamDefaultWriter(stream),
                 'constructor should throw a TypeError exception');
 }, 'WritableStreamDefaultWriter constructor should throw when stream argument is locked');
 
+const operations = [
+  op('get', 'size'),
+  op('get', 'highWaterMark'),
+  op('get', 'type'),
+  op('validate', 'type'),
+  op('validate', 'size'),
+  op('tonumber', 'highWaterMark'),
+  op('validate', 'highWaterMark'),
+  op('get', 'write'),
+  op('validate', 'write'),
+  op('get', 'close'),
+  op('validate', 'close'),
+  op('get', 'abort'),
+  op('validate', 'abort'),
+  op('get', 'start'),
+  op('validate', 'start')
+];
+
+for (const failureOp of operations) {
+  test(() => {
+    const record = new OpRecorder(failureOp);
+    const underlyingSink = createRecordingObjectWithProperties(record, ['type', 'start', 'write', 'close', 'abort']);
+    const strategy = createRecordingStrategy(record);
+
+    try {
+      new WritableStream(underlyingSink, strategy);
+      assert_unreached('constructor should throw');
+    } catch (e) {
+      assert_equals(typeof e, 'object', 'e should be an object');
+    }
+
+    assert_equals(record.actual(), expectedAsString(operations, failureOp),
+                  'operations should be performed in the right order');
+  }, `WritableStream constructor should stop after ${failureOp} fails`);
+}
+
 done();