.
+function test_stringifier_attribute(aObject, aAttribute, aIsUnforgeable) {
+ // Step 1.
+ test(function() {
+ [null, undefined].forEach(function(v) {
+ assert_throws(new TypeError(), function() {
+ aObject.toString.call(v);
+ });
+ });
+ });
+
+ // TODO Step 2: security check.
+
+ // Step 3.
+ test(function() {
+ assert_false("Window" in window && aObject instanceof window.Window);
+ [{}, window].forEach(function(v) {
+ assert_throws(new TypeError(), function() {
+ aObject.toString.call(v)
+ });
+ });
+ });
+
+ // Step 4-6.
+ var expected_value;
+ test(function() {
+ expected_value = aObject[aAttribute];
+ assert_equals(aObject[aAttribute], expected_value,
+ "The attribute " + aAttribute + " should be pure.");
+ });
+
+ var test_error = { name: "test" };
+ test(function() {
+ if (!aIsUnforgeable) {
+ Object.defineProperty(aObject, aAttribute, {
+ configurable: true,
+ get: function() { throw test_error; }
+ });
+ }
+ assert_equals(aObject.toString(), expected_value);
+ });
+
+ test(function() {
+ if (!aIsUnforgeable) {
+ Object.defineProperty(aObject, aAttribute, {
+ configurable: true,
+ value: { toString: function() { throw test_error; } }
+ });
+ }
+ assert_equals(aObject.toString(), expected_value);
+ });
+}
diff --git a/test/fixtures/web-platform-tests/common/stringifiers.js.headers b/test/fixtures/web-platform-tests/common/stringifiers.js.headers
new file mode 100644
index 00000000000000..6805c323df5a97
--- /dev/null
+++ b/test/fixtures/web-platform-tests/common/stringifiers.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/web-platform-tests/common/subset-tests-by-key.js b/test/fixtures/web-platform-tests/common/subset-tests-by-key.js
new file mode 100644
index 00000000000000..d87ea9f76ee25f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/common/subset-tests-by-key.js
@@ -0,0 +1,76 @@
+// Only test a subset of tests with ?include=Foo or ?exclude=Foo in the URL.
+// Can be used together with
+// Sample usage:
+// for (const test of tests) {
+// subsetTestByKey("Foo", async_test, test.fn, test.name);
+// }
+(function() {
+ var subTestKeyPattern = null;
+ var match;
+ var collectKeys = false;
+ var collectCounts = false;
+ var keys = {};
+ var exclude = false;
+ if (location.search) {
+ match = /(?:^\?|&)(include|exclude)=([^&]+)?/.exec(location.search);
+ if (match) {
+ subTestKeyPattern = new RegExp(`^${match[2]}$`);
+ if (match[1] === 'exclude') {
+ exclude = true;
+ }
+ }
+ // Below is utility code to generate for copy/paste into tests.
+ // Sample usage:
+ // test.html?get-keys
+ match = /(?:^\?|&)get-keys(&get-counts)?(?:&|$)/.exec(location.search);
+ if (match) {
+ collectKeys = true;
+ if (match[1]) {
+ collectCounts = true;
+ }
+ add_completion_callback(() => {
+ var metas = [];
+ var template = ' ';
+ if (collectCounts) {
+ template += ' ';
+ }
+ for (var key in keys) {
+ var meta = template.replace("%s", key);
+ if (collectCounts) {
+ meta = meta.replace("%s", keys[key]);
+ }
+ metas.push(meta);
+ }
+ var pre = document.createElement('pre');
+ pre.textContent = metas.join('\n') + '\n';
+ document.body.insertBefore(pre, document.body.firstChild);
+ document.getSelection().selectAllChildren(pre);
+ });
+ }
+ }
+ function shouldRunSubTest(key) {
+ if (key && subTestKeyPattern) {
+ var found = subTestKeyPattern.test(key);
+ if (exclude) {
+ return !found;
+ }
+ return found;
+ }
+ return true;
+ }
+ function subsetTestByKey(key, testFunc, ...args) {
+ if (collectKeys) {
+ if (collectCounts && key in keys) {
+ keys[key]++;
+ } else {
+ keys[key] = 1;
+ }
+ }
+ if (shouldRunSubTest(key)) {
+ return testFunc(...args);
+ }
+ return null;
+ }
+ self.shouldRunSubTest = shouldRunSubTest;
+ self.subsetTestByKey = subsetTestByKey;
+})();
diff --git a/test/fixtures/web-platform-tests/common/subset-tests.js b/test/fixtures/web-platform-tests/common/subset-tests.js
new file mode 100644
index 00000000000000..3713819c887726
--- /dev/null
+++ b/test/fixtures/web-platform-tests/common/subset-tests.js
@@ -0,0 +1,53 @@
+// Only test a subset of tests with, e.g., ?1-10 in the URL.
+// Can be used together with
+// Sample usage:
+// for (const test of tests) {
+// subsetTest(async_test, test.fn, test.name);
+// }
+(function() {
+ var subTestStart = 0;
+ var subTestEnd = Infinity;
+ var match;
+ if (location.search) {
+ match = /(?:^\?|&)(\d+)-(\d+|last)(?:&|$)/.exec(location.search);
+ if (match) {
+ subTestStart = parseInt(match[1], 10);
+ if (match[2] !== "last") {
+ subTestEnd = parseInt(match[2], 10);
+ }
+ }
+ // Below is utility code to generate for copy/paste into tests.
+ // Sample usage:
+ // test.html?split=1000
+ match = /(?:^\?|&)split=(\d+)(?:&|$)/.exec(location.search);
+ if (match) {
+ var testsPerVariant = parseInt(match[1], 10);
+ add_completion_callback(tests => {
+ var total = tests.length;
+ var template = ' ';
+ var metas = [];
+ for (var i = 1; i < total - testsPerVariant; i = i + testsPerVariant) {
+ metas.push(template.replace("%s", i).replace("%s", i + testsPerVariant - 1));
+ }
+ metas.push(template.replace("%s", i).replace("%s", "last"));
+ var pre = document.createElement('pre');
+ pre.textContent = metas.join('\n');
+ document.body.insertBefore(pre, document.body.firstChild);
+ document.getSelection().selectAllChildren(pre);
+ });
+ }
+ }
+ function shouldRunSubTest(currentSubTest) {
+ return currentSubTest >= subTestStart && currentSubTest <= subTestEnd;
+ }
+ var currentSubTest = 0;
+ function subsetTest(testFunc, ...args) {
+ currentSubTest++;
+ if (shouldRunSubTest(currentSubTest)) {
+ return testFunc(...args);
+ }
+ return null;
+ }
+ self.shouldRunSubTest = shouldRunSubTest;
+ self.subsetTest = subsetTest;
+})();
diff --git a/test/fixtures/web-platform-tests/common/test-setting-immutable-prototype.js b/test/fixtures/web-platform-tests/common/test-setting-immutable-prototype.js
new file mode 100644
index 00000000000000..92e61c07424166
--- /dev/null
+++ b/test/fixtures/web-platform-tests/common/test-setting-immutable-prototype.js
@@ -0,0 +1,57 @@
+self.testSettingImmutablePrototypeToNewValueOnly =
+ (prefix, target, newValue, newValueString, { isSameOriginDomain }) => {
+ test(() => {
+ assert_throws(new TypeError, () => {
+ Object.setPrototypeOf(target, newValue);
+ });
+ }, `${prefix}: setting the prototype to ${newValueString} via Object.setPrototypeOf should throw a TypeError`);
+
+ let dunderProtoError = "SecurityError";
+ let dunderProtoErrorName = "\"SecurityError\" DOMException";
+ if (isSameOriginDomain) {
+ dunderProtoError = new TypeError();
+ dunderProtoErrorName = "TypeError";
+ }
+
+ test(() => {
+ assert_throws(dunderProtoError, function() {
+ target.__proto__ = newValue;
+ });
+ }, `${prefix}: setting the prototype to ${newValueString} via __proto__ should throw a ${dunderProtoErrorName}`);
+
+ test(() => {
+ assert_false(Reflect.setPrototypeOf(target, newValue));
+ }, `${prefix}: setting the prototype to ${newValueString} via Reflect.setPrototypeOf should return false`);
+};
+
+self.testSettingImmutablePrototype =
+ (prefix, target, originalValue, { isSameOriginDomain }, newValue = {}, newValueString = "an empty object") => {
+ testSettingImmutablePrototypeToNewValueOnly(prefix, target, newValue, newValueString, { isSameOriginDomain });
+
+ const originalValueString = originalValue === null ? "null" : "its original value";
+
+ test(() => {
+ assert_equals(Object.getPrototypeOf(target), originalValue);
+ }, `${prefix}: the prototype must still be ${originalValueString}`);
+
+ test(() => {
+ Object.setPrototypeOf(target, originalValue);
+ }, `${prefix}: setting the prototype to ${originalValueString} via Object.setPrototypeOf should not throw`);
+
+ if (isSameOriginDomain) {
+ test(() => {
+ target.__proto__ = originalValue;
+ }, `${prefix}: setting the prototype to ${originalValueString} via __proto__ should not throw`);
+ } else {
+ test(() => {
+ assert_throws("SecurityError", function() {
+ target.__proto__ = newValue;
+ });
+ }, `${prefix}: setting the prototype to ${originalValueString} via __proto__ should throw a "SecurityError" since ` +
+ `it ends up in CrossOriginGetOwnProperty`);
+ }
+
+ test(() => {
+ assert_true(Reflect.setPrototypeOf(target, originalValue));
+ }, `${prefix}: setting the prototype to ${originalValueString} via Reflect.setPrototypeOf should return true`);
+};
diff --git a/test/fixtures/web-platform-tests/common/test-setting-immutable-prototype.js.headers b/test/fixtures/web-platform-tests/common/test-setting-immutable-prototype.js.headers
new file mode 100644
index 00000000000000..6805c323df5a97
--- /dev/null
+++ b/test/fixtures/web-platform-tests/common/test-setting-immutable-prototype.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/web-platform-tests/common/text-plain.txt b/test/fixtures/web-platform-tests/common/text-plain.txt
new file mode 100644
index 00000000000000..97ca870b6d5ad6
--- /dev/null
+++ b/test/fixtures/web-platform-tests/common/text-plain.txt
@@ -0,0 +1,4 @@
+This is a sample text/plain document.
+
+This is not an HTML document.
+
diff --git a/test/fixtures/web-platform-tests/common/utils.js b/test/fixtures/web-platform-tests/common/utils.js
new file mode 100644
index 00000000000000..bcdc256d917406
--- /dev/null
+++ b/test/fixtures/web-platform-tests/common/utils.js
@@ -0,0 +1,80 @@
+function make_absolute_url(options) {
+ var loc = window.location;
+ var protocol = get(options, "protocol", loc.protocol);
+ if (protocol[protocol.length - 1] != ":") {
+ protocol += ":";
+ }
+
+ var hostname = get(options, "hostname", loc.hostname);
+
+ var subdomain = get(options, "subdomain");
+ if (subdomain) {
+ hostname = subdomain + "." + hostname;
+ }
+
+ var port = get(options, "port", loc.port)
+ var path = get(options, "path", loc.pathname);
+ var query = get(options, "query", loc.search);
+ var hash = get(options, "hash", loc.hash)
+
+ var url = protocol + "//" + hostname;
+ if (port) {
+ url += ":" + port;
+ }
+
+ if (path[0] != "/") {
+ url += "/";
+ }
+ url += path;
+ if (query) {
+ if (query[0] != "?") {
+ url += "?";
+ }
+ url += query;
+ }
+ if (hash) {
+ if (hash[0] != "#") {
+ url += "#";
+ }
+ url += hash;
+ }
+ return url;
+}
+
+function get(obj, name, default_val) {
+ if (obj.hasOwnProperty(name)) {
+ return obj[name];
+ }
+ return default_val;
+}
+
+function token() {
+ var uuid = [to_hex(rand_int(32), 8),
+ to_hex(rand_int(16), 4),
+ to_hex(0x4000 | rand_int(12), 4),
+ to_hex(0x8000 | rand_int(14), 4),
+ to_hex(rand_int(48), 12)].join("-")
+ return uuid;
+}
+
+function rand_int(bits) {
+ if (bits < 1 || bits > 53) {
+ throw new TypeError();
+ } else {
+ if (bits >= 1 && bits <= 30) {
+ return 0 | ((1 << bits) * Math.random());
+ } else {
+ var high = (0 | ((1 << (bits - 30)) * Math.random())) * (1 << 30);
+ var low = 0 | ((1 << 30) * Math.random());
+ return high + low;
+ }
+ }
+}
+
+function to_hex(x, length) {
+ var rv = x.toString(16);
+ while (rv.length < length) {
+ rv = "0" + rv;
+ }
+ return rv;
+}
diff --git a/test/fixtures/web-platform-tests/common/utils.js.headers b/test/fixtures/web-platform-tests/common/utils.js.headers
new file mode 100644
index 00000000000000..6805c323df5a97
--- /dev/null
+++ b/test/fixtures/web-platform-tests/common/utils.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/web-platform-tests/common/worklet-reftest.js b/test/fixtures/web-platform-tests/common/worklet-reftest.js
new file mode 100644
index 00000000000000..abdda5b05e7f59
--- /dev/null
+++ b/test/fixtures/web-platform-tests/common/worklet-reftest.js
@@ -0,0 +1,33 @@
+// Imports code into a worklet. E.g.
+//
+// importWorklet(CSS.paintWorklet, {url: 'script.js'});
+// importWorklet(CSS.paintWorklet, '/* javascript string */');
+function importWorklet(worklet, code) {
+ let url;
+ if (typeof code === 'object') {
+ url = code.url;
+ } else {
+ const blob = new Blob([code], {type: 'text/javascript'});
+ url = URL.createObjectURL(blob);
+ }
+
+ return worklet.addModule(url);
+}
+
+// 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.
+async function importWorkletAndTerminateTestAfterAsyncPaint(worklet, code) {
+ if (typeof worklet === 'undefined') {
+ takeScreenshot();
+ return;
+ }
+
+ await importWorklet(worklet, code);
+
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ takeScreenshot();
+ });
+ });
+}
diff --git a/test/fixtures/web-platform-tests/resources/.gitignore b/test/fixtures/web-platform-tests/resources/.gitignore
new file mode 100644
index 00000000000000..04fdeda1cc4ea1
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/.gitignore
@@ -0,0 +1,3 @@
+ROBIN-TODO.txt
+scratch
+node_modules
diff --git a/test/fixtures/web-platform-tests/resources/.htaccess b/test/fixtures/web-platform-tests/resources/.htaccess
new file mode 100644
index 00000000000000..fd46101ca0099e
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/.htaccess
@@ -0,0 +1,2 @@
+# make tests that use utf-16 not inherit the encoding for testharness.js et. al.
+AddCharset utf-8 .css .js
diff --git a/test/fixtures/web-platform-tests/resources/LICENSE b/test/fixtures/web-platform-tests/resources/LICENSE
new file mode 100644
index 00000000000000..45896e6be2bd51
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/LICENSE
@@ -0,0 +1,30 @@
+W3C 3-clause BSD License
+
+http://www.w3.org/Consortium/Legal/2008/03-bsd-license.html
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of works must retain the original copyright notice,
+ this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the original copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name of the W3C nor the names of its contributors may be
+ used to endorse or promote products derived from this work without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/test/fixtures/web-platform-tests/resources/META.yml b/test/fixtures/web-platform-tests/resources/META.yml
new file mode 100644
index 00000000000000..8f988f99a82e09
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/META.yml
@@ -0,0 +1,3 @@
+suggested_reviewers:
+ - jgraham
+ - gsnedders
diff --git a/test/fixtures/web-platform-tests/resources/check-layout-th.js b/test/fixtures/web-platform-tests/resources/check-layout-th.js
new file mode 100644
index 00000000000000..928b0cf2a1041e
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/check-layout-th.js
@@ -0,0 +1,196 @@
+(function() {
+// Test is initiated from body.onload, so explicit done() call is required.
+setup({ explicit_done: true });
+
+function checkSubtreeExpectedValues(t, parent, prefix)
+{
+ var checkedLayout = checkExpectedValues(t, parent, prefix);
+ Array.prototype.forEach.call(parent.childNodes, function(node) {
+ checkedLayout |= checkSubtreeExpectedValues(t, node, prefix);
+ });
+ return checkedLayout;
+}
+
+function checkAttribute(output, node, attribute)
+{
+ var result = node.getAttribute && node.getAttribute(attribute);
+ output.checked |= !!result;
+ return result;
+}
+
+function assert_tolerance(actual, expected, message)
+{
+ if (isNaN(expected) || Math.abs(actual - expected) >= 1) {
+ assert_equals(actual, Number(expected), message);
+ }
+}
+
+function checkExpectedValues(t, node, prefix)
+{
+ var output = { checked: false };
+
+ var expectedWidth = checkAttribute(output, node, "data-expected-width");
+ if (expectedWidth) {
+ assert_tolerance(node.offsetWidth, expectedWidth, prefix + "width");
+ }
+
+ var expectedHeight = checkAttribute(output, node, "data-expected-height");
+ if (expectedHeight) {
+ assert_tolerance(node.offsetHeight, expectedHeight, prefix + "height");
+ }
+
+ var expectedOffset = checkAttribute(output, node, "data-offset-x");
+ if (expectedOffset) {
+ assert_tolerance(node.offsetLeft, expectedOffset, prefix + "offsetLeft");
+ }
+
+ var expectedOffset = checkAttribute(output, node, "data-offset-y");
+ if (expectedOffset) {
+ assert_tolerance(node.offsetTop, expectedOffset, prefix + "offsetTop");
+ }
+
+ var expectedWidth = checkAttribute(output, node, "data-expected-client-width");
+ if (expectedWidth) {
+ assert_tolerance(node.clientWidth, expectedWidth, prefix + "clientWidth");
+ }
+
+ var expectedHeight = checkAttribute(output, node, "data-expected-client-height");
+ if (expectedHeight) {
+ assert_tolerance(node.clientHeight, expectedHeight, prefix + "clientHeight");
+ }
+
+ var expectedWidth = checkAttribute(output, node, "data-expected-scroll-width");
+ if (expectedWidth) {
+ assert_tolerance(node.scrollWidth, expectedWidth, prefix + "scrollWidth");
+ }
+
+ var expectedHeight = checkAttribute(output, node, "data-expected-scroll-height");
+ if (expectedHeight) {
+ assert_tolerance(node.scrollHeight, expectedHeight, prefix + "scrollHeight");
+ }
+
+ var expectedWidth = checkAttribute(output, node, "data-expected-bounding-client-rect-width");
+ if (expectedWidth) {
+ assert_tolerance(node.getBoundingClientRect().width, expectedWidth, prefix + "getBoundingClientRect().width");
+ }
+
+ var expectedOffset = checkAttribute(output, node, "data-total-x");
+ if (expectedOffset) {
+ var totalLeft = node.clientLeft + node.offsetLeft;
+ assert_tolerance(totalLeft, expectedOffset, prefix +
+ "clientLeft+offsetLeft (" + node.clientLeft + " + " + node.offsetLeft + ")");
+ }
+
+ var expectedOffset = checkAttribute(output, node, "data-total-y");
+ if (expectedOffset) {
+ var totalTop = node.clientTop + node.offsetTop;
+ assert_tolerance(totalTop, expectedOffset, prefix +
+ "clientTop+offsetTop (" + node.clientTop + " + " + node.offsetTop + ")");
+ }
+
+ var expectedDisplay = checkAttribute(output, node, "data-expected-display");
+ if (expectedDisplay) {
+ var actualDisplay = getComputedStyle(node).display;
+ assert_equals(actualDisplay, expectedDisplay, prefix + "display");
+ }
+
+ var expectedPaddingTop = checkAttribute(output, node, "data-expected-padding-top");
+ if (expectedPaddingTop) {
+ var actualPaddingTop = getComputedStyle(node).paddingTop;
+ // Trim the unit "px" from the output.
+ actualPaddingTop = actualPaddingTop.slice(0, -2);
+ assert_equals(actualPaddingTop, expectedPaddingTop, prefix + "padding-top");
+ }
+
+ var expectedPaddingBottom = checkAttribute(output, node, "data-expected-padding-bottom");
+ if (expectedPaddingBottom) {
+ var actualPaddingBottom = getComputedStyle(node).paddingBottom;
+ // Trim the unit "px" from the output.
+ actualPaddingBottom = actualPaddingBottom.slice(0, -2);
+ assert_equals(actualPaddingBottom, expectedPaddingBottom, prefix + "padding-bottom");
+ }
+
+ var expectedPaddingLeft = checkAttribute(output, node, "data-expected-padding-left");
+ if (expectedPaddingLeft) {
+ var actualPaddingLeft = getComputedStyle(node).paddingLeft;
+ // Trim the unit "px" from the output.
+ actualPaddingLeft = actualPaddingLeft.slice(0, -2);
+ assert_equals(actualPaddingLeft, expectedPaddingLeft, prefix + "padding-left");
+ }
+
+ var expectedPaddingRight = checkAttribute(output, node, "data-expected-padding-right");
+ if (expectedPaddingRight) {
+ var actualPaddingRight = getComputedStyle(node).paddingRight;
+ // Trim the unit "px" from the output.
+ actualPaddingRight = actualPaddingRight.slice(0, -2);
+ assert_equals(actualPaddingRight, expectedPaddingRight, prefix + "padding-right");
+ }
+
+ var expectedMarginTop = checkAttribute(output, node, "data-expected-margin-top");
+ if (expectedMarginTop) {
+ var actualMarginTop = getComputedStyle(node).marginTop;
+ // Trim the unit "px" from the output.
+ actualMarginTop = actualMarginTop.slice(0, -2);
+ assert_equals(actualMarginTop, expectedMarginTop, prefix + "margin-top");
+ }
+
+ var expectedMarginBottom = checkAttribute(output, node, "data-expected-margin-bottom");
+ if (expectedMarginBottom) {
+ var actualMarginBottom = getComputedStyle(node).marginBottom;
+ // Trim the unit "px" from the output.
+ actualMarginBottom = actualMarginBottom.slice(0, -2);
+ assert_equals(actualMarginBottom, expectedMarginBottom, prefix + "margin-bottom");
+ }
+
+ var expectedMarginLeft = checkAttribute(output, node, "data-expected-margin-left");
+ if (expectedMarginLeft) {
+ var actualMarginLeft = getComputedStyle(node).marginLeft;
+ // Trim the unit "px" from the output.
+ actualMarginLeft = actualMarginLeft.slice(0, -2);
+ assert_equals(actualMarginLeft, expectedMarginLeft, prefix + "margin-left");
+ }
+
+ var expectedMarginRight = checkAttribute(output, node, "data-expected-margin-right");
+ if (expectedMarginRight) {
+ var actualMarginRight = getComputedStyle(node).marginRight;
+ // Trim the unit "px" from the output.
+ actualMarginRight = actualMarginRight.slice(0, -2);
+ assert_equals(actualMarginRight, expectedMarginRight, prefix + "margin-right");
+ }
+
+ return output.checked;
+}
+
+var testNumber = 0;
+
+window.checkLayout = function(selectorList, callDone = true)
+{
+ if (!selectorList) {
+ console.error("You must provide a CSS selector of nodes to check.");
+ return;
+ }
+ var nodes = document.querySelectorAll(selectorList);
+ nodes = Array.prototype.slice.call(nodes);
+ var checkedLayout = false;
+ Array.prototype.forEach.call(nodes, function(node) {
+ test(function(t) {
+ var container = node.parentNode.className == 'container' ? node.parentNode : node;
+ var prefix = "\n" + container.outerHTML + "\n";
+ var passed = false;
+ try {
+ checkedLayout |= checkExpectedValues(t, node.parentNode, prefix);
+ checkedLayout |= checkSubtreeExpectedValues(t, node, prefix);
+ passed = true;
+ } finally {
+ checkedLayout |= !passed;
+ }
+ }, selectorList + ' ' + String(++testNumber));
+ });
+ if (!checkedLayout) {
+ console.error("No valid data-* attributes found in selector list : " + selectorList);
+ }
+ if (callDone)
+ done();
+};
+
+})();
diff --git a/test/fixtures/web-platform-tests/resources/chromium/README.md b/test/fixtures/web-platform-tests/resources/chromium/README.md
new file mode 100644
index 00000000000000..b53c6a8c4232db
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/chromium/README.md
@@ -0,0 +1,4 @@
+This directory contains Chromium-specific test resources.
+
+The files `mojo_bindings.js` and `*.mojom.js` are manually copied from the
+Chromium build process's generated files and should not be edited manually.
diff --git a/test/fixtures/web-platform-tests/resources/chromium/device.mojom.js b/test/fixtures/web-platform-tests/resources/chromium/device.mojom.js
new file mode 100644
index 00000000000000..435fc1fc7addab
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/chromium/device.mojom.js
@@ -0,0 +1,3424 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+(function() {
+ var mojomId = 'device/usb/public/mojom/device.mojom';
+ if (mojo.internal.isMojomLoaded(mojomId)) {
+ console.warn('The following mojom is loaded multiple times: ' + mojomId);
+ return;
+ }
+ mojo.internal.markMojomLoaded(mojomId);
+ var bindings = mojo;
+ var associatedBindings = mojo;
+ var codec = mojo.internal;
+ var validator = mojo.internal;
+
+ var exports = mojo.internal.exposeNamespace('device.mojom');
+ var string16$ =
+ mojo.internal.exposeNamespace('mojo.common.mojom');
+ if (mojo.config.autoLoadMojomDeps) {
+ mojo.internal.loadMojomIfNecessary(
+ 'mojo/public/mojom/base/string16.mojom', '../../../../mojo/public/mojom/base/string16.mojom.js');
+ }
+
+
+ var UsbOpenDeviceError = {};
+ UsbOpenDeviceError.OK = 0;
+ UsbOpenDeviceError.ACCESS_DENIED = UsbOpenDeviceError.OK + 1;
+ UsbOpenDeviceError.ALREADY_OPEN = UsbOpenDeviceError.ACCESS_DENIED + 1;
+
+ UsbOpenDeviceError.isKnownEnumValue = function(value) {
+ switch (value) {
+ case 0:
+ case 1:
+ case 2:
+ return true;
+ }
+ return false;
+ };
+
+ UsbOpenDeviceError.validate = function(enumValue) {
+ var isExtensible = false;
+ if (isExtensible || this.isKnownEnumValue(enumValue))
+ return validator.validationError.NONE;
+
+ return validator.validationError.UNKNOWN_ENUM_VALUE;
+ };
+ var UsbTransferDirection = {};
+ UsbTransferDirection.INBOUND = 0;
+ UsbTransferDirection.OUTBOUND = UsbTransferDirection.INBOUND + 1;
+
+ UsbTransferDirection.isKnownEnumValue = function(value) {
+ switch (value) {
+ case 0:
+ case 1:
+ return true;
+ }
+ return false;
+ };
+
+ UsbTransferDirection.validate = function(enumValue) {
+ var isExtensible = false;
+ if (isExtensible || this.isKnownEnumValue(enumValue))
+ return validator.validationError.NONE;
+
+ return validator.validationError.UNKNOWN_ENUM_VALUE;
+ };
+ var UsbControlTransferType = {};
+ UsbControlTransferType.STANDARD = 0;
+ UsbControlTransferType.CLASS = UsbControlTransferType.STANDARD + 1;
+ UsbControlTransferType.VENDOR = UsbControlTransferType.CLASS + 1;
+ UsbControlTransferType.RESERVED = UsbControlTransferType.VENDOR + 1;
+
+ UsbControlTransferType.isKnownEnumValue = function(value) {
+ switch (value) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ return true;
+ }
+ return false;
+ };
+
+ UsbControlTransferType.validate = function(enumValue) {
+ var isExtensible = false;
+ if (isExtensible || this.isKnownEnumValue(enumValue))
+ return validator.validationError.NONE;
+
+ return validator.validationError.UNKNOWN_ENUM_VALUE;
+ };
+ var UsbControlTransferRecipient = {};
+ UsbControlTransferRecipient.DEVICE = 0;
+ UsbControlTransferRecipient.INTERFACE = UsbControlTransferRecipient.DEVICE + 1;
+ UsbControlTransferRecipient.ENDPOINT = UsbControlTransferRecipient.INTERFACE + 1;
+ UsbControlTransferRecipient.OTHER = UsbControlTransferRecipient.ENDPOINT + 1;
+
+ UsbControlTransferRecipient.isKnownEnumValue = function(value) {
+ switch (value) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ return true;
+ }
+ return false;
+ };
+
+ UsbControlTransferRecipient.validate = function(enumValue) {
+ var isExtensible = false;
+ if (isExtensible || this.isKnownEnumValue(enumValue))
+ return validator.validationError.NONE;
+
+ return validator.validationError.UNKNOWN_ENUM_VALUE;
+ };
+ var UsbTransferType = {};
+ UsbTransferType.CONTROL = 0;
+ UsbTransferType.ISOCHRONOUS = UsbTransferType.CONTROL + 1;
+ UsbTransferType.BULK = UsbTransferType.ISOCHRONOUS + 1;
+ UsbTransferType.INTERRUPT = UsbTransferType.BULK + 1;
+
+ UsbTransferType.isKnownEnumValue = function(value) {
+ switch (value) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ return true;
+ }
+ return false;
+ };
+
+ UsbTransferType.validate = function(enumValue) {
+ var isExtensible = false;
+ if (isExtensible || this.isKnownEnumValue(enumValue))
+ return validator.validationError.NONE;
+
+ return validator.validationError.UNKNOWN_ENUM_VALUE;
+ };
+ var UsbTransferStatus = {};
+ UsbTransferStatus.COMPLETED = 0;
+ UsbTransferStatus.TRANSFER_ERROR = UsbTransferStatus.COMPLETED + 1;
+ UsbTransferStatus.TIMEOUT = UsbTransferStatus.TRANSFER_ERROR + 1;
+ UsbTransferStatus.CANCELLED = UsbTransferStatus.TIMEOUT + 1;
+ UsbTransferStatus.STALLED = UsbTransferStatus.CANCELLED + 1;
+ UsbTransferStatus.DISCONNECT = UsbTransferStatus.STALLED + 1;
+ UsbTransferStatus.BABBLE = UsbTransferStatus.DISCONNECT + 1;
+ UsbTransferStatus.SHORT_PACKET = UsbTransferStatus.BABBLE + 1;
+ UsbTransferStatus.PERMISSION_DENIED = UsbTransferStatus.SHORT_PACKET + 1;
+
+ UsbTransferStatus.isKnownEnumValue = function(value) {
+ switch (value) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ return true;
+ }
+ return false;
+ };
+
+ UsbTransferStatus.validate = function(enumValue) {
+ var isExtensible = false;
+ if (isExtensible || this.isKnownEnumValue(enumValue))
+ return validator.validationError.NONE;
+
+ return validator.validationError.UNKNOWN_ENUM_VALUE;
+ };
+
+ function UsbEndpointInfo(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbEndpointInfo.prototype.initDefaults_ = function() {
+ this.endpointNumber = 0;
+ this.direction = 0;
+ this.type = 0;
+ this.packetSize = 0;
+ };
+ UsbEndpointInfo.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbEndpointInfo.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate UsbEndpointInfo.direction
+ err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 4, UsbTransferDirection);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbEndpointInfo.type
+ err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 8, UsbTransferType);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbEndpointInfo.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbEndpointInfo.decode = function(decoder) {
+ var packed;
+ var val = new UsbEndpointInfo();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.endpointNumber = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.direction = decoder.decodeStruct(codec.Int32);
+ val.type = decoder.decodeStruct(codec.Int32);
+ val.packetSize = decoder.decodeStruct(codec.Uint32);
+ return val;
+ };
+
+ UsbEndpointInfo.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbEndpointInfo.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.endpointNumber);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStruct(codec.Int32, val.direction);
+ encoder.encodeStruct(codec.Int32, val.type);
+ encoder.encodeStruct(codec.Uint32, val.packetSize);
+ };
+ function UsbAlternateInterfaceInfo(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbAlternateInterfaceInfo.prototype.initDefaults_ = function() {
+ this.alternateSetting = 0;
+ this.classCode = 0;
+ this.subclassCode = 0;
+ this.protocolCode = 0;
+ this.interfaceName = null;
+ this.endpoints = null;
+ };
+ UsbAlternateInterfaceInfo.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbAlternateInterfaceInfo.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 32}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+
+
+
+ // validate UsbAlternateInterfaceInfo.interfaceName
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 8, string16$.String16, true);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbAlternateInterfaceInfo.endpoints
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 16, 8, new codec.PointerTo(UsbEndpointInfo), false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbAlternateInterfaceInfo.encodedSize = codec.kStructHeaderSize + 24;
+
+ UsbAlternateInterfaceInfo.decode = function(decoder) {
+ var packed;
+ var val = new UsbAlternateInterfaceInfo();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.alternateSetting = decoder.decodeStruct(codec.Uint8);
+ val.classCode = decoder.decodeStruct(codec.Uint8);
+ val.subclassCode = decoder.decodeStruct(codec.Uint8);
+ val.protocolCode = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.interfaceName = decoder.decodeStructPointer(string16$.String16);
+ val.endpoints = decoder.decodeArrayPointer(new codec.PointerTo(UsbEndpointInfo));
+ return val;
+ };
+
+ UsbAlternateInterfaceInfo.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbAlternateInterfaceInfo.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.alternateSetting);
+ encoder.encodeStruct(codec.Uint8, val.classCode);
+ encoder.encodeStruct(codec.Uint8, val.subclassCode);
+ encoder.encodeStruct(codec.Uint8, val.protocolCode);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStructPointer(string16$.String16, val.interfaceName);
+ encoder.encodeArrayPointer(new codec.PointerTo(UsbEndpointInfo), val.endpoints);
+ };
+ function UsbInterfaceInfo(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbInterfaceInfo.prototype.initDefaults_ = function() {
+ this.interfaceNumber = 0;
+ this.alternates = null;
+ };
+ UsbInterfaceInfo.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbInterfaceInfo.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate UsbInterfaceInfo.alternates
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 8, new codec.PointerTo(UsbAlternateInterfaceInfo), false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbInterfaceInfo.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbInterfaceInfo.decode = function(decoder) {
+ var packed;
+ var val = new UsbInterfaceInfo();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.interfaceNumber = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.alternates = decoder.decodeArrayPointer(new codec.PointerTo(UsbAlternateInterfaceInfo));
+ return val;
+ };
+
+ UsbInterfaceInfo.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbInterfaceInfo.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.interfaceNumber);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeArrayPointer(new codec.PointerTo(UsbAlternateInterfaceInfo), val.alternates);
+ };
+ function UsbConfigurationInfo(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbConfigurationInfo.prototype.initDefaults_ = function() {
+ this.configurationValue = 0;
+ this.configurationName = null;
+ this.interfaces = null;
+ };
+ UsbConfigurationInfo.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbConfigurationInfo.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 32}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate UsbConfigurationInfo.configurationName
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 8, string16$.String16, true);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbConfigurationInfo.interfaces
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 16, 8, new codec.PointerTo(UsbInterfaceInfo), false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbConfigurationInfo.encodedSize = codec.kStructHeaderSize + 24;
+
+ UsbConfigurationInfo.decode = function(decoder) {
+ var packed;
+ var val = new UsbConfigurationInfo();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.configurationValue = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.configurationName = decoder.decodeStructPointer(string16$.String16);
+ val.interfaces = decoder.decodeArrayPointer(new codec.PointerTo(UsbInterfaceInfo));
+ return val;
+ };
+
+ UsbConfigurationInfo.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbConfigurationInfo.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.configurationValue);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStructPointer(string16$.String16, val.configurationName);
+ encoder.encodeArrayPointer(new codec.PointerTo(UsbInterfaceInfo), val.interfaces);
+ };
+ function UsbDeviceInfo(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDeviceInfo.prototype.initDefaults_ = function() {
+ this.guid = null;
+ this.usbVersionMajor = 0;
+ this.usbVersionMinor = 0;
+ this.usbVersionSubminor = 0;
+ this.classCode = 0;
+ this.subclassCode = 0;
+ this.protocolCode = 0;
+ this.vendorId = 0;
+ this.productId = 0;
+ this.deviceVersionMajor = 0;
+ this.deviceVersionMinor = 0;
+ this.deviceVersionSubminor = 0;
+ this.activeConfiguration = 0;
+ this.manufacturerName = null;
+ this.productName = null;
+ this.serialNumber = null;
+ this.configurations = null;
+ };
+ UsbDeviceInfo.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDeviceInfo.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 64}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDeviceInfo.guid
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+
+
+
+
+
+
+
+
+
+
+ // validate UsbDeviceInfo.manufacturerName
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 24, string16$.String16, true);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDeviceInfo.productName
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 32, string16$.String16, true);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDeviceInfo.serialNumber
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 40, string16$.String16, true);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate UsbDeviceInfo.configurations
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 48, 8, new codec.PointerTo(UsbConfigurationInfo), false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDeviceInfo.encodedSize = codec.kStructHeaderSize + 56;
+
+ UsbDeviceInfo.decode = function(decoder) {
+ var packed;
+ var val = new UsbDeviceInfo();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.guid = decoder.decodeStruct(codec.String);
+ val.usbVersionMajor = decoder.decodeStruct(codec.Uint8);
+ val.usbVersionMinor = decoder.decodeStruct(codec.Uint8);
+ val.usbVersionSubminor = decoder.decodeStruct(codec.Uint8);
+ val.classCode = decoder.decodeStruct(codec.Uint8);
+ val.subclassCode = decoder.decodeStruct(codec.Uint8);
+ val.protocolCode = decoder.decodeStruct(codec.Uint8);
+ val.vendorId = decoder.decodeStruct(codec.Uint16);
+ val.productId = decoder.decodeStruct(codec.Uint16);
+ val.deviceVersionMajor = decoder.decodeStruct(codec.Uint8);
+ val.deviceVersionMinor = decoder.decodeStruct(codec.Uint8);
+ val.deviceVersionSubminor = decoder.decodeStruct(codec.Uint8);
+ val.activeConfiguration = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.manufacturerName = decoder.decodeStructPointer(string16$.String16);
+ val.productName = decoder.decodeStructPointer(string16$.String16);
+ val.serialNumber = decoder.decodeStructPointer(string16$.String16);
+ val.configurations = decoder.decodeArrayPointer(new codec.PointerTo(UsbConfigurationInfo));
+ return val;
+ };
+
+ UsbDeviceInfo.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDeviceInfo.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.guid);
+ encoder.encodeStruct(codec.Uint8, val.usbVersionMajor);
+ encoder.encodeStruct(codec.Uint8, val.usbVersionMinor);
+ encoder.encodeStruct(codec.Uint8, val.usbVersionSubminor);
+ encoder.encodeStruct(codec.Uint8, val.classCode);
+ encoder.encodeStruct(codec.Uint8, val.subclassCode);
+ encoder.encodeStruct(codec.Uint8, val.protocolCode);
+ encoder.encodeStruct(codec.Uint16, val.vendorId);
+ encoder.encodeStruct(codec.Uint16, val.productId);
+ encoder.encodeStruct(codec.Uint8, val.deviceVersionMajor);
+ encoder.encodeStruct(codec.Uint8, val.deviceVersionMinor);
+ encoder.encodeStruct(codec.Uint8, val.deviceVersionSubminor);
+ encoder.encodeStruct(codec.Uint8, val.activeConfiguration);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStructPointer(string16$.String16, val.manufacturerName);
+ encoder.encodeStructPointer(string16$.String16, val.productName);
+ encoder.encodeStructPointer(string16$.String16, val.serialNumber);
+ encoder.encodeArrayPointer(new codec.PointerTo(UsbConfigurationInfo), val.configurations);
+ };
+ function UsbControlTransferParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbControlTransferParams.prototype.initDefaults_ = function() {
+ this.type = 0;
+ this.recipient = 0;
+ this.request = 0;
+ this.value = 0;
+ this.index = 0;
+ };
+ UsbControlTransferParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbControlTransferParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbControlTransferParams.type
+ err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, UsbControlTransferType);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbControlTransferParams.recipient
+ err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 4, UsbControlTransferRecipient);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbControlTransferParams.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbControlTransferParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbControlTransferParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.type = decoder.decodeStruct(codec.Int32);
+ val.recipient = decoder.decodeStruct(codec.Int32);
+ val.request = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ val.value = decoder.decodeStruct(codec.Uint16);
+ val.index = decoder.decodeStruct(codec.Uint16);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbControlTransferParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbControlTransferParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Int32, val.type);
+ encoder.encodeStruct(codec.Int32, val.recipient);
+ encoder.encodeStruct(codec.Uint8, val.request);
+ encoder.skip(1);
+ encoder.encodeStruct(codec.Uint16, val.value);
+ encoder.encodeStruct(codec.Uint16, val.index);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbIsochronousPacket(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbIsochronousPacket.prototype.initDefaults_ = function() {
+ this.length = 0;
+ this.transferredLength = 0;
+ this.status = 0;
+ };
+ UsbIsochronousPacket.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbIsochronousPacket.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+
+ // validate UsbIsochronousPacket.status
+ err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 8, UsbTransferStatus);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbIsochronousPacket.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbIsochronousPacket.decode = function(decoder) {
+ var packed;
+ var val = new UsbIsochronousPacket();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.length = decoder.decodeStruct(codec.Uint32);
+ val.transferredLength = decoder.decodeStruct(codec.Uint32);
+ val.status = decoder.decodeStruct(codec.Int32);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbIsochronousPacket.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbIsochronousPacket.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint32, val.length);
+ encoder.encodeStruct(codec.Uint32, val.transferredLength);
+ encoder.encodeStruct(codec.Int32, val.status);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_Open_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_Open_Params.prototype.initDefaults_ = function() {
+ };
+ UsbDevice_Open_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_Open_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_Open_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+ UsbDevice_Open_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_Open_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ UsbDevice_Open_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_Open_Params.encodedSize);
+ encoder.writeUint32(0);
+ };
+ function UsbDevice_Open_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_Open_ResponseParams.prototype.initDefaults_ = function() {
+ this.error = 0;
+ };
+ UsbDevice_Open_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_Open_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_Open_ResponseParams.error
+ err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, UsbOpenDeviceError);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_Open_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_Open_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_Open_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.error = decoder.decodeStruct(codec.Int32);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_Open_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_Open_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Int32, val.error);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_Close_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_Close_Params.prototype.initDefaults_ = function() {
+ };
+ UsbDevice_Close_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_Close_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_Close_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+ UsbDevice_Close_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_Close_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ UsbDevice_Close_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_Close_Params.encodedSize);
+ encoder.writeUint32(0);
+ };
+ function UsbDevice_Close_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_Close_ResponseParams.prototype.initDefaults_ = function() {
+ };
+ UsbDevice_Close_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_Close_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_Close_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+ UsbDevice_Close_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_Close_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ UsbDevice_Close_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_Close_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ };
+ function UsbDevice_SetConfiguration_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_SetConfiguration_Params.prototype.initDefaults_ = function() {
+ this.value = 0;
+ };
+ UsbDevice_SetConfiguration_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_SetConfiguration_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_SetConfiguration_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_SetConfiguration_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_SetConfiguration_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.value = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_SetConfiguration_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_SetConfiguration_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.value);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_SetConfiguration_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_SetConfiguration_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ UsbDevice_SetConfiguration_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_SetConfiguration_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_SetConfiguration_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_SetConfiguration_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_SetConfiguration_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_SetConfiguration_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_SetConfiguration_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_ClaimInterface_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_ClaimInterface_Params.prototype.initDefaults_ = function() {
+ this.interfaceNumber = 0;
+ };
+ UsbDevice_ClaimInterface_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_ClaimInterface_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_ClaimInterface_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_ClaimInterface_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_ClaimInterface_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.interfaceNumber = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_ClaimInterface_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_ClaimInterface_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.interfaceNumber);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_ClaimInterface_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_ClaimInterface_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ UsbDevice_ClaimInterface_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_ClaimInterface_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_ClaimInterface_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_ClaimInterface_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_ClaimInterface_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_ClaimInterface_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_ClaimInterface_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_ReleaseInterface_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_ReleaseInterface_Params.prototype.initDefaults_ = function() {
+ this.interfaceNumber = 0;
+ };
+ UsbDevice_ReleaseInterface_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_ReleaseInterface_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_ReleaseInterface_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_ReleaseInterface_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_ReleaseInterface_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.interfaceNumber = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_ReleaseInterface_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_ReleaseInterface_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.interfaceNumber);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_ReleaseInterface_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_ReleaseInterface_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ UsbDevice_ReleaseInterface_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_ReleaseInterface_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_ReleaseInterface_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_ReleaseInterface_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_ReleaseInterface_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_ReleaseInterface_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_ReleaseInterface_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_SetInterfaceAlternateSetting_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_SetInterfaceAlternateSetting_Params.prototype.initDefaults_ = function() {
+ this.interfaceNumber = 0;
+ this.alternateSetting = 0;
+ };
+ UsbDevice_SetInterfaceAlternateSetting_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_SetInterfaceAlternateSetting_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_SetInterfaceAlternateSetting_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_SetInterfaceAlternateSetting_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_SetInterfaceAlternateSetting_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.interfaceNumber = decoder.decodeStruct(codec.Uint8);
+ val.alternateSetting = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_SetInterfaceAlternateSetting_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_SetInterfaceAlternateSetting_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.interfaceNumber);
+ encoder.encodeStruct(codec.Uint8, val.alternateSetting);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_SetInterfaceAlternateSetting_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_SetInterfaceAlternateSetting_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ UsbDevice_SetInterfaceAlternateSetting_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_SetInterfaceAlternateSetting_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_SetInterfaceAlternateSetting_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_SetInterfaceAlternateSetting_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_SetInterfaceAlternateSetting_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_SetInterfaceAlternateSetting_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_SetInterfaceAlternateSetting_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_Reset_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_Reset_Params.prototype.initDefaults_ = function() {
+ };
+ UsbDevice_Reset_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_Reset_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_Reset_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+ UsbDevice_Reset_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_Reset_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ UsbDevice_Reset_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_Reset_Params.encodedSize);
+ encoder.writeUint32(0);
+ };
+ function UsbDevice_Reset_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_Reset_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ UsbDevice_Reset_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_Reset_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_Reset_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_Reset_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_Reset_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_Reset_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_Reset_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_ClearHalt_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_ClearHalt_Params.prototype.initDefaults_ = function() {
+ this.endpoint = 0;
+ };
+ UsbDevice_ClearHalt_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_ClearHalt_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_ClearHalt_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_ClearHalt_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_ClearHalt_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.endpoint = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_ClearHalt_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_ClearHalt_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.endpoint);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_ClearHalt_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_ClearHalt_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ UsbDevice_ClearHalt_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_ClearHalt_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_ClearHalt_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_ClearHalt_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_ClearHalt_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_ClearHalt_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_ClearHalt_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_ControlTransferIn_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_ControlTransferIn_Params.prototype.initDefaults_ = function() {
+ this.params = null;
+ this.length = 0;
+ this.timeout = 0;
+ };
+ UsbDevice_ControlTransferIn_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_ControlTransferIn_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_ControlTransferIn_Params.params
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, UsbControlTransferParams, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_ControlTransferIn_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbDevice_ControlTransferIn_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_ControlTransferIn_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.params = decoder.decodeStructPointer(UsbControlTransferParams);
+ val.length = decoder.decodeStruct(codec.Uint32);
+ val.timeout = decoder.decodeStruct(codec.Uint32);
+ return val;
+ };
+
+ UsbDevice_ControlTransferIn_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_ControlTransferIn_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStructPointer(UsbControlTransferParams, val.params);
+ encoder.encodeStruct(codec.Uint32, val.length);
+ encoder.encodeStruct(codec.Uint32, val.timeout);
+ };
+ function UsbDevice_ControlTransferIn_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_ControlTransferIn_ResponseParams.prototype.initDefaults_ = function() {
+ this.status = 0;
+ this.data = null;
+ };
+ UsbDevice_ControlTransferIn_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_ControlTransferIn_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_ControlTransferIn_ResponseParams.status
+ err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, UsbTransferStatus);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_ControlTransferIn_ResponseParams.data
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_ControlTransferIn_ResponseParams.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbDevice_ControlTransferIn_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_ControlTransferIn_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.status = decoder.decodeStruct(codec.Int32);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.data = decoder.decodeArrayPointer(codec.Uint8);
+ return val;
+ };
+
+ UsbDevice_ControlTransferIn_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_ControlTransferIn_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Int32, val.status);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeArrayPointer(codec.Uint8, val.data);
+ };
+ function UsbDevice_ControlTransferOut_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_ControlTransferOut_Params.prototype.initDefaults_ = function() {
+ this.params = null;
+ this.data = null;
+ this.timeout = 0;
+ };
+ UsbDevice_ControlTransferOut_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_ControlTransferOut_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 32}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_ControlTransferOut_Params.params
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, UsbControlTransferParams, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_ControlTransferOut_Params.data
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_ControlTransferOut_Params.encodedSize = codec.kStructHeaderSize + 24;
+
+ UsbDevice_ControlTransferOut_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_ControlTransferOut_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.params = decoder.decodeStructPointer(UsbControlTransferParams);
+ val.data = decoder.decodeArrayPointer(codec.Uint8);
+ val.timeout = decoder.decodeStruct(codec.Uint32);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_ControlTransferOut_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_ControlTransferOut_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStructPointer(UsbControlTransferParams, val.params);
+ encoder.encodeArrayPointer(codec.Uint8, val.data);
+ encoder.encodeStruct(codec.Uint32, val.timeout);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_ControlTransferOut_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_ControlTransferOut_ResponseParams.prototype.initDefaults_ = function() {
+ this.status = 0;
+ };
+ UsbDevice_ControlTransferOut_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_ControlTransferOut_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_ControlTransferOut_ResponseParams.status
+ err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, UsbTransferStatus);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_ControlTransferOut_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_ControlTransferOut_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_ControlTransferOut_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.status = decoder.decodeStruct(codec.Int32);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_ControlTransferOut_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_ControlTransferOut_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Int32, val.status);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_GenericTransferIn_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_GenericTransferIn_Params.prototype.initDefaults_ = function() {
+ this.endpointNumber = 0;
+ this.length = 0;
+ this.timeout = 0;
+ };
+ UsbDevice_GenericTransferIn_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_GenericTransferIn_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_GenericTransferIn_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbDevice_GenericTransferIn_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_GenericTransferIn_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.endpointNumber = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.length = decoder.decodeStruct(codec.Uint32);
+ val.timeout = decoder.decodeStruct(codec.Uint32);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_GenericTransferIn_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_GenericTransferIn_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.endpointNumber);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStruct(codec.Uint32, val.length);
+ encoder.encodeStruct(codec.Uint32, val.timeout);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_GenericTransferIn_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_GenericTransferIn_ResponseParams.prototype.initDefaults_ = function() {
+ this.status = 0;
+ this.data = null;
+ };
+ UsbDevice_GenericTransferIn_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_GenericTransferIn_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_GenericTransferIn_ResponseParams.status
+ err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, UsbTransferStatus);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_GenericTransferIn_ResponseParams.data
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_GenericTransferIn_ResponseParams.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbDevice_GenericTransferIn_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_GenericTransferIn_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.status = decoder.decodeStruct(codec.Int32);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.data = decoder.decodeArrayPointer(codec.Uint8);
+ return val;
+ };
+
+ UsbDevice_GenericTransferIn_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_GenericTransferIn_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Int32, val.status);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeArrayPointer(codec.Uint8, val.data);
+ };
+ function UsbDevice_GenericTransferOut_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_GenericTransferOut_Params.prototype.initDefaults_ = function() {
+ this.endpointNumber = 0;
+ this.timeout = 0;
+ this.data = null;
+ };
+ UsbDevice_GenericTransferOut_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_GenericTransferOut_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate UsbDevice_GenericTransferOut_Params.data
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_GenericTransferOut_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbDevice_GenericTransferOut_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_GenericTransferOut_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.endpointNumber = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.timeout = decoder.decodeStruct(codec.Uint32);
+ val.data = decoder.decodeArrayPointer(codec.Uint8);
+ return val;
+ };
+
+ UsbDevice_GenericTransferOut_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_GenericTransferOut_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.endpointNumber);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStruct(codec.Uint32, val.timeout);
+ encoder.encodeArrayPointer(codec.Uint8, val.data);
+ };
+ function UsbDevice_GenericTransferOut_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_GenericTransferOut_ResponseParams.prototype.initDefaults_ = function() {
+ this.status = 0;
+ };
+ UsbDevice_GenericTransferOut_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_GenericTransferOut_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_GenericTransferOut_ResponseParams.status
+ err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, UsbTransferStatus);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_GenericTransferOut_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_GenericTransferOut_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_GenericTransferOut_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.status = decoder.decodeStruct(codec.Int32);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDevice_GenericTransferOut_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_GenericTransferOut_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Int32, val.status);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDevice_IsochronousTransferIn_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_IsochronousTransferIn_Params.prototype.initDefaults_ = function() {
+ this.endpointNumber = 0;
+ this.timeout = 0;
+ this.packetLengths = null;
+ };
+ UsbDevice_IsochronousTransferIn_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_IsochronousTransferIn_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate UsbDevice_IsochronousTransferIn_Params.packetLengths
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 4, codec.Uint32, false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_IsochronousTransferIn_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbDevice_IsochronousTransferIn_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_IsochronousTransferIn_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.endpointNumber = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.timeout = decoder.decodeStruct(codec.Uint32);
+ val.packetLengths = decoder.decodeArrayPointer(codec.Uint32);
+ return val;
+ };
+
+ UsbDevice_IsochronousTransferIn_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_IsochronousTransferIn_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.endpointNumber);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStruct(codec.Uint32, val.timeout);
+ encoder.encodeArrayPointer(codec.Uint32, val.packetLengths);
+ };
+ function UsbDevice_IsochronousTransferIn_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_IsochronousTransferIn_ResponseParams.prototype.initDefaults_ = function() {
+ this.data = null;
+ this.packets = null;
+ };
+ UsbDevice_IsochronousTransferIn_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_IsochronousTransferIn_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_IsochronousTransferIn_ResponseParams.data
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 0, 1, codec.Uint8, false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_IsochronousTransferIn_ResponseParams.packets
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 8, new codec.PointerTo(UsbIsochronousPacket), false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_IsochronousTransferIn_ResponseParams.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbDevice_IsochronousTransferIn_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_IsochronousTransferIn_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.data = decoder.decodeArrayPointer(codec.Uint8);
+ val.packets = decoder.decodeArrayPointer(new codec.PointerTo(UsbIsochronousPacket));
+ return val;
+ };
+
+ UsbDevice_IsochronousTransferIn_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_IsochronousTransferIn_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeArrayPointer(codec.Uint8, val.data);
+ encoder.encodeArrayPointer(new codec.PointerTo(UsbIsochronousPacket), val.packets);
+ };
+ function UsbDevice_IsochronousTransferOut_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_IsochronousTransferOut_Params.prototype.initDefaults_ = function() {
+ this.endpointNumber = 0;
+ this.timeout = 0;
+ this.data = null;
+ this.packetLengths = null;
+ };
+ UsbDevice_IsochronousTransferOut_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_IsochronousTransferOut_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 32}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate UsbDevice_IsochronousTransferOut_Params.data
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_IsochronousTransferOut_Params.packetLengths
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 16, 4, codec.Uint32, false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_IsochronousTransferOut_Params.encodedSize = codec.kStructHeaderSize + 24;
+
+ UsbDevice_IsochronousTransferOut_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_IsochronousTransferOut_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.endpointNumber = decoder.decodeStruct(codec.Uint8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.timeout = decoder.decodeStruct(codec.Uint32);
+ val.data = decoder.decodeArrayPointer(codec.Uint8);
+ val.packetLengths = decoder.decodeArrayPointer(codec.Uint32);
+ return val;
+ };
+
+ UsbDevice_IsochronousTransferOut_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_IsochronousTransferOut_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint8, val.endpointNumber);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStruct(codec.Uint32, val.timeout);
+ encoder.encodeArrayPointer(codec.Uint8, val.data);
+ encoder.encodeArrayPointer(codec.Uint32, val.packetLengths);
+ };
+ function UsbDevice_IsochronousTransferOut_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDevice_IsochronousTransferOut_ResponseParams.prototype.initDefaults_ = function() {
+ this.packets = null;
+ };
+ UsbDevice_IsochronousTransferOut_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDevice_IsochronousTransferOut_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDevice_IsochronousTransferOut_ResponseParams.packets
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 0, 8, new codec.PointerTo(UsbIsochronousPacket), false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDevice_IsochronousTransferOut_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDevice_IsochronousTransferOut_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDevice_IsochronousTransferOut_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.packets = decoder.decodeArrayPointer(new codec.PointerTo(UsbIsochronousPacket));
+ return val;
+ };
+
+ UsbDevice_IsochronousTransferOut_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDevice_IsochronousTransferOut_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeArrayPointer(new codec.PointerTo(UsbIsochronousPacket), val.packets);
+ };
+ var kUsbDevice_Open_Name = 0;
+ var kUsbDevice_Close_Name = 1;
+ var kUsbDevice_SetConfiguration_Name = 2;
+ var kUsbDevice_ClaimInterface_Name = 3;
+ var kUsbDevice_ReleaseInterface_Name = 4;
+ var kUsbDevice_SetInterfaceAlternateSetting_Name = 5;
+ var kUsbDevice_Reset_Name = 6;
+ var kUsbDevice_ClearHalt_Name = 7;
+ var kUsbDevice_ControlTransferIn_Name = 8;
+ var kUsbDevice_ControlTransferOut_Name = 9;
+ var kUsbDevice_GenericTransferIn_Name = 10;
+ var kUsbDevice_GenericTransferOut_Name = 11;
+ var kUsbDevice_IsochronousTransferIn_Name = 12;
+ var kUsbDevice_IsochronousTransferOut_Name = 13;
+
+ function UsbDevicePtr(handleOrPtrInfo) {
+ this.ptr = new bindings.InterfacePtrController(UsbDevice,
+ handleOrPtrInfo);
+ }
+
+ function UsbDeviceAssociatedPtr(associatedInterfacePtrInfo) {
+ this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+ UsbDevice, associatedInterfacePtrInfo);
+ }
+
+ UsbDeviceAssociatedPtr.prototype =
+ Object.create(UsbDevicePtr.prototype);
+ UsbDeviceAssociatedPtr.prototype.constructor =
+ UsbDeviceAssociatedPtr;
+
+ function UsbDeviceProxy(receiver) {
+ this.receiver_ = receiver;
+ }
+ UsbDevicePtr.prototype.open = function() {
+ return UsbDeviceProxy.prototype.open
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.open = function() {
+ var params = new UsbDevice_Open_Params();
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_Open_Name,
+ codec.align(UsbDevice_Open_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_Open_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_Open_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.close = function() {
+ return UsbDeviceProxy.prototype.close
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.close = function() {
+ var params = new UsbDevice_Close_Params();
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_Close_Name,
+ codec.align(UsbDevice_Close_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_Close_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_Close_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.setConfiguration = function() {
+ return UsbDeviceProxy.prototype.setConfiguration
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.setConfiguration = function(value) {
+ var params = new UsbDevice_SetConfiguration_Params();
+ params.value = value;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_SetConfiguration_Name,
+ codec.align(UsbDevice_SetConfiguration_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_SetConfiguration_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_SetConfiguration_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.claimInterface = function() {
+ return UsbDeviceProxy.prototype.claimInterface
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.claimInterface = function(interfaceNumber) {
+ var params = new UsbDevice_ClaimInterface_Params();
+ params.interfaceNumber = interfaceNumber;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_ClaimInterface_Name,
+ codec.align(UsbDevice_ClaimInterface_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_ClaimInterface_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_ClaimInterface_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.releaseInterface = function() {
+ return UsbDeviceProxy.prototype.releaseInterface
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.releaseInterface = function(interfaceNumber) {
+ var params = new UsbDevice_ReleaseInterface_Params();
+ params.interfaceNumber = interfaceNumber;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_ReleaseInterface_Name,
+ codec.align(UsbDevice_ReleaseInterface_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_ReleaseInterface_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_ReleaseInterface_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.setInterfaceAlternateSetting = function() {
+ return UsbDeviceProxy.prototype.setInterfaceAlternateSetting
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.setInterfaceAlternateSetting = function(interfaceNumber, alternateSetting) {
+ var params = new UsbDevice_SetInterfaceAlternateSetting_Params();
+ params.interfaceNumber = interfaceNumber;
+ params.alternateSetting = alternateSetting;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_SetInterfaceAlternateSetting_Name,
+ codec.align(UsbDevice_SetInterfaceAlternateSetting_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_SetInterfaceAlternateSetting_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_SetInterfaceAlternateSetting_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.reset = function() {
+ return UsbDeviceProxy.prototype.reset
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.reset = function() {
+ var params = new UsbDevice_Reset_Params();
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_Reset_Name,
+ codec.align(UsbDevice_Reset_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_Reset_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_Reset_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.clearHalt = function() {
+ return UsbDeviceProxy.prototype.clearHalt
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.clearHalt = function(endpoint) {
+ var params = new UsbDevice_ClearHalt_Params();
+ params.endpoint = endpoint;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_ClearHalt_Name,
+ codec.align(UsbDevice_ClearHalt_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_ClearHalt_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_ClearHalt_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.controlTransferIn = function() {
+ return UsbDeviceProxy.prototype.controlTransferIn
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.controlTransferIn = function(params, length, timeout) {
+ var params = new UsbDevice_ControlTransferIn_Params();
+ params.params = params;
+ params.length = length;
+ params.timeout = timeout;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_ControlTransferIn_Name,
+ codec.align(UsbDevice_ControlTransferIn_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_ControlTransferIn_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_ControlTransferIn_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.controlTransferOut = function() {
+ return UsbDeviceProxy.prototype.controlTransferOut
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.controlTransferOut = function(params, data, timeout) {
+ var params = new UsbDevice_ControlTransferOut_Params();
+ params.params = params;
+ params.data = data;
+ params.timeout = timeout;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_ControlTransferOut_Name,
+ codec.align(UsbDevice_ControlTransferOut_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_ControlTransferOut_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_ControlTransferOut_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.genericTransferIn = function() {
+ return UsbDeviceProxy.prototype.genericTransferIn
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.genericTransferIn = function(endpointNumber, length, timeout) {
+ var params = new UsbDevice_GenericTransferIn_Params();
+ params.endpointNumber = endpointNumber;
+ params.length = length;
+ params.timeout = timeout;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_GenericTransferIn_Name,
+ codec.align(UsbDevice_GenericTransferIn_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_GenericTransferIn_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_GenericTransferIn_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.genericTransferOut = function() {
+ return UsbDeviceProxy.prototype.genericTransferOut
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.genericTransferOut = function(endpointNumber, data, timeout) {
+ var params = new UsbDevice_GenericTransferOut_Params();
+ params.endpointNumber = endpointNumber;
+ params.data = data;
+ params.timeout = timeout;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_GenericTransferOut_Name,
+ codec.align(UsbDevice_GenericTransferOut_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_GenericTransferOut_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_GenericTransferOut_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.isochronousTransferIn = function() {
+ return UsbDeviceProxy.prototype.isochronousTransferIn
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.isochronousTransferIn = function(endpointNumber, packetLengths, timeout) {
+ var params = new UsbDevice_IsochronousTransferIn_Params();
+ params.endpointNumber = endpointNumber;
+ params.packetLengths = packetLengths;
+ params.timeout = timeout;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_IsochronousTransferIn_Name,
+ codec.align(UsbDevice_IsochronousTransferIn_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_IsochronousTransferIn_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_IsochronousTransferIn_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDevicePtr.prototype.isochronousTransferOut = function() {
+ return UsbDeviceProxy.prototype.isochronousTransferOut
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceProxy.prototype.isochronousTransferOut = function(endpointNumber, data, packetLengths, timeout) {
+ var params = new UsbDevice_IsochronousTransferOut_Params();
+ params.endpointNumber = endpointNumber;
+ params.data = data;
+ params.packetLengths = packetLengths;
+ params.timeout = timeout;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_IsochronousTransferOut_Name,
+ codec.align(UsbDevice_IsochronousTransferOut_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDevice_IsochronousTransferOut_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDevice_IsochronousTransferOut_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+
+ function UsbDeviceStub(delegate) {
+ this.delegate_ = delegate;
+ }
+ UsbDeviceStub.prototype.open = function() {
+ return this.delegate_ && this.delegate_.open && this.delegate_.open();
+ }
+ UsbDeviceStub.prototype.close = function() {
+ return this.delegate_ && this.delegate_.close && this.delegate_.close();
+ }
+ UsbDeviceStub.prototype.setConfiguration = function(value) {
+ return this.delegate_ && this.delegate_.setConfiguration && this.delegate_.setConfiguration(value);
+ }
+ UsbDeviceStub.prototype.claimInterface = function(interfaceNumber) {
+ return this.delegate_ && this.delegate_.claimInterface && this.delegate_.claimInterface(interfaceNumber);
+ }
+ UsbDeviceStub.prototype.releaseInterface = function(interfaceNumber) {
+ return this.delegate_ && this.delegate_.releaseInterface && this.delegate_.releaseInterface(interfaceNumber);
+ }
+ UsbDeviceStub.prototype.setInterfaceAlternateSetting = function(interfaceNumber, alternateSetting) {
+ return this.delegate_ && this.delegate_.setInterfaceAlternateSetting && this.delegate_.setInterfaceAlternateSetting(interfaceNumber, alternateSetting);
+ }
+ UsbDeviceStub.prototype.reset = function() {
+ return this.delegate_ && this.delegate_.reset && this.delegate_.reset();
+ }
+ UsbDeviceStub.prototype.clearHalt = function(endpoint) {
+ return this.delegate_ && this.delegate_.clearHalt && this.delegate_.clearHalt(endpoint);
+ }
+ UsbDeviceStub.prototype.controlTransferIn = function(params, length, timeout) {
+ return this.delegate_ && this.delegate_.controlTransferIn && this.delegate_.controlTransferIn(params, length, timeout);
+ }
+ UsbDeviceStub.prototype.controlTransferOut = function(params, data, timeout) {
+ return this.delegate_ && this.delegate_.controlTransferOut && this.delegate_.controlTransferOut(params, data, timeout);
+ }
+ UsbDeviceStub.prototype.genericTransferIn = function(endpointNumber, length, timeout) {
+ return this.delegate_ && this.delegate_.genericTransferIn && this.delegate_.genericTransferIn(endpointNumber, length, timeout);
+ }
+ UsbDeviceStub.prototype.genericTransferOut = function(endpointNumber, data, timeout) {
+ return this.delegate_ && this.delegate_.genericTransferOut && this.delegate_.genericTransferOut(endpointNumber, data, timeout);
+ }
+ UsbDeviceStub.prototype.isochronousTransferIn = function(endpointNumber, packetLengths, timeout) {
+ return this.delegate_ && this.delegate_.isochronousTransferIn && this.delegate_.isochronousTransferIn(endpointNumber, packetLengths, timeout);
+ }
+ UsbDeviceStub.prototype.isochronousTransferOut = function(endpointNumber, data, packetLengths, timeout) {
+ return this.delegate_ && this.delegate_.isochronousTransferOut && this.delegate_.isochronousTransferOut(endpointNumber, data, packetLengths, timeout);
+ }
+
+ UsbDeviceStub.prototype.accept = function(message) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+ default:
+ return false;
+ }
+ };
+
+ UsbDeviceStub.prototype.acceptWithResponder =
+ function(message, responder) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+ case kUsbDevice_Open_Name:
+ var params = reader.decodeStruct(UsbDevice_Open_Params);
+ this.open().then(function(response) {
+ var responseParams =
+ new UsbDevice_Open_ResponseParams();
+ responseParams.error = response.error;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_Open_Name,
+ codec.align(UsbDevice_Open_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_Open_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_Close_Name:
+ var params = reader.decodeStruct(UsbDevice_Close_Params);
+ this.close().then(function(response) {
+ var responseParams =
+ new UsbDevice_Close_ResponseParams();
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_Close_Name,
+ codec.align(UsbDevice_Close_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_Close_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_SetConfiguration_Name:
+ var params = reader.decodeStruct(UsbDevice_SetConfiguration_Params);
+ this.setConfiguration(params.value).then(function(response) {
+ var responseParams =
+ new UsbDevice_SetConfiguration_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_SetConfiguration_Name,
+ codec.align(UsbDevice_SetConfiguration_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_SetConfiguration_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_ClaimInterface_Name:
+ var params = reader.decodeStruct(UsbDevice_ClaimInterface_Params);
+ this.claimInterface(params.interfaceNumber).then(function(response) {
+ var responseParams =
+ new UsbDevice_ClaimInterface_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_ClaimInterface_Name,
+ codec.align(UsbDevice_ClaimInterface_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_ClaimInterface_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_ReleaseInterface_Name:
+ var params = reader.decodeStruct(UsbDevice_ReleaseInterface_Params);
+ this.releaseInterface(params.interfaceNumber).then(function(response) {
+ var responseParams =
+ new UsbDevice_ReleaseInterface_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_ReleaseInterface_Name,
+ codec.align(UsbDevice_ReleaseInterface_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_ReleaseInterface_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_SetInterfaceAlternateSetting_Name:
+ var params = reader.decodeStruct(UsbDevice_SetInterfaceAlternateSetting_Params);
+ this.setInterfaceAlternateSetting(params.interfaceNumber, params.alternateSetting).then(function(response) {
+ var responseParams =
+ new UsbDevice_SetInterfaceAlternateSetting_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_SetInterfaceAlternateSetting_Name,
+ codec.align(UsbDevice_SetInterfaceAlternateSetting_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_SetInterfaceAlternateSetting_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_Reset_Name:
+ var params = reader.decodeStruct(UsbDevice_Reset_Params);
+ this.reset().then(function(response) {
+ var responseParams =
+ new UsbDevice_Reset_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_Reset_Name,
+ codec.align(UsbDevice_Reset_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_Reset_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_ClearHalt_Name:
+ var params = reader.decodeStruct(UsbDevice_ClearHalt_Params);
+ this.clearHalt(params.endpoint).then(function(response) {
+ var responseParams =
+ new UsbDevice_ClearHalt_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_ClearHalt_Name,
+ codec.align(UsbDevice_ClearHalt_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_ClearHalt_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_ControlTransferIn_Name:
+ var params = reader.decodeStruct(UsbDevice_ControlTransferIn_Params);
+ this.controlTransferIn(params.params, params.length, params.timeout).then(function(response) {
+ var responseParams =
+ new UsbDevice_ControlTransferIn_ResponseParams();
+ responseParams.status = response.status;
+ responseParams.data = response.data;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_ControlTransferIn_Name,
+ codec.align(UsbDevice_ControlTransferIn_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_ControlTransferIn_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_ControlTransferOut_Name:
+ var params = reader.decodeStruct(UsbDevice_ControlTransferOut_Params);
+ this.controlTransferOut(params.params, params.data, params.timeout).then(function(response) {
+ var responseParams =
+ new UsbDevice_ControlTransferOut_ResponseParams();
+ responseParams.status = response.status;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_ControlTransferOut_Name,
+ codec.align(UsbDevice_ControlTransferOut_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_ControlTransferOut_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_GenericTransferIn_Name:
+ var params = reader.decodeStruct(UsbDevice_GenericTransferIn_Params);
+ this.genericTransferIn(params.endpointNumber, params.length, params.timeout).then(function(response) {
+ var responseParams =
+ new UsbDevice_GenericTransferIn_ResponseParams();
+ responseParams.status = response.status;
+ responseParams.data = response.data;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_GenericTransferIn_Name,
+ codec.align(UsbDevice_GenericTransferIn_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_GenericTransferIn_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_GenericTransferOut_Name:
+ var params = reader.decodeStruct(UsbDevice_GenericTransferOut_Params);
+ this.genericTransferOut(params.endpointNumber, params.data, params.timeout).then(function(response) {
+ var responseParams =
+ new UsbDevice_GenericTransferOut_ResponseParams();
+ responseParams.status = response.status;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_GenericTransferOut_Name,
+ codec.align(UsbDevice_GenericTransferOut_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_GenericTransferOut_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_IsochronousTransferIn_Name:
+ var params = reader.decodeStruct(UsbDevice_IsochronousTransferIn_Params);
+ this.isochronousTransferIn(params.endpointNumber, params.packetLengths, params.timeout).then(function(response) {
+ var responseParams =
+ new UsbDevice_IsochronousTransferIn_ResponseParams();
+ responseParams.data = response.data;
+ responseParams.packets = response.packets;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_IsochronousTransferIn_Name,
+ codec.align(UsbDevice_IsochronousTransferIn_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_IsochronousTransferIn_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kUsbDevice_IsochronousTransferOut_Name:
+ var params = reader.decodeStruct(UsbDevice_IsochronousTransferOut_Params);
+ this.isochronousTransferOut(params.endpointNumber, params.data, params.packetLengths, params.timeout).then(function(response) {
+ var responseParams =
+ new UsbDevice_IsochronousTransferOut_ResponseParams();
+ responseParams.packets = response.packets;
+ var builder = new codec.MessageV1Builder(
+ kUsbDevice_IsochronousTransferOut_Name,
+ codec.align(UsbDevice_IsochronousTransferOut_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDevice_IsochronousTransferOut_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ function validateUsbDeviceRequest(messageValidator) {
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+ case kUsbDevice_Open_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_Open_Params;
+ break;
+ case kUsbDevice_Close_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_Close_Params;
+ break;
+ case kUsbDevice_SetConfiguration_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_SetConfiguration_Params;
+ break;
+ case kUsbDevice_ClaimInterface_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_ClaimInterface_Params;
+ break;
+ case kUsbDevice_ReleaseInterface_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_ReleaseInterface_Params;
+ break;
+ case kUsbDevice_SetInterfaceAlternateSetting_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_SetInterfaceAlternateSetting_Params;
+ break;
+ case kUsbDevice_Reset_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_Reset_Params;
+ break;
+ case kUsbDevice_ClearHalt_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_ClearHalt_Params;
+ break;
+ case kUsbDevice_ControlTransferIn_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_ControlTransferIn_Params;
+ break;
+ case kUsbDevice_ControlTransferOut_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_ControlTransferOut_Params;
+ break;
+ case kUsbDevice_GenericTransferIn_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_GenericTransferIn_Params;
+ break;
+ case kUsbDevice_GenericTransferOut_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_GenericTransferOut_Params;
+ break;
+ case kUsbDevice_IsochronousTransferIn_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_IsochronousTransferIn_Params;
+ break;
+ case kUsbDevice_IsochronousTransferOut_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDevice_IsochronousTransferOut_Params;
+ break;
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+ }
+
+ function validateUsbDeviceResponse(messageValidator) {
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+ case kUsbDevice_Open_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_Open_ResponseParams;
+ break;
+ case kUsbDevice_Close_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_Close_ResponseParams;
+ break;
+ case kUsbDevice_SetConfiguration_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_SetConfiguration_ResponseParams;
+ break;
+ case kUsbDevice_ClaimInterface_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_ClaimInterface_ResponseParams;
+ break;
+ case kUsbDevice_ReleaseInterface_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_ReleaseInterface_ResponseParams;
+ break;
+ case kUsbDevice_SetInterfaceAlternateSetting_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_SetInterfaceAlternateSetting_ResponseParams;
+ break;
+ case kUsbDevice_Reset_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_Reset_ResponseParams;
+ break;
+ case kUsbDevice_ClearHalt_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_ClearHalt_ResponseParams;
+ break;
+ case kUsbDevice_ControlTransferIn_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_ControlTransferIn_ResponseParams;
+ break;
+ case kUsbDevice_ControlTransferOut_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_ControlTransferOut_ResponseParams;
+ break;
+ case kUsbDevice_GenericTransferIn_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_GenericTransferIn_ResponseParams;
+ break;
+ case kUsbDevice_GenericTransferOut_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_GenericTransferOut_ResponseParams;
+ break;
+ case kUsbDevice_IsochronousTransferIn_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_IsochronousTransferIn_ResponseParams;
+ break;
+ case kUsbDevice_IsochronousTransferOut_Name:
+ if (message.isResponse())
+ paramsClass = UsbDevice_IsochronousTransferOut_ResponseParams;
+ break;
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+ }
+
+ var UsbDevice = {
+ name: 'device.mojom.UsbDevice',
+ kVersion: 0,
+ ptrClass: UsbDevicePtr,
+ proxyClass: UsbDeviceProxy,
+ stubClass: UsbDeviceStub,
+ validateRequest: validateUsbDeviceRequest,
+ validateResponse: validateUsbDeviceResponse,
+ };
+ UsbDeviceStub.prototype.validator = validateUsbDeviceRequest;
+ UsbDeviceProxy.prototype.validator = validateUsbDeviceResponse;
+ exports.UsbOpenDeviceError = UsbOpenDeviceError;
+ exports.UsbTransferDirection = UsbTransferDirection;
+ exports.UsbControlTransferType = UsbControlTransferType;
+ exports.UsbControlTransferRecipient = UsbControlTransferRecipient;
+ exports.UsbTransferType = UsbTransferType;
+ exports.UsbTransferStatus = UsbTransferStatus;
+ exports.UsbEndpointInfo = UsbEndpointInfo;
+ exports.UsbAlternateInterfaceInfo = UsbAlternateInterfaceInfo;
+ exports.UsbInterfaceInfo = UsbInterfaceInfo;
+ exports.UsbConfigurationInfo = UsbConfigurationInfo;
+ exports.UsbDeviceInfo = UsbDeviceInfo;
+ exports.UsbControlTransferParams = UsbControlTransferParams;
+ exports.UsbIsochronousPacket = UsbIsochronousPacket;
+ exports.UsbDevice = UsbDevice;
+ exports.UsbDevicePtr = UsbDevicePtr;
+ exports.UsbDeviceAssociatedPtr = UsbDeviceAssociatedPtr;
+})();
diff --git a/test/fixtures/web-platform-tests/resources/chromium/device.mojom.js.headers b/test/fixtures/web-platform-tests/resources/chromium/device.mojom.js.headers
new file mode 100644
index 00000000000000..6805c323df5a97
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/chromium/device.mojom.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/web-platform-tests/resources/chromium/device_manager.mojom.js b/test/fixtures/web-platform-tests/resources/chromium/device_manager.mojom.js
new file mode 100644
index 00000000000000..2d76263ba9b7a3
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/chromium/device_manager.mojom.js
@@ -0,0 +1,843 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+(function() {
+ var mojomId = 'device/usb/public/mojom/device_manager.mojom';
+ if (mojo.internal.isMojomLoaded(mojomId)) {
+ console.warn('The following mojom is loaded multiple times: ' + mojomId);
+ return;
+ }
+ mojo.internal.markMojomLoaded(mojomId);
+ var bindings = mojo;
+ var associatedBindings = mojo;
+ var codec = mojo.internal;
+ var validator = mojo.internal;
+
+ var exports = mojo.internal.exposeNamespace('device.mojom');
+ var device$ =
+ mojo.internal.exposeNamespace('device.mojom');
+ if (mojo.config.autoLoadMojomDeps) {
+ mojo.internal.loadMojomIfNecessary(
+ 'device/usb/public/mojom/device.mojom', 'device.mojom.js');
+ }
+ var string16$ =
+ mojo.internal.exposeNamespace('mojo.common.mojom');
+ if (mojo.config.autoLoadMojomDeps) {
+ mojo.internal.loadMojomIfNecessary(
+ 'mojo/public/mojom/base/string16.mojom', '../../../../mojo/public/mojom/base/string16.mojom.js');
+ }
+
+
+
+ function UsbDeviceFilter(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDeviceFilter.prototype.initDefaults_ = function() {
+ this.hasVendorId = false;
+ this.hasProductId = false;
+ this.hasClassCode = false;
+ this.hasSubclassCode = false;
+ this.hasProtocolCode = false;
+ this.classCode = 0;
+ this.vendorId = 0;
+ this.productId = 0;
+ this.subclassCode = 0;
+ this.protocolCode = 0;
+ this.serialNumber = null;
+ };
+ UsbDeviceFilter.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDeviceFilter.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+
+
+
+
+
+
+
+
+
+ // validate UsbDeviceFilter.serialNumber
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 8, string16$.String16, true);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDeviceFilter.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbDeviceFilter.decode = function(decoder) {
+ var packed;
+ var val = new UsbDeviceFilter();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.hasVendorId = (packed >> 0) & 1 ? true : false;
+ val.hasProductId = (packed >> 1) & 1 ? true : false;
+ val.hasClassCode = (packed >> 2) & 1 ? true : false;
+ val.hasSubclassCode = (packed >> 3) & 1 ? true : false;
+ val.hasProtocolCode = (packed >> 4) & 1 ? true : false;
+ val.classCode = decoder.decodeStruct(codec.Uint8);
+ val.vendorId = decoder.decodeStruct(codec.Uint16);
+ val.productId = decoder.decodeStruct(codec.Uint16);
+ val.subclassCode = decoder.decodeStruct(codec.Uint8);
+ val.protocolCode = decoder.decodeStruct(codec.Uint8);
+ val.serialNumber = decoder.decodeStructPointer(string16$.String16);
+ return val;
+ };
+
+ UsbDeviceFilter.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDeviceFilter.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.hasVendorId & 1) << 0
+ packed |= (val.hasProductId & 1) << 1
+ packed |= (val.hasClassCode & 1) << 2
+ packed |= (val.hasSubclassCode & 1) << 3
+ packed |= (val.hasProtocolCode & 1) << 4
+ encoder.writeUint8(packed);
+ encoder.encodeStruct(codec.Uint8, val.classCode);
+ encoder.encodeStruct(codec.Uint16, val.vendorId);
+ encoder.encodeStruct(codec.Uint16, val.productId);
+ encoder.encodeStruct(codec.Uint8, val.subclassCode);
+ encoder.encodeStruct(codec.Uint8, val.protocolCode);
+ encoder.encodeStructPointer(string16$.String16, val.serialNumber);
+ };
+ function UsbEnumerationOptions(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbEnumerationOptions.prototype.initDefaults_ = function() {
+ this.filters = null;
+ };
+ UsbEnumerationOptions.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbEnumerationOptions.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbEnumerationOptions.filters
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 0, 8, new codec.PointerTo(UsbDeviceFilter), false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbEnumerationOptions.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbEnumerationOptions.decode = function(decoder) {
+ var packed;
+ var val = new UsbEnumerationOptions();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.filters = decoder.decodeArrayPointer(new codec.PointerTo(UsbDeviceFilter));
+ return val;
+ };
+
+ UsbEnumerationOptions.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbEnumerationOptions.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeArrayPointer(new codec.PointerTo(UsbDeviceFilter), val.filters);
+ };
+ function UsbDeviceManager_GetDevices_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDeviceManager_GetDevices_Params.prototype.initDefaults_ = function() {
+ this.options = null;
+ };
+ UsbDeviceManager_GetDevices_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDeviceManager_GetDevices_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDeviceManager_GetDevices_Params.options
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, UsbEnumerationOptions, true);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDeviceManager_GetDevices_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDeviceManager_GetDevices_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDeviceManager_GetDevices_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.options = decoder.decodeStructPointer(UsbEnumerationOptions);
+ return val;
+ };
+
+ UsbDeviceManager_GetDevices_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDeviceManager_GetDevices_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStructPointer(UsbEnumerationOptions, val.options);
+ };
+ function UsbDeviceManager_GetDevices_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDeviceManager_GetDevices_ResponseParams.prototype.initDefaults_ = function() {
+ this.results = null;
+ };
+ UsbDeviceManager_GetDevices_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDeviceManager_GetDevices_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDeviceManager_GetDevices_ResponseParams.results
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 0, 8, new codec.PointerTo(device$.UsbDeviceInfo), false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDeviceManager_GetDevices_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDeviceManager_GetDevices_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new UsbDeviceManager_GetDevices_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.results = decoder.decodeArrayPointer(new codec.PointerTo(device$.UsbDeviceInfo));
+ return val;
+ };
+
+ UsbDeviceManager_GetDevices_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDeviceManager_GetDevices_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeArrayPointer(new codec.PointerTo(device$.UsbDeviceInfo), val.results);
+ };
+ function UsbDeviceManager_GetDevice_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDeviceManager_GetDevice_Params.prototype.initDefaults_ = function() {
+ this.guid = null;
+ this.deviceRequest = new bindings.InterfaceRequest();
+ };
+ UsbDeviceManager_GetDevice_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDeviceManager_GetDevice_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDeviceManager_GetDevice_Params.guid
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDeviceManager_GetDevice_Params.deviceRequest
+ err = messageValidator.validateInterfaceRequest(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDeviceManager_GetDevice_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+ UsbDeviceManager_GetDevice_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDeviceManager_GetDevice_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.guid = decoder.decodeStruct(codec.String);
+ val.deviceRequest = decoder.decodeStruct(codec.InterfaceRequest);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ UsbDeviceManager_GetDevice_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDeviceManager_GetDevice_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.guid);
+ encoder.encodeStruct(codec.InterfaceRequest, val.deviceRequest);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function UsbDeviceManager_SetClient_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDeviceManager_SetClient_Params.prototype.initDefaults_ = function() {
+ this.client = new UsbDeviceManagerClientPtr();
+ };
+ UsbDeviceManager_SetClient_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDeviceManager_SetClient_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDeviceManager_SetClient_Params.client
+ err = messageValidator.validateInterface(offset + codec.kStructHeaderSize + 0, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDeviceManager_SetClient_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDeviceManager_SetClient_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDeviceManager_SetClient_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.client = decoder.decodeStruct(new codec.Interface(UsbDeviceManagerClientPtr));
+ return val;
+ };
+
+ UsbDeviceManager_SetClient_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDeviceManager_SetClient_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(new codec.Interface(UsbDeviceManagerClientPtr), val.client);
+ };
+ function UsbDeviceManagerClient_OnDeviceAdded_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDeviceManagerClient_OnDeviceAdded_Params.prototype.initDefaults_ = function() {
+ this.deviceInfo = null;
+ };
+ UsbDeviceManagerClient_OnDeviceAdded_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDeviceManagerClient_OnDeviceAdded_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDeviceManagerClient_OnDeviceAdded_Params.deviceInfo
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, device$.UsbDeviceInfo, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDeviceManagerClient_OnDeviceAdded_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDeviceManagerClient_OnDeviceAdded_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDeviceManagerClient_OnDeviceAdded_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.deviceInfo = decoder.decodeStructPointer(device$.UsbDeviceInfo);
+ return val;
+ };
+
+ UsbDeviceManagerClient_OnDeviceAdded_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDeviceManagerClient_OnDeviceAdded_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStructPointer(device$.UsbDeviceInfo, val.deviceInfo);
+ };
+ function UsbDeviceManagerClient_OnDeviceRemoved_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ UsbDeviceManagerClient_OnDeviceRemoved_Params.prototype.initDefaults_ = function() {
+ this.deviceInfo = null;
+ };
+ UsbDeviceManagerClient_OnDeviceRemoved_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ UsbDeviceManagerClient_OnDeviceRemoved_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate UsbDeviceManagerClient_OnDeviceRemoved_Params.deviceInfo
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, device$.UsbDeviceInfo, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ UsbDeviceManagerClient_OnDeviceRemoved_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ UsbDeviceManagerClient_OnDeviceRemoved_Params.decode = function(decoder) {
+ var packed;
+ var val = new UsbDeviceManagerClient_OnDeviceRemoved_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.deviceInfo = decoder.decodeStructPointer(device$.UsbDeviceInfo);
+ return val;
+ };
+
+ UsbDeviceManagerClient_OnDeviceRemoved_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(UsbDeviceManagerClient_OnDeviceRemoved_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStructPointer(device$.UsbDeviceInfo, val.deviceInfo);
+ };
+ var kUsbDeviceManager_GetDevices_Name = 0;
+ var kUsbDeviceManager_GetDevice_Name = 1;
+ var kUsbDeviceManager_SetClient_Name = 2;
+
+ function UsbDeviceManagerPtr(handleOrPtrInfo) {
+ this.ptr = new bindings.InterfacePtrController(UsbDeviceManager,
+ handleOrPtrInfo);
+ }
+
+ function UsbDeviceManagerAssociatedPtr(associatedInterfacePtrInfo) {
+ this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+ UsbDeviceManager, associatedInterfacePtrInfo);
+ }
+
+ UsbDeviceManagerAssociatedPtr.prototype =
+ Object.create(UsbDeviceManagerPtr.prototype);
+ UsbDeviceManagerAssociatedPtr.prototype.constructor =
+ UsbDeviceManagerAssociatedPtr;
+
+ function UsbDeviceManagerProxy(receiver) {
+ this.receiver_ = receiver;
+ }
+ UsbDeviceManagerPtr.prototype.getDevices = function() {
+ return UsbDeviceManagerProxy.prototype.getDevices
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceManagerProxy.prototype.getDevices = function(options) {
+ var params = new UsbDeviceManager_GetDevices_Params();
+ params.options = options;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kUsbDeviceManager_GetDevices_Name,
+ codec.align(UsbDeviceManager_GetDevices_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(UsbDeviceManager_GetDevices_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(UsbDeviceManager_GetDevices_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ UsbDeviceManagerPtr.prototype.getDevice = function() {
+ return UsbDeviceManagerProxy.prototype.getDevice
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceManagerProxy.prototype.getDevice = function(guid, deviceRequest) {
+ var params = new UsbDeviceManager_GetDevice_Params();
+ params.guid = guid;
+ params.deviceRequest = deviceRequest;
+ var builder = new codec.MessageV0Builder(
+ kUsbDeviceManager_GetDevice_Name,
+ codec.align(UsbDeviceManager_GetDevice_Params.encodedSize));
+ builder.encodeStruct(UsbDeviceManager_GetDevice_Params, params);
+ var message = builder.finish();
+ this.receiver_.accept(message);
+ };
+ UsbDeviceManagerPtr.prototype.setClient = function() {
+ return UsbDeviceManagerProxy.prototype.setClient
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceManagerProxy.prototype.setClient = function(client) {
+ var params = new UsbDeviceManager_SetClient_Params();
+ params.client = client;
+ var builder = new codec.MessageV0Builder(
+ kUsbDeviceManager_SetClient_Name,
+ codec.align(UsbDeviceManager_SetClient_Params.encodedSize));
+ builder.encodeStruct(UsbDeviceManager_SetClient_Params, params);
+ var message = builder.finish();
+ this.receiver_.accept(message);
+ };
+
+ function UsbDeviceManagerStub(delegate) {
+ this.delegate_ = delegate;
+ }
+ UsbDeviceManagerStub.prototype.getDevices = function(options) {
+ return this.delegate_ && this.delegate_.getDevices && this.delegate_.getDevices(options);
+ }
+ UsbDeviceManagerStub.prototype.getDevice = function(guid, deviceRequest) {
+ return this.delegate_ && this.delegate_.getDevice && this.delegate_.getDevice(guid, deviceRequest);
+ }
+ UsbDeviceManagerStub.prototype.setClient = function(client) {
+ return this.delegate_ && this.delegate_.setClient && this.delegate_.setClient(client);
+ }
+
+ UsbDeviceManagerStub.prototype.accept = function(message) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+ case kUsbDeviceManager_GetDevice_Name:
+ var params = reader.decodeStruct(UsbDeviceManager_GetDevice_Params);
+ this.getDevice(params.guid, params.deviceRequest);
+ return true;
+ case kUsbDeviceManager_SetClient_Name:
+ var params = reader.decodeStruct(UsbDeviceManager_SetClient_Params);
+ this.setClient(params.client);
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ UsbDeviceManagerStub.prototype.acceptWithResponder =
+ function(message, responder) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+ case kUsbDeviceManager_GetDevices_Name:
+ var params = reader.decodeStruct(UsbDeviceManager_GetDevices_Params);
+ this.getDevices(params.options).then(function(response) {
+ var responseParams =
+ new UsbDeviceManager_GetDevices_ResponseParams();
+ responseParams.results = response.results;
+ var builder = new codec.MessageV1Builder(
+ kUsbDeviceManager_GetDevices_Name,
+ codec.align(UsbDeviceManager_GetDevices_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(UsbDeviceManager_GetDevices_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ function validateUsbDeviceManagerRequest(messageValidator) {
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+ case kUsbDeviceManager_GetDevices_Name:
+ if (message.expectsResponse())
+ paramsClass = UsbDeviceManager_GetDevices_Params;
+ break;
+ case kUsbDeviceManager_GetDevice_Name:
+ if (!message.expectsResponse() && !message.isResponse())
+ paramsClass = UsbDeviceManager_GetDevice_Params;
+ break;
+ case kUsbDeviceManager_SetClient_Name:
+ if (!message.expectsResponse() && !message.isResponse())
+ paramsClass = UsbDeviceManager_SetClient_Params;
+ break;
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+ }
+
+ function validateUsbDeviceManagerResponse(messageValidator) {
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+ case kUsbDeviceManager_GetDevices_Name:
+ if (message.isResponse())
+ paramsClass = UsbDeviceManager_GetDevices_ResponseParams;
+ break;
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+ }
+
+ var UsbDeviceManager = {
+ name: 'device.mojom.UsbDeviceManager',
+ kVersion: 0,
+ ptrClass: UsbDeviceManagerPtr,
+ proxyClass: UsbDeviceManagerProxy,
+ stubClass: UsbDeviceManagerStub,
+ validateRequest: validateUsbDeviceManagerRequest,
+ validateResponse: validateUsbDeviceManagerResponse,
+ };
+ UsbDeviceManagerStub.prototype.validator = validateUsbDeviceManagerRequest;
+ UsbDeviceManagerProxy.prototype.validator = validateUsbDeviceManagerResponse;
+ var kUsbDeviceManagerClient_OnDeviceAdded_Name = 0;
+ var kUsbDeviceManagerClient_OnDeviceRemoved_Name = 1;
+
+ function UsbDeviceManagerClientPtr(handleOrPtrInfo) {
+ this.ptr = new bindings.InterfacePtrController(UsbDeviceManagerClient,
+ handleOrPtrInfo);
+ }
+
+ function UsbDeviceManagerClientAssociatedPtr(associatedInterfacePtrInfo) {
+ this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+ UsbDeviceManagerClient, associatedInterfacePtrInfo);
+ }
+
+ UsbDeviceManagerClientAssociatedPtr.prototype =
+ Object.create(UsbDeviceManagerClientPtr.prototype);
+ UsbDeviceManagerClientAssociatedPtr.prototype.constructor =
+ UsbDeviceManagerClientAssociatedPtr;
+
+ function UsbDeviceManagerClientProxy(receiver) {
+ this.receiver_ = receiver;
+ }
+ UsbDeviceManagerClientPtr.prototype.onDeviceAdded = function() {
+ return UsbDeviceManagerClientProxy.prototype.onDeviceAdded
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceManagerClientProxy.prototype.onDeviceAdded = function(deviceInfo) {
+ var params = new UsbDeviceManagerClient_OnDeviceAdded_Params();
+ params.deviceInfo = deviceInfo;
+ var builder = new codec.MessageV0Builder(
+ kUsbDeviceManagerClient_OnDeviceAdded_Name,
+ codec.align(UsbDeviceManagerClient_OnDeviceAdded_Params.encodedSize));
+ builder.encodeStruct(UsbDeviceManagerClient_OnDeviceAdded_Params, params);
+ var message = builder.finish();
+ this.receiver_.accept(message);
+ };
+ UsbDeviceManagerClientPtr.prototype.onDeviceRemoved = function() {
+ return UsbDeviceManagerClientProxy.prototype.onDeviceRemoved
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ UsbDeviceManagerClientProxy.prototype.onDeviceRemoved = function(deviceInfo) {
+ var params = new UsbDeviceManagerClient_OnDeviceRemoved_Params();
+ params.deviceInfo = deviceInfo;
+ var builder = new codec.MessageV0Builder(
+ kUsbDeviceManagerClient_OnDeviceRemoved_Name,
+ codec.align(UsbDeviceManagerClient_OnDeviceRemoved_Params.encodedSize));
+ builder.encodeStruct(UsbDeviceManagerClient_OnDeviceRemoved_Params, params);
+ var message = builder.finish();
+ this.receiver_.accept(message);
+ };
+
+ function UsbDeviceManagerClientStub(delegate) {
+ this.delegate_ = delegate;
+ }
+ UsbDeviceManagerClientStub.prototype.onDeviceAdded = function(deviceInfo) {
+ return this.delegate_ && this.delegate_.onDeviceAdded && this.delegate_.onDeviceAdded(deviceInfo);
+ }
+ UsbDeviceManagerClientStub.prototype.onDeviceRemoved = function(deviceInfo) {
+ return this.delegate_ && this.delegate_.onDeviceRemoved && this.delegate_.onDeviceRemoved(deviceInfo);
+ }
+
+ UsbDeviceManagerClientStub.prototype.accept = function(message) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+ case kUsbDeviceManagerClient_OnDeviceAdded_Name:
+ var params = reader.decodeStruct(UsbDeviceManagerClient_OnDeviceAdded_Params);
+ this.onDeviceAdded(params.deviceInfo);
+ return true;
+ case kUsbDeviceManagerClient_OnDeviceRemoved_Name:
+ var params = reader.decodeStruct(UsbDeviceManagerClient_OnDeviceRemoved_Params);
+ this.onDeviceRemoved(params.deviceInfo);
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ UsbDeviceManagerClientStub.prototype.acceptWithResponder =
+ function(message, responder) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+ default:
+ return false;
+ }
+ };
+
+ function validateUsbDeviceManagerClientRequest(messageValidator) {
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+ case kUsbDeviceManagerClient_OnDeviceAdded_Name:
+ if (!message.expectsResponse() && !message.isResponse())
+ paramsClass = UsbDeviceManagerClient_OnDeviceAdded_Params;
+ break;
+ case kUsbDeviceManagerClient_OnDeviceRemoved_Name:
+ if (!message.expectsResponse() && !message.isResponse())
+ paramsClass = UsbDeviceManagerClient_OnDeviceRemoved_Params;
+ break;
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+ }
+
+ function validateUsbDeviceManagerClientResponse(messageValidator) {
+ return validator.validationError.NONE;
+ }
+
+ var UsbDeviceManagerClient = {
+ name: 'device.mojom.UsbDeviceManagerClient',
+ kVersion: 0,
+ ptrClass: UsbDeviceManagerClientPtr,
+ proxyClass: UsbDeviceManagerClientProxy,
+ stubClass: UsbDeviceManagerClientStub,
+ validateRequest: validateUsbDeviceManagerClientRequest,
+ validateResponse: null,
+ };
+ UsbDeviceManagerClientStub.prototype.validator = validateUsbDeviceManagerClientRequest;
+ UsbDeviceManagerClientProxy.prototype.validator = null;
+ exports.UsbDeviceFilter = UsbDeviceFilter;
+ exports.UsbEnumerationOptions = UsbEnumerationOptions;
+ exports.UsbDeviceManager = UsbDeviceManager;
+ exports.UsbDeviceManagerPtr = UsbDeviceManagerPtr;
+ exports.UsbDeviceManagerAssociatedPtr = UsbDeviceManagerAssociatedPtr;
+ exports.UsbDeviceManagerClient = UsbDeviceManagerClient;
+ exports.UsbDeviceManagerClientPtr = UsbDeviceManagerClientPtr;
+ exports.UsbDeviceManagerClientAssociatedPtr = UsbDeviceManagerClientAssociatedPtr;
+})();
diff --git a/test/fixtures/web-platform-tests/resources/chromium/device_manager.mojom.js.headers b/test/fixtures/web-platform-tests/resources/chromium/device_manager.mojom.js.headers
new file mode 100644
index 00000000000000..6805c323df5a97
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/chromium/device_manager.mojom.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth.mojom.js b/test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth.mojom.js
new file mode 100644
index 00000000000000..da443362248a1a
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth.mojom.js
@@ -0,0 +1,5323 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+(function() {
+ var mojomId = 'device/bluetooth/public/mojom/test/fake_bluetooth.mojom';
+ if (mojo.internal.isMojomLoaded(mojomId)) {
+ console.warn('The following mojom is loaded multiple times: ' + mojomId);
+ return;
+ }
+ mojo.internal.markMojomLoaded(mojomId);
+ var bindings = mojo;
+ var associatedBindings = mojo;
+ var codec = mojo.internal;
+ var validator = mojo.internal;
+
+ var exports = mojo.internal.exposeNamespace('bluetooth.mojom');
+ var uuid$ =
+ mojo.internal.exposeNamespace('bluetooth.mojom');
+ if (mojo.config.autoLoadMojomDeps) {
+ mojo.internal.loadMojomIfNecessary(
+ 'device/bluetooth/public/mojom/uuid.mojom', '../uuid.mojom.js');
+ }
+
+
+ var kHCISuccess = 0x0000;
+ var kHCIConnectionTimeout = 0x0008;
+ var kGATTSuccess = 0x0000;
+ var kGATTInvalidHandle = 0x0001;
+ var CentralState = {};
+ CentralState.ABSENT = 0;
+ CentralState.POWERED_ON = CentralState.ABSENT + 1;
+ CentralState.POWERED_OFF = CentralState.POWERED_ON + 1;
+
+ CentralState.isKnownEnumValue = function(value) {
+ switch (value) {
+ case 0:
+ case 1:
+ case 2:
+ return true;
+ }
+ return false;
+ };
+
+ CentralState.validate = function(enumValue) {
+ var isExtensible = false;
+ if (isExtensible || this.isKnownEnumValue(enumValue))
+ return validator.validationError.NONE;
+
+ return validator.validationError.UNKNOWN_ENUM_VALUE;
+ };
+
+ function Appearance(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ Appearance.prototype.initDefaults_ = function() {
+ this.hasValue = false;
+ this.value = 0;
+ };
+ Appearance.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ Appearance.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ return validator.validationError.NONE;
+ };
+
+ Appearance.encodedSize = codec.kStructHeaderSize + 8;
+
+ Appearance.decode = function(decoder) {
+ var packed;
+ var val = new Appearance();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.hasValue = (packed >> 0) & 1 ? true : false;
+ val.value = decoder.decodeStruct(codec.Int8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ Appearance.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(Appearance.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.hasValue & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.encodeStruct(codec.Int8, val.value);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function Power(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ Power.prototype.initDefaults_ = function() {
+ this.hasValue = false;
+ this.value = 0;
+ };
+ Power.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ Power.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ return validator.validationError.NONE;
+ };
+
+ Power.encodedSize = codec.kStructHeaderSize + 8;
+
+ Power.decode = function(decoder) {
+ var packed;
+ var val = new Power();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.hasValue = (packed >> 0) & 1 ? true : false;
+ val.value = decoder.decodeStruct(codec.Int8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ Power.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(Power.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.hasValue & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.encodeStruct(codec.Int8, val.value);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function ServiceDataMap(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ ServiceDataMap.prototype.initDefaults_ = function() {
+ this.serviceData = null;
+ };
+ ServiceDataMap.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ ServiceDataMap.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate ServiceDataMap.serviceData
+ err = messageValidator.validateMapPointer(offset + codec.kStructHeaderSize + 0, false, codec.String, new codec.ArrayOf(codec.Uint8), false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ ServiceDataMap.encodedSize = codec.kStructHeaderSize + 8;
+
+ ServiceDataMap.decode = function(decoder) {
+ var packed;
+ var val = new ServiceDataMap();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.serviceData = decoder.decodeMapPointer(codec.String, new codec.ArrayOf(codec.Uint8));
+ return val;
+ };
+
+ ServiceDataMap.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(ServiceDataMap.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeMapPointer(codec.String, new codec.ArrayOf(codec.Uint8), val.serviceData);
+ };
+ function ScanRecord(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ ScanRecord.prototype.initDefaults_ = function() {
+ this.name = null;
+ this.uuids = null;
+ this.appearance = null;
+ this.txPower = null;
+ this.manufacturerData = null;
+ this.serviceData = null;
+ };
+ ScanRecord.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ ScanRecord.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 56}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate ScanRecord.name
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, true)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate ScanRecord.uuids
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 8, new codec.PointerTo(uuid$.UUID), true, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate ScanRecord.appearance
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 16, Appearance, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate ScanRecord.txPower
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 24, Power, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate ScanRecord.manufacturerData
+ err = messageValidator.validateMapPointer(offset + codec.kStructHeaderSize + 32, true, codec.Uint8, new codec.ArrayOf(codec.Uint8), false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate ScanRecord.serviceData
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 40, ServiceDataMap, true);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ ScanRecord.encodedSize = codec.kStructHeaderSize + 48;
+
+ ScanRecord.decode = function(decoder) {
+ var packed;
+ var val = new ScanRecord();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.name = decoder.decodeStruct(codec.NullableString);
+ val.uuids = decoder.decodeArrayPointer(new codec.PointerTo(uuid$.UUID));
+ val.appearance = decoder.decodeStructPointer(Appearance);
+ val.txPower = decoder.decodeStructPointer(Power);
+ val.manufacturerData = decoder.decodeMapPointer(codec.Uint8, new codec.ArrayOf(codec.Uint8));
+ val.serviceData = decoder.decodeStructPointer(ServiceDataMap);
+ return val;
+ };
+
+ ScanRecord.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(ScanRecord.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.NullableString, val.name);
+ encoder.encodeArrayPointer(new codec.PointerTo(uuid$.UUID), val.uuids);
+ encoder.encodeStructPointer(Appearance, val.appearance);
+ encoder.encodeStructPointer(Power, val.txPower);
+ encoder.encodeMapPointer(codec.Uint8, new codec.ArrayOf(codec.Uint8), val.manufacturerData);
+ encoder.encodeStructPointer(ServiceDataMap, val.serviceData);
+ };
+ function ScanResult(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ ScanResult.prototype.initDefaults_ = function() {
+ this.deviceAddress = null;
+ this.rssi = 0;
+ this.scanRecord = null;
+ };
+ ScanResult.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ ScanResult.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 32}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate ScanResult.deviceAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate ScanResult.scanRecord
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 16, ScanRecord, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ ScanResult.encodedSize = codec.kStructHeaderSize + 24;
+
+ ScanResult.decode = function(decoder) {
+ var packed;
+ var val = new ScanResult();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.deviceAddress = decoder.decodeStruct(codec.String);
+ val.rssi = decoder.decodeStruct(codec.Int8);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.scanRecord = decoder.decodeStructPointer(ScanRecord);
+ return val;
+ };
+
+ ScanResult.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(ScanResult.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.deviceAddress);
+ encoder.encodeStruct(codec.Int8, val.rssi);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStructPointer(ScanRecord, val.scanRecord);
+ };
+ function CharacteristicProperties(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ CharacteristicProperties.prototype.initDefaults_ = function() {
+ this.broadcast = false;
+ this.read = false;
+ this.writeWithoutResponse = false;
+ this.write = false;
+ this.notify = false;
+ this.indicate = false;
+ this.authenticatedSignedWrites = false;
+ this.extendedProperties = false;
+ };
+ CharacteristicProperties.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ CharacteristicProperties.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+
+
+
+
+
+
+ return validator.validationError.NONE;
+ };
+
+ CharacteristicProperties.encodedSize = codec.kStructHeaderSize + 8;
+
+ CharacteristicProperties.decode = function(decoder) {
+ var packed;
+ var val = new CharacteristicProperties();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.broadcast = (packed >> 0) & 1 ? true : false;
+ val.read = (packed >> 1) & 1 ? true : false;
+ val.writeWithoutResponse = (packed >> 2) & 1 ? true : false;
+ val.write = (packed >> 3) & 1 ? true : false;
+ val.notify = (packed >> 4) & 1 ? true : false;
+ val.indicate = (packed >> 5) & 1 ? true : false;
+ val.authenticatedSignedWrites = (packed >> 6) & 1 ? true : false;
+ val.extendedProperties = (packed >> 7) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ CharacteristicProperties.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(CharacteristicProperties.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.broadcast & 1) << 0
+ packed |= (val.read & 1) << 1
+ packed |= (val.writeWithoutResponse & 1) << 2
+ packed |= (val.write & 1) << 3
+ packed |= (val.notify & 1) << 4
+ packed |= (val.indicate & 1) << 5
+ packed |= (val.authenticatedSignedWrites & 1) << 6
+ packed |= (val.extendedProperties & 1) << 7
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeBluetooth_SetLESupported_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetooth_SetLESupported_Params.prototype.initDefaults_ = function() {
+ this.available = false;
+ };
+ FakeBluetooth_SetLESupported_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetooth_SetLESupported_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetooth_SetLESupported_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeBluetooth_SetLESupported_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetooth_SetLESupported_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.available = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeBluetooth_SetLESupported_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetooth_SetLESupported_Params.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.available & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeBluetooth_SetLESupported_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetooth_SetLESupported_ResponseParams.prototype.initDefaults_ = function() {
+ };
+ FakeBluetooth_SetLESupported_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetooth_SetLESupported_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetooth_SetLESupported_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+ FakeBluetooth_SetLESupported_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetooth_SetLESupported_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ FakeBluetooth_SetLESupported_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetooth_SetLESupported_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ };
+ function FakeBluetooth_SimulateCentral_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetooth_SimulateCentral_Params.prototype.initDefaults_ = function() {
+ this.state = 0;
+ };
+ FakeBluetooth_SimulateCentral_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetooth_SimulateCentral_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeBluetooth_SimulateCentral_Params.state
+ err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, CentralState);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetooth_SimulateCentral_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeBluetooth_SimulateCentral_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetooth_SimulateCentral_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.state = decoder.decodeStruct(codec.Int32);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeBluetooth_SimulateCentral_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetooth_SimulateCentral_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Int32, val.state);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeBluetooth_SimulateCentral_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetooth_SimulateCentral_ResponseParams.prototype.initDefaults_ = function() {
+ this.fakeCentral = new FakeCentralPtr();
+ };
+ FakeBluetooth_SimulateCentral_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetooth_SimulateCentral_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeBluetooth_SimulateCentral_ResponseParams.fakeCentral
+ err = messageValidator.validateInterface(offset + codec.kStructHeaderSize + 0, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetooth_SimulateCentral_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeBluetooth_SimulateCentral_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetooth_SimulateCentral_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.fakeCentral = decoder.decodeStruct(new codec.Interface(FakeCentralPtr));
+ return val;
+ };
+
+ FakeBluetooth_SimulateCentral_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetooth_SimulateCentral_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(new codec.Interface(FakeCentralPtr), val.fakeCentral);
+ };
+ function FakeBluetooth_AllResponsesConsumed_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetooth_AllResponsesConsumed_Params.prototype.initDefaults_ = function() {
+ };
+ FakeBluetooth_AllResponsesConsumed_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetooth_AllResponsesConsumed_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetooth_AllResponsesConsumed_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+ FakeBluetooth_AllResponsesConsumed_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetooth_AllResponsesConsumed_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ FakeBluetooth_AllResponsesConsumed_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetooth_AllResponsesConsumed_Params.encodedSize);
+ encoder.writeUint32(0);
+ };
+ function FakeBluetooth_AllResponsesConsumed_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetooth_AllResponsesConsumed_ResponseParams.prototype.initDefaults_ = function() {
+ this.consumed = false;
+ };
+ FakeBluetooth_AllResponsesConsumed_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetooth_AllResponsesConsumed_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetooth_AllResponsesConsumed_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeBluetooth_AllResponsesConsumed_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetooth_AllResponsesConsumed_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.consumed = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeBluetooth_AllResponsesConsumed_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetooth_AllResponsesConsumed_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.consumed & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_SimulatePreconnectedPeripheral_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SimulatePreconnectedPeripheral_Params.prototype.initDefaults_ = function() {
+ this.address = null;
+ this.name = null;
+ this.knownServiceUuids = null;
+ };
+ FakeCentral_SimulatePreconnectedPeripheral_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SimulatePreconnectedPeripheral_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 32}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SimulatePreconnectedPeripheral_Params.address
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SimulatePreconnectedPeripheral_Params.name
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SimulatePreconnectedPeripheral_Params.knownServiceUuids
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 16, 8, new codec.PointerTo(uuid$.UUID), false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SimulatePreconnectedPeripheral_Params.encodedSize = codec.kStructHeaderSize + 24;
+
+ FakeCentral_SimulatePreconnectedPeripheral_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SimulatePreconnectedPeripheral_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.address = decoder.decodeStruct(codec.String);
+ val.name = decoder.decodeStruct(codec.String);
+ val.knownServiceUuids = decoder.decodeArrayPointer(new codec.PointerTo(uuid$.UUID));
+ return val;
+ };
+
+ FakeCentral_SimulatePreconnectedPeripheral_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SimulatePreconnectedPeripheral_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.address);
+ encoder.encodeStruct(codec.String, val.name);
+ encoder.encodeArrayPointer(new codec.PointerTo(uuid$.UUID), val.knownServiceUuids);
+ };
+ function FakeCentral_SimulatePreconnectedPeripheral_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.prototype.initDefaults_ = function() {
+ };
+ FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+ FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SimulatePreconnectedPeripheral_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ };
+ function FakeCentral_SimulateAdvertisementReceived_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SimulateAdvertisementReceived_Params.prototype.initDefaults_ = function() {
+ this.result = null;
+ };
+ FakeCentral_SimulateAdvertisementReceived_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SimulateAdvertisementReceived_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SimulateAdvertisementReceived_Params.result
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, ScanResult, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SimulateAdvertisementReceived_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SimulateAdvertisementReceived_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SimulateAdvertisementReceived_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.result = decoder.decodeStructPointer(ScanResult);
+ return val;
+ };
+
+ FakeCentral_SimulateAdvertisementReceived_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SimulateAdvertisementReceived_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStructPointer(ScanResult, val.result);
+ };
+ function FakeCentral_SimulateAdvertisementReceived_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SimulateAdvertisementReceived_ResponseParams.prototype.initDefaults_ = function() {
+ };
+ FakeCentral_SimulateAdvertisementReceived_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SimulateAdvertisementReceived_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SimulateAdvertisementReceived_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+ FakeCentral_SimulateAdvertisementReceived_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SimulateAdvertisementReceived_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ FakeCentral_SimulateAdvertisementReceived_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SimulateAdvertisementReceived_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ };
+ function FakeCentral_SetNextGATTConnectionResponse_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextGATTConnectionResponse_Params.prototype.initDefaults_ = function() {
+ this.address = null;
+ this.code = 0;
+ };
+ FakeCentral_SetNextGATTConnectionResponse_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextGATTConnectionResponse_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextGATTConnectionResponse_Params.address
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextGATTConnectionResponse_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+ FakeCentral_SetNextGATTConnectionResponse_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextGATTConnectionResponse_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.address = decoder.decodeStruct(codec.String);
+ val.code = decoder.decodeStruct(codec.Uint16);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_SetNextGATTConnectionResponse_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextGATTConnectionResponse_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.address);
+ encoder.encodeStruct(codec.Uint16, val.code);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_SetNextGATTConnectionResponse_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextGATTConnectionResponse_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_SetNextGATTConnectionResponse_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextGATTConnectionResponse_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextGATTConnectionResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SetNextGATTConnectionResponse_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextGATTConnectionResponse_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_SetNextGATTConnectionResponse_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextGATTConnectionResponse_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_SetNextGATTDiscoveryResponse_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextGATTDiscoveryResponse_Params.prototype.initDefaults_ = function() {
+ this.address = null;
+ this.code = 0;
+ };
+ FakeCentral_SetNextGATTDiscoveryResponse_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextGATTDiscoveryResponse_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextGATTDiscoveryResponse_Params.address
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextGATTDiscoveryResponse_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+ FakeCentral_SetNextGATTDiscoveryResponse_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextGATTDiscoveryResponse_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.address = decoder.decodeStruct(codec.String);
+ val.code = decoder.decodeStruct(codec.Uint16);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_SetNextGATTDiscoveryResponse_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextGATTDiscoveryResponse_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.address);
+ encoder.encodeStruct(codec.Uint16, val.code);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_SimulateGATTDisconnection_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SimulateGATTDisconnection_Params.prototype.initDefaults_ = function() {
+ this.address = null;
+ };
+ FakeCentral_SimulateGATTDisconnection_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SimulateGATTDisconnection_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SimulateGATTDisconnection_Params.address
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SimulateGATTDisconnection_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SimulateGATTDisconnection_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SimulateGATTDisconnection_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.address = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_SimulateGATTDisconnection_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SimulateGATTDisconnection_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.address);
+ };
+ function FakeCentral_SimulateGATTDisconnection_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SimulateGATTDisconnection_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_SimulateGATTDisconnection_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SimulateGATTDisconnection_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SimulateGATTDisconnection_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SimulateGATTDisconnection_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SimulateGATTDisconnection_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_SimulateGATTDisconnection_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SimulateGATTDisconnection_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_SimulateGATTServicesChanged_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SimulateGATTServicesChanged_Params.prototype.initDefaults_ = function() {
+ this.address = null;
+ };
+ FakeCentral_SimulateGATTServicesChanged_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SimulateGATTServicesChanged_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SimulateGATTServicesChanged_Params.address
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SimulateGATTServicesChanged_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SimulateGATTServicesChanged_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SimulateGATTServicesChanged_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.address = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_SimulateGATTServicesChanged_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SimulateGATTServicesChanged_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.address);
+ };
+ function FakeCentral_SimulateGATTServicesChanged_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SimulateGATTServicesChanged_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_SimulateGATTServicesChanged_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SimulateGATTServicesChanged_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SimulateGATTServicesChanged_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SimulateGATTServicesChanged_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SimulateGATTServicesChanged_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_SimulateGATTServicesChanged_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SimulateGATTServicesChanged_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_AddFakeService_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_AddFakeService_Params.prototype.initDefaults_ = function() {
+ this.peripheralAddress = null;
+ this.serviceUuid = null;
+ };
+ FakeCentral_AddFakeService_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_AddFakeService_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeService_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeService_Params.serviceUuid
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 8, uuid$.UUID, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_AddFakeService_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+ FakeCentral_AddFakeService_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_AddFakeService_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ val.serviceUuid = decoder.decodeStructPointer(uuid$.UUID);
+ return val;
+ };
+
+ FakeCentral_AddFakeService_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_AddFakeService_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ encoder.encodeStructPointer(uuid$.UUID, val.serviceUuid);
+ };
+ function FakeCentral_AddFakeService_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_AddFakeService_ResponseParams.prototype.initDefaults_ = function() {
+ this.serviceId = null;
+ };
+ FakeCentral_AddFakeService_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_AddFakeService_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeService_ResponseParams.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, true)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_AddFakeService_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_AddFakeService_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_AddFakeService_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.serviceId = decoder.decodeStruct(codec.NullableString);
+ return val;
+ };
+
+ FakeCentral_AddFakeService_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_AddFakeService_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.NullableString, val.serviceId);
+ };
+ function FakeCentral_RemoveFakeService_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_RemoveFakeService_Params.prototype.initDefaults_ = function() {
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_RemoveFakeService_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_RemoveFakeService_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_RemoveFakeService_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_RemoveFakeService_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_RemoveFakeService_Params.encodedSize = codec.kStructHeaderSize + 16;
+
+ FakeCentral_RemoveFakeService_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_RemoveFakeService_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_RemoveFakeService_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_RemoveFakeService_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_RemoveFakeService_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_RemoveFakeService_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_RemoveFakeService_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_RemoveFakeService_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_RemoveFakeService_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_RemoveFakeService_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_RemoveFakeService_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_RemoveFakeService_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_RemoveFakeService_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_AddFakeCharacteristic_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_AddFakeCharacteristic_Params.prototype.initDefaults_ = function() {
+ this.characteristicUuid = null;
+ this.properties = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_AddFakeCharacteristic_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_AddFakeCharacteristic_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 40}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeCharacteristic_Params.characteristicUuid
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, uuid$.UUID, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeCharacteristic_Params.properties
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 8, CharacteristicProperties, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeCharacteristic_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeCharacteristic_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_AddFakeCharacteristic_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+ FakeCentral_AddFakeCharacteristic_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_AddFakeCharacteristic_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.characteristicUuid = decoder.decodeStructPointer(uuid$.UUID);
+ val.properties = decoder.decodeStructPointer(CharacteristicProperties);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_AddFakeCharacteristic_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_AddFakeCharacteristic_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStructPointer(uuid$.UUID, val.characteristicUuid);
+ encoder.encodeStructPointer(CharacteristicProperties, val.properties);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_AddFakeCharacteristic_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_AddFakeCharacteristic_ResponseParams.prototype.initDefaults_ = function() {
+ this.characteristicId = null;
+ };
+ FakeCentral_AddFakeCharacteristic_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_AddFakeCharacteristic_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeCharacteristic_ResponseParams.characteristicId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, true)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_AddFakeCharacteristic_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_AddFakeCharacteristic_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_AddFakeCharacteristic_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.characteristicId = decoder.decodeStruct(codec.NullableString);
+ return val;
+ };
+
+ FakeCentral_AddFakeCharacteristic_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_AddFakeCharacteristic_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.NullableString, val.characteristicId);
+ };
+ function FakeCentral_RemoveFakeCharacteristic_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_RemoveFakeCharacteristic_Params.prototype.initDefaults_ = function() {
+ this.identifier = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_RemoveFakeCharacteristic_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_RemoveFakeCharacteristic_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 32}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_RemoveFakeCharacteristic_Params.identifier
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_RemoveFakeCharacteristic_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_RemoveFakeCharacteristic_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_RemoveFakeCharacteristic_Params.encodedSize = codec.kStructHeaderSize + 24;
+
+ FakeCentral_RemoveFakeCharacteristic_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_RemoveFakeCharacteristic_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.identifier = decoder.decodeStruct(codec.String);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_RemoveFakeCharacteristic_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_RemoveFakeCharacteristic_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.identifier);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_RemoveFakeCharacteristic_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_RemoveFakeCharacteristic_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_RemoveFakeCharacteristic_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_RemoveFakeCharacteristic_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_RemoveFakeCharacteristic_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_RemoveFakeCharacteristic_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_RemoveFakeCharacteristic_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_RemoveFakeCharacteristic_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_RemoveFakeCharacteristic_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_AddFakeDescriptor_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_AddFakeDescriptor_Params.prototype.initDefaults_ = function() {
+ this.descriptorUuid = null;
+ this.characteristicId = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_AddFakeDescriptor_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_AddFakeDescriptor_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 40}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeDescriptor_Params.descriptorUuid
+ err = messageValidator.validateStructPointer(offset + codec.kStructHeaderSize + 0, uuid$.UUID, false);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeDescriptor_Params.characteristicId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeDescriptor_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeDescriptor_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_AddFakeDescriptor_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+ FakeCentral_AddFakeDescriptor_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_AddFakeDescriptor_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.descriptorUuid = decoder.decodeStructPointer(uuid$.UUID);
+ val.characteristicId = decoder.decodeStruct(codec.String);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_AddFakeDescriptor_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_AddFakeDescriptor_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStructPointer(uuid$.UUID, val.descriptorUuid);
+ encoder.encodeStruct(codec.String, val.characteristicId);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_AddFakeDescriptor_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_AddFakeDescriptor_ResponseParams.prototype.initDefaults_ = function() {
+ this.descriptorId = null;
+ };
+ FakeCentral_AddFakeDescriptor_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_AddFakeDescriptor_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_AddFakeDescriptor_ResponseParams.descriptorId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, true)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_AddFakeDescriptor_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_AddFakeDescriptor_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_AddFakeDescriptor_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.descriptorId = decoder.decodeStruct(codec.NullableString);
+ return val;
+ };
+
+ FakeCentral_AddFakeDescriptor_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_AddFakeDescriptor_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.NullableString, val.descriptorId);
+ };
+ function FakeCentral_RemoveFakeDescriptor_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_RemoveFakeDescriptor_Params.prototype.initDefaults_ = function() {
+ this.descriptorId = null;
+ this.characteristicId = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_RemoveFakeDescriptor_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_RemoveFakeDescriptor_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 40}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_RemoveFakeDescriptor_Params.descriptorId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_RemoveFakeDescriptor_Params.characteristicId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_RemoveFakeDescriptor_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_RemoveFakeDescriptor_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_RemoveFakeDescriptor_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+ FakeCentral_RemoveFakeDescriptor_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_RemoveFakeDescriptor_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.descriptorId = decoder.decodeStruct(codec.String);
+ val.characteristicId = decoder.decodeStruct(codec.String);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_RemoveFakeDescriptor_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_RemoveFakeDescriptor_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.descriptorId);
+ encoder.encodeStruct(codec.String, val.characteristicId);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_RemoveFakeDescriptor_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_RemoveFakeDescriptor_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_RemoveFakeDescriptor_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_RemoveFakeDescriptor_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_RemoveFakeDescriptor_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_RemoveFakeDescriptor_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_RemoveFakeDescriptor_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_RemoveFakeDescriptor_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_RemoveFakeDescriptor_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_SetNextReadCharacteristicResponse_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextReadCharacteristicResponse_Params.prototype.initDefaults_ = function() {
+ this.gattCode = 0;
+ this.value = null;
+ this.characteristicId = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_SetNextReadCharacteristicResponse_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextReadCharacteristicResponse_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 48}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate FakeCentral_SetNextReadCharacteristicResponse_Params.value
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, true, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextReadCharacteristicResponse_Params.characteristicId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextReadCharacteristicResponse_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextReadCharacteristicResponse_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 32, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextReadCharacteristicResponse_Params.encodedSize = codec.kStructHeaderSize + 40;
+
+ FakeCentral_SetNextReadCharacteristicResponse_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextReadCharacteristicResponse_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.gattCode = decoder.decodeStruct(codec.Uint16);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.value = decoder.decodeArrayPointer(codec.Uint8);
+ val.characteristicId = decoder.decodeStruct(codec.String);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_SetNextReadCharacteristicResponse_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextReadCharacteristicResponse_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint16, val.gattCode);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeArrayPointer(codec.Uint8, val.value);
+ encoder.encodeStruct(codec.String, val.characteristicId);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_SetNextReadCharacteristicResponse_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextReadCharacteristicResponse_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_SetNextWriteCharacteristicResponse_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextWriteCharacteristicResponse_Params.prototype.initDefaults_ = function() {
+ this.gattCode = 0;
+ this.characteristicId = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_SetNextWriteCharacteristicResponse_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextWriteCharacteristicResponse_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 40}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate FakeCentral_SetNextWriteCharacteristicResponse_Params.characteristicId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextWriteCharacteristicResponse_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextWriteCharacteristicResponse_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextWriteCharacteristicResponse_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+ FakeCentral_SetNextWriteCharacteristicResponse_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextWriteCharacteristicResponse_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.gattCode = decoder.decodeStruct(codec.Uint16);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.characteristicId = decoder.decodeStruct(codec.String);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_SetNextWriteCharacteristicResponse_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextWriteCharacteristicResponse_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint16, val.gattCode);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStruct(codec.String, val.characteristicId);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_SetNextSubscribeToNotificationsResponse_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextSubscribeToNotificationsResponse_Params.prototype.initDefaults_ = function() {
+ this.gattCode = 0;
+ this.characteristicId = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_SetNextSubscribeToNotificationsResponse_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextSubscribeToNotificationsResponse_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 40}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate FakeCentral_SetNextSubscribeToNotificationsResponse_Params.characteristicId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextSubscribeToNotificationsResponse_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextSubscribeToNotificationsResponse_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextSubscribeToNotificationsResponse_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+ FakeCentral_SetNextSubscribeToNotificationsResponse_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextSubscribeToNotificationsResponse_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.gattCode = decoder.decodeStruct(codec.Uint16);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.characteristicId = decoder.decodeStruct(codec.String);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_SetNextSubscribeToNotificationsResponse_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextSubscribeToNotificationsResponse_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint16, val.gattCode);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStruct(codec.String, val.characteristicId);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.prototype.initDefaults_ = function() {
+ this.gattCode = 0;
+ this.characteristicId = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 40}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.characteristicId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+ FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.gattCode = decoder.decodeStruct(codec.Uint16);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.characteristicId = decoder.decodeStruct(codec.String);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint16, val.gattCode);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStruct(codec.String, val.characteristicId);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_IsNotifying_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_IsNotifying_Params.prototype.initDefaults_ = function() {
+ this.characteristicId = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_IsNotifying_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_IsNotifying_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 32}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_IsNotifying_Params.characteristicId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_IsNotifying_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_IsNotifying_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_IsNotifying_Params.encodedSize = codec.kStructHeaderSize + 24;
+
+ FakeCentral_IsNotifying_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_IsNotifying_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.characteristicId = decoder.decodeStruct(codec.String);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_IsNotifying_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_IsNotifying_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.characteristicId);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_IsNotifying_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_IsNotifying_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ this.isNotifying = false;
+ };
+ FakeCentral_IsNotifying_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_IsNotifying_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_IsNotifying_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_IsNotifying_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_IsNotifying_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ val.isNotifying = (packed >> 1) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_IsNotifying_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_IsNotifying_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ packed |= (val.isNotifying & 1) << 1
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_GetLastWrittenCharacteristicValue_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_GetLastWrittenCharacteristicValue_Params.prototype.initDefaults_ = function() {
+ this.characteristicId = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_GetLastWrittenCharacteristicValue_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_GetLastWrittenCharacteristicValue_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 32}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_GetLastWrittenCharacteristicValue_Params.characteristicId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_GetLastWrittenCharacteristicValue_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_GetLastWrittenCharacteristicValue_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_GetLastWrittenCharacteristicValue_Params.encodedSize = codec.kStructHeaderSize + 24;
+
+ FakeCentral_GetLastWrittenCharacteristicValue_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_GetLastWrittenCharacteristicValue_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.characteristicId = decoder.decodeStruct(codec.String);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_GetLastWrittenCharacteristicValue_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_GetLastWrittenCharacteristicValue_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.characteristicId);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ this.value = null;
+ };
+ FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.value
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, true, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.encodedSize = codec.kStructHeaderSize + 16;
+
+ FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.value = decoder.decodeArrayPointer(codec.Uint8);
+ return val;
+ };
+
+ FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeArrayPointer(codec.Uint8, val.value);
+ };
+ function FakeCentral_SetNextReadDescriptorResponse_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextReadDescriptorResponse_Params.prototype.initDefaults_ = function() {
+ this.gattCode = 0;
+ this.value = null;
+ this.descriptorId = null;
+ this.characteristicId = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_SetNextReadDescriptorResponse_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextReadDescriptorResponse_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 56}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate FakeCentral_SetNextReadDescriptorResponse_Params.value
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, true, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextReadDescriptorResponse_Params.descriptorId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextReadDescriptorResponse_Params.characteristicId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextReadDescriptorResponse_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 32, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextReadDescriptorResponse_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 40, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextReadDescriptorResponse_Params.encodedSize = codec.kStructHeaderSize + 48;
+
+ FakeCentral_SetNextReadDescriptorResponse_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextReadDescriptorResponse_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.gattCode = decoder.decodeStruct(codec.Uint16);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.value = decoder.decodeArrayPointer(codec.Uint8);
+ val.descriptorId = decoder.decodeStruct(codec.String);
+ val.characteristicId = decoder.decodeStruct(codec.String);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_SetNextReadDescriptorResponse_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextReadDescriptorResponse_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint16, val.gattCode);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeArrayPointer(codec.Uint8, val.value);
+ encoder.encodeStruct(codec.String, val.descriptorId);
+ encoder.encodeStruct(codec.String, val.characteristicId);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_SetNextReadDescriptorResponse_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextReadDescriptorResponse_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_SetNextReadDescriptorResponse_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextReadDescriptorResponse_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextReadDescriptorResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SetNextReadDescriptorResponse_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextReadDescriptorResponse_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_SetNextReadDescriptorResponse_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextReadDescriptorResponse_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_SetNextWriteDescriptorResponse_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextWriteDescriptorResponse_Params.prototype.initDefaults_ = function() {
+ this.gattCode = 0;
+ this.descriptorId = null;
+ this.characteristicId = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_SetNextWriteDescriptorResponse_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextWriteDescriptorResponse_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 48}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate FakeCentral_SetNextWriteDescriptorResponse_Params.descriptorId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextWriteDescriptorResponse_Params.characteristicId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextWriteDescriptorResponse_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_SetNextWriteDescriptorResponse_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 32, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextWriteDescriptorResponse_Params.encodedSize = codec.kStructHeaderSize + 40;
+
+ FakeCentral_SetNextWriteDescriptorResponse_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextWriteDescriptorResponse_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.gattCode = decoder.decodeStruct(codec.Uint16);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.descriptorId = decoder.decodeStruct(codec.String);
+ val.characteristicId = decoder.decodeStruct(codec.String);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_SetNextWriteDescriptorResponse_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextWriteDescriptorResponse_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint16, val.gattCode);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStruct(codec.String, val.descriptorId);
+ encoder.encodeStruct(codec.String, val.characteristicId);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_SetNextWriteDescriptorResponse_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ };
+ FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_SetNextWriteDescriptorResponse_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeCentral_GetLastWrittenDescriptorValue_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_GetLastWrittenDescriptorValue_Params.prototype.initDefaults_ = function() {
+ this.descriptorId = null;
+ this.characteristicId = null;
+ this.serviceId = null;
+ this.peripheralAddress = null;
+ };
+ FakeCentral_GetLastWrittenDescriptorValue_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_GetLastWrittenDescriptorValue_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 40}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_GetLastWrittenDescriptorValue_Params.descriptorId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_GetLastWrittenDescriptorValue_Params.characteristicId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_GetLastWrittenDescriptorValue_Params.serviceId
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeCentral_GetLastWrittenDescriptorValue_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 24, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_GetLastWrittenDescriptorValue_Params.encodedSize = codec.kStructHeaderSize + 32;
+
+ FakeCentral_GetLastWrittenDescriptorValue_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_GetLastWrittenDescriptorValue_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.descriptorId = decoder.decodeStruct(codec.String);
+ val.characteristicId = decoder.decodeStruct(codec.String);
+ val.serviceId = decoder.decodeStruct(codec.String);
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeCentral_GetLastWrittenDescriptorValue_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_GetLastWrittenDescriptorValue_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.descriptorId);
+ encoder.encodeStruct(codec.String, val.characteristicId);
+ encoder.encodeStruct(codec.String, val.serviceId);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeCentral_GetLastWrittenDescriptorValue_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.prototype.initDefaults_ = function() {
+ this.success = false;
+ this.value = null;
+ };
+ FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 24}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+
+ // validate FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.value
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 8, 1, codec.Uint8, true, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.encodedSize = codec.kStructHeaderSize + 16;
+
+ FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeCentral_GetLastWrittenDescriptorValue_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ packed = decoder.readUint8();
+ val.success = (packed >> 0) & 1 ? true : false;
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.value = decoder.decodeArrayPointer(codec.Uint8);
+ return val;
+ };
+
+ FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ packed = 0;
+ packed |= (val.success & 1) << 0
+ encoder.writeUint8(packed);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeArrayPointer(codec.Uint8, val.value);
+ };
+ var kFakeBluetooth_SetLESupported_Name = 0;
+ var kFakeBluetooth_SimulateCentral_Name = 1;
+ var kFakeBluetooth_AllResponsesConsumed_Name = 2;
+
+ function FakeBluetoothPtr(handleOrPtrInfo) {
+ this.ptr = new bindings.InterfacePtrController(FakeBluetooth,
+ handleOrPtrInfo);
+ }
+
+ function FakeBluetoothAssociatedPtr(associatedInterfacePtrInfo) {
+ this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+ FakeBluetooth, associatedInterfacePtrInfo);
+ }
+
+ FakeBluetoothAssociatedPtr.prototype =
+ Object.create(FakeBluetoothPtr.prototype);
+ FakeBluetoothAssociatedPtr.prototype.constructor =
+ FakeBluetoothAssociatedPtr;
+
+ function FakeBluetoothProxy(receiver) {
+ this.receiver_ = receiver;
+ }
+ FakeBluetoothPtr.prototype.setLESupported = function() {
+ return FakeBluetoothProxy.prototype.setLESupported
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeBluetoothProxy.prototype.setLESupported = function(available) {
+ var params = new FakeBluetooth_SetLESupported_Params();
+ params.available = available;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetooth_SetLESupported_Name,
+ codec.align(FakeBluetooth_SetLESupported_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeBluetooth_SetLESupported_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeBluetooth_SetLESupported_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeBluetoothPtr.prototype.simulateCentral = function() {
+ return FakeBluetoothProxy.prototype.simulateCentral
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeBluetoothProxy.prototype.simulateCentral = function(state) {
+ var params = new FakeBluetooth_SimulateCentral_Params();
+ params.state = state;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetooth_SimulateCentral_Name,
+ codec.align(FakeBluetooth_SimulateCentral_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeBluetooth_SimulateCentral_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeBluetooth_SimulateCentral_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeBluetoothPtr.prototype.allResponsesConsumed = function() {
+ return FakeBluetoothProxy.prototype.allResponsesConsumed
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeBluetoothProxy.prototype.allResponsesConsumed = function() {
+ var params = new FakeBluetooth_AllResponsesConsumed_Params();
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetooth_AllResponsesConsumed_Name,
+ codec.align(FakeBluetooth_AllResponsesConsumed_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeBluetooth_AllResponsesConsumed_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeBluetooth_AllResponsesConsumed_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+
+ function FakeBluetoothStub(delegate) {
+ this.delegate_ = delegate;
+ }
+ FakeBluetoothStub.prototype.setLESupported = function(available) {
+ return this.delegate_ && this.delegate_.setLESupported && this.delegate_.setLESupported(available);
+ }
+ FakeBluetoothStub.prototype.simulateCentral = function(state) {
+ return this.delegate_ && this.delegate_.simulateCentral && this.delegate_.simulateCentral(state);
+ }
+ FakeBluetoothStub.prototype.allResponsesConsumed = function() {
+ return this.delegate_ && this.delegate_.allResponsesConsumed && this.delegate_.allResponsesConsumed();
+ }
+
+ FakeBluetoothStub.prototype.accept = function(message) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+ default:
+ return false;
+ }
+ };
+
+ FakeBluetoothStub.prototype.acceptWithResponder =
+ function(message, responder) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+ case kFakeBluetooth_SetLESupported_Name:
+ var params = reader.decodeStruct(FakeBluetooth_SetLESupported_Params);
+ this.setLESupported(params.available).then(function(response) {
+ var responseParams =
+ new FakeBluetooth_SetLESupported_ResponseParams();
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetooth_SetLESupported_Name,
+ codec.align(FakeBluetooth_SetLESupported_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeBluetooth_SetLESupported_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeBluetooth_SimulateCentral_Name:
+ var params = reader.decodeStruct(FakeBluetooth_SimulateCentral_Params);
+ this.simulateCentral(params.state).then(function(response) {
+ var responseParams =
+ new FakeBluetooth_SimulateCentral_ResponseParams();
+ responseParams.fakeCentral = response.fakeCentral;
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetooth_SimulateCentral_Name,
+ codec.align(FakeBluetooth_SimulateCentral_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeBluetooth_SimulateCentral_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeBluetooth_AllResponsesConsumed_Name:
+ var params = reader.decodeStruct(FakeBluetooth_AllResponsesConsumed_Params);
+ this.allResponsesConsumed().then(function(response) {
+ var responseParams =
+ new FakeBluetooth_AllResponsesConsumed_ResponseParams();
+ responseParams.consumed = response.consumed;
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetooth_AllResponsesConsumed_Name,
+ codec.align(FakeBluetooth_AllResponsesConsumed_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeBluetooth_AllResponsesConsumed_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ function validateFakeBluetoothRequest(messageValidator) {
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+ case kFakeBluetooth_SetLESupported_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeBluetooth_SetLESupported_Params;
+ break;
+ case kFakeBluetooth_SimulateCentral_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeBluetooth_SimulateCentral_Params;
+ break;
+ case kFakeBluetooth_AllResponsesConsumed_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeBluetooth_AllResponsesConsumed_Params;
+ break;
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+ }
+
+ function validateFakeBluetoothResponse(messageValidator) {
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+ case kFakeBluetooth_SetLESupported_Name:
+ if (message.isResponse())
+ paramsClass = FakeBluetooth_SetLESupported_ResponseParams;
+ break;
+ case kFakeBluetooth_SimulateCentral_Name:
+ if (message.isResponse())
+ paramsClass = FakeBluetooth_SimulateCentral_ResponseParams;
+ break;
+ case kFakeBluetooth_AllResponsesConsumed_Name:
+ if (message.isResponse())
+ paramsClass = FakeBluetooth_AllResponsesConsumed_ResponseParams;
+ break;
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+ }
+
+ var FakeBluetooth = {
+ name: 'bluetooth.mojom.FakeBluetooth',
+ kVersion: 0,
+ ptrClass: FakeBluetoothPtr,
+ proxyClass: FakeBluetoothProxy,
+ stubClass: FakeBluetoothStub,
+ validateRequest: validateFakeBluetoothRequest,
+ validateResponse: validateFakeBluetoothResponse,
+ };
+ FakeBluetoothStub.prototype.validator = validateFakeBluetoothRequest;
+ FakeBluetoothProxy.prototype.validator = validateFakeBluetoothResponse;
+ var kFakeCentral_SimulatePreconnectedPeripheral_Name = 0;
+ var kFakeCentral_SimulateAdvertisementReceived_Name = 1;
+ var kFakeCentral_SetNextGATTConnectionResponse_Name = 2;
+ var kFakeCentral_SetNextGATTDiscoveryResponse_Name = 3;
+ var kFakeCentral_SimulateGATTDisconnection_Name = 4;
+ var kFakeCentral_SimulateGATTServicesChanged_Name = 5;
+ var kFakeCentral_AddFakeService_Name = 6;
+ var kFakeCentral_RemoveFakeService_Name = 7;
+ var kFakeCentral_AddFakeCharacteristic_Name = 8;
+ var kFakeCentral_RemoveFakeCharacteristic_Name = 9;
+ var kFakeCentral_AddFakeDescriptor_Name = 10;
+ var kFakeCentral_RemoveFakeDescriptor_Name = 11;
+ var kFakeCentral_SetNextReadCharacteristicResponse_Name = 12;
+ var kFakeCentral_SetNextWriteCharacteristicResponse_Name = 13;
+ var kFakeCentral_SetNextSubscribeToNotificationsResponse_Name = 14;
+ var kFakeCentral_SetNextUnsubscribeFromNotificationsResponse_Name = 15;
+ var kFakeCentral_IsNotifying_Name = 16;
+ var kFakeCentral_GetLastWrittenCharacteristicValue_Name = 17;
+ var kFakeCentral_SetNextReadDescriptorResponse_Name = 18;
+ var kFakeCentral_SetNextWriteDescriptorResponse_Name = 19;
+ var kFakeCentral_GetLastWrittenDescriptorValue_Name = 20;
+
+ function FakeCentralPtr(handleOrPtrInfo) {
+ this.ptr = new bindings.InterfacePtrController(FakeCentral,
+ handleOrPtrInfo);
+ }
+
+ function FakeCentralAssociatedPtr(associatedInterfacePtrInfo) {
+ this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+ FakeCentral, associatedInterfacePtrInfo);
+ }
+
+ FakeCentralAssociatedPtr.prototype =
+ Object.create(FakeCentralPtr.prototype);
+ FakeCentralAssociatedPtr.prototype.constructor =
+ FakeCentralAssociatedPtr;
+
+ function FakeCentralProxy(receiver) {
+ this.receiver_ = receiver;
+ }
+ FakeCentralPtr.prototype.simulatePreconnectedPeripheral = function() {
+ return FakeCentralProxy.prototype.simulatePreconnectedPeripheral
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.simulatePreconnectedPeripheral = function(address, name, knownServiceUuids) {
+ var params = new FakeCentral_SimulatePreconnectedPeripheral_Params();
+ params.address = address;
+ params.name = name;
+ params.knownServiceUuids = knownServiceUuids;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SimulatePreconnectedPeripheral_Name,
+ codec.align(FakeCentral_SimulatePreconnectedPeripheral_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_SimulatePreconnectedPeripheral_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_SimulatePreconnectedPeripheral_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.simulateAdvertisementReceived = function() {
+ return FakeCentralProxy.prototype.simulateAdvertisementReceived
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.simulateAdvertisementReceived = function(result) {
+ var params = new FakeCentral_SimulateAdvertisementReceived_Params();
+ params.result = result;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SimulateAdvertisementReceived_Name,
+ codec.align(FakeCentral_SimulateAdvertisementReceived_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_SimulateAdvertisementReceived_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_SimulateAdvertisementReceived_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.setNextGATTConnectionResponse = function() {
+ return FakeCentralProxy.prototype.setNextGATTConnectionResponse
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.setNextGATTConnectionResponse = function(address, code) {
+ var params = new FakeCentral_SetNextGATTConnectionResponse_Params();
+ params.address = address;
+ params.code = code;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextGATTConnectionResponse_Name,
+ codec.align(FakeCentral_SetNextGATTConnectionResponse_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_SetNextGATTConnectionResponse_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_SetNextGATTConnectionResponse_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.setNextGATTDiscoveryResponse = function() {
+ return FakeCentralProxy.prototype.setNextGATTDiscoveryResponse
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.setNextGATTDiscoveryResponse = function(address, code) {
+ var params = new FakeCentral_SetNextGATTDiscoveryResponse_Params();
+ params.address = address;
+ params.code = code;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextGATTDiscoveryResponse_Name,
+ codec.align(FakeCentral_SetNextGATTDiscoveryResponse_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_SetNextGATTDiscoveryResponse_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.simulateGATTDisconnection = function() {
+ return FakeCentralProxy.prototype.simulateGATTDisconnection
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.simulateGATTDisconnection = function(address) {
+ var params = new FakeCentral_SimulateGATTDisconnection_Params();
+ params.address = address;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SimulateGATTDisconnection_Name,
+ codec.align(FakeCentral_SimulateGATTDisconnection_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_SimulateGATTDisconnection_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_SimulateGATTDisconnection_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.simulateGATTServicesChanged = function() {
+ return FakeCentralProxy.prototype.simulateGATTServicesChanged
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.simulateGATTServicesChanged = function(address) {
+ var params = new FakeCentral_SimulateGATTServicesChanged_Params();
+ params.address = address;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SimulateGATTServicesChanged_Name,
+ codec.align(FakeCentral_SimulateGATTServicesChanged_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_SimulateGATTServicesChanged_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_SimulateGATTServicesChanged_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.addFakeService = function() {
+ return FakeCentralProxy.prototype.addFakeService
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.addFakeService = function(peripheralAddress, serviceUuid) {
+ var params = new FakeCentral_AddFakeService_Params();
+ params.peripheralAddress = peripheralAddress;
+ params.serviceUuid = serviceUuid;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_AddFakeService_Name,
+ codec.align(FakeCentral_AddFakeService_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_AddFakeService_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_AddFakeService_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.removeFakeService = function() {
+ return FakeCentralProxy.prototype.removeFakeService
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.removeFakeService = function(serviceId, peripheralAddress) {
+ var params = new FakeCentral_RemoveFakeService_Params();
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_RemoveFakeService_Name,
+ codec.align(FakeCentral_RemoveFakeService_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_RemoveFakeService_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_RemoveFakeService_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.addFakeCharacteristic = function() {
+ return FakeCentralProxy.prototype.addFakeCharacteristic
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.addFakeCharacteristic = function(characteristicUuid, properties, serviceId, peripheralAddress) {
+ var params = new FakeCentral_AddFakeCharacteristic_Params();
+ params.characteristicUuid = characteristicUuid;
+ params.properties = properties;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_AddFakeCharacteristic_Name,
+ codec.align(FakeCentral_AddFakeCharacteristic_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_AddFakeCharacteristic_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_AddFakeCharacteristic_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.removeFakeCharacteristic = function() {
+ return FakeCentralProxy.prototype.removeFakeCharacteristic
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.removeFakeCharacteristic = function(identifier, serviceId, peripheralAddress) {
+ var params = new FakeCentral_RemoveFakeCharacteristic_Params();
+ params.identifier = identifier;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_RemoveFakeCharacteristic_Name,
+ codec.align(FakeCentral_RemoveFakeCharacteristic_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_RemoveFakeCharacteristic_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_RemoveFakeCharacteristic_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.addFakeDescriptor = function() {
+ return FakeCentralProxy.prototype.addFakeDescriptor
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.addFakeDescriptor = function(descriptorUuid, characteristicId, serviceId, peripheralAddress) {
+ var params = new FakeCentral_AddFakeDescriptor_Params();
+ params.descriptorUuid = descriptorUuid;
+ params.characteristicId = characteristicId;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_AddFakeDescriptor_Name,
+ codec.align(FakeCentral_AddFakeDescriptor_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_AddFakeDescriptor_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_AddFakeDescriptor_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.removeFakeDescriptor = function() {
+ return FakeCentralProxy.prototype.removeFakeDescriptor
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.removeFakeDescriptor = function(descriptorId, characteristicId, serviceId, peripheralAddress) {
+ var params = new FakeCentral_RemoveFakeDescriptor_Params();
+ params.descriptorId = descriptorId;
+ params.characteristicId = characteristicId;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_RemoveFakeDescriptor_Name,
+ codec.align(FakeCentral_RemoveFakeDescriptor_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_RemoveFakeDescriptor_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_RemoveFakeDescriptor_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.setNextReadCharacteristicResponse = function() {
+ return FakeCentralProxy.prototype.setNextReadCharacteristicResponse
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.setNextReadCharacteristicResponse = function(gattCode, value, characteristicId, serviceId, peripheralAddress) {
+ var params = new FakeCentral_SetNextReadCharacteristicResponse_Params();
+ params.gattCode = gattCode;
+ params.value = value;
+ params.characteristicId = characteristicId;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextReadCharacteristicResponse_Name,
+ codec.align(FakeCentral_SetNextReadCharacteristicResponse_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_SetNextReadCharacteristicResponse_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_SetNextReadCharacteristicResponse_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.setNextWriteCharacteristicResponse = function() {
+ return FakeCentralProxy.prototype.setNextWriteCharacteristicResponse
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.setNextWriteCharacteristicResponse = function(gattCode, characteristicId, serviceId, peripheralAddress) {
+ var params = new FakeCentral_SetNextWriteCharacteristicResponse_Params();
+ params.gattCode = gattCode;
+ params.characteristicId = characteristicId;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextWriteCharacteristicResponse_Name,
+ codec.align(FakeCentral_SetNextWriteCharacteristicResponse_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_SetNextWriteCharacteristicResponse_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.setNextSubscribeToNotificationsResponse = function() {
+ return FakeCentralProxy.prototype.setNextSubscribeToNotificationsResponse
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.setNextSubscribeToNotificationsResponse = function(gattCode, characteristicId, serviceId, peripheralAddress) {
+ var params = new FakeCentral_SetNextSubscribeToNotificationsResponse_Params();
+ params.gattCode = gattCode;
+ params.characteristicId = characteristicId;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextSubscribeToNotificationsResponse_Name,
+ codec.align(FakeCentral_SetNextSubscribeToNotificationsResponse_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_SetNextSubscribeToNotificationsResponse_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.setNextUnsubscribeFromNotificationsResponse = function() {
+ return FakeCentralProxy.prototype.setNextUnsubscribeFromNotificationsResponse
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.setNextUnsubscribeFromNotificationsResponse = function(gattCode, characteristicId, serviceId, peripheralAddress) {
+ var params = new FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params();
+ params.gattCode = gattCode;
+ params.characteristicId = characteristicId;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextUnsubscribeFromNotificationsResponse_Name,
+ codec.align(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.isNotifying = function() {
+ return FakeCentralProxy.prototype.isNotifying
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.isNotifying = function(characteristicId, serviceId, peripheralAddress) {
+ var params = new FakeCentral_IsNotifying_Params();
+ params.characteristicId = characteristicId;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_IsNotifying_Name,
+ codec.align(FakeCentral_IsNotifying_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_IsNotifying_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_IsNotifying_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.getLastWrittenCharacteristicValue = function() {
+ return FakeCentralProxy.prototype.getLastWrittenCharacteristicValue
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.getLastWrittenCharacteristicValue = function(characteristicId, serviceId, peripheralAddress) {
+ var params = new FakeCentral_GetLastWrittenCharacteristicValue_Params();
+ params.characteristicId = characteristicId;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_GetLastWrittenCharacteristicValue_Name,
+ codec.align(FakeCentral_GetLastWrittenCharacteristicValue_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_GetLastWrittenCharacteristicValue_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.setNextReadDescriptorResponse = function() {
+ return FakeCentralProxy.prototype.setNextReadDescriptorResponse
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.setNextReadDescriptorResponse = function(gattCode, value, descriptorId, characteristicId, serviceId, peripheralAddress) {
+ var params = new FakeCentral_SetNextReadDescriptorResponse_Params();
+ params.gattCode = gattCode;
+ params.value = value;
+ params.descriptorId = descriptorId;
+ params.characteristicId = characteristicId;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextReadDescriptorResponse_Name,
+ codec.align(FakeCentral_SetNextReadDescriptorResponse_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_SetNextReadDescriptorResponse_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_SetNextReadDescriptorResponse_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.setNextWriteDescriptorResponse = function() {
+ return FakeCentralProxy.prototype.setNextWriteDescriptorResponse
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.setNextWriteDescriptorResponse = function(gattCode, descriptorId, characteristicId, serviceId, peripheralAddress) {
+ var params = new FakeCentral_SetNextWriteDescriptorResponse_Params();
+ params.gattCode = gattCode;
+ params.descriptorId = descriptorId;
+ params.characteristicId = characteristicId;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextWriteDescriptorResponse_Name,
+ codec.align(FakeCentral_SetNextWriteDescriptorResponse_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_SetNextWriteDescriptorResponse_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_SetNextWriteDescriptorResponse_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeCentralPtr.prototype.getLastWrittenDescriptorValue = function() {
+ return FakeCentralProxy.prototype.getLastWrittenDescriptorValue
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeCentralProxy.prototype.getLastWrittenDescriptorValue = function(descriptorId, characteristicId, serviceId, peripheralAddress) {
+ var params = new FakeCentral_GetLastWrittenDescriptorValue_Params();
+ params.descriptorId = descriptorId;
+ params.characteristicId = characteristicId;
+ params.serviceId = serviceId;
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_GetLastWrittenDescriptorValue_Name,
+ codec.align(FakeCentral_GetLastWrittenDescriptorValue_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeCentral_GetLastWrittenDescriptorValue_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeCentral_GetLastWrittenDescriptorValue_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+
+ function FakeCentralStub(delegate) {
+ this.delegate_ = delegate;
+ }
+ FakeCentralStub.prototype.simulatePreconnectedPeripheral = function(address, name, knownServiceUuids) {
+ return this.delegate_ && this.delegate_.simulatePreconnectedPeripheral && this.delegate_.simulatePreconnectedPeripheral(address, name, knownServiceUuids);
+ }
+ FakeCentralStub.prototype.simulateAdvertisementReceived = function(result) {
+ return this.delegate_ && this.delegate_.simulateAdvertisementReceived && this.delegate_.simulateAdvertisementReceived(result);
+ }
+ FakeCentralStub.prototype.setNextGATTConnectionResponse = function(address, code) {
+ return this.delegate_ && this.delegate_.setNextGATTConnectionResponse && this.delegate_.setNextGATTConnectionResponse(address, code);
+ }
+ FakeCentralStub.prototype.setNextGATTDiscoveryResponse = function(address, code) {
+ return this.delegate_ && this.delegate_.setNextGATTDiscoveryResponse && this.delegate_.setNextGATTDiscoveryResponse(address, code);
+ }
+ FakeCentralStub.prototype.simulateGATTDisconnection = function(address) {
+ return this.delegate_ && this.delegate_.simulateGATTDisconnection && this.delegate_.simulateGATTDisconnection(address);
+ }
+ FakeCentralStub.prototype.simulateGATTServicesChanged = function(address) {
+ return this.delegate_ && this.delegate_.simulateGATTServicesChanged && this.delegate_.simulateGATTServicesChanged(address);
+ }
+ FakeCentralStub.prototype.addFakeService = function(peripheralAddress, serviceUuid) {
+ return this.delegate_ && this.delegate_.addFakeService && this.delegate_.addFakeService(peripheralAddress, serviceUuid);
+ }
+ FakeCentralStub.prototype.removeFakeService = function(serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.removeFakeService && this.delegate_.removeFakeService(serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.addFakeCharacteristic = function(characteristicUuid, properties, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.addFakeCharacteristic && this.delegate_.addFakeCharacteristic(characteristicUuid, properties, serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.removeFakeCharacteristic = function(identifier, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.removeFakeCharacteristic && this.delegate_.removeFakeCharacteristic(identifier, serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.addFakeDescriptor = function(descriptorUuid, characteristicId, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.addFakeDescriptor && this.delegate_.addFakeDescriptor(descriptorUuid, characteristicId, serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.removeFakeDescriptor = function(descriptorId, characteristicId, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.removeFakeDescriptor && this.delegate_.removeFakeDescriptor(descriptorId, characteristicId, serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.setNextReadCharacteristicResponse = function(gattCode, value, characteristicId, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.setNextReadCharacteristicResponse && this.delegate_.setNextReadCharacteristicResponse(gattCode, value, characteristicId, serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.setNextWriteCharacteristicResponse = function(gattCode, characteristicId, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.setNextWriteCharacteristicResponse && this.delegate_.setNextWriteCharacteristicResponse(gattCode, characteristicId, serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.setNextSubscribeToNotificationsResponse = function(gattCode, characteristicId, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.setNextSubscribeToNotificationsResponse && this.delegate_.setNextSubscribeToNotificationsResponse(gattCode, characteristicId, serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.setNextUnsubscribeFromNotificationsResponse = function(gattCode, characteristicId, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.setNextUnsubscribeFromNotificationsResponse && this.delegate_.setNextUnsubscribeFromNotificationsResponse(gattCode, characteristicId, serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.isNotifying = function(characteristicId, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.isNotifying && this.delegate_.isNotifying(characteristicId, serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.getLastWrittenCharacteristicValue = function(characteristicId, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.getLastWrittenCharacteristicValue && this.delegate_.getLastWrittenCharacteristicValue(characteristicId, serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.setNextReadDescriptorResponse = function(gattCode, value, descriptorId, characteristicId, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.setNextReadDescriptorResponse && this.delegate_.setNextReadDescriptorResponse(gattCode, value, descriptorId, characteristicId, serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.setNextWriteDescriptorResponse = function(gattCode, descriptorId, characteristicId, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.setNextWriteDescriptorResponse && this.delegate_.setNextWriteDescriptorResponse(gattCode, descriptorId, characteristicId, serviceId, peripheralAddress);
+ }
+ FakeCentralStub.prototype.getLastWrittenDescriptorValue = function(descriptorId, characteristicId, serviceId, peripheralAddress) {
+ return this.delegate_ && this.delegate_.getLastWrittenDescriptorValue && this.delegate_.getLastWrittenDescriptorValue(descriptorId, characteristicId, serviceId, peripheralAddress);
+ }
+
+ FakeCentralStub.prototype.accept = function(message) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+ default:
+ return false;
+ }
+ };
+
+ FakeCentralStub.prototype.acceptWithResponder =
+ function(message, responder) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+ case kFakeCentral_SimulatePreconnectedPeripheral_Name:
+ var params = reader.decodeStruct(FakeCentral_SimulatePreconnectedPeripheral_Params);
+ this.simulatePreconnectedPeripheral(params.address, params.name, params.knownServiceUuids).then(function(response) {
+ var responseParams =
+ new FakeCentral_SimulatePreconnectedPeripheral_ResponseParams();
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SimulatePreconnectedPeripheral_Name,
+ codec.align(FakeCentral_SimulatePreconnectedPeripheral_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_SimulatePreconnectedPeripheral_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_SimulateAdvertisementReceived_Name:
+ var params = reader.decodeStruct(FakeCentral_SimulateAdvertisementReceived_Params);
+ this.simulateAdvertisementReceived(params.result).then(function(response) {
+ var responseParams =
+ new FakeCentral_SimulateAdvertisementReceived_ResponseParams();
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SimulateAdvertisementReceived_Name,
+ codec.align(FakeCentral_SimulateAdvertisementReceived_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_SimulateAdvertisementReceived_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_SetNextGATTConnectionResponse_Name:
+ var params = reader.decodeStruct(FakeCentral_SetNextGATTConnectionResponse_Params);
+ this.setNextGATTConnectionResponse(params.address, params.code).then(function(response) {
+ var responseParams =
+ new FakeCentral_SetNextGATTConnectionResponse_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextGATTConnectionResponse_Name,
+ codec.align(FakeCentral_SetNextGATTConnectionResponse_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_SetNextGATTConnectionResponse_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_SetNextGATTDiscoveryResponse_Name:
+ var params = reader.decodeStruct(FakeCentral_SetNextGATTDiscoveryResponse_Params);
+ this.setNextGATTDiscoveryResponse(params.address, params.code).then(function(response) {
+ var responseParams =
+ new FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextGATTDiscoveryResponse_Name,
+ codec.align(FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_SimulateGATTDisconnection_Name:
+ var params = reader.decodeStruct(FakeCentral_SimulateGATTDisconnection_Params);
+ this.simulateGATTDisconnection(params.address).then(function(response) {
+ var responseParams =
+ new FakeCentral_SimulateGATTDisconnection_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SimulateGATTDisconnection_Name,
+ codec.align(FakeCentral_SimulateGATTDisconnection_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_SimulateGATTDisconnection_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_SimulateGATTServicesChanged_Name:
+ var params = reader.decodeStruct(FakeCentral_SimulateGATTServicesChanged_Params);
+ this.simulateGATTServicesChanged(params.address).then(function(response) {
+ var responseParams =
+ new FakeCentral_SimulateGATTServicesChanged_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SimulateGATTServicesChanged_Name,
+ codec.align(FakeCentral_SimulateGATTServicesChanged_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_SimulateGATTServicesChanged_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_AddFakeService_Name:
+ var params = reader.decodeStruct(FakeCentral_AddFakeService_Params);
+ this.addFakeService(params.peripheralAddress, params.serviceUuid).then(function(response) {
+ var responseParams =
+ new FakeCentral_AddFakeService_ResponseParams();
+ responseParams.serviceId = response.serviceId;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_AddFakeService_Name,
+ codec.align(FakeCentral_AddFakeService_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_AddFakeService_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_RemoveFakeService_Name:
+ var params = reader.decodeStruct(FakeCentral_RemoveFakeService_Params);
+ this.removeFakeService(params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_RemoveFakeService_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_RemoveFakeService_Name,
+ codec.align(FakeCentral_RemoveFakeService_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_RemoveFakeService_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_AddFakeCharacteristic_Name:
+ var params = reader.decodeStruct(FakeCentral_AddFakeCharacteristic_Params);
+ this.addFakeCharacteristic(params.characteristicUuid, params.properties, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_AddFakeCharacteristic_ResponseParams();
+ responseParams.characteristicId = response.characteristicId;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_AddFakeCharacteristic_Name,
+ codec.align(FakeCentral_AddFakeCharacteristic_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_AddFakeCharacteristic_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_RemoveFakeCharacteristic_Name:
+ var params = reader.decodeStruct(FakeCentral_RemoveFakeCharacteristic_Params);
+ this.removeFakeCharacteristic(params.identifier, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_RemoveFakeCharacteristic_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_RemoveFakeCharacteristic_Name,
+ codec.align(FakeCentral_RemoveFakeCharacteristic_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_RemoveFakeCharacteristic_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_AddFakeDescriptor_Name:
+ var params = reader.decodeStruct(FakeCentral_AddFakeDescriptor_Params);
+ this.addFakeDescriptor(params.descriptorUuid, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_AddFakeDescriptor_ResponseParams();
+ responseParams.descriptorId = response.descriptorId;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_AddFakeDescriptor_Name,
+ codec.align(FakeCentral_AddFakeDescriptor_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_AddFakeDescriptor_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_RemoveFakeDescriptor_Name:
+ var params = reader.decodeStruct(FakeCentral_RemoveFakeDescriptor_Params);
+ this.removeFakeDescriptor(params.descriptorId, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_RemoveFakeDescriptor_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_RemoveFakeDescriptor_Name,
+ codec.align(FakeCentral_RemoveFakeDescriptor_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_RemoveFakeDescriptor_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_SetNextReadCharacteristicResponse_Name:
+ var params = reader.decodeStruct(FakeCentral_SetNextReadCharacteristicResponse_Params);
+ this.setNextReadCharacteristicResponse(params.gattCode, params.value, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_SetNextReadCharacteristicResponse_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextReadCharacteristicResponse_Name,
+ codec.align(FakeCentral_SetNextReadCharacteristicResponse_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_SetNextReadCharacteristicResponse_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_SetNextWriteCharacteristicResponse_Name:
+ var params = reader.decodeStruct(FakeCentral_SetNextWriteCharacteristicResponse_Params);
+ this.setNextWriteCharacteristicResponse(params.gattCode, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextWriteCharacteristicResponse_Name,
+ codec.align(FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_SetNextSubscribeToNotificationsResponse_Name:
+ var params = reader.decodeStruct(FakeCentral_SetNextSubscribeToNotificationsResponse_Params);
+ this.setNextSubscribeToNotificationsResponse(params.gattCode, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextSubscribeToNotificationsResponse_Name,
+ codec.align(FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_SetNextUnsubscribeFromNotificationsResponse_Name:
+ var params = reader.decodeStruct(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params);
+ this.setNextUnsubscribeFromNotificationsResponse(params.gattCode, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextUnsubscribeFromNotificationsResponse_Name,
+ codec.align(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_IsNotifying_Name:
+ var params = reader.decodeStruct(FakeCentral_IsNotifying_Params);
+ this.isNotifying(params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_IsNotifying_ResponseParams();
+ responseParams.success = response.success;
+ responseParams.isNotifying = response.isNotifying;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_IsNotifying_Name,
+ codec.align(FakeCentral_IsNotifying_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_IsNotifying_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_GetLastWrittenCharacteristicValue_Name:
+ var params = reader.decodeStruct(FakeCentral_GetLastWrittenCharacteristicValue_Params);
+ this.getLastWrittenCharacteristicValue(params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams();
+ responseParams.success = response.success;
+ responseParams.value = response.value;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_GetLastWrittenCharacteristicValue_Name,
+ codec.align(FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_SetNextReadDescriptorResponse_Name:
+ var params = reader.decodeStruct(FakeCentral_SetNextReadDescriptorResponse_Params);
+ this.setNextReadDescriptorResponse(params.gattCode, params.value, params.descriptorId, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_SetNextReadDescriptorResponse_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextReadDescriptorResponse_Name,
+ codec.align(FakeCentral_SetNextReadDescriptorResponse_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_SetNextReadDescriptorResponse_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_SetNextWriteDescriptorResponse_Name:
+ var params = reader.decodeStruct(FakeCentral_SetNextWriteDescriptorResponse_Params);
+ this.setNextWriteDescriptorResponse(params.gattCode, params.descriptorId, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_SetNextWriteDescriptorResponse_ResponseParams();
+ responseParams.success = response.success;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_SetNextWriteDescriptorResponse_Name,
+ codec.align(FakeCentral_SetNextWriteDescriptorResponse_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_SetNextWriteDescriptorResponse_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeCentral_GetLastWrittenDescriptorValue_Name:
+ var params = reader.decodeStruct(FakeCentral_GetLastWrittenDescriptorValue_Params);
+ this.getLastWrittenDescriptorValue(params.descriptorId, params.characteristicId, params.serviceId, params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeCentral_GetLastWrittenDescriptorValue_ResponseParams();
+ responseParams.success = response.success;
+ responseParams.value = response.value;
+ var builder = new codec.MessageV1Builder(
+ kFakeCentral_GetLastWrittenDescriptorValue_Name,
+ codec.align(FakeCentral_GetLastWrittenDescriptorValue_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeCentral_GetLastWrittenDescriptorValue_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ function validateFakeCentralRequest(messageValidator) {
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+ case kFakeCentral_SimulatePreconnectedPeripheral_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_SimulatePreconnectedPeripheral_Params;
+ break;
+ case kFakeCentral_SimulateAdvertisementReceived_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_SimulateAdvertisementReceived_Params;
+ break;
+ case kFakeCentral_SetNextGATTConnectionResponse_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_SetNextGATTConnectionResponse_Params;
+ break;
+ case kFakeCentral_SetNextGATTDiscoveryResponse_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_SetNextGATTDiscoveryResponse_Params;
+ break;
+ case kFakeCentral_SimulateGATTDisconnection_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_SimulateGATTDisconnection_Params;
+ break;
+ case kFakeCentral_SimulateGATTServicesChanged_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_SimulateGATTServicesChanged_Params;
+ break;
+ case kFakeCentral_AddFakeService_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_AddFakeService_Params;
+ break;
+ case kFakeCentral_RemoveFakeService_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_RemoveFakeService_Params;
+ break;
+ case kFakeCentral_AddFakeCharacteristic_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_AddFakeCharacteristic_Params;
+ break;
+ case kFakeCentral_RemoveFakeCharacteristic_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_RemoveFakeCharacteristic_Params;
+ break;
+ case kFakeCentral_AddFakeDescriptor_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_AddFakeDescriptor_Params;
+ break;
+ case kFakeCentral_RemoveFakeDescriptor_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_RemoveFakeDescriptor_Params;
+ break;
+ case kFakeCentral_SetNextReadCharacteristicResponse_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_SetNextReadCharacteristicResponse_Params;
+ break;
+ case kFakeCentral_SetNextWriteCharacteristicResponse_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_SetNextWriteCharacteristicResponse_Params;
+ break;
+ case kFakeCentral_SetNextSubscribeToNotificationsResponse_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_SetNextSubscribeToNotificationsResponse_Params;
+ break;
+ case kFakeCentral_SetNextUnsubscribeFromNotificationsResponse_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_SetNextUnsubscribeFromNotificationsResponse_Params;
+ break;
+ case kFakeCentral_IsNotifying_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_IsNotifying_Params;
+ break;
+ case kFakeCentral_GetLastWrittenCharacteristicValue_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_GetLastWrittenCharacteristicValue_Params;
+ break;
+ case kFakeCentral_SetNextReadDescriptorResponse_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_SetNextReadDescriptorResponse_Params;
+ break;
+ case kFakeCentral_SetNextWriteDescriptorResponse_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_SetNextWriteDescriptorResponse_Params;
+ break;
+ case kFakeCentral_GetLastWrittenDescriptorValue_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeCentral_GetLastWrittenDescriptorValue_Params;
+ break;
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+ }
+
+ function validateFakeCentralResponse(messageValidator) {
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+ case kFakeCentral_SimulatePreconnectedPeripheral_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_SimulatePreconnectedPeripheral_ResponseParams;
+ break;
+ case kFakeCentral_SimulateAdvertisementReceived_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_SimulateAdvertisementReceived_ResponseParams;
+ break;
+ case kFakeCentral_SetNextGATTConnectionResponse_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_SetNextGATTConnectionResponse_ResponseParams;
+ break;
+ case kFakeCentral_SetNextGATTDiscoveryResponse_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_SetNextGATTDiscoveryResponse_ResponseParams;
+ break;
+ case kFakeCentral_SimulateGATTDisconnection_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_SimulateGATTDisconnection_ResponseParams;
+ break;
+ case kFakeCentral_SimulateGATTServicesChanged_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_SimulateGATTServicesChanged_ResponseParams;
+ break;
+ case kFakeCentral_AddFakeService_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_AddFakeService_ResponseParams;
+ break;
+ case kFakeCentral_RemoveFakeService_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_RemoveFakeService_ResponseParams;
+ break;
+ case kFakeCentral_AddFakeCharacteristic_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_AddFakeCharacteristic_ResponseParams;
+ break;
+ case kFakeCentral_RemoveFakeCharacteristic_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_RemoveFakeCharacteristic_ResponseParams;
+ break;
+ case kFakeCentral_AddFakeDescriptor_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_AddFakeDescriptor_ResponseParams;
+ break;
+ case kFakeCentral_RemoveFakeDescriptor_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_RemoveFakeDescriptor_ResponseParams;
+ break;
+ case kFakeCentral_SetNextReadCharacteristicResponse_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_SetNextReadCharacteristicResponse_ResponseParams;
+ break;
+ case kFakeCentral_SetNextWriteCharacteristicResponse_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_SetNextWriteCharacteristicResponse_ResponseParams;
+ break;
+ case kFakeCentral_SetNextSubscribeToNotificationsResponse_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_SetNextSubscribeToNotificationsResponse_ResponseParams;
+ break;
+ case kFakeCentral_SetNextUnsubscribeFromNotificationsResponse_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_SetNextUnsubscribeFromNotificationsResponse_ResponseParams;
+ break;
+ case kFakeCentral_IsNotifying_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_IsNotifying_ResponseParams;
+ break;
+ case kFakeCentral_GetLastWrittenCharacteristicValue_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_GetLastWrittenCharacteristicValue_ResponseParams;
+ break;
+ case kFakeCentral_SetNextReadDescriptorResponse_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_SetNextReadDescriptorResponse_ResponseParams;
+ break;
+ case kFakeCentral_SetNextWriteDescriptorResponse_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_SetNextWriteDescriptorResponse_ResponseParams;
+ break;
+ case kFakeCentral_GetLastWrittenDescriptorValue_Name:
+ if (message.isResponse())
+ paramsClass = FakeCentral_GetLastWrittenDescriptorValue_ResponseParams;
+ break;
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+ }
+
+ var FakeCentral = {
+ name: 'bluetooth.mojom.FakeCentral',
+ kVersion: 0,
+ ptrClass: FakeCentralPtr,
+ proxyClass: FakeCentralProxy,
+ stubClass: FakeCentralStub,
+ validateRequest: validateFakeCentralRequest,
+ validateResponse: validateFakeCentralResponse,
+ };
+ FakeCentralStub.prototype.validator = validateFakeCentralRequest;
+ FakeCentralProxy.prototype.validator = validateFakeCentralResponse;
+ exports.kHCISuccess = kHCISuccess;
+ exports.kHCIConnectionTimeout = kHCIConnectionTimeout;
+ exports.kGATTSuccess = kGATTSuccess;
+ exports.kGATTInvalidHandle = kGATTInvalidHandle;
+ exports.CentralState = CentralState;
+ exports.Appearance = Appearance;
+ exports.Power = Power;
+ exports.ServiceDataMap = ServiceDataMap;
+ exports.ScanRecord = ScanRecord;
+ exports.ScanResult = ScanResult;
+ exports.CharacteristicProperties = CharacteristicProperties;
+ exports.FakeBluetooth = FakeBluetooth;
+ exports.FakeBluetoothPtr = FakeBluetoothPtr;
+ exports.FakeBluetoothAssociatedPtr = FakeBluetoothAssociatedPtr;
+ exports.FakeCentral = FakeCentral;
+ exports.FakeCentralPtr = FakeCentralPtr;
+ exports.FakeCentralAssociatedPtr = FakeCentralAssociatedPtr;
+})();
diff --git a/test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth.mojom.js.headers b/test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth.mojom.js.headers
new file mode 100644
index 00000000000000..6805c323df5a97
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth.mojom.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth_chooser.mojom.js b/test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth_chooser.mojom.js
new file mode 100644
index 00000000000000..42739393c10984
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth_chooser.mojom.js
@@ -0,0 +1,822 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+(function() {
+ var mojomId = 'content/shell/common/layout_test/fake_bluetooth_chooser.mojom';
+ if (mojo.internal.isMojomLoaded(mojomId)) {
+ console.warn('The following mojom is loaded multiple times: ' + mojomId);
+ return;
+ }
+ mojo.internal.markMojomLoaded(mojomId);
+ var bindings = mojo;
+ var associatedBindings = mojo;
+ var codec = mojo.internal;
+ var validator = mojo.internal;
+
+ var exports = mojo.internal.exposeNamespace('content.mojom');
+
+
+ var ChooserEventType = {};
+ ChooserEventType.CHOOSER_OPENED = 0;
+ ChooserEventType.SCAN_STARTED = ChooserEventType.CHOOSER_OPENED + 1;
+ ChooserEventType.DEVICE_UPDATE = ChooserEventType.SCAN_STARTED + 1;
+ ChooserEventType.ADAPTER_REMOVED = ChooserEventType.DEVICE_UPDATE + 1;
+ ChooserEventType.ADAPTER_DISABLED = ChooserEventType.ADAPTER_REMOVED + 1;
+ ChooserEventType.ADAPTER_ENABLED = ChooserEventType.ADAPTER_DISABLED + 1;
+ ChooserEventType.DISCOVERY_FAILED_TO_START = ChooserEventType.ADAPTER_ENABLED + 1;
+ ChooserEventType.DISCOVERING = ChooserEventType.DISCOVERY_FAILED_TO_START + 1;
+ ChooserEventType.DISCOVERY_IDLE = ChooserEventType.DISCOVERING + 1;
+ ChooserEventType.ADD_DEVICE = ChooserEventType.DISCOVERY_IDLE + 1;
+
+ ChooserEventType.isKnownEnumValue = function(value) {
+ switch (value) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ return true;
+ }
+ return false;
+ };
+
+ ChooserEventType.validate = function(enumValue) {
+ var isExtensible = false;
+ if (isExtensible || this.isKnownEnumValue(enumValue))
+ return validator.validationError.NONE;
+
+ return validator.validationError.UNKNOWN_ENUM_VALUE;
+ };
+
+ function FakeBluetoothChooserEvent(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetoothChooserEvent.prototype.initDefaults_ = function() {
+ this.type = 0;
+ this.origin = null;
+ this.peripheralAddress = null;
+ };
+ FakeBluetoothChooserEvent.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetoothChooserEvent.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 32}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeBluetoothChooserEvent.type
+ err = messageValidator.validateEnum(offset + codec.kStructHeaderSize + 0, ChooserEventType);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeBluetoothChooserEvent.origin
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 8, true)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeBluetoothChooserEvent.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 16, true)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetoothChooserEvent.encodedSize = codec.kStructHeaderSize + 24;
+
+ FakeBluetoothChooserEvent.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetoothChooserEvent();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.type = decoder.decodeStruct(codec.Int32);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ val.origin = decoder.decodeStruct(codec.NullableString);
+ val.peripheralAddress = decoder.decodeStruct(codec.NullableString);
+ return val;
+ };
+
+ FakeBluetoothChooserEvent.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetoothChooserEvent.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Int32, val.type);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.encodeStruct(codec.NullableString, val.origin);
+ encoder.encodeStruct(codec.NullableString, val.peripheralAddress);
+ };
+ function FakeBluetoothChooser_WaitForEvents_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetoothChooser_WaitForEvents_Params.prototype.initDefaults_ = function() {
+ this.numOfEvents = 0;
+ };
+ FakeBluetoothChooser_WaitForEvents_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetoothChooser_WaitForEvents_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetoothChooser_WaitForEvents_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeBluetoothChooser_WaitForEvents_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetoothChooser_WaitForEvents_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.numOfEvents = decoder.decodeStruct(codec.Uint32);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ decoder.skip(1);
+ return val;
+ };
+
+ FakeBluetoothChooser_WaitForEvents_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetoothChooser_WaitForEvents_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.Uint32, val.numOfEvents);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ encoder.skip(1);
+ };
+ function FakeBluetoothChooser_WaitForEvents_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetoothChooser_WaitForEvents_ResponseParams.prototype.initDefaults_ = function() {
+ this.events = null;
+ };
+ FakeBluetoothChooser_WaitForEvents_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetoothChooser_WaitForEvents_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeBluetoothChooser_WaitForEvents_ResponseParams.events
+ err = messageValidator.validateArrayPointer(offset + codec.kStructHeaderSize + 0, 8, new codec.PointerTo(FakeBluetoothChooserEvent), false, [0], 0);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetoothChooser_WaitForEvents_ResponseParams.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeBluetoothChooser_WaitForEvents_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetoothChooser_WaitForEvents_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.events = decoder.decodeArrayPointer(new codec.PointerTo(FakeBluetoothChooserEvent));
+ return val;
+ };
+
+ FakeBluetoothChooser_WaitForEvents_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetoothChooser_WaitForEvents_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeArrayPointer(new codec.PointerTo(FakeBluetoothChooserEvent), val.events);
+ };
+ function FakeBluetoothChooser_SelectPeripheral_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetoothChooser_SelectPeripheral_Params.prototype.initDefaults_ = function() {
+ this.peripheralAddress = null;
+ };
+ FakeBluetoothChooser_SelectPeripheral_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetoothChooser_SelectPeripheral_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 16}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+
+ // validate FakeBluetoothChooser_SelectPeripheral_Params.peripheralAddress
+ err = messageValidator.validateStringPointer(offset + codec.kStructHeaderSize + 0, false)
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetoothChooser_SelectPeripheral_Params.encodedSize = codec.kStructHeaderSize + 8;
+
+ FakeBluetoothChooser_SelectPeripheral_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetoothChooser_SelectPeripheral_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ val.peripheralAddress = decoder.decodeStruct(codec.String);
+ return val;
+ };
+
+ FakeBluetoothChooser_SelectPeripheral_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetoothChooser_SelectPeripheral_Params.encodedSize);
+ encoder.writeUint32(0);
+ encoder.encodeStruct(codec.String, val.peripheralAddress);
+ };
+ function FakeBluetoothChooser_SelectPeripheral_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetoothChooser_SelectPeripheral_ResponseParams.prototype.initDefaults_ = function() {
+ };
+ FakeBluetoothChooser_SelectPeripheral_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetoothChooser_SelectPeripheral_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetoothChooser_SelectPeripheral_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+ FakeBluetoothChooser_SelectPeripheral_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetoothChooser_SelectPeripheral_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ FakeBluetoothChooser_SelectPeripheral_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetoothChooser_SelectPeripheral_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ };
+ function FakeBluetoothChooser_Cancel_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetoothChooser_Cancel_Params.prototype.initDefaults_ = function() {
+ };
+ FakeBluetoothChooser_Cancel_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetoothChooser_Cancel_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetoothChooser_Cancel_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+ FakeBluetoothChooser_Cancel_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetoothChooser_Cancel_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ FakeBluetoothChooser_Cancel_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetoothChooser_Cancel_Params.encodedSize);
+ encoder.writeUint32(0);
+ };
+ function FakeBluetoothChooser_Cancel_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetoothChooser_Cancel_ResponseParams.prototype.initDefaults_ = function() {
+ };
+ FakeBluetoothChooser_Cancel_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetoothChooser_Cancel_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetoothChooser_Cancel_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+ FakeBluetoothChooser_Cancel_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetoothChooser_Cancel_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ FakeBluetoothChooser_Cancel_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetoothChooser_Cancel_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ };
+ function FakeBluetoothChooser_Rescan_Params(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetoothChooser_Rescan_Params.prototype.initDefaults_ = function() {
+ };
+ FakeBluetoothChooser_Rescan_Params.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetoothChooser_Rescan_Params.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetoothChooser_Rescan_Params.encodedSize = codec.kStructHeaderSize + 0;
+
+ FakeBluetoothChooser_Rescan_Params.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetoothChooser_Rescan_Params();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ FakeBluetoothChooser_Rescan_Params.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetoothChooser_Rescan_Params.encodedSize);
+ encoder.writeUint32(0);
+ };
+ function FakeBluetoothChooser_Rescan_ResponseParams(values) {
+ this.initDefaults_();
+ this.initFields_(values);
+ }
+
+
+ FakeBluetoothChooser_Rescan_ResponseParams.prototype.initDefaults_ = function() {
+ };
+ FakeBluetoothChooser_Rescan_ResponseParams.prototype.initFields_ = function(fields) {
+ for(var field in fields) {
+ if (this.hasOwnProperty(field))
+ this[field] = fields[field];
+ }
+ };
+
+ FakeBluetoothChooser_Rescan_ResponseParams.validate = function(messageValidator, offset) {
+ var err;
+ err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ var kVersionSizes = [
+ {version: 0, numBytes: 8}
+ ];
+ err = messageValidator.validateStructVersion(offset, kVersionSizes);
+ if (err !== validator.validationError.NONE)
+ return err;
+
+ return validator.validationError.NONE;
+ };
+
+ FakeBluetoothChooser_Rescan_ResponseParams.encodedSize = codec.kStructHeaderSize + 0;
+
+ FakeBluetoothChooser_Rescan_ResponseParams.decode = function(decoder) {
+ var packed;
+ var val = new FakeBluetoothChooser_Rescan_ResponseParams();
+ var numberOfBytes = decoder.readUint32();
+ var version = decoder.readUint32();
+ return val;
+ };
+
+ FakeBluetoothChooser_Rescan_ResponseParams.encode = function(encoder, val) {
+ var packed;
+ encoder.writeUint32(FakeBluetoothChooser_Rescan_ResponseParams.encodedSize);
+ encoder.writeUint32(0);
+ };
+ var kFakeBluetoothChooser_WaitForEvents_Name = 457051710;
+ var kFakeBluetoothChooser_SelectPeripheral_Name = 1924310743;
+ var kFakeBluetoothChooser_Cancel_Name = 1388880682;
+ var kFakeBluetoothChooser_Rescan_Name = 2112671529;
+
+ function FakeBluetoothChooserPtr(handleOrPtrInfo) {
+ this.ptr = new bindings.InterfacePtrController(FakeBluetoothChooser,
+ handleOrPtrInfo);
+ }
+
+ function FakeBluetoothChooserAssociatedPtr(associatedInterfacePtrInfo) {
+ this.ptr = new associatedBindings.AssociatedInterfacePtrController(
+ FakeBluetoothChooser, associatedInterfacePtrInfo);
+ }
+
+ FakeBluetoothChooserAssociatedPtr.prototype =
+ Object.create(FakeBluetoothChooserPtr.prototype);
+ FakeBluetoothChooserAssociatedPtr.prototype.constructor =
+ FakeBluetoothChooserAssociatedPtr;
+
+ function FakeBluetoothChooserProxy(receiver) {
+ this.receiver_ = receiver;
+ }
+ FakeBluetoothChooserPtr.prototype.waitForEvents = function() {
+ return FakeBluetoothChooserProxy.prototype.waitForEvents
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeBluetoothChooserProxy.prototype.waitForEvents = function(numOfEvents) {
+ var params = new FakeBluetoothChooser_WaitForEvents_Params();
+ params.numOfEvents = numOfEvents;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetoothChooser_WaitForEvents_Name,
+ codec.align(FakeBluetoothChooser_WaitForEvents_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeBluetoothChooser_WaitForEvents_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeBluetoothChooser_WaitForEvents_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeBluetoothChooserPtr.prototype.selectPeripheral = function() {
+ return FakeBluetoothChooserProxy.prototype.selectPeripheral
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeBluetoothChooserProxy.prototype.selectPeripheral = function(peripheralAddress) {
+ var params = new FakeBluetoothChooser_SelectPeripheral_Params();
+ params.peripheralAddress = peripheralAddress;
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetoothChooser_SelectPeripheral_Name,
+ codec.align(FakeBluetoothChooser_SelectPeripheral_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeBluetoothChooser_SelectPeripheral_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeBluetoothChooser_SelectPeripheral_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeBluetoothChooserPtr.prototype.cancel = function() {
+ return FakeBluetoothChooserProxy.prototype.cancel
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeBluetoothChooserProxy.prototype.cancel = function() {
+ var params = new FakeBluetoothChooser_Cancel_Params();
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetoothChooser_Cancel_Name,
+ codec.align(FakeBluetoothChooser_Cancel_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeBluetoothChooser_Cancel_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeBluetoothChooser_Cancel_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+ FakeBluetoothChooserPtr.prototype.rescan = function() {
+ return FakeBluetoothChooserProxy.prototype.rescan
+ .apply(this.ptr.getProxy(), arguments);
+ };
+
+ FakeBluetoothChooserProxy.prototype.rescan = function() {
+ var params = new FakeBluetoothChooser_Rescan_Params();
+ return new Promise(function(resolve, reject) {
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetoothChooser_Rescan_Name,
+ codec.align(FakeBluetoothChooser_Rescan_Params.encodedSize),
+ codec.kMessageExpectsResponse, 0);
+ builder.encodeStruct(FakeBluetoothChooser_Rescan_Params, params);
+ var message = builder.finish();
+ this.receiver_.acceptAndExpectResponse(message).then(function(message) {
+ var reader = new codec.MessageReader(message);
+ var responseParams =
+ reader.decodeStruct(FakeBluetoothChooser_Rescan_ResponseParams);
+ resolve(responseParams);
+ }).catch(function(result) {
+ reject(Error("Connection error: " + result));
+ });
+ }.bind(this));
+ };
+
+ function FakeBluetoothChooserStub(delegate) {
+ this.delegate_ = delegate;
+ }
+ FakeBluetoothChooserStub.prototype.waitForEvents = function(numOfEvents) {
+ return this.delegate_ && this.delegate_.waitForEvents && this.delegate_.waitForEvents(numOfEvents);
+ }
+ FakeBluetoothChooserStub.prototype.selectPeripheral = function(peripheralAddress) {
+ return this.delegate_ && this.delegate_.selectPeripheral && this.delegate_.selectPeripheral(peripheralAddress);
+ }
+ FakeBluetoothChooserStub.prototype.cancel = function() {
+ return this.delegate_ && this.delegate_.cancel && this.delegate_.cancel();
+ }
+ FakeBluetoothChooserStub.prototype.rescan = function() {
+ return this.delegate_ && this.delegate_.rescan && this.delegate_.rescan();
+ }
+
+ FakeBluetoothChooserStub.prototype.accept = function(message) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+ default:
+ return false;
+ }
+ };
+
+ FakeBluetoothChooserStub.prototype.acceptWithResponder =
+ function(message, responder) {
+ var reader = new codec.MessageReader(message);
+ switch (reader.messageName) {
+ case kFakeBluetoothChooser_WaitForEvents_Name:
+ var params = reader.decodeStruct(FakeBluetoothChooser_WaitForEvents_Params);
+ this.waitForEvents(params.numOfEvents).then(function(response) {
+ var responseParams =
+ new FakeBluetoothChooser_WaitForEvents_ResponseParams();
+ responseParams.events = response.events;
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetoothChooser_WaitForEvents_Name,
+ codec.align(FakeBluetoothChooser_WaitForEvents_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeBluetoothChooser_WaitForEvents_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeBluetoothChooser_SelectPeripheral_Name:
+ var params = reader.decodeStruct(FakeBluetoothChooser_SelectPeripheral_Params);
+ this.selectPeripheral(params.peripheralAddress).then(function(response) {
+ var responseParams =
+ new FakeBluetoothChooser_SelectPeripheral_ResponseParams();
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetoothChooser_SelectPeripheral_Name,
+ codec.align(FakeBluetoothChooser_SelectPeripheral_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeBluetoothChooser_SelectPeripheral_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeBluetoothChooser_Cancel_Name:
+ var params = reader.decodeStruct(FakeBluetoothChooser_Cancel_Params);
+ this.cancel().then(function(response) {
+ var responseParams =
+ new FakeBluetoothChooser_Cancel_ResponseParams();
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetoothChooser_Cancel_Name,
+ codec.align(FakeBluetoothChooser_Cancel_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeBluetoothChooser_Cancel_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ case kFakeBluetoothChooser_Rescan_Name:
+ var params = reader.decodeStruct(FakeBluetoothChooser_Rescan_Params);
+ this.rescan().then(function(response) {
+ var responseParams =
+ new FakeBluetoothChooser_Rescan_ResponseParams();
+ var builder = new codec.MessageV1Builder(
+ kFakeBluetoothChooser_Rescan_Name,
+ codec.align(FakeBluetoothChooser_Rescan_ResponseParams.encodedSize),
+ codec.kMessageIsResponse, reader.requestID);
+ builder.encodeStruct(FakeBluetoothChooser_Rescan_ResponseParams,
+ responseParams);
+ var message = builder.finish();
+ responder.accept(message);
+ });
+ return true;
+ default:
+ return false;
+ }
+ };
+
+ function validateFakeBluetoothChooserRequest(messageValidator) {
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+ case kFakeBluetoothChooser_WaitForEvents_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeBluetoothChooser_WaitForEvents_Params;
+ break;
+ case kFakeBluetoothChooser_SelectPeripheral_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeBluetoothChooser_SelectPeripheral_Params;
+ break;
+ case kFakeBluetoothChooser_Cancel_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeBluetoothChooser_Cancel_Params;
+ break;
+ case kFakeBluetoothChooser_Rescan_Name:
+ if (message.expectsResponse())
+ paramsClass = FakeBluetoothChooser_Rescan_Params;
+ break;
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+ }
+
+ function validateFakeBluetoothChooserResponse(messageValidator) {
+ var message = messageValidator.message;
+ var paramsClass = null;
+ switch (message.getName()) {
+ case kFakeBluetoothChooser_WaitForEvents_Name:
+ if (message.isResponse())
+ paramsClass = FakeBluetoothChooser_WaitForEvents_ResponseParams;
+ break;
+ case kFakeBluetoothChooser_SelectPeripheral_Name:
+ if (message.isResponse())
+ paramsClass = FakeBluetoothChooser_SelectPeripheral_ResponseParams;
+ break;
+ case kFakeBluetoothChooser_Cancel_Name:
+ if (message.isResponse())
+ paramsClass = FakeBluetoothChooser_Cancel_ResponseParams;
+ break;
+ case kFakeBluetoothChooser_Rescan_Name:
+ if (message.isResponse())
+ paramsClass = FakeBluetoothChooser_Rescan_ResponseParams;
+ break;
+ }
+ if (paramsClass === null)
+ return validator.validationError.NONE;
+ return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
+ }
+
+ var FakeBluetoothChooser = {
+ name: 'content.mojom.FakeBluetoothChooser',
+ kVersion: 0,
+ ptrClass: FakeBluetoothChooserPtr,
+ proxyClass: FakeBluetoothChooserProxy,
+ stubClass: FakeBluetoothChooserStub,
+ validateRequest: validateFakeBluetoothChooserRequest,
+ validateResponse: validateFakeBluetoothChooserResponse,
+ };
+ FakeBluetoothChooserStub.prototype.validator = validateFakeBluetoothChooserRequest;
+ FakeBluetoothChooserProxy.prototype.validator = validateFakeBluetoothChooserResponse;
+ exports.ChooserEventType = ChooserEventType;
+ exports.FakeBluetoothChooserEvent = FakeBluetoothChooserEvent;
+ exports.FakeBluetoothChooser = FakeBluetoothChooser;
+ exports.FakeBluetoothChooserPtr = FakeBluetoothChooserPtr;
+ exports.FakeBluetoothChooserAssociatedPtr = FakeBluetoothChooserAssociatedPtr;
+})();
diff --git a/test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth_chooser.mojom.js.headers b/test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth_chooser.mojom.js.headers
new file mode 100644
index 00000000000000..6805c323df5a97
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth_chooser.mojom.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/web-platform-tests/resources/chromium/generic_sensor_mocks.js b/test/fixtures/web-platform-tests/resources/chromium/generic_sensor_mocks.js
new file mode 100644
index 00000000000000..24a469199ff1e4
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/chromium/generic_sensor_mocks.js
@@ -0,0 +1,220 @@
+'use strict';
+
+var GenericSensorTest = (() => {
+ // Class that mocks Sensor interface defined in
+ // https://cs.chromium.org/chromium/src/services/device/public/mojom/sensor.mojom
+ class MockSensor {
+ constructor(sensorRequest, handle, offset, size, reportingMode) {
+ this.client_ = null;
+ this.reportingMode_ = reportingMode;
+ this.sensorReadingTimerId_ = null;
+ this.requestedFrequencies_ = [];
+ let rv = handle.mapBuffer(offset, size);
+ assert_equals(rv.result, Mojo.RESULT_OK, "Failed to map shared buffer");
+ this.buffer_ = new Float64Array(rv.buffer);
+ this.buffer_.fill(0);
+ this.binding_ = new mojo.Binding(device.mojom.Sensor, this,
+ sensorRequest);
+ this.binding_.setConnectionErrorHandler(() => {
+ this.reset();
+ });
+ }
+
+ getDefaultConfiguration() {
+ return Promise.resolve({frequency: 5});
+ }
+
+ addConfiguration(configuration) {
+ assert_not_equals(configuration, null, "Invalid sensor configuration.");
+
+ this.requestedFrequencies_.push(configuration.frequency);
+ // Sort using descending order.
+ this.requestedFrequencies_.sort(
+ (first, second) => { return second - first });
+
+ this.startReading();
+
+ return Promise.resolve({success: true});
+ }
+
+ removeConfiguration(configuration) {
+ let index = this.requestedFrequencies_.indexOf(configuration.frequency);
+ if (index == -1)
+ return;
+
+ this.requestedFrequencies_.splice(index, 1);
+
+ if (this.isReading) {
+ this.stopReading();
+ if (this.requestedFrequencies_.length !== 0)
+ this.startReading();
+ }
+ }
+
+ suspend() {
+ this.stopReading();
+ }
+
+ resume() {
+ this.startReading();
+ }
+
+ reset() {
+ this.stopReading();
+ this.requestedFrequencies_ = [];
+ this.buffer_.fill(0);
+ this.binding_.close();
+ }
+
+ startReading() {
+ if (this.isReading) {
+ console.warn("Sensor reading is already started.");
+ return;
+ }
+
+ if (this.requestedFrequencies_.length == 0) {
+ console.warn("Sensor reading cannot be started as" +
+ "there are no configurations added.");
+ return;
+ }
+
+ const maxFrequencyHz = this.requestedFrequencies_[0];
+ const timeoutMs = (1 / maxFrequencyHz) * 1000;
+ this.sensorReadingTimerId_ = window.setInterval(() => {
+ // For all tests sensor reading should have monotonically
+ // increasing timestamp in seconds.
+ this.buffer_[1] = window.performance.now() * 0.001;
+ if (this.reportingMode_ === device.mojom.ReportingMode.ON_CHANGE) {
+ this.client_.sensorReadingChanged();
+ }
+ }, timeoutMs);
+ }
+
+ stopReading() {
+ if (this.isReading) {
+ window.clearInterval(this.sensorReadingTimerId_);
+ this.sensorReadingTimerId_ = null;
+ }
+ }
+
+ get isReading() {
+ this.sensorReadingTimerId_ !== null;
+ }
+ }
+
+ // Class that mocks SensorProvider interface defined in
+ // https://cs.chromium.org/chromium/src/services/device/public/mojom/sensor_provider.mojom
+ class MockSensorProvider {
+ constructor() {
+ this.readingSizeInBytes_ =
+ device.mojom.SensorInitParams.kReadBufferSizeForTests;
+ this.sharedBufferSizeInBytes_ = this.readingSizeInBytes_ *
+ device.mojom.SensorType.LAST;
+ let rv = Mojo.createSharedBuffer(this.sharedBufferSizeInBytes_);
+ assert_equals(rv.result, Mojo.RESULT_OK, "Failed to create buffer");
+ this.sharedBufferHandle_ = rv.handle;
+ this.activeSensor_ = null;
+ this.isContinuous_ = false;
+ this.binding_ = new mojo.Binding(device.mojom.SensorProvider, this);
+
+ this.interceptor_ = new MojoInterfaceInterceptor(
+ device.mojom.SensorProvider.name);
+ this.interceptor_.oninterfacerequest = e => {
+ this.binding_.bind(e.handle);
+ this.binding_.setConnectionErrorHandler(() => {
+ console.error("Mojo connection error");
+ this.reset();
+ });
+ };
+ this.interceptor_.start();
+ }
+
+ async getSensor(type) {
+ const offset = (device.mojom.SensorType.LAST - type) *
+ this.readingSizeInBytes_;
+ const reportingMode = device.mojom.ReportingMode.ON_CHANGE;
+
+ let sensorPtr = new device.mojom.SensorPtr();
+ if (this.activeSensor_ == null) {
+ let mockSensor = new MockSensor(
+ mojo.makeRequest(sensorPtr), this.sharedBufferHandle_, offset,
+ this.readingSizeInBytes_, reportingMode);
+ this.activeSensor_ = mockSensor;
+ this.activeSensor_.client_ = new device.mojom.SensorClientPtr();
+ }
+
+ let rv = this.sharedBufferHandle_.duplicateBufferHandle();
+
+ assert_equals(rv.result, Mojo.RESULT_OK);
+ let maxAllowedFrequencyHz = 60;
+ if (type == device.mojom.SensorType.AMBIENT_LIGHT ||
+ type == device.mojom.SensorType.MAGNETOMETER) {
+ maxAllowedFrequencyHz = 10;
+ }
+
+ let initParams = new device.mojom.SensorInitParams({
+ sensor: sensorPtr,
+ clientRequest: mojo.makeRequest(this.activeSensor_.client_),
+ memory: rv.handle,
+ bufferOffset: offset,
+ mode: reportingMode,
+ defaultConfiguration: {frequency: 5},
+ minimumFrequency: 1,
+ maximumFrequency: maxAllowedFrequencyHz
+ });
+
+ return {result: device.mojom.SensorCreationResult.SUCCESS,
+ initParams: initParams};
+ }
+
+ reset() {
+ if (this.activeSensor_ !== null) {
+ this.activeSensor_.reset();
+ this.activeSensor_ = null;
+ }
+ this.binding_.close();
+ this.interceptor_.stop();
+ }
+ }
+
+ let testInternal = {
+ initialized: false,
+ sensorProvider: null
+ }
+
+ class GenericSensorTestChromium {
+ constructor() {
+ Object.freeze(this); // Make it immutable.
+ }
+
+ initialize() {
+ if (testInternal.initialized)
+ throw new Error('Call reset() before initialize().');
+
+ if (window.testRunner) { // Grant sensor permissions for Chromium testrunner.
+ ['accelerometer', 'gyroscope',
+ 'magnetometer', 'ambient-light-sensor'].forEach((entry) => {
+ window.testRunner.setPermission(entry, 'granted',
+ location.origin, location.origin);
+ });
+ }
+
+ testInternal.sensorProvider = new MockSensorProvider;
+ testInternal.initialized = true;
+ }
+ // Resets state of sensor mocks between test runs.
+ async reset() {
+ if (!testInternal.initialized)
+ throw new Error('Call initialize() before reset().');
+ testInternal.sensorProvider.reset();
+ testInternal.sensorProvider = null;
+ testInternal.initialized = false;
+
+ // Wait for an event loop iteration to let any pending mojo commands in
+ // the sensor provider finish.
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+ }
+
+ return GenericSensorTestChromium;
+})();
diff --git a/test/fixtures/web-platform-tests/resources/chromium/generic_sensor_mocks.js.headers b/test/fixtures/web-platform-tests/resources/chromium/generic_sensor_mocks.js.headers
new file mode 100644
index 00000000000000..6805c323df5a97
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/chromium/generic_sensor_mocks.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/web-platform-tests/resources/chromium/mojo_bindings.js b/test/fixtures/web-platform-tests/resources/chromium/mojo_bindings.js
new file mode 100644
index 00000000000000..67d6a8828551c1
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/chromium/mojo_bindings.js
@@ -0,0 +1,5129 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+if (mojo && mojo.internal) {
+ throw new Error('The Mojo bindings library has been initialized.');
+}
+
+var mojo = mojo || {};
+mojo.internal = {};
+mojo.internal.global = this;
+mojo.config = {
+ // Whether to automatically load mojom dependencies.
+ // For example, if foo.mojom imports bar.mojom, |autoLoadMojomDeps| set to
+ // true means that loading foo.mojom.js will insert a
+ //
+ // No duplicate loading, although unnecessary:
+ //
+ //
+ //
+ // Load bar.mojom.js twice; should be avoided:
+ //
+ //
+ //
+ // -->
+ autoLoadMojomDeps: true
+};
+
+(function() {
+ var internal = mojo.internal;
+
+ var LoadState = {
+ PENDING_LOAD: 1,
+ LOADED: 2
+ };
+
+ var mojomRegistry = new Map();
+
+ function exposeNamespace(namespace) {
+ var current = internal.global;
+ var parts = namespace.split('.');
+
+ for (var part; parts.length && (part = parts.shift());) {
+ if (!current[part]) {
+ current[part] = {};
+ }
+ current = current[part];
+ }
+
+ return current;
+ }
+
+ function isMojomPendingLoad(id) {
+ return mojomRegistry.get(id) === LoadState.PENDING_LOAD;
+ }
+
+ function isMojomLoaded(id) {
+ return mojomRegistry.get(id) === LoadState.LOADED;
+ }
+
+ function markMojomPendingLoad(id) {
+ if (isMojomLoaded(id)) {
+ throw new Error('The following mojom file has been loaded: ' + id);
+ }
+
+ mojomRegistry.set(id, LoadState.PENDING_LOAD);
+ }
+
+ function markMojomLoaded(id) {
+ mojomRegistry.set(id, LoadState.LOADED);
+ }
+
+ function loadMojomIfNecessary(id, relativePath) {
+ if (mojomRegistry.has(id)) {
+ return;
+ }
+
+ if (internal.global.document === undefined) {
+ throw new Error(
+ 'Mojom dependency autoloading is not implemented in workers. ' +
+ 'Please see config variable mojo.config.autoLoadMojomDeps for more ' +
+ 'details.');
+ }
+
+ markMojomPendingLoad(id);
+ var url = new URL(relativePath, document.currentScript.src).href;
+ internal.global.document.write('
+
+```
+
+### Full documentation
+
+For detailed API documentation please visit [https://web-platform-tests.org/writing-tests/testharness-api.html](https://web-platform-tests.org/writing-tests/testharness-api.html).
+
+### Tutorials
+
+You can also read a tutorial on
+[Using testharness.js](http://darobin.github.com/test-harness-tutorial/docs/using-testharness.html).
diff --git a/test/fixtures/web-platform-tests/resources/sriharness.js b/test/fixtures/web-platform-tests/resources/sriharness.js
new file mode 100644
index 00000000000000..9d7fa76a7d65f6
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/sriharness.js
@@ -0,0 +1,100 @@
+var SRIScriptTest = function(pass, name, src, integrityValue, crossoriginValue, nonce) {
+ this.pass = pass;
+ this.name = "Script: " + name;
+ this.src = src;
+ this.integrityValue = integrityValue;
+ this.crossoriginValue = crossoriginValue;
+ this.nonce = nonce;
+}
+
+SRIScriptTest.prototype.execute = function() {
+ var test = async_test(this.name);
+ var e = document.createElement("script");
+ e.src = this.src;
+ e.setAttribute("integrity", this.integrityValue);
+ if(this.crossoriginValue) {
+ e.setAttribute("crossorigin", this.crossoriginValue);
+ }
+ if(this.nonce) {
+ e.setAttribute("nonce", this.nonce);
+ }
+ if(this.pass) {
+ e.addEventListener("load", function() {test.done()});
+ e.addEventListener("error", function() {
+ test.step(function(){ assert_unreached("Good load fired error handler.") })
+ });
+ } else {
+ e.addEventListener("load", function() {
+ test.step(function() { assert_unreached("Bad load succeeded.") })
+ });
+ e.addEventListener("error", function() {test.done()});
+ }
+ document.body.appendChild(e);
+};
+
+// tests
+// Style tests must be done synchronously because they rely on the presence
+// and absence of global style, which can affect later tests. Thus, instead
+// of executing them one at a time, the style tests are implemented as a
+// queue that builds up a list of tests, and then executes them one at a
+// time.
+var SRIStyleTest = function(queue, pass, name, attrs, customCallback, altPassValue) {
+ this.pass = pass;
+ this.name = "Style: " + name;
+ this.customCallback = customCallback || function () {};
+ this.attrs = attrs || {};
+ this.passValue = altPassValue || "rgb(255, 255, 0)";
+
+ this.test = async_test(this.name);
+
+ this.queue = queue;
+ this.queue.push(this);
+}
+
+SRIStyleTest.prototype.execute = function() {
+ var that = this;
+ var container = document.getElementById("container");
+ while (container.hasChildNodes()) {
+ container.removeChild(container.firstChild);
+ }
+
+ var test = this.test;
+
+ var div = document.createElement("div");
+ div.className = "testdiv";
+ var e = document.createElement("link");
+ this.attrs.rel = this.attrs.rel || "stylesheet";
+ for (var key in this.attrs) {
+ if (this.attrs.hasOwnProperty(key)) {
+ e.setAttribute(key, this.attrs[key]);
+ }
+ }
+
+ if(this.pass) {
+ e.addEventListener("load", function() {
+ test.step(function() {
+ var background = window.getComputedStyle(div, null).getPropertyValue("background-color");
+ assert_equals(background, that.passValue);
+ test.done();
+ });
+ });
+ e.addEventListener("error", function() {
+ test.step(function(){ assert_unreached("Good load fired error handler.") })
+ });
+ } else {
+ e.addEventListener("load", function() {
+ test.step(function() { assert_unreached("Bad load succeeded.") })
+ });
+ e.addEventListener("error", function() {
+ test.step(function() {
+ var background = window.getComputedStyle(div, null).getPropertyValue("background-color");
+ assert_not_equals(background, that.passValue);
+ test.done();
+ });
+ });
+ }
+ container.appendChild(div);
+ container.appendChild(e);
+ this.customCallback(e, container);
+};
+
diff --git a/test/fixtures/web-platform-tests/resources/test/README.md b/test/fixtures/web-platform-tests/resources/test/README.md
new file mode 100644
index 00000000000000..b756b91797f71c
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/test/README.md
@@ -0,0 +1,90 @@
+# `testharness.js` test suite
+
+The test suite for the `testharness.js` testing framework.
+
+## Executing Tests
+
+Install the following dependencies:
+
+- [Python 2.7.9+](https://www.python.org/)
+- [the tox Python package](https://tox.readthedocs.io/en/latest/)
+- [the Mozilla Firefox web browser](https://mozilla.org/firefox)
+- [the GeckoDriver server](https://github.com/mozilla/geckodriver)
+
+Make sure `geckodriver` can be found in your `PATH`.
+
+Currently, the tests should be run with the latest *Firefox Nightly*. In order to
+specify the path to Firefox Nightly, use the following command-line option:
+
+ tox -- --binary=/path/to/FirefoxNightly
+
+### Automated Script
+
+Alternatively, you may run `tools/ci/ci_resources_unittest.sh`, which only depends on
+Python 2. The script will install other dependencies automatically and start `tox` with
+the correct arguments.
+
+## Authoring Tests
+
+Test cases are expressed as `.html` files located within the `tests/unit/` and
+`tests/funtional/` sub-directories. Each test should include the
+`testharness.js` library with the following markup:
+
+
+
+
+This should be followed by one or more `
+
+### Unit tests
+
+The "unit test" type allows for concisely testing the expected behavior of
+assertion methods. These tests may define any number of sub-tests; the
+acceptance criteria is simply that all tests executed pass.
+
+### Functional tests
+
+Thoroughly testing the behavior of the harness itself requires ensuring a
+number of considerations which cannot be verified with the "unit testing"
+strategy. These include:
+
+- Ensuring that some tests are not run
+- Ensuring conditions that cause test failures
+- Ensuring conditions that cause harness errors
+
+Functional tests allow for these details to be verified. Every functional test
+must include a summary of the expected results as a JSON string within a
+`
+
+`testharness.js` is expected to function consistently in a number of
+distinct environments. In order to verify this expectation, each functional
+test may be executed under a number of distinct conditions. These conditions
+are applied using WPT's "test variants" pattern. The available variants are
+defined in the `variants.js` file; this file must be included before
+`testharness.js`. Every test must specify at least one variant.
diff --git a/test/fixtures/web-platform-tests/resources/test/conftest.py b/test/fixtures/web-platform-tests/resources/test/conftest.py
new file mode 100644
index 00000000000000..8765bf835dfc24
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/test/conftest.py
@@ -0,0 +1,223 @@
+import io
+import json
+import os
+import ssl
+import urllib2
+
+import html5lib
+import pytest
+from selenium import webdriver
+
+from wptserver import WPTServer
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+WPT_ROOT = os.path.normpath(os.path.join(HERE, '..', '..'))
+HARNESS = os.path.join(HERE, 'harness.html')
+TEST_TYPES = ('functional', 'unit')
+
+def pytest_addoption(parser):
+ parser.addoption("--binary", action="store", default=None, help="path to browser binary")
+
+def pytest_collect_file(path, parent):
+ if path.ext.lower() != '.html':
+ return
+
+ # Tests are organized in directories by type
+ test_type = os.path.relpath(str(path), HERE).split(os.path.sep)[1]
+
+ return HTMLItem(str(path), test_type, parent)
+
+def pytest_configure(config):
+ config.driver = webdriver.Firefox(firefox_binary=config.getoption("--binary"))
+ config.server = WPTServer(WPT_ROOT)
+ config.server.start()
+ # Although the name of the `_create_unverified_context` method suggests
+ # that it is not intended for external consumption, the standard library's
+ # documentation explicitly endorses its use:
+ #
+ # > To revert to the previous, unverified, behavior
+ # > ssl._create_unverified_context() can be passed to the context
+ # > parameter.
+ #
+ # https://docs.python.org/2/library/httplib.html#httplib.HTTPSConnection
+ config.ssl_context = ssl._create_unverified_context()
+ config.add_cleanup(config.server.stop)
+ config.add_cleanup(config.driver.quit)
+
+def resolve_uri(context, uri):
+ if uri.startswith('/'):
+ base = WPT_ROOT
+ path = uri[1:]
+ else:
+ base = os.path.dirname(context)
+ path = uri
+
+ return os.path.exists(os.path.join(base, path))
+
+class HTMLItem(pytest.Item, pytest.Collector):
+ def __init__(self, filename, test_type, parent):
+ self.url = parent.session.config.server.url(filename)
+ self.type = test_type
+ self.variants = []
+ # Some tests are reliant on the WPT servers substitution functionality,
+ # so tests must be retrieved from the server rather than read from the
+ # file system directly.
+ handle = urllib2.urlopen(self.url,
+ context=parent.session.config.ssl_context)
+ try:
+ markup = handle.read()
+ finally:
+ handle.close()
+
+ if test_type not in TEST_TYPES:
+ raise ValueError('Unrecognized test type: "%s"' % test_type)
+
+ parsed = html5lib.parse(markup, namespaceHTMLElements=False)
+ name = None
+ includes_variants_script = False
+ self.expected = None
+
+ for element in parsed.getiterator():
+ if not name and element.tag == 'title':
+ name = element.text
+ continue
+ if element.tag == 'meta' and element.attrib.get('name') == 'variant':
+ self.variants.append(element.attrib.get('content'))
+ continue
+ if element.tag == 'script':
+ if element.attrib.get('id') == 'expected':
+ self.expected = json.loads(unicode(element.text))
+
+ src = element.attrib.get('src', '')
+
+ if 'variants.js' in src:
+ includes_variants_script = True
+ if not resolve_uri(filename, src):
+ raise ValueError('Could not resolve path "%s" from %s' % (src, filename))
+
+ if not name:
+ raise ValueError('No name found in file: %s' % filename)
+ elif self.type == 'functional':
+ if not self.expected:
+ raise ValueError('Functional tests must specify expected report data')
+ if not includes_variants_script:
+ raise ValueError('No variants script found in file: %s' % filename)
+ if len(self.variants) == 0:
+ raise ValueError('No test variants specified in file %s' % filename)
+ elif self.type == 'unit' and self.expected:
+ raise ValueError('Unit tests must not specify expected report data')
+
+ super(HTMLItem, self).__init__(name, parent)
+
+
+ def reportinfo(self):
+ return self.fspath, None, self.url
+
+ def repr_failure(self, excinfo):
+ return pytest.Collector.repr_failure(self, excinfo)
+
+ def runtest(self):
+ if self.type == 'unit':
+ self._run_unit_test()
+ elif self.type == 'functional':
+ self._run_functional_test()
+ else:
+ raise NotImplementedError
+
+ def _run_unit_test(self):
+ driver = self.session.config.driver
+ server = self.session.config.server
+
+ driver.get(server.url(HARNESS))
+
+ actual = driver.execute_async_script(
+ 'runTest("%s", "foo", arguments[0])' % self.url
+ )
+
+ summarized = self._summarize(actual)
+
+ assert summarized[u'summarized_status'][u'status_string'] == u'OK', summarized[u'summarized_status'][u'message']
+ for test in summarized[u'summarized_tests']:
+ msg = "%s\n%s" % (test[u'name'], test[u'message'])
+ assert test[u'status_string'] == u'PASS', msg
+
+ def _run_functional_test(self):
+ for variant in self.variants:
+ self._run_functional_test_variant(variant)
+
+ def _run_functional_test_variant(self, variant):
+ driver = self.session.config.driver
+ server = self.session.config.server
+
+ driver.get(server.url(HARNESS))
+
+ test_url = self.url + variant
+ actual = driver.execute_async_script('runTest("%s", "foo", arguments[0])' % test_url)
+
+ # Test object ordering is not guaranteed. This weak assertion verifies
+ # that the indices are unique and sequential
+ indices = [test_obj.get('index') for test_obj in actual['tests']]
+ self._assert_sequence(indices)
+
+ summarized = self._summarize(actual)
+ self.expected[u'summarized_tests'].sort(key=lambda test_obj: test_obj.get('name'))
+
+ assert summarized == self.expected
+
+ def _summarize(self, actual):
+ summarized = {}
+
+ summarized[u'summarized_status'] = self._summarize_status(actual['status'])
+ summarized[u'summarized_tests'] = [
+ self._summarize_test(test) for test in actual['tests']]
+ summarized[u'summarized_tests'].sort(key=lambda test_obj: test_obj.get('name'))
+ summarized[u'type'] = actual['type']
+
+ return summarized
+
+ @staticmethod
+ def _assert_sequence(nums):
+ if nums and len(nums) > 0:
+ assert nums == range(1, nums[-1] + 1)
+
+ @staticmethod
+ def _scrub_stack(test_obj):
+ copy = dict(test_obj)
+ del copy['stack']
+ return copy
+
+ @staticmethod
+ def _expand_status(status_obj):
+ for key, value in [item for item in status_obj.items()]:
+ # In "status" and "test" objects, the "status" value enum
+ # definitions are interspersed with properties for unrelated
+ # metadata. The following condition is a best-effort attempt to
+ # ignore non-enum properties.
+ if key != key.upper() or not isinstance(value, int):
+ continue
+
+ del status_obj[key]
+
+ if status_obj['status'] == value:
+ status_obj[u'status_string'] = key
+
+ del status_obj['status']
+
+ return status_obj
+
+ @staticmethod
+ def _summarize_test(test_obj):
+ del test_obj['index']
+
+ assert 'phase' in test_obj
+ assert 'phases' in test_obj
+ assert 'COMPLETE' in test_obj['phases']
+ assert test_obj['phase'] == test_obj['phases']['COMPLETE']
+ del test_obj['phases']
+ del test_obj['phase']
+
+ return HTMLItem._expand_status(HTMLItem._scrub_stack(test_obj))
+
+ @staticmethod
+ def _summarize_status(status_obj):
+ return HTMLItem._expand_status(HTMLItem._scrub_stack(status_obj))
diff --git a/test/fixtures/web-platform-tests/resources/test/idl-helper.js b/test/fixtures/web-platform-tests/resources/test/idl-helper.js
new file mode 100644
index 00000000000000..2b73527ff2bece
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/test/idl-helper.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var typedefFrom = interfaceFrom;
+var dictionaryFrom = interfaceFrom;
+function interfaceFrom(i) {
+ var idl = new IdlArray();
+ idl.add_idls(i);
+ for (var prop in idl.members) {
+ return idl.members[prop];
+ }
+}
+
+function memberFrom(m) {
+ var idl = new IdlArray();
+ idl.add_idls('interface A { ' + m + '; };');
+ return idl.members["A"].members[0];
+}
+
+function typeFrom(type) {
+ var ast = WebIDL2.parse('interface Foo { ' + type + ' a(); };');
+ ast = ast[0]; // get the first fragment
+ ast = ast.members[0]; // get the first member
+ return ast.idlType; // get the type of the first field
+}
diff --git a/test/fixtures/web-platform-tests/resources/test/tests/functional/worker-error.js b/test/fixtures/web-platform-tests/resources/test/tests/functional/worker-error.js
new file mode 100644
index 00000000000000..7b89602f04b510
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/test/tests/functional/worker-error.js
@@ -0,0 +1,8 @@
+importScripts("/resources/testharness.js");
+
+// The following sub-test ensures that the worker is not interpreted as a
+// single-page test. The subsequent uncaught exception should therefore be
+// interpreted as a harness error rather than a single-page test failure.
+test(function() {}, "worker test that completes successfully before exception");
+
+throw new Error("This failure is expected.");
diff --git a/test/fixtures/web-platform-tests/resources/test/tests/functional/worker-uncaught-single.js b/test/fixtures/web-platform-tests/resources/test/tests/functional/worker-uncaught-single.js
new file mode 100644
index 00000000000000..d7f00382c0b92f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/test/tests/functional/worker-uncaught-single.js
@@ -0,0 +1,6 @@
+importScripts("/resources/testharness.js");
+
+// Because this script does not define any sub-tests, it should be interpreted
+// as a single-page test, and the uncaught exception should be reported as a
+// test failure (harness status: OK).
+throw new Error("This failure is expected.");
diff --git a/test/fixtures/web-platform-tests/resources/test/tests/functional/worker.js b/test/fixtures/web-platform-tests/resources/test/tests/functional/worker.js
new file mode 100644
index 00000000000000..a923bc2d89ecff
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/test/tests/functional/worker.js
@@ -0,0 +1,34 @@
+importScripts("/resources/testharness.js");
+
+test(
+ function(test) {
+ assert_true(true, "True is true");
+ },
+ "Worker test that completes successfully");
+
+test(
+ function(test) {
+ assert_true(false, "Failing test");
+ },
+ "Worker test that fails ('FAIL')");
+
+async_test(
+ function(test) {
+ assert_true(true, "True is true");
+ },
+ "Worker test that times out ('TIMEOUT')");
+
+async_test("Worker test that doesn't run ('NOT RUN')");
+
+async_test(
+ function(test) {
+ self.setTimeout(
+ function() {
+ test.done();
+ },
+ 0);
+ },
+ "Worker async_test that completes successfully");
+
+// An explicit done() is required for dedicated and shared web workers.
+done();
diff --git a/test/fixtures/web-platform-tests/resources/test/tests/unit/META.yml b/test/fixtures/web-platform-tests/resources/test/tests/unit/META.yml
new file mode 100644
index 00000000000000..cb9e3f87620a63
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/test/tests/unit/META.yml
@@ -0,0 +1,2 @@
+suggested_reviewers:
+ - tobie
diff --git a/test/fixtures/web-platform-tests/resources/test/tox.ini b/test/fixtures/web-platform-tests/resources/test/tox.ini
new file mode 100644
index 00000000000000..d3a30f870a1572
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/test/tox.ini
@@ -0,0 +1,16 @@
+[tox]
+# wptserve etc. are Python2-only.
+envlist = py27
+skipsdist=True
+
+[testenv]
+passenv=DISPLAY # Necessary for the spawned GeckoDriver process to connect to
+ # the appropriate display.
+deps =
+ html5lib
+ pytest>=2.9
+ pyvirtualdisplay
+ selenium
+ requests
+
+commands = pytest {posargs} -vv tests
diff --git a/test/fixtures/web-platform-tests/resources/test/variants.js b/test/fixtures/web-platform-tests/resources/test/variants.js
new file mode 100644
index 00000000000000..611d27803447a1
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/test/variants.js
@@ -0,0 +1,57 @@
+(function() {
+ 'use strict';
+ var variants = {
+ /**
+ * Tests are executed in the absence of the global Promise constructor by
+ * default in order to verify support for the Server browser engine.
+ *
+ * https://github.com/w3c/web-platform-tests/issues/6266
+ */
+ 'default': {
+ description: 'Global Promise constructor removed.',
+ apply: function() {
+ delete window.Promise;
+ }
+ },
+ /**
+ * This variant allows for testing functionality that is fundamentally
+ * dependent on Promise support, e.g. the `promise_test` function
+ */
+ 'keep-promise': {
+ description: 'No modification of global environment.',
+ apply: function() {}
+ }
+ };
+ var match = window.location.search.match(/\?(.*)$/);
+ var variantName = (match && match[1]) || 'default';
+
+ if (!Object.hasOwnProperty.call(variants, variantName)) {
+ window.location = 'javascript:"Unrecognized variant: ' + variantName + '";';
+ document.close();
+ return;
+ }
+
+ if (typeof test === 'function') {
+ test(function() {
+ assert_unreached('variants.js must be included before testharness.js');
+ });
+ }
+ var variant = variants[variantName];
+
+ var variantNode = document.createElement('div');
+ variantNode.innerHTML = 'This testharness.js test was executed with ' +
+ 'the variant named, "' + variantName + '". ' + variant.description +
+ '
Refer to the test harness README file for more information.
';
+ function onReady() {
+ if (document.readyState !== 'complete') {
+ return;
+ }
+
+ document.body.insertBefore(variantNode, document.body.firstChild);
+ }
+
+ onReady();
+ document.addEventListener('readystatechange', onReady);
+
+ variant.apply();
+}());
diff --git a/test/fixtures/web-platform-tests/resources/test/wptserver.py b/test/fixtures/web-platform-tests/resources/test/wptserver.py
new file mode 100644
index 00000000000000..fa32c33b9d78d6
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/test/wptserver.py
@@ -0,0 +1,54 @@
+import logging
+import os
+import subprocess
+import time
+import sys
+import urllib2
+
+
+class WPTServer(object):
+ def __init__(self, wpt_root):
+ self.wpt_root = wpt_root
+
+ # This is a terrible hack to get the default config of wptserve.
+ sys.path.insert(0, os.path.join(wpt_root, "tools"))
+ from serve.serve import build_config
+ with build_config() as config:
+ self.host = config["browser_host"]
+ self.http_port = config["ports"]["http"][0]
+ self.https_port = config["ports"]["https"][0]
+
+ self.base_url = 'http://%s:%s' % (self.host, self.http_port)
+ self.https_base_url = 'https://%s:%s' % (self.host, self.https_port)
+
+ def start(self):
+ self.devnull = open(os.devnull, 'w')
+ wptserve_cmd = [os.path.join(self.wpt_root, 'wpt'), 'serve']
+ logging.info('Executing %s' % ' '.join(wptserve_cmd))
+ self.proc = subprocess.Popen(
+ wptserve_cmd,
+ stderr=self.devnull,
+ cwd=self.wpt_root)
+
+ for retry in range(5):
+ # Exponential backoff.
+ time.sleep(2 ** retry)
+ exit_code = self.proc.poll()
+ if exit_code != None:
+ logging.warn('Command "%s" exited with %s', ' '.join(wptserve_cmd), exit_code)
+ break
+ try:
+ urllib2.urlopen(self.base_url, timeout=1)
+ return
+ except urllib2.URLError:
+ pass
+
+ raise Exception('Could not start wptserve on %s' % self.base_url)
+
+ def stop(self):
+ self.proc.terminate()
+ self.proc.wait()
+ self.devnull.close()
+
+ def url(self, abs_path):
+ return self.https_base_url + '/' + os.path.relpath(abs_path, self.wpt_root)
diff --git a/test/fixtures/web-platform-tests/resources/testdriver-vendor.js b/test/fixtures/web-platform-tests/resources/testdriver-vendor.js
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/test/fixtures/web-platform-tests/resources/testdriver-vendor.js.headers b/test/fixtures/web-platform-tests/resources/testdriver-vendor.js.headers
new file mode 100644
index 00000000000000..5e8f640c6659d1
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/testdriver-vendor.js.headers
@@ -0,0 +1,2 @@
+Content-Type: text/javascript; charset=utf-8
+Cache-Control: max-age=3600
diff --git a/test/fixtures/web-platform-tests/resources/testdriver.js b/test/fixtures/web-platform-tests/resources/testdriver.js
new file mode 100644
index 00000000000000..42ec824d015ab5
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/testdriver.js
@@ -0,0 +1,210 @@
+(function() {
+ "use strict";
+ var idCounter = 0;
+
+ function getInViewCenterPoint(rect) {
+ var left = Math.max(0, rect.left);
+ var right = Math.min(window.innerWidth, rect.right);
+ var top = Math.max(0, rect.top);
+ var bottom = Math.min(window.innerHeight, rect.bottom);
+
+ var x = 0.5 * (left + right);
+ var y = 0.5 * (top + bottom);
+
+ return [x, y];
+ }
+
+ function getPointerInteractablePaintTree(element) {
+ if (!window.document.contains(element)) {
+ return [];
+ }
+
+ var rectangles = element.getClientRects();
+
+ if (rectangles.length === 0) {
+ return [];
+ }
+
+ var centerPoint = getInViewCenterPoint(rectangles[0]);
+
+ if ("elementsFromPoint" in document) {
+ return document.elementsFromPoint(centerPoint[0], centerPoint[1]);
+ } else if ("msElementsFromPoint" in document) {
+ var rv = document.msElementsFromPoint(centerPoint[0], centerPoint[1]);
+ return Array.prototype.slice.call(rv ? rv : []);
+ } else {
+ throw new Error("document.elementsFromPoint unsupported");
+ }
+ }
+
+ function inView(element) {
+ var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
+ return pointerInteractablePaintTree.indexOf(element) !== -1;
+ }
+
+
+ /**
+ * @namespace
+ */
+ window.test_driver = {
+ /**
+ * Trigger user interaction in order to grant additional privileges to
+ * a provided function.
+ *
+ * https://html.spec.whatwg.org/#triggered-by-user-activation
+ *
+ * @param {String} intent - a description of the action which much be
+ * triggered by user interaction
+ * @param {Function} action - code requiring escalated privileges
+ *
+ * @returns {Promise} fulfilled following user interaction and
+ * execution of the provided `action` function;
+ * rejected if interaction fails or the provided
+ * function throws an error
+ */
+ bless: function(intent, action) {
+ var button = document.createElement("button");
+ button.innerHTML = "This test requires user interaction. " +
+ "Please click here to allow " + intent + ".";
+ button.id = "wpt-test-driver-bless-" + (idCounter += 1);
+ const elem = document.body || document.documentElement;
+ elem.appendChild(button);
+
+ return new Promise(function(resolve, reject) {
+ button.addEventListener("click", resolve);
+
+ test_driver.click(button).catch(reject);
+ }).then(function() {
+ button.remove();
+
+ if (typeof action === "function") {
+ return action();
+ }
+ });
+ },
+
+ /**
+ * Triggers a user-initiated click
+ *
+ * This matches the behaviour of the {@link
+ * https://w3c.github.io/webdriver/webdriver-spec.html#element-click|WebDriver
+ * Element Click command}.
+ *
+ * @param {Element} element - element to be clicked
+ * @returns {Promise} fulfilled after click occurs, or rejected in
+ * the cases the WebDriver command errors
+ */
+ click: function(element) {
+ if (window.top !== window) {
+ return Promise.reject(new Error("can only click in top-level window"));
+ }
+
+ if (!window.document.contains(element)) {
+ return Promise.reject(new Error("element in different document or shadow tree"));
+ }
+
+ if (!inView(element)) {
+ element.scrollIntoView({behavior: "instant",
+ block: "end",
+ inline: "nearest"});
+ }
+
+ var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
+ if (pointerInteractablePaintTree.length === 0 ||
+ !element.contains(pointerInteractablePaintTree[0])) {
+ return Promise.reject(new Error("element click intercepted error"));
+ }
+
+ var rect = element.getClientRects()[0];
+ var centerPoint = getInViewCenterPoint(rect);
+ return window.test_driver_internal.click(element,
+ {x: centerPoint[0],
+ y: centerPoint[1]});
+ },
+
+ /**
+ * Send keys to an element
+ *
+ * This matches the behaviour of the {@link
+ * https://w3c.github.io/webdriver/webdriver-spec.html#element-send-keys|WebDriver
+ * Send Keys command}.
+ *
+ * @param {Element} element - element to send keys to
+ * @param {String} keys - keys to send to the element
+ * @returns {Promise} fulfilled after keys are sent, or rejected in
+ * the cases the WebDriver command errors
+ */
+ send_keys: function(element, keys) {
+ if (window.top !== window) {
+ return Promise.reject(new Error("can only send keys in top-level window"));
+ }
+
+ if (!window.document.contains(element)) {
+ return Promise.reject(new Error("element in different document or shadow tree"));
+ }
+
+ if (!inView(element)) {
+ element.scrollIntoView({behavior: "instant",
+ block: "end",
+ inline: "nearest"});
+ }
+
+ var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
+ if (pointerInteractablePaintTree.length === 0 ||
+ !element.contains(pointerInteractablePaintTree[0])) {
+ return Promise.reject(new Error("element send_keys intercepted error"));
+ }
+
+ return window.test_driver_internal.send_keys(element, keys);
+ },
+
+ /**
+ * Freeze the current page
+ *
+ * The freeze function transitions the page from the HIDDEN state to
+ * the FROZEN state as described in {@link
+ * https://github.com/WICG/page-lifecycle/blob/master/README.md|Lifecycle API
+ * for Web Pages}
+ *
+ * @returns {Promise} fulfilled after the freeze request is sent, or rejected
+ * in case the WebDriver command errors
+ */
+ freeze: function() {
+ return window.test_driver_internal.freeze();
+ }
+ };
+
+ window.test_driver_internal = {
+ /**
+ * Triggers a user-initiated click
+ *
+ * @param {Element} element - element to be clicked
+ * @param {{x: number, y: number} coords - viewport coordinates to click at
+ * @returns {Promise} fulfilled after click occurs or rejected if click fails
+ */
+ click: function(element, coords) {
+ return Promise.reject(new Error("unimplemented"));
+ },
+
+ /**
+ * Triggers a user-initiated click
+ *
+ * @param {Element} element - element to be clicked
+ * @param {String} keys - keys to send to the element
+ * @returns {Promise} fulfilled after keys are sent or rejected if click fails
+ */
+ send_keys: function(element, keys) {
+ return Promise.reject(new Error("unimplemented"));
+ },
+
+ /**
+ * Freeze the current page
+ *
+ * @returns {Promise} fulfilled after freeze request is sent, otherwise
+ * it gets rejected
+ */
+ freeze: function() {
+ return Promise.reject(new Error("unimplemented"));
+ }
+ };
+})();
diff --git a/test/fixtures/web-platform-tests/resources/testdriver.js.headers b/test/fixtures/web-platform-tests/resources/testdriver.js.headers
new file mode 100644
index 00000000000000..5e8f640c6659d1
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/testdriver.js.headers
@@ -0,0 +1,2 @@
+Content-Type: text/javascript; charset=utf-8
+Cache-Control: max-age=3600
diff --git a/test/fixtures/web-platform-tests/resources/testharness.css.headers b/test/fixtures/web-platform-tests/resources/testharness.css.headers
new file mode 100644
index 00000000000000..e828b629858d07
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/testharness.css.headers
@@ -0,0 +1,2 @@
+Content-Type: text/css;charset=utf-8
+Cache-Control: max-age=3600
diff --git a/test/fixtures/web-platform-tests/resources/testharness.js b/test/fixtures/web-platform-tests/resources/testharness.js
new file mode 100644
index 00000000000000..f0c24635017dad
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/testharness.js
@@ -0,0 +1,3388 @@
+/*global self*/
+/*jshint latedef: nofunc*/
+/*
+Distributed under both the W3C Test Suite License [1] and the W3C
+3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
+policies and contribution forms [3].
+
+[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
+[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
+[3] http://www.w3.org/2004/10/27-testcases
+*/
+
+/* Documentation: https://web-platform-tests.org/writing-tests/testharness-api.html
+ * (../docs/_writing-tests/testharness-api.md) */
+
+(function (global_scope)
+{
+ var debug = false;
+ // default timeout is 10 seconds, test can override if needed
+ var settings = {
+ output:true,
+ harness_timeout:{
+ "normal":10000,
+ "long":60000
+ },
+ test_timeout:null,
+ message_events: ["start", "test_state", "result", "completion"]
+ };
+
+ var xhtml_ns = "http://www.w3.org/1999/xhtml";
+
+ /*
+ * TestEnvironment is an abstraction for the environment in which the test
+ * harness is used. Each implementation of a test environment has to provide
+ * the following interface:
+ *
+ * interface TestEnvironment {
+ * // Invoked after the global 'tests' object has been created and it's
+ * // safe to call add_*_callback() to register event handlers.
+ * void on_tests_ready();
+ *
+ * // Invoked after setup() has been called to notify the test environment
+ * // of changes to the test harness properties.
+ * void on_new_harness_properties(object properties);
+ *
+ * // Should return a new unique default test name.
+ * DOMString next_default_test_name();
+ *
+ * // Should return the test harness timeout duration in milliseconds.
+ * float test_timeout();
+ * };
+ */
+
+ /*
+ * A test environment with a DOM. The global object is 'window'. By default
+ * test results are displayed in a table. Any parent windows receive
+ * callbacks or messages via postMessage() when test events occur. See
+ * apisample11.html and apisample12.html.
+ */
+ function WindowTestEnvironment() {
+ this.name_counter = 0;
+ this.window_cache = null;
+ this.output_handler = null;
+ this.all_loaded = false;
+ var this_obj = this;
+ this.message_events = [];
+ this.dispatched_messages = [];
+
+ this.message_functions = {
+ start: [add_start_callback, remove_start_callback,
+ function (properties) {
+ this_obj._dispatch("start_callback", [properties],
+ {type: "start", properties: properties});
+ }],
+
+ test_state: [add_test_state_callback, remove_test_state_callback,
+ function(test) {
+ this_obj._dispatch("test_state_callback", [test],
+ {type: "test_state",
+ test: test.structured_clone()});
+ }],
+ result: [add_result_callback, remove_result_callback,
+ function (test) {
+ this_obj.output_handler.show_status();
+ this_obj._dispatch("result_callback", [test],
+ {type: "result",
+ test: test.structured_clone()});
+ }],
+ completion: [add_completion_callback, remove_completion_callback,
+ function (tests, harness_status) {
+ var cloned_tests = map(tests, function(test) {
+ return test.structured_clone();
+ });
+ this_obj._dispatch("completion_callback", [tests, harness_status],
+ {type: "complete",
+ tests: cloned_tests,
+ status: harness_status.structured_clone()});
+ }]
+ }
+
+ on_event(window, 'load', function() {
+ this_obj.all_loaded = true;
+ });
+
+ on_event(window, 'message', function(event) {
+ if (event.data && event.data.type === "getmessages" && event.source) {
+ // A window can post "getmessages" to receive a duplicate of every
+ // message posted by this environment so far. This allows subscribers
+ // from fetch_tests_from_window to 'catch up' to the current state of
+ // this environment.
+ for (var i = 0; i < this_obj.dispatched_messages.length; ++i)
+ {
+ event.source.postMessage(this_obj.dispatched_messages[i], "*");
+ }
+ }
+ });
+ }
+
+ WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) {
+ this.dispatched_messages.push(message_arg);
+ this._forEach_windows(
+ function(w, same_origin) {
+ if (same_origin) {
+ try {
+ var has_selector = selector in w;
+ } catch(e) {
+ // If document.domain was set at some point same_origin can be
+ // wrong and the above will fail.
+ has_selector = false;
+ }
+ if (has_selector) {
+ try {
+ w[selector].apply(undefined, callback_args);
+ } catch (e) {
+ if (debug) {
+ throw e;
+ }
+ }
+ }
+ }
+ if (supports_post_message(w) && w !== self) {
+ w.postMessage(message_arg, "*");
+ }
+ });
+ };
+
+ WindowTestEnvironment.prototype._forEach_windows = function(callback) {
+ // Iterate of the the windows [self ... top, opener]. The callback is passed
+ // two objects, the first one is the windows object itself, the second one
+ // is a boolean indicating whether or not its on the same origin as the
+ // current window.
+ var cache = this.window_cache;
+ if (!cache) {
+ cache = [[self, true]];
+ var w = self;
+ var i = 0;
+ var so;
+ while (w != w.parent) {
+ w = w.parent;
+ so = is_same_origin(w);
+ cache.push([w, so]);
+ i++;
+ }
+ w = window.opener;
+ if (w) {
+ cache.push([w, is_same_origin(w)]);
+ }
+ this.window_cache = cache;
+ }
+
+ forEach(cache,
+ function(a) {
+ callback.apply(null, a);
+ });
+ };
+
+ WindowTestEnvironment.prototype.on_tests_ready = function() {
+ var output = new Output();
+ this.output_handler = output;
+
+ var this_obj = this;
+
+ add_start_callback(function (properties) {
+ this_obj.output_handler.init(properties);
+ });
+
+ add_test_state_callback(function(test) {
+ this_obj.output_handler.show_status();
+ });
+
+ add_result_callback(function (test) {
+ this_obj.output_handler.show_status();
+ });
+
+ add_completion_callback(function (tests, harness_status) {
+ this_obj.output_handler.show_results(tests, harness_status);
+ });
+ this.setup_messages(settings.message_events);
+ };
+
+ WindowTestEnvironment.prototype.setup_messages = function(new_events) {
+ var this_obj = this;
+ forEach(settings.message_events, function(x) {
+ var current_dispatch = this_obj.message_events.indexOf(x) !== -1;
+ var new_dispatch = new_events.indexOf(x) !== -1;
+ if (!current_dispatch && new_dispatch) {
+ this_obj.message_functions[x][0](this_obj.message_functions[x][2]);
+ } else if (current_dispatch && !new_dispatch) {
+ this_obj.message_functions[x][1](this_obj.message_functions[x][2]);
+ }
+ });
+ this.message_events = new_events;
+ }
+
+ WindowTestEnvironment.prototype.next_default_test_name = function() {
+ var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
+ this.name_counter++;
+ return get_title() + suffix;
+ };
+
+ WindowTestEnvironment.prototype.on_new_harness_properties = function(properties) {
+ this.output_handler.setup(properties);
+ if (properties.hasOwnProperty("message_events")) {
+ this.setup_messages(properties.message_events);
+ }
+ };
+
+ WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
+ on_event(window, 'load', callback);
+ };
+
+ WindowTestEnvironment.prototype.test_timeout = function() {
+ var metas = document.getElementsByTagName("meta");
+ for (var i = 0; i < metas.length; i++) {
+ if (metas[i].name == "timeout") {
+ if (metas[i].content == "long") {
+ return settings.harness_timeout.long;
+ }
+ break;
+ }
+ }
+ return settings.harness_timeout.normal;
+ };
+
+ /*
+ * Base TestEnvironment implementation for a generic web worker.
+ *
+ * Workers accumulate test results. One or more clients can connect and
+ * retrieve results from a worker at any time.
+ *
+ * WorkerTestEnvironment supports communicating with a client via a
+ * MessagePort. The mechanism for determining the appropriate MessagePort
+ * for communicating with a client depends on the type of worker and is
+ * implemented by the various specializations of WorkerTestEnvironment
+ * below.
+ *
+ * A client document using testharness can use fetch_tests_from_worker() to
+ * retrieve results from a worker. See apisample16.html.
+ */
+ function WorkerTestEnvironment() {
+ this.name_counter = 0;
+ this.all_loaded = true;
+ this.message_list = [];
+ this.message_ports = [];
+ }
+
+ WorkerTestEnvironment.prototype._dispatch = function(message) {
+ this.message_list.push(message);
+ for (var i = 0; i < this.message_ports.length; ++i)
+ {
+ this.message_ports[i].postMessage(message);
+ }
+ };
+
+ // The only requirement is that port has a postMessage() method. It doesn't
+ // have to be an instance of a MessagePort, and often isn't.
+ WorkerTestEnvironment.prototype._add_message_port = function(port) {
+ this.message_ports.push(port);
+ for (var i = 0; i < this.message_list.length; ++i)
+ {
+ port.postMessage(this.message_list[i]);
+ }
+ };
+
+ WorkerTestEnvironment.prototype.next_default_test_name = function() {
+ var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
+ this.name_counter++;
+ return get_title() + suffix;
+ };
+
+ WorkerTestEnvironment.prototype.on_new_harness_properties = function() {};
+
+ WorkerTestEnvironment.prototype.on_tests_ready = function() {
+ var this_obj = this;
+ add_start_callback(
+ function(properties) {
+ this_obj._dispatch({
+ type: "start",
+ properties: properties,
+ });
+ });
+ add_test_state_callback(
+ function(test) {
+ this_obj._dispatch({
+ type: "test_state",
+ test: test.structured_clone()
+ });
+ });
+ add_result_callback(
+ function(test) {
+ this_obj._dispatch({
+ type: "result",
+ test: test.structured_clone()
+ });
+ });
+ add_completion_callback(
+ function(tests, harness_status) {
+ this_obj._dispatch({
+ type: "complete",
+ tests: map(tests,
+ function(test) {
+ return test.structured_clone();
+ }),
+ status: harness_status.structured_clone()
+ });
+ });
+ };
+
+ WorkerTestEnvironment.prototype.add_on_loaded_callback = function() {};
+
+ WorkerTestEnvironment.prototype.test_timeout = function() {
+ // Tests running in a worker don't have a default timeout. I.e. all
+ // worker tests behave as if settings.explicit_timeout is true.
+ return null;
+ };
+
+ /*
+ * Dedicated web workers.
+ * https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope
+ *
+ * This class is used as the test_environment when testharness is running
+ * inside a dedicated worker.
+ */
+ function DedicatedWorkerTestEnvironment() {
+ WorkerTestEnvironment.call(this);
+ // self is an instance of DedicatedWorkerGlobalScope which exposes
+ // a postMessage() method for communicating via the message channel
+ // established when the worker is created.
+ this._add_message_port(self);
+ }
+ DedicatedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
+
+ DedicatedWorkerTestEnvironment.prototype.on_tests_ready = function() {
+ WorkerTestEnvironment.prototype.on_tests_ready.call(this);
+ // In the absence of an onload notification, we a require dedicated
+ // workers to explicitly signal when the tests are done.
+ tests.wait_for_finish = true;
+ };
+
+ /*
+ * Shared web workers.
+ * https://html.spec.whatwg.org/multipage/workers.html#sharedworkerglobalscope
+ *
+ * This class is used as the test_environment when testharness is running
+ * inside a shared web worker.
+ */
+ function SharedWorkerTestEnvironment() {
+ WorkerTestEnvironment.call(this);
+ var this_obj = this;
+ // Shared workers receive message ports via the 'onconnect' event for
+ // each connection.
+ self.addEventListener("connect",
+ function(message_event) {
+ this_obj._add_message_port(message_event.source);
+ }, false);
+ }
+ SharedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
+
+ SharedWorkerTestEnvironment.prototype.on_tests_ready = function() {
+ WorkerTestEnvironment.prototype.on_tests_ready.call(this);
+ // In the absence of an onload notification, we a require shared
+ // workers to explicitly signal when the tests are done.
+ tests.wait_for_finish = true;
+ };
+
+ /*
+ * Service workers.
+ * http://www.w3.org/TR/service-workers/
+ *
+ * This class is used as the test_environment when testharness is running
+ * inside a service worker.
+ */
+ function ServiceWorkerTestEnvironment() {
+ WorkerTestEnvironment.call(this);
+ this.all_loaded = false;
+ this.on_loaded_callback = null;
+ var this_obj = this;
+ self.addEventListener("message",
+ function(event) {
+ if (event.data && event.data.type && event.data.type === "connect") {
+ if (event.ports && event.ports[0]) {
+ // If a MessageChannel was passed, then use it to
+ // send results back to the main window. This
+ // allows the tests to work even if the browser
+ // does not fully support MessageEvent.source in
+ // ServiceWorkers yet.
+ this_obj._add_message_port(event.ports[0]);
+ event.ports[0].start();
+ } else {
+ // If there is no MessageChannel, then attempt to
+ // use the MessageEvent.source to send results
+ // back to the main window.
+ this_obj._add_message_port(event.source);
+ }
+ }
+ }, false);
+
+ // The oninstall event is received after the service worker script and
+ // all imported scripts have been fetched and executed. It's the
+ // equivalent of an onload event for a document. All tests should have
+ // been added by the time this event is received, thus it's not
+ // necessary to wait until the onactivate event. However, tests for
+ // installed service workers need another event which is equivalent to
+ // the onload event because oninstall is fired only on installation. The
+ // onmessage event is used for that purpose since tests using
+ // testharness.js should ask the result to its service worker by
+ // PostMessage. If the onmessage event is triggered on the service
+ // worker's context, that means the worker's script has been evaluated.
+ on_event(self, "install", on_all_loaded);
+ on_event(self, "message", on_all_loaded);
+ function on_all_loaded() {
+ if (this_obj.all_loaded)
+ return;
+ this_obj.all_loaded = true;
+ if (this_obj.on_loaded_callback) {
+ this_obj.on_loaded_callback();
+ }
+ }
+ }
+
+ ServiceWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
+
+ ServiceWorkerTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
+ if (this.all_loaded) {
+ callback();
+ } else {
+ this.on_loaded_callback = callback;
+ }
+ };
+
+ /*
+ * JavaScript shells.
+ *
+ * This class is used as the test_environment when testharness is running
+ * inside a JavaScript shell.
+ */
+ function ShellTestEnvironment() {
+ this.name_counter = 0;
+ this.all_loaded = false;
+ this.on_loaded_callback = null;
+ Promise.resolve().then(function() {
+ this.all_loaded = true
+ if (this.on_loaded_callback) {
+ this.on_loaded_callback();
+ }
+ }.bind(this));
+ this.message_list = [];
+ this.message_ports = [];
+ }
+
+ ShellTestEnvironment.prototype.next_default_test_name = function() {
+ var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
+ this.name_counter++;
+ return "Untitled" + suffix;
+ };
+
+ ShellTestEnvironment.prototype.on_new_harness_properties = function() {};
+
+ ShellTestEnvironment.prototype.on_tests_ready = function() {};
+
+ ShellTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
+ if (this.all_loaded) {
+ callback();
+ } else {
+ this.on_loaded_callback = callback;
+ }
+ };
+
+ ShellTestEnvironment.prototype.test_timeout = function() {
+ // Tests running in a shell don't have a default timeout, so behave as
+ // if settings.explicit_timeout is true.
+ return null;
+ };
+
+ function create_test_environment() {
+ if ('document' in global_scope) {
+ return new WindowTestEnvironment();
+ }
+ if ('DedicatedWorkerGlobalScope' in global_scope &&
+ global_scope instanceof DedicatedWorkerGlobalScope) {
+ return new DedicatedWorkerTestEnvironment();
+ }
+ if ('SharedWorkerGlobalScope' in global_scope &&
+ global_scope instanceof SharedWorkerGlobalScope) {
+ return new SharedWorkerTestEnvironment();
+ }
+ if ('ServiceWorkerGlobalScope' in global_scope &&
+ global_scope instanceof ServiceWorkerGlobalScope) {
+ return new ServiceWorkerTestEnvironment();
+ }
+ if ('WorkerGlobalScope' in global_scope &&
+ global_scope instanceof WorkerGlobalScope) {
+ return new DedicatedWorkerTestEnvironment();
+ }
+
+ if (!('self' in global_scope)) {
+ return new ShellTestEnvironment();
+ }
+
+ throw new Error("Unsupported test environment");
+ }
+
+ var test_environment = create_test_environment();
+
+ function is_shared_worker(worker) {
+ return 'SharedWorker' in global_scope && worker instanceof SharedWorker;
+ }
+
+ function is_service_worker(worker) {
+ // The worker object may be from another execution context,
+ // so do not use instanceof here.
+ return 'ServiceWorker' in global_scope &&
+ Object.prototype.toString.call(worker) == '[object ServiceWorker]';
+ }
+
+ /*
+ * API functions
+ */
+ function test(func, name, properties)
+ {
+ var test_name = name ? name : test_environment.next_default_test_name();
+ properties = properties ? properties : {};
+ var test_obj = new Test(test_name, properties);
+ test_obj.step(func, test_obj, test_obj);
+ if (test_obj.phase === test_obj.phases.STARTED) {
+ test_obj.done();
+ }
+ }
+
+ function async_test(func, name, properties)
+ {
+ if (typeof func !== "function") {
+ properties = name;
+ name = func;
+ func = null;
+ }
+ var test_name = name ? name : test_environment.next_default_test_name();
+ properties = properties ? properties : {};
+ var test_obj = new Test(test_name, properties);
+ if (func) {
+ test_obj.step(func, test_obj, test_obj);
+ }
+ return test_obj;
+ }
+
+ function promise_test(func, name, properties) {
+ var test = async_test(name, properties);
+ test._is_promise_test = true;
+
+ // If there is no promise tests queue make one.
+ if (!tests.promise_tests) {
+ tests.promise_tests = Promise.resolve();
+ }
+ tests.promise_tests = tests.promise_tests.then(function() {
+ return new Promise(function(resolve) {
+ var promise = test.step(func, test, test);
+
+ test.step(function() {
+ assert_not_equals(promise, undefined);
+ });
+
+ // Test authors may use the `step` method within a
+ // `promise_test` even though this reflects a mixture of
+ // asynchronous control flow paradigms. The "done" callback
+ // should be registered prior to the resolution of the
+ // user-provided Promise to avoid timeouts in cases where the
+ // Promise does not settle but a `step` function has thrown an
+ // error.
+ add_test_done_callback(test, resolve);
+
+ Promise.resolve(promise)
+ .catch(test.step_func(
+ function(value) {
+ if (value instanceof AssertionError) {
+ throw value;
+ }
+ assert(false, "promise_test", null,
+ "Unhandled rejection with value: ${value}", {value:value});
+ }))
+ .then(function() {
+ test.done();
+ });
+ });
+ });
+ }
+
+ function promise_rejects(test, expected, promise, description) {
+ return promise.then(test.unreached_func("Should have rejected: " + description)).catch(function(e) {
+ assert_throws(expected, function() { throw e }, description);
+ });
+ }
+
+ /**
+ * This constructor helper allows DOM events to be handled using Promises,
+ * which can make it a lot easier to test a very specific series of events,
+ * including ensuring that unexpected events are not fired at any point.
+ */
+ function EventWatcher(test, watchedNode, eventTypes)
+ {
+ if (typeof eventTypes == 'string') {
+ eventTypes = [eventTypes];
+ }
+
+ var waitingFor = null;
+
+ // This is null unless we are recording all events, in which case it
+ // will be an Array object.
+ var recordedEvents = null;
+
+ var eventHandler = test.step_func(function(evt) {
+ assert_true(!!waitingFor,
+ 'Not expecting event, but got ' + evt.type + ' event');
+ assert_equals(evt.type, waitingFor.types[0],
+ 'Expected ' + waitingFor.types[0] + ' event, but got ' +
+ evt.type + ' event instead');
+
+ if (Array.isArray(recordedEvents)) {
+ recordedEvents.push(evt);
+ }
+
+ if (waitingFor.types.length > 1) {
+ // Pop first event from array
+ waitingFor.types.shift();
+ return;
+ }
+ // We need to null out waitingFor before calling the resolve function
+ // since the Promise's resolve handlers may call wait_for() which will
+ // need to set waitingFor.
+ var resolveFunc = waitingFor.resolve;
+ waitingFor = null;
+ // Likewise, we should reset the state of recordedEvents.
+ var result = recordedEvents || evt;
+ recordedEvents = null;
+ resolveFunc(result);
+ });
+
+ for (var i = 0; i < eventTypes.length; i++) {
+ watchedNode.addEventListener(eventTypes[i], eventHandler, false);
+ }
+
+ /**
+ * Returns a Promise that will resolve after the specified event or
+ * series of events has occurred.
+ *
+ * @param options An optional options object. If the 'record' property
+ * on this object has the value 'all', when the Promise
+ * returned by this function is resolved, *all* Event
+ * objects that were waited for will be returned as an
+ * array.
+ *
+ * For example,
+ *
+ * ```js
+ * const watcher = new EventWatcher(t, div, [ 'animationstart',
+ * 'animationiteration',
+ * 'animationend' ]);
+ * return watcher.wait_for([ 'animationstart', 'animationend' ],
+ * { record: 'all' }).then(evts => {
+ * assert_equals(evts[0].elapsedTime, 0.0);
+ * assert_equals(evts[1].elapsedTime, 2.0);
+ * });
+ * ```
+ */
+ this.wait_for = function(types, options) {
+ if (waitingFor) {
+ return Promise.reject('Already waiting for an event or events');
+ }
+ if (typeof types == 'string') {
+ types = [types];
+ }
+ if (options && options.record && options.record === 'all') {
+ recordedEvents = [];
+ }
+ return new Promise(function(resolve, reject) {
+ waitingFor = {
+ types: types,
+ resolve: resolve,
+ reject: reject
+ };
+ });
+ };
+
+ function stop_watching() {
+ for (var i = 0; i < eventTypes.length; i++) {
+ watchedNode.removeEventListener(eventTypes[i], eventHandler, false);
+ }
+ };
+
+ test._add_cleanup(stop_watching);
+
+ return this;
+ }
+ expose(EventWatcher, 'EventWatcher');
+
+ function setup(func_or_properties, maybe_properties)
+ {
+ var func = null;
+ var properties = {};
+ if (arguments.length === 2) {
+ func = func_or_properties;
+ properties = maybe_properties;
+ } else if (func_or_properties instanceof Function) {
+ func = func_or_properties;
+ } else {
+ properties = func_or_properties;
+ }
+ tests.setup(func, properties);
+ test_environment.on_new_harness_properties(properties);
+ }
+
+ function done() {
+ if (tests.tests.length === 0) {
+ tests.set_file_is_test();
+ }
+ if (tests.file_is_test) {
+ // file is test files never have asynchronous cleanup logic,
+ // meaning the fully-sycnronous `done` funtion can be used here.
+ tests.tests[0].done();
+ }
+ tests.end_wait();
+ }
+
+ function generate_tests(func, args, properties) {
+ forEach(args, function(x, i)
+ {
+ var name = x[0];
+ test(function()
+ {
+ func.apply(this, x.slice(1));
+ },
+ name,
+ Array.isArray(properties) ? properties[i] : properties);
+ });
+ }
+
+ function on_event(object, event, callback)
+ {
+ object.addEventListener(event, callback, false);
+ }
+
+ function step_timeout(f, t) {
+ var outer_this = this;
+ var args = Array.prototype.slice.call(arguments, 2);
+ return setTimeout(function() {
+ f.apply(outer_this, args);
+ }, t * tests.timeout_multiplier);
+ }
+
+ expose(test, 'test');
+ expose(async_test, 'async_test');
+ expose(promise_test, 'promise_test');
+ expose(promise_rejects, 'promise_rejects');
+ expose(generate_tests, 'generate_tests');
+ expose(setup, 'setup');
+ expose(done, 'done');
+ expose(on_event, 'on_event');
+ expose(step_timeout, 'step_timeout');
+
+ /*
+ * Return a string truncated to the given length, with ... added at the end
+ * if it was longer.
+ */
+ function truncate(s, len)
+ {
+ if (s.length > len) {
+ return s.substring(0, len - 3) + "...";
+ }
+ return s;
+ }
+
+ /*
+ * Return true if object is probably a Node object.
+ */
+ function is_node(object)
+ {
+ // I use duck-typing instead of instanceof, because
+ // instanceof doesn't work if the node is from another window (like an
+ // iframe's contentWindow):
+ // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
+ try {
+ var has_node_properties = ("nodeType" in object &&
+ "nodeName" in object &&
+ "nodeValue" in object &&
+ "childNodes" in object);
+ } catch (e) {
+ // We're probably cross-origin, which means we aren't a node
+ return false;
+ }
+
+ if (has_node_properties) {
+ try {
+ object.nodeType;
+ } catch (e) {
+ // The object is probably Node.prototype or another prototype
+ // object that inherits from it, and not a Node instance.
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ var replacements = {
+ "0": "0",
+ "1": "x01",
+ "2": "x02",
+ "3": "x03",
+ "4": "x04",
+ "5": "x05",
+ "6": "x06",
+ "7": "x07",
+ "8": "b",
+ "9": "t",
+ "10": "n",
+ "11": "v",
+ "12": "f",
+ "13": "r",
+ "14": "x0e",
+ "15": "x0f",
+ "16": "x10",
+ "17": "x11",
+ "18": "x12",
+ "19": "x13",
+ "20": "x14",
+ "21": "x15",
+ "22": "x16",
+ "23": "x17",
+ "24": "x18",
+ "25": "x19",
+ "26": "x1a",
+ "27": "x1b",
+ "28": "x1c",
+ "29": "x1d",
+ "30": "x1e",
+ "31": "x1f",
+ "0xfffd": "ufffd",
+ "0xfffe": "ufffe",
+ "0xffff": "uffff",
+ };
+
+ /*
+ * Convert a value to a nice, human-readable string
+ */
+ function format_value(val, seen)
+ {
+ if (!seen) {
+ seen = [];
+ }
+ if (typeof val === "object" && val !== null) {
+ if (seen.indexOf(val) >= 0) {
+ return "[...]";
+ }
+ seen.push(val);
+ }
+ if (Array.isArray(val)) {
+ return "[" + val.map(function(x) {return format_value(x, seen);}).join(", ") + "]";
+ }
+
+ switch (typeof val) {
+ case "string":
+ val = val.replace("\\", "\\\\");
+ for (var p in replacements) {
+ var replace = "\\" + replacements[p];
+ val = val.replace(RegExp(String.fromCharCode(p), "g"), replace);
+ }
+ return '"' + val.replace(/"/g, '\\"') + '"';
+ case "boolean":
+ case "undefined":
+ return String(val);
+ case "number":
+ // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
+ // special-case.
+ if (val === -0 && 1/val === -Infinity) {
+ return "-0";
+ }
+ return String(val);
+ case "object":
+ if (val === null) {
+ return "null";
+ }
+
+ // Special-case Node objects, since those come up a lot in my tests. I
+ // ignore namespaces.
+ if (is_node(val)) {
+ switch (val.nodeType) {
+ case Node.ELEMENT_NODE:
+ var ret = "<" + val.localName;
+ for (var i = 0; i < val.attributes.length; i++) {
+ ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"';
+ }
+ ret += ">" + val.innerHTML + "" + val.localName + ">";
+ return "Element node " + truncate(ret, 60);
+ case Node.TEXT_NODE:
+ return 'Text node "' + truncate(val.data, 60) + '"';
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60));
+ case Node.COMMENT_NODE:
+ return "Comment node ";
+ case Node.DOCUMENT_NODE:
+ return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
+ case Node.DOCUMENT_TYPE_NODE:
+ return "DocumentType node";
+ case Node.DOCUMENT_FRAGMENT_NODE:
+ return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
+ default:
+ return "Node object of unknown type";
+ }
+ }
+
+ /* falls through */
+ default:
+ try {
+ return typeof val + ' "' + truncate(String(val), 1000) + '"';
+ } catch(e) {
+ return ("[stringifying object threw " + String(e) +
+ " with type " + String(typeof e) + "]");
+ }
+ }
+ }
+ expose(format_value, "format_value");
+
+ /*
+ * Assertions
+ */
+
+ function assert_true(actual, description)
+ {
+ assert(actual === true, "assert_true", description,
+ "expected true got ${actual}", {actual:actual});
+ }
+ expose(assert_true, "assert_true");
+
+ function assert_false(actual, description)
+ {
+ assert(actual === false, "assert_false", description,
+ "expected false got ${actual}", {actual:actual});
+ }
+ expose(assert_false, "assert_false");
+
+ function same_value(x, y) {
+ if (y !== y) {
+ //NaN case
+ return x !== x;
+ }
+ if (x === 0 && y === 0) {
+ //Distinguish +0 and -0
+ return 1/x === 1/y;
+ }
+ return x === y;
+ }
+
+ function assert_equals(actual, expected, description)
+ {
+ /*
+ * Test if two primitives are equal or two objects
+ * are the same object
+ */
+ if (typeof actual != typeof expected) {
+ assert(false, "assert_equals", description,
+ "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}",
+ {expected:expected, actual:actual});
+ return;
+ }
+ assert(same_value(actual, expected), "assert_equals", description,
+ "expected ${expected} but got ${actual}",
+ {expected:expected, actual:actual});
+ }
+ expose(assert_equals, "assert_equals");
+
+ function assert_not_equals(actual, expected, description)
+ {
+ /*
+ * Test if two primitives are unequal or two objects
+ * are different objects
+ */
+ assert(!same_value(actual, expected), "assert_not_equals", description,
+ "got disallowed value ${actual}",
+ {actual:actual});
+ }
+ expose(assert_not_equals, "assert_not_equals");
+
+ function assert_in_array(actual, expected, description)
+ {
+ assert(expected.indexOf(actual) != -1, "assert_in_array", description,
+ "value ${actual} not in array ${expected}",
+ {actual:actual, expected:expected});
+ }
+ expose(assert_in_array, "assert_in_array");
+
+ function assert_object_equals(actual, expected, description)
+ {
+ assert(typeof actual === "object" && actual !== null, "assert_object_equals", description,
+ "value is ${actual}, expected object",
+ {actual: actual});
+ //This needs to be improved a great deal
+ function check_equal(actual, expected, stack)
+ {
+ stack.push(actual);
+
+ var p;
+ for (p in actual) {
+ assert(expected.hasOwnProperty(p), "assert_object_equals", description,
+ "unexpected property ${p}", {p:p});
+
+ if (typeof actual[p] === "object" && actual[p] !== null) {
+ if (stack.indexOf(actual[p]) === -1) {
+ check_equal(actual[p], expected[p], stack);
+ }
+ } else {
+ assert(same_value(actual[p], expected[p]), "assert_object_equals", description,
+ "property ${p} expected ${expected} got ${actual}",
+ {p:p, expected:expected, actual:actual});
+ }
+ }
+ for (p in expected) {
+ assert(actual.hasOwnProperty(p),
+ "assert_object_equals", description,
+ "expected property ${p} missing", {p:p});
+ }
+ stack.pop();
+ }
+ check_equal(actual, expected, []);
+ }
+ expose(assert_object_equals, "assert_object_equals");
+
+ function assert_array_equals(actual, expected, description)
+ {
+ assert(typeof actual === "object" && actual !== null && "length" in actual,
+ "assert_array_equals", description,
+ "value is ${actual}, expected array",
+ {actual:actual});
+ assert(actual.length === expected.length,
+ "assert_array_equals", description,
+ "lengths differ, expected ${expected} got ${actual}",
+ {expected:expected.length, actual:actual.length});
+
+ for (var i = 0; i < actual.length; i++) {
+ assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
+ "assert_array_equals", description,
+ "property ${i}, property expected to be ${expected} but was ${actual}",
+ {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
+ actual:actual.hasOwnProperty(i) ? "present" : "missing"});
+ assert(same_value(expected[i], actual[i]),
+ "assert_array_equals", description,
+ "property ${i}, expected ${expected} but got ${actual}",
+ {i:i, expected:expected[i], actual:actual[i]});
+ }
+ }
+ expose(assert_array_equals, "assert_array_equals");
+
+ function assert_array_approx_equals(actual, expected, epsilon, description)
+ {
+ /*
+ * Test if two primitive arrays are equal within +/- epsilon
+ */
+ assert(actual.length === expected.length,
+ "assert_array_approx_equals", description,
+ "lengths differ, expected ${expected} got ${actual}",
+ {expected:expected.length, actual:actual.length});
+
+ for (var i = 0; i < actual.length; i++) {
+ assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
+ "assert_array_approx_equals", description,
+ "property ${i}, property expected to be ${expected} but was ${actual}",
+ {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
+ actual:actual.hasOwnProperty(i) ? "present" : "missing"});
+ assert(typeof actual[i] === "number",
+ "assert_array_approx_equals", description,
+ "property ${i}, expected a number but got a ${type_actual}",
+ {i:i, type_actual:typeof actual[i]});
+ assert(Math.abs(actual[i] - expected[i]) <= epsilon,
+ "assert_array_approx_equals", description,
+ "property ${i}, expected ${expected} +/- ${epsilon}, expected ${expected} but got ${actual}",
+ {i:i, expected:expected[i], actual:actual[i]});
+ }
+ }
+ expose(assert_array_approx_equals, "assert_array_approx_equals");
+
+ function assert_approx_equals(actual, expected, epsilon, description)
+ {
+ /*
+ * Test if two primitive numbers are equal within +/- epsilon
+ */
+ assert(typeof actual === "number",
+ "assert_approx_equals", description,
+ "expected a number but got a ${type_actual}",
+ {type_actual:typeof actual});
+
+ assert(Math.abs(actual - expected) <= epsilon,
+ "assert_approx_equals", description,
+ "expected ${expected} +/- ${epsilon} but got ${actual}",
+ {expected:expected, actual:actual, epsilon:epsilon});
+ }
+ expose(assert_approx_equals, "assert_approx_equals");
+
+ function assert_less_than(actual, expected, description)
+ {
+ /*
+ * Test if a primitive number is less than another
+ */
+ assert(typeof actual === "number",
+ "assert_less_than", description,
+ "expected a number but got a ${type_actual}",
+ {type_actual:typeof actual});
+
+ assert(actual < expected,
+ "assert_less_than", description,
+ "expected a number less than ${expected} but got ${actual}",
+ {expected:expected, actual:actual});
+ }
+ expose(assert_less_than, "assert_less_than");
+
+ function assert_greater_than(actual, expected, description)
+ {
+ /*
+ * Test if a primitive number is greater than another
+ */
+ assert(typeof actual === "number",
+ "assert_greater_than", description,
+ "expected a number but got a ${type_actual}",
+ {type_actual:typeof actual});
+
+ assert(actual > expected,
+ "assert_greater_than", description,
+ "expected a number greater than ${expected} but got ${actual}",
+ {expected:expected, actual:actual});
+ }
+ expose(assert_greater_than, "assert_greater_than");
+
+ function assert_between_exclusive(actual, lower, upper, description)
+ {
+ /*
+ * Test if a primitive number is between two others
+ */
+ assert(typeof actual === "number",
+ "assert_between_exclusive", description,
+ "expected a number but got a ${type_actual}",
+ {type_actual:typeof actual});
+
+ assert(actual > lower && actual < upper,
+ "assert_between_exclusive", description,
+ "expected a number greater than ${lower} " +
+ "and less than ${upper} but got ${actual}",
+ {lower:lower, upper:upper, actual:actual});
+ }
+ expose(assert_between_exclusive, "assert_between_exclusive");
+
+ function assert_less_than_equal(actual, expected, description)
+ {
+ /*
+ * Test if a primitive number is less than or equal to another
+ */
+ assert(typeof actual === "number",
+ "assert_less_than_equal", description,
+ "expected a number but got a ${type_actual}",
+ {type_actual:typeof actual});
+
+ assert(actual <= expected,
+ "assert_less_than_equal", description,
+ "expected a number less than or equal to ${expected} but got ${actual}",
+ {expected:expected, actual:actual});
+ }
+ expose(assert_less_than_equal, "assert_less_than_equal");
+
+ function assert_greater_than_equal(actual, expected, description)
+ {
+ /*
+ * Test if a primitive number is greater than or equal to another
+ */
+ assert(typeof actual === "number",
+ "assert_greater_than_equal", description,
+ "expected a number but got a ${type_actual}",
+ {type_actual:typeof actual});
+
+ assert(actual >= expected,
+ "assert_greater_than_equal", description,
+ "expected a number greater than or equal to ${expected} but got ${actual}",
+ {expected:expected, actual:actual});
+ }
+ expose(assert_greater_than_equal, "assert_greater_than_equal");
+
+ function assert_between_inclusive(actual, lower, upper, description)
+ {
+ /*
+ * Test if a primitive number is between to two others or equal to either of them
+ */
+ assert(typeof actual === "number",
+ "assert_between_inclusive", description,
+ "expected a number but got a ${type_actual}",
+ {type_actual:typeof actual});
+
+ assert(actual >= lower && actual <= upper,
+ "assert_between_inclusive", description,
+ "expected a number greater than or equal to ${lower} " +
+ "and less than or equal to ${upper} but got ${actual}",
+ {lower:lower, upper:upper, actual:actual});
+ }
+ expose(assert_between_inclusive, "assert_between_inclusive");
+
+ function assert_regexp_match(actual, expected, description) {
+ /*
+ * Test if a string (actual) matches a regexp (expected)
+ */
+ assert(expected.test(actual),
+ "assert_regexp_match", description,
+ "expected ${expected} but got ${actual}",
+ {expected:expected, actual:actual});
+ }
+ expose(assert_regexp_match, "assert_regexp_match");
+
+ function assert_class_string(object, class_string, description) {
+ assert_equals({}.toString.call(object), "[object " + class_string + "]",
+ description);
+ }
+ expose(assert_class_string, "assert_class_string");
+
+
+ function _assert_own_property(name) {
+ return function(object, property_name, description)
+ {
+ assert(object.hasOwnProperty(property_name),
+ name, description,
+ "expected property ${p} missing", {p:property_name});
+ };
+ }
+ expose(_assert_own_property("assert_exists"), "assert_exists");
+ expose(_assert_own_property("assert_own_property"), "assert_own_property");
+
+ function assert_not_exists(object, property_name, description)
+ {
+ assert(!object.hasOwnProperty(property_name),
+ "assert_not_exists", description,
+ "unexpected property ${p} found", {p:property_name});
+ }
+ expose(assert_not_exists, "assert_not_exists");
+
+ function _assert_inherits(name) {
+ return function (object, property_name, description)
+ {
+ assert(typeof object === "object" || typeof object === "function",
+ name, description,
+ "provided value is not an object");
+
+ assert("hasOwnProperty" in object,
+ name, description,
+ "provided value is an object but has no hasOwnProperty method");
+
+ assert(!object.hasOwnProperty(property_name),
+ name, description,
+ "property ${p} found on object expected in prototype chain",
+ {p:property_name});
+
+ assert(property_name in object,
+ name, description,
+ "property ${p} not found in prototype chain",
+ {p:property_name});
+ };
+ }
+ expose(_assert_inherits("assert_inherits"), "assert_inherits");
+ expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
+
+ function assert_readonly(object, property_name, description)
+ {
+ var initial_value = object[property_name];
+ try {
+ //Note that this can have side effects in the case where
+ //the property has PutForwards
+ object[property_name] = initial_value + "a"; //XXX use some other value here?
+ assert(same_value(object[property_name], initial_value),
+ "assert_readonly", description,
+ "changing property ${p} succeeded",
+ {p:property_name});
+ } finally {
+ object[property_name] = initial_value;
+ }
+ }
+ expose(assert_readonly, "assert_readonly");
+
+ /**
+ * Assert an Exception with the expected code is thrown.
+ *
+ * @param {object|number|string} code The expected exception code.
+ * @param {Function} func Function which should throw.
+ * @param {string} description Error description for the case that the error is not thrown.
+ */
+ function assert_throws(code, func, description)
+ {
+ try {
+ func.call(this);
+ assert(false, "assert_throws", description,
+ "${func} did not throw", {func:func});
+ } catch (e) {
+ if (e instanceof AssertionError) {
+ throw e;
+ }
+
+ assert(typeof e === "object",
+ "assert_throws", description,
+ "${func} threw ${e} with type ${type}, not an object",
+ {func:func, e:e, type:typeof e});
+
+ assert(e !== null,
+ "assert_throws", description,
+ "${func} threw null, not an object",
+ {func:func});
+
+ if (code === null) {
+ throw new AssertionError('Test bug: need to pass exception to assert_throws()');
+ }
+ if (typeof code === "object") {
+ assert("name" in e && e.name == code.name,
+ "assert_throws", description,
+ "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
+ {func:func, actual:e, actual_name:e.name,
+ expected:code,
+ expected_name:code.name});
+ return;
+ }
+
+ var code_name_map = {
+ INDEX_SIZE_ERR: 'IndexSizeError',
+ HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
+ WRONG_DOCUMENT_ERR: 'WrongDocumentError',
+ INVALID_CHARACTER_ERR: 'InvalidCharacterError',
+ NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
+ NOT_FOUND_ERR: 'NotFoundError',
+ NOT_SUPPORTED_ERR: 'NotSupportedError',
+ INUSE_ATTRIBUTE_ERR: 'InUseAttributeError',
+ INVALID_STATE_ERR: 'InvalidStateError',
+ SYNTAX_ERR: 'SyntaxError',
+ INVALID_MODIFICATION_ERR: 'InvalidModificationError',
+ NAMESPACE_ERR: 'NamespaceError',
+ INVALID_ACCESS_ERR: 'InvalidAccessError',
+ TYPE_MISMATCH_ERR: 'TypeMismatchError',
+ SECURITY_ERR: 'SecurityError',
+ NETWORK_ERR: 'NetworkError',
+ ABORT_ERR: 'AbortError',
+ URL_MISMATCH_ERR: 'URLMismatchError',
+ QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
+ TIMEOUT_ERR: 'TimeoutError',
+ INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',
+ DATA_CLONE_ERR: 'DataCloneError'
+ };
+
+ var name = code in code_name_map ? code_name_map[code] : code;
+
+ var name_code_map = {
+ IndexSizeError: 1,
+ HierarchyRequestError: 3,
+ WrongDocumentError: 4,
+ InvalidCharacterError: 5,
+ NoModificationAllowedError: 7,
+ NotFoundError: 8,
+ NotSupportedError: 9,
+ InUseAttributeError: 10,
+ InvalidStateError: 11,
+ SyntaxError: 12,
+ InvalidModificationError: 13,
+ NamespaceError: 14,
+ InvalidAccessError: 15,
+ TypeMismatchError: 17,
+ SecurityError: 18,
+ NetworkError: 19,
+ AbortError: 20,
+ URLMismatchError: 21,
+ QuotaExceededError: 22,
+ TimeoutError: 23,
+ InvalidNodeTypeError: 24,
+ DataCloneError: 25,
+
+ EncodingError: 0,
+ NotReadableError: 0,
+ UnknownError: 0,
+ ConstraintError: 0,
+ DataError: 0,
+ TransactionInactiveError: 0,
+ ReadOnlyError: 0,
+ VersionError: 0,
+ OperationError: 0,
+ NotAllowedError: 0
+ };
+
+ if (!(name in name_code_map)) {
+ throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');
+ }
+
+ var required_props = { code: name_code_map[name] };
+
+ if (required_props.code === 0 ||
+ ("name" in e &&
+ e.name !== e.name.toUpperCase() &&
+ e.name !== "DOMException")) {
+ // New style exception: also test the name property.
+ required_props.name = name;
+ }
+
+ //We'd like to test that e instanceof the appropriate interface,
+ //but we can't, because we don't know what window it was created
+ //in. It might be an instanceof the appropriate interface on some
+ //unknown other window. TODO: Work around this somehow?
+
+ for (var prop in required_props) {
+ assert(prop in e && e[prop] == required_props[prop],
+ "assert_throws", description,
+ "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
+ {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
+ }
+ }
+ }
+ expose(assert_throws, "assert_throws");
+
+ function assert_unreached(description) {
+ assert(false, "assert_unreached", description,
+ "Reached unreachable code");
+ }
+ expose(assert_unreached, "assert_unreached");
+
+ function assert_any(assert_func, actual, expected_array)
+ {
+ var args = [].slice.call(arguments, 3);
+ var errors = [];
+ var passed = false;
+ forEach(expected_array,
+ function(expected)
+ {
+ try {
+ assert_func.apply(this, [actual, expected].concat(args));
+ passed = true;
+ } catch (e) {
+ errors.push(e.message);
+ }
+ });
+ if (!passed) {
+ throw new AssertionError(errors.join("\n\n"));
+ }
+ }
+ expose(assert_any, "assert_any");
+
+ function Test(name, properties)
+ {
+ if (tests.file_is_test && tests.tests.length) {
+ throw new Error("Tried to create a test with file_is_test");
+ }
+ this.name = name;
+
+ this.phase = tests.is_aborted ?
+ this.phases.COMPLETE : this.phases.INITIAL;
+
+ this.status = this.NOTRUN;
+ this.timeout_id = null;
+ this.index = null;
+
+ this.properties = properties;
+ var timeout = properties.timeout ? properties.timeout : settings.test_timeout;
+ if (timeout !== null) {
+ this.timeout_length = timeout * tests.timeout_multiplier;
+ } else {
+ this.timeout_length = null;
+ }
+
+ this.message = null;
+ this.stack = null;
+
+ this.steps = [];
+ this._is_promise_test = false;
+
+ this.cleanup_callbacks = [];
+ this._user_defined_cleanup_count = 0;
+ this._done_callbacks = [];
+
+ tests.push(this);
+ }
+
+ Test.statuses = {
+ PASS:0,
+ FAIL:1,
+ TIMEOUT:2,
+ NOTRUN:3
+ };
+
+ Test.prototype = merge({}, Test.statuses);
+
+ Test.prototype.phases = {
+ INITIAL:0,
+ STARTED:1,
+ HAS_RESULT:2,
+ CLEANING:3,
+ COMPLETE:4
+ };
+
+ Test.prototype.structured_clone = function()
+ {
+ if (!this._structured_clone) {
+ var msg = this.message;
+ msg = msg ? String(msg) : msg;
+ this._structured_clone = merge({
+ name:String(this.name),
+ properties:merge({}, this.properties),
+ phases:merge({}, this.phases)
+ }, Test.statuses);
+ }
+ this._structured_clone.status = this.status;
+ this._structured_clone.message = this.message;
+ this._structured_clone.stack = this.stack;
+ this._structured_clone.index = this.index;
+ this._structured_clone.phase = this.phase;
+ return this._structured_clone;
+ };
+
+ Test.prototype.step = function(func, this_obj)
+ {
+ if (this.phase > this.phases.STARTED) {
+ return;
+ }
+ this.phase = this.phases.STARTED;
+ //If we don't get a result before the harness times out that will be a test timout
+ this.set_status(this.TIMEOUT, "Test timed out");
+
+ tests.started = true;
+ tests.notify_test_state(this);
+
+ if (this.timeout_id === null) {
+ this.set_timeout();
+ }
+
+ this.steps.push(func);
+
+ if (arguments.length === 1) {
+ this_obj = this;
+ }
+
+ try {
+ return func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
+ } catch (e) {
+ if (this.phase >= this.phases.HAS_RESULT) {
+ return;
+ }
+ var message = String((typeof e === "object" && e !== null) ? e.message : e);
+ var stack = e.stack ? e.stack : null;
+
+ this.set_status(this.FAIL, message, stack);
+ this.phase = this.phases.HAS_RESULT;
+ this.done();
+ }
+ };
+
+ Test.prototype.step_func = function(func, this_obj)
+ {
+ var test_this = this;
+
+ if (arguments.length === 1) {
+ this_obj = test_this;
+ }
+
+ return function()
+ {
+ return test_this.step.apply(test_this, [func, this_obj].concat(
+ Array.prototype.slice.call(arguments)));
+ };
+ };
+
+ Test.prototype.step_func_done = function(func, this_obj)
+ {
+ var test_this = this;
+
+ if (arguments.length === 1) {
+ this_obj = test_this;
+ }
+
+ return function()
+ {
+ if (func) {
+ test_this.step.apply(test_this, [func, this_obj].concat(
+ Array.prototype.slice.call(arguments)));
+ }
+ test_this.done();
+ };
+ };
+
+ Test.prototype.unreached_func = function(description)
+ {
+ return this.step_func(function() {
+ assert_unreached(description);
+ });
+ };
+
+ Test.prototype.step_timeout = function(f, timeout) {
+ var test_this = this;
+ var args = Array.prototype.slice.call(arguments, 2);
+ return setTimeout(this.step_func(function() {
+ return f.apply(test_this, args);
+ }), timeout * tests.timeout_multiplier);
+ }
+
+ /*
+ * Private method for registering cleanup functions. `testharness.js`
+ * internals should use this method instead of the public `add_cleanup`
+ * method in order to hide implementation details from the harness status
+ * message in the case errors.
+ */
+ Test.prototype._add_cleanup = function(callback) {
+ this.cleanup_callbacks.push(callback);
+ };
+
+ /*
+ * Schedule a function to be run after the test result is known, regardless
+ * of passing or failing state. The behavior of this function will not
+ * influence the result of the test, but if an exception is thrown, the
+ * test harness will report an error.
+ */
+ Test.prototype.add_cleanup = function(callback) {
+ this._user_defined_cleanup_count += 1;
+ this._add_cleanup(callback);
+ };
+
+ Test.prototype.set_timeout = function()
+ {
+ if (this.timeout_length !== null) {
+ var this_obj = this;
+ this.timeout_id = setTimeout(function()
+ {
+ this_obj.timeout();
+ }, this.timeout_length);
+ }
+ };
+
+ Test.prototype.set_status = function(status, message, stack)
+ {
+ this.status = status;
+ this.message = message;
+ this.stack = stack ? stack : null;
+ };
+
+ Test.prototype.timeout = function()
+ {
+ this.timeout_id = null;
+ this.set_status(this.TIMEOUT, "Test timed out");
+ this.phase = this.phases.HAS_RESULT;
+ this.done();
+ };
+
+ Test.prototype.force_timeout = Test.prototype.timeout;
+
+ /**
+ * Update the test status, initiate "cleanup" functions, and signal test
+ * completion.
+ */
+ Test.prototype.done = function()
+ {
+ if (this.phase >= this.phases.CLEANING) {
+ return;
+ }
+
+ if (this.phase <= this.phases.STARTED) {
+ this.set_status(this.PASS, null);
+ }
+
+ if (global_scope.clearTimeout) {
+ clearTimeout(this.timeout_id);
+ }
+
+ this.cleanup();
+ };
+
+ function add_test_done_callback(test, callback)
+ {
+ if (test.phase === test.phases.COMPLETE) {
+ callback();
+ return;
+ }
+
+ test._done_callbacks.push(callback);
+ }
+
+ /*
+ * Invoke all specified cleanup functions. If one or more produce an error,
+ * the context is in an unpredictable state, so all further testing should
+ * be cancelled.
+ */
+ Test.prototype.cleanup = function() {
+ var error_count = 0;
+ var bad_value_count = 0;
+ function on_error() {
+ error_count += 1;
+ // Abort tests immediately so that tests declared within subsequent
+ // cleanup functions are not run.
+ tests.abort();
+ }
+ var this_obj = this;
+ var results = [];
+
+ this.phase = this.phases.CLEANING;
+
+ forEach(this.cleanup_callbacks,
+ function(cleanup_callback) {
+ var result;
+
+ try {
+ result = cleanup_callback();
+ } catch (e) {
+ on_error();
+ return;
+ }
+
+ if (!is_valid_cleanup_result(this_obj, result)) {
+ bad_value_count += 1;
+ // Abort tests immediately so that tests declared
+ // within subsequent cleanup functions are not run.
+ tests.abort();
+ }
+
+ results.push(result);
+ });
+
+ if (!this._is_promise_test) {
+ cleanup_done(this_obj, error_count, bad_value_count);
+ } else {
+ all_async(results,
+ function(result, done) {
+ if (result && typeof result.then === "function") {
+ result
+ .then(null, on_error)
+ .then(done);
+ } else {
+ done();
+ }
+ },
+ function() {
+ cleanup_done(this_obj, error_count, bad_value_count);
+ });
+ }
+ };
+
+ /**
+ * Determine if the return value of a cleanup function is valid for a given
+ * test. Any test may return the value `undefined`. Tests created with
+ * `promise_test` may alternatively return "thenable" object values.
+ */
+ function is_valid_cleanup_result(test, result) {
+ if (result === undefined) {
+ return true;
+ }
+
+ if (test._is_promise_test) {
+ return result && typeof result.then === "function";
+ }
+
+ return false;
+ }
+
+ function cleanup_done(test, error_count, bad_value_count) {
+ if (error_count || bad_value_count) {
+ var total = test._user_defined_cleanup_count;
+
+ tests.status.status = tests.status.ERROR;
+ tests.status.message = "Test named '" + test.name +
+ "' specified " + total +
+ " 'cleanup' function" + (total > 1 ? "s" : "");
+
+ if (error_count) {
+ tests.status.message += ", and " + error_count + " failed";
+ }
+
+ if (bad_value_count) {
+ var type = test._is_promise_test ?
+ "non-thenable" : "non-undefined";
+ tests.status.message += ", and " + bad_value_count +
+ " returned a " + type + " value";
+ }
+
+ tests.status.message += ".";
+
+ tests.status.stack = null;
+ }
+
+ test.phase = test.phases.COMPLETE;
+ tests.result(test);
+ forEach(test._done_callbacks,
+ function(callback) {
+ callback();
+ });
+ test._done_callbacks.length = 0;
+ }
+
+ /*
+ * A RemoteTest object mirrors a Test object on a remote worker. The
+ * associated RemoteWorker updates the RemoteTest object in response to
+ * received events. In turn, the RemoteTest object replicates these events
+ * on the local document. This allows listeners (test result reporting
+ * etc..) to transparently handle local and remote events.
+ */
+ function RemoteTest(clone) {
+ var this_obj = this;
+ Object.keys(clone).forEach(
+ function(key) {
+ this_obj[key] = clone[key];
+ });
+ this.index = null;
+ this.phase = this.phases.INITIAL;
+ this.update_state_from(clone);
+ this._done_callbacks = [];
+ tests.push(this);
+ }
+
+ RemoteTest.prototype.structured_clone = function() {
+ var clone = {};
+ Object.keys(this).forEach(
+ (function(key) {
+ var value = this[key];
+ // `RemoteTest` instances are responsible for managing
+ // their own "done" callback functions, so those functions
+ // are not relevant in other execution contexts. Because of
+ // this (and because Function values cannot be serialized
+ // for cross-realm transmittance), the property should not
+ // be considered when cloning instances.
+ if (key === '_done_callbacks' ) {
+ return;
+ }
+
+ if (typeof value === "object" && value !== null) {
+ clone[key] = merge({}, value);
+ } else {
+ clone[key] = value;
+ }
+ }).bind(this));
+ clone.phases = merge({}, this.phases);
+ return clone;
+ };
+
+ /**
+ * `RemoteTest` instances are objects which represent tests running in
+ * another realm. They do not define "cleanup" functions (if necessary,
+ * such functions are defined on the associated `Test` instance within the
+ * external realm). However, `RemoteTests` may have "done" callbacks (e.g.
+ * as attached by the `Tests` instance responsible for tracking the overall
+ * test status in the parent realm). The `cleanup` method delegates to
+ * `done` in order to ensure that such callbacks are invoked following the
+ * completion of the `RemoteTest`.
+ */
+ RemoteTest.prototype.cleanup = function() {
+ this.done();
+ };
+ RemoteTest.prototype.phases = Test.prototype.phases;
+ RemoteTest.prototype.update_state_from = function(clone) {
+ this.status = clone.status;
+ this.message = clone.message;
+ this.stack = clone.stack;
+ if (this.phase === this.phases.INITIAL) {
+ this.phase = this.phases.STARTED;
+ }
+ };
+ RemoteTest.prototype.done = function() {
+ this.phase = this.phases.COMPLETE;
+
+ forEach(this._done_callbacks,
+ function(callback) {
+ callback();
+ });
+ }
+
+ /*
+ * A RemoteContext listens for test events from a remote test context, such
+ * as another window or a worker. These events are then used to construct
+ * and maintain RemoteTest objects that mirror the tests running in the
+ * remote context.
+ *
+ * An optional third parameter can be used as a predicate to filter incoming
+ * MessageEvents.
+ */
+ function RemoteContext(remote, message_target, message_filter) {
+ this.running = true;
+ this.tests = new Array();
+
+ var this_obj = this;
+ // If remote context is cross origin assigning to onerror is not
+ // possible, so silently catch those errors.
+ try {
+ remote.onerror = function(error) { this_obj.remote_error(error); };
+ } catch (e) {
+ // Ignore.
+ }
+
+ // Keeping a reference to the remote object and the message handler until
+ // remote_done() is seen prevents the remote object and its message channel
+ // from going away before all the messages are dispatched.
+ this.remote = remote;
+ this.message_target = message_target;
+ this.message_handler = function(message) {
+ var passesFilter = !message_filter || message_filter(message);
+ // The reference to the `running` property in the following
+ // condition is unnecessary because that value is only set to
+ // `false` after the `message_handler` function has been
+ // unsubscribed.
+ // TODO: Simplify the condition by removing the reference.
+ if (this_obj.running && message.data && passesFilter &&
+ (message.data.type in this_obj.message_handlers)) {
+ this_obj.message_handlers[message.data.type].call(this_obj, message.data);
+ }
+ };
+
+ if (self.Promise) {
+ this.done = new Promise(function(resolve) {
+ this_obj.doneResolve = resolve;
+ });
+ }
+
+ this.message_target.addEventListener("message", this.message_handler);
+ }
+
+ RemoteContext.prototype.remote_error = function(error) {
+ var message = error.message || String(error);
+ var filename = (error.filename ? " " + error.filename: "");
+ // FIXME: Display remote error states separately from main document
+ // error state.
+ tests.set_status(tests.status.ERROR,
+ "Error in remote" + filename + ": " + message,
+ error.stack);
+
+ if (error.preventDefault) {
+ error.preventDefault();
+ }
+ };
+
+ RemoteContext.prototype.test_state = function(data) {
+ var remote_test = this.tests[data.test.index];
+ if (!remote_test) {
+ remote_test = new RemoteTest(data.test);
+ this.tests[data.test.index] = remote_test;
+ }
+ remote_test.update_state_from(data.test);
+ tests.notify_test_state(remote_test);
+ };
+
+ RemoteContext.prototype.test_done = function(data) {
+ var remote_test = this.tests[data.test.index];
+ remote_test.update_state_from(data.test);
+ remote_test.done();
+ tests.result(remote_test);
+ };
+
+ RemoteContext.prototype.remote_done = function(data) {
+ if (tests.status.status === null &&
+ data.status.status !== data.status.OK) {
+ tests.set_status(data.status.status, data.status.message, data.status.sack);
+ }
+
+ this.message_target.removeEventListener("message", this.message_handler);
+ this.running = false;
+
+ // If remote context is cross origin assigning to onerror is not
+ // possible, so silently catch those errors.
+ try {
+ this.remote.onerror = null;
+ } catch (e) {
+ // Ignore.
+ }
+
+ this.remote = null;
+ this.message_target = null;
+ if (this.doneResolve) {
+ this.doneResolve();
+ }
+
+ if (tests.all_done()) {
+ tests.complete();
+ }
+ };
+
+ RemoteContext.prototype.message_handlers = {
+ test_state: RemoteContext.prototype.test_state,
+ result: RemoteContext.prototype.test_done,
+ complete: RemoteContext.prototype.remote_done
+ };
+
+ /*
+ * Harness
+ */
+
+ function TestsStatus()
+ {
+ this.status = null;
+ this.message = null;
+ this.stack = null;
+ }
+
+ TestsStatus.statuses = {
+ OK:0,
+ ERROR:1,
+ TIMEOUT:2
+ };
+
+ TestsStatus.prototype = merge({}, TestsStatus.statuses);
+
+ TestsStatus.prototype.structured_clone = function()
+ {
+ if (!this._structured_clone) {
+ var msg = this.message;
+ msg = msg ? String(msg) : msg;
+ this._structured_clone = merge({
+ status:this.status,
+ message:msg,
+ stack:this.stack
+ }, TestsStatus.statuses);
+ }
+ return this._structured_clone;
+ };
+
+ function Tests()
+ {
+ this.tests = [];
+ this.num_pending = 0;
+
+ this.phases = {
+ INITIAL:0,
+ SETUP:1,
+ HAVE_TESTS:2,
+ HAVE_RESULTS:3,
+ COMPLETE:4
+ };
+ this.phase = this.phases.INITIAL;
+
+ this.properties = {};
+
+ this.wait_for_finish = false;
+ this.processing_callbacks = false;
+
+ this.allow_uncaught_exception = false;
+
+ this.file_is_test = false;
+
+ this.timeout_multiplier = 1;
+ this.timeout_length = test_environment.test_timeout();
+ this.timeout_id = null;
+
+ this.start_callbacks = [];
+ this.test_state_callbacks = [];
+ this.test_done_callbacks = [];
+ this.all_done_callbacks = [];
+
+ this.pending_remotes = [];
+
+ this.status = new TestsStatus();
+
+ var this_obj = this;
+
+ test_environment.add_on_loaded_callback(function() {
+ if (this_obj.all_done()) {
+ this_obj.complete();
+ }
+ });
+
+ this.set_timeout();
+ }
+
+ Tests.prototype.setup = function(func, properties)
+ {
+ if (this.phase >= this.phases.HAVE_RESULTS) {
+ return;
+ }
+
+ if (this.phase < this.phases.SETUP) {
+ this.phase = this.phases.SETUP;
+ }
+
+ this.properties = properties;
+
+ for (var p in properties) {
+ if (properties.hasOwnProperty(p)) {
+ var value = properties[p];
+ if (p == "allow_uncaught_exception") {
+ this.allow_uncaught_exception = value;
+ } else if (p == "explicit_done" && value) {
+ this.wait_for_finish = true;
+ } else if (p == "explicit_timeout" && value) {
+ this.timeout_length = null;
+ if (this.timeout_id)
+ {
+ clearTimeout(this.timeout_id);
+ }
+ } else if (p == "timeout_multiplier") {
+ this.timeout_multiplier = value;
+ }
+ }
+ }
+
+ if (func) {
+ try {
+ func();
+ } catch (e) {
+ this.status.status = this.status.ERROR;
+ this.status.message = String(e);
+ this.status.stack = e.stack ? e.stack : null;
+ }
+ }
+ this.set_timeout();
+ };
+
+ Tests.prototype.set_file_is_test = function() {
+ if (this.tests.length > 0) {
+ throw new Error("Tried to set file as test after creating a test");
+ }
+ this.wait_for_finish = true;
+ this.file_is_test = true;
+ // Create the test, which will add it to the list of tests
+ async_test();
+ };
+
+ Tests.prototype.set_status = function(status, message, stack)
+ {
+ this.status.status = status;
+ this.status.message = message;
+ this.status.stack = stack ? stack : null;
+ };
+
+ Tests.prototype.set_timeout = function() {
+ if (global_scope.clearTimeout) {
+ var this_obj = this;
+ clearTimeout(this.timeout_id);
+ if (this.timeout_length !== null) {
+ this.timeout_id = setTimeout(function() {
+ this_obj.timeout();
+ }, this.timeout_length);
+ }
+ }
+ };
+
+ Tests.prototype.timeout = function() {
+ var test_in_cleanup = null;
+
+ if (this.status.status === null) {
+ forEach(this.tests,
+ function(test) {
+ // No more than one test is expected to be in the
+ // "CLEANUP" phase at any time
+ if (test.phase === test.phases.CLEANING) {
+ test_in_cleanup = test;
+ }
+
+ test.phase = test.phases.COMPLETE;
+ });
+
+ // Timeouts that occur while a test is in the "cleanup" phase
+ // indicate that some global state was not properly reverted. This
+ // invalidates the overall test execution, so the timeout should be
+ // reported as an error and cancel the execution of any remaining
+ // tests.
+ if (test_in_cleanup) {
+ this.status.status = this.status.ERROR;
+ this.status.message = "Timeout while running cleanup for " +
+ "test named \"" + test_in_cleanup.name + "\".";
+ tests.status.stack = null;
+ } else {
+ this.status.status = this.status.TIMEOUT;
+ }
+ }
+
+ this.complete();
+ };
+
+ Tests.prototype.end_wait = function()
+ {
+ this.wait_for_finish = false;
+ if (this.all_done()) {
+ this.complete();
+ }
+ };
+
+ Tests.prototype.push = function(test)
+ {
+ if (this.phase < this.phases.HAVE_TESTS) {
+ this.start();
+ }
+ this.num_pending++;
+ test.index = this.tests.push(test);
+ this.notify_test_state(test);
+ };
+
+ Tests.prototype.notify_test_state = function(test) {
+ var this_obj = this;
+ forEach(this.test_state_callbacks,
+ function(callback) {
+ callback(test, this_obj);
+ });
+ };
+
+ Tests.prototype.all_done = function() {
+ return this.tests.length > 0 && test_environment.all_loaded &&
+ (this.num_pending === 0 || this.is_aborted) && !this.wait_for_finish &&
+ !this.processing_callbacks &&
+ !this.pending_remotes.some(function(w) { return w.running; });
+ };
+
+ Tests.prototype.start = function() {
+ this.phase = this.phases.HAVE_TESTS;
+ this.notify_start();
+ };
+
+ Tests.prototype.notify_start = function() {
+ var this_obj = this;
+ forEach (this.start_callbacks,
+ function(callback)
+ {
+ callback(this_obj.properties);
+ });
+ };
+
+ Tests.prototype.result = function(test)
+ {
+ // If the harness has already transitioned beyond the `HAVE_RESULTS`
+ // phase, subsequent tests should not cause it to revert.
+ if (this.phase <= this.phases.HAVE_RESULTS) {
+ this.phase = this.phases.HAVE_RESULTS;
+ }
+ this.num_pending--;
+ this.notify_result(test);
+ };
+
+ Tests.prototype.notify_result = function(test) {
+ var this_obj = this;
+ this.processing_callbacks = true;
+ forEach(this.test_done_callbacks,
+ function(callback)
+ {
+ callback(test, this_obj);
+ });
+ this.processing_callbacks = false;
+ if (this_obj.all_done()) {
+ this_obj.complete();
+ }
+ };
+
+ Tests.prototype.complete = function() {
+ if (this.phase === this.phases.COMPLETE) {
+ return;
+ }
+ var this_obj = this;
+ var all_complete = function() {
+ this_obj.phase = this_obj.phases.COMPLETE;
+ this_obj.notify_complete();
+ };
+ var incomplete = filter(this.tests,
+ function(test) {
+ return test.phase < test.phases.COMPLETE;
+ });
+
+ /**
+ * To preserve legacy behavior, overall test completion must be
+ * signaled synchronously.
+ */
+ if (incomplete.length === 0) {
+ all_complete();
+ return;
+ }
+
+ all_async(incomplete,
+ function(test, testDone)
+ {
+ if (test.phase === test.phases.INITIAL) {
+ test.phase = test.phases.COMPLETE;
+ testDone();
+ } else {
+ add_test_done_callback(test, testDone);
+ test.cleanup();
+ }
+ },
+ all_complete);
+ };
+
+ /**
+ * Update the harness status to reflect an unrecoverable harness error that
+ * should cancel all further testing. Update all previously-defined tests
+ * which have not yet started to indicate that they will not be executed.
+ */
+ Tests.prototype.abort = function() {
+ this.status.status = this.status.ERROR;
+ this.is_aborted = true;
+
+ forEach(this.tests,
+ function(test) {
+ if (test.phase === test.phases.INITIAL) {
+ test.phase = test.phases.COMPLETE;
+ }
+ });
+ };
+
+ /*
+ * Determine if any tests share the same `name` property. Return an array
+ * containing the names of any such duplicates.
+ */
+ Tests.prototype.find_duplicates = function() {
+ var names = Object.create(null);
+ var duplicates = [];
+
+ forEach (this.tests,
+ function(test)
+ {
+ if (test.name in names && duplicates.indexOf(test.name) === -1) {
+ duplicates.push(test.name);
+ }
+ names[test.name] = true;
+ });
+
+ return duplicates;
+ };
+
+ Tests.prototype.notify_complete = function() {
+ var this_obj = this;
+ var duplicates;
+
+ if (this.status.status === null) {
+ duplicates = this.find_duplicates();
+
+ // Test names are presumed to be unique within test files--this
+ // allows consumers to use them for identification purposes.
+ // Duplicated names violate this expectation and should therefore
+ // be reported as an error.
+ if (duplicates.length) {
+ this.status.status = this.status.ERROR;
+ this.status.message =
+ duplicates.length + ' duplicate test name' +
+ (duplicates.length > 1 ? 's' : '') + ': "' +
+ duplicates.join('", "') + '"';
+ } else {
+ this.status.status = this.status.OK;
+ }
+ }
+
+ forEach (this.all_done_callbacks,
+ function(callback)
+ {
+ callback(this_obj.tests, this_obj.status);
+ });
+ };
+
+ /*
+ * Constructs a RemoteContext that tracks tests from a specific worker.
+ */
+ Tests.prototype.create_remote_worker = function(worker) {
+ var message_port;
+
+ if (is_service_worker(worker)) {
+ if (window.MessageChannel) {
+ // The ServiceWorker's implicit MessagePort is currently not
+ // reliably accessible from the ServiceWorkerGlobalScope due to
+ // Blink setting MessageEvent.source to null for messages sent
+ // via ServiceWorker.postMessage(). Until that's resolved,
+ // create an explicit MessageChannel and pass one end to the
+ // worker.
+ var message_channel = new MessageChannel();
+ message_port = message_channel.port1;
+ message_port.start();
+ worker.postMessage({type: "connect"}, [message_channel.port2]);
+ } else {
+ // If MessageChannel is not available, then try the
+ // ServiceWorker.postMessage() approach using MessageEvent.source
+ // on the other end.
+ message_port = navigator.serviceWorker;
+ worker.postMessage({type: "connect"});
+ }
+ } else if (is_shared_worker(worker)) {
+ message_port = worker.port;
+ message_port.start();
+ } else {
+ message_port = worker;
+ }
+
+ return new RemoteContext(worker, message_port);
+ };
+
+ /*
+ * Constructs a RemoteContext that tracks tests from a specific window.
+ */
+ Tests.prototype.create_remote_window = function(remote) {
+ remote.postMessage({type: "getmessages"}, "*");
+ return new RemoteContext(
+ remote,
+ window,
+ function(msg) {
+ return msg.source === remote;
+ }
+ );
+ };
+
+ Tests.prototype.fetch_tests_from_worker = function(worker) {
+ if (this.phase >= this.phases.COMPLETE) {
+ return;
+ }
+
+ var remoteContext = this.create_remote_worker(worker);
+ this.pending_remotes.push(remoteContext);
+ return remoteContext.done;
+ };
+
+ function fetch_tests_from_worker(port) {
+ return tests.fetch_tests_from_worker(port);
+ }
+ expose(fetch_tests_from_worker, 'fetch_tests_from_worker');
+
+ Tests.prototype.fetch_tests_from_window = function(remote) {
+ if (this.phase >= this.phases.COMPLETE) {
+ return;
+ }
+
+ this.pending_remotes.push(this.create_remote_window(remote));
+ };
+
+ function fetch_tests_from_window(window) {
+ tests.fetch_tests_from_window(window);
+ }
+ expose(fetch_tests_from_window, 'fetch_tests_from_window');
+
+ function timeout() {
+ if (tests.timeout_length === null) {
+ tests.timeout();
+ }
+ }
+ expose(timeout, 'timeout');
+
+ function add_start_callback(callback) {
+ tests.start_callbacks.push(callback);
+ }
+
+ function add_test_state_callback(callback) {
+ tests.test_state_callbacks.push(callback);
+ }
+
+ function add_result_callback(callback) {
+ tests.test_done_callbacks.push(callback);
+ }
+
+ function add_completion_callback(callback) {
+ tests.all_done_callbacks.push(callback);
+ }
+
+ expose(add_start_callback, 'add_start_callback');
+ expose(add_test_state_callback, 'add_test_state_callback');
+ expose(add_result_callback, 'add_result_callback');
+ expose(add_completion_callback, 'add_completion_callback');
+
+ function remove(array, item) {
+ var index = array.indexOf(item);
+ if (index > -1) {
+ array.splice(index, 1);
+ }
+ }
+
+ function remove_start_callback(callback) {
+ remove(tests.start_callbacks, callback);
+ }
+
+ function remove_test_state_callback(callback) {
+ remove(tests.test_state_callbacks, callback);
+ }
+
+ function remove_result_callback(callback) {
+ remove(tests.test_done_callbacks, callback);
+ }
+
+ function remove_completion_callback(callback) {
+ remove(tests.all_done_callbacks, callback);
+ }
+
+ /*
+ * Output listener
+ */
+
+ function Output() {
+ this.output_document = document;
+ this.output_node = null;
+ this.enabled = settings.output;
+ this.phase = this.INITIAL;
+ }
+
+ Output.prototype.INITIAL = 0;
+ Output.prototype.STARTED = 1;
+ Output.prototype.HAVE_RESULTS = 2;
+ Output.prototype.COMPLETE = 3;
+
+ Output.prototype.setup = function(properties) {
+ if (this.phase > this.INITIAL) {
+ return;
+ }
+
+ //If output is disabled in testharnessreport.js the test shouldn't be
+ //able to override that
+ this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
+ properties.output : settings.output);
+ };
+
+ Output.prototype.init = function(properties) {
+ if (this.phase >= this.STARTED) {
+ return;
+ }
+ if (properties.output_document) {
+ this.output_document = properties.output_document;
+ } else {
+ this.output_document = document;
+ }
+ this.phase = this.STARTED;
+ };
+
+ Output.prototype.resolve_log = function() {
+ var output_document;
+ if (typeof this.output_document === "function") {
+ output_document = this.output_document.apply(undefined);
+ } else {
+ output_document = this.output_document;
+ }
+ if (!output_document) {
+ return;
+ }
+ var node = output_document.getElementById("log");
+ if (!node) {
+ if (!document.readyState == "loading") {
+ return;
+ }
+ node = output_document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ node.id = "log";
+ if (output_document.body) {
+ output_document.body.appendChild(node);
+ } else {
+ var is_html = false;
+ var is_svg = false;
+ var output_window = output_document.defaultView;
+ if (output_window && "SVGSVGElement" in output_window) {
+ is_svg = output_document.documentElement instanceof output_window.SVGSVGElement;
+ } else if (output_window) {
+ is_html = (output_document.namespaceURI == "http://www.w3.org/1999/xhtml" &&
+ output_document.localName == "html");
+ }
+ if (is_svg) {
+ var foreignObject = output_document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
+ foreignObject.setAttribute("width", "100%");
+ foreignObject.setAttribute("height", "100%");
+ output_document.documentElement.appendChild(foreignObject);
+ foreignObject.appendChild(node);
+ } else if (is_html) {
+ var body = output_document.createElementNS("http://www.w3.org/1999/xhtml", "body");
+ output_document.documentElement.appendChild(body);
+ body.appendChild(node);
+ } else {
+ output_document.documentElement.appendChild(node);
+ }
+ }
+ }
+ this.output_document = output_document;
+ this.output_node = node;
+ };
+
+ Output.prototype.show_status = function() {
+ if (this.phase < this.STARTED) {
+ this.init();
+ }
+ if (!this.enabled) {
+ return;
+ }
+ if (this.phase < this.HAVE_RESULTS) {
+ this.resolve_log();
+ this.phase = this.HAVE_RESULTS;
+ }
+ var done_count = tests.tests.length - tests.num_pending;
+ if (this.output_node) {
+ if (done_count < 100 ||
+ (done_count < 1000 && done_count % 100 === 0) ||
+ done_count % 1000 === 0) {
+ this.output_node.textContent = "Running, " +
+ done_count + " complete, " +
+ tests.num_pending + " remain";
+ }
+ }
+ };
+
+ Output.prototype.show_results = function (tests, harness_status) {
+ if (this.phase >= this.COMPLETE) {
+ return;
+ }
+ if (!this.enabled) {
+ return;
+ }
+ if (!this.output_node) {
+ this.resolve_log();
+ }
+ this.phase = this.COMPLETE;
+
+ var log = this.output_node;
+ if (!log) {
+ return;
+ }
+ var output_document = this.output_document;
+
+ while (log.lastChild) {
+ log.removeChild(log.lastChild);
+ }
+
+ var stylesheet = output_document.createElementNS(xhtml_ns, "style");
+ stylesheet.textContent = stylesheetContent;
+ var heads = output_document.getElementsByTagName("head");
+ if (heads.length) {
+ heads[0].appendChild(stylesheet);
+ }
+
+ var status_text_harness = {};
+ status_text_harness[harness_status.OK] = "OK";
+ status_text_harness[harness_status.ERROR] = "Error";
+ status_text_harness[harness_status.TIMEOUT] = "Timeout";
+
+ var status_text = {};
+ status_text[Test.prototype.PASS] = "Pass";
+ status_text[Test.prototype.FAIL] = "Fail";
+ status_text[Test.prototype.TIMEOUT] = "Timeout";
+ status_text[Test.prototype.NOTRUN] = "Not Run";
+
+ var status_number = {};
+ forEach(tests,
+ function(test) {
+ var status = status_text[test.status];
+ if (status_number.hasOwnProperty(status)) {
+ status_number[status] += 1;
+ } else {
+ status_number[status] = 1;
+ }
+ });
+
+ function status_class(status)
+ {
+ return status.replace(/\s/g, '').toLowerCase();
+ }
+
+ var summary_template = ["section", {"id":"summary"},
+ ["h2", {}, "Summary"],
+ function()
+ {
+
+ var status = status_text_harness[harness_status.status];
+ var rv = [["section", {},
+ ["p", {},
+ "Harness status: ",
+ ["span", {"class":status_class(status)},
+ status
+ ],
+ ]
+ ]];
+
+ if (harness_status.status === harness_status.ERROR) {
+ rv[0].push(["pre", {}, harness_status.message]);
+ if (harness_status.stack) {
+ rv[0].push(["pre", {}, harness_status.stack]);
+ }
+ }
+ return rv;
+ },
+ ["p", {}, "Found ${num_tests} tests"],
+ function() {
+ var rv = [["div", {}]];
+ var i = 0;
+ while (status_text.hasOwnProperty(i)) {
+ if (status_number.hasOwnProperty(status_text[i])) {
+ var status = status_text[i];
+ rv[0].push(["div", {"class":status_class(status)},
+ ["label", {},
+ ["input", {type:"checkbox", checked:"checked"}],
+ status_number[status] + " " + status]]);
+ }
+ i++;
+ }
+ return rv;
+ },
+ ];
+
+ log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));
+
+ forEach(output_document.querySelectorAll("section#summary label"),
+ function(element)
+ {
+ on_event(element, "click",
+ function(e)
+ {
+ if (output_document.getElementById("results") === null) {
+ e.preventDefault();
+ return;
+ }
+ var result_class = element.parentNode.getAttribute("class");
+ var style_element = output_document.querySelector("style#hide-" + result_class);
+ var input_element = element.querySelector("input");
+ if (!style_element && !input_element.checked) {
+ style_element = output_document.createElementNS(xhtml_ns, "style");
+ style_element.id = "hide-" + result_class;
+ style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}";
+ output_document.body.appendChild(style_element);
+ } else if (style_element && input_element.checked) {
+ style_element.parentNode.removeChild(style_element);
+ }
+ });
+ });
+
+ // This use of innerHTML plus manual escaping is not recommended in
+ // general, but is necessary here for performance. Using textContent
+ // on each individual adds tens of seconds of execution time for
+ // large test suites (tens of thousands of tests).
+ function escape_html(s)
+ {
+ return s.replace(/\&/g, "&")
+ .replace(/Details" +
+ "Result Test Name " +
+ (assertions ? "Assertion " : "") +
+ "Message " +
+ "";
+ for (var i = 0; i < tests.length; i++) {
+ html += '' +
+ escape_html(status_text[tests[i].status]) +
+ " " +
+ escape_html(tests[i].name) +
+ " " +
+ (assertions ? escape_html(get_assertion(tests[i])) + " " : "") +
+ escape_html(tests[i].message ? tests[i].message : " ") +
+ (tests[i].stack ? "" +
+ escape_html(tests[i].stack) +
+ " ": "") +
+ " ";
+ }
+ html += "
";
+ try {
+ log.lastChild.innerHTML = html;
+ } catch (e) {
+ log.appendChild(document.createElementNS(xhtml_ns, "p"))
+ .textContent = "Setting innerHTML for the log threw an exception.";
+ log.appendChild(document.createElementNS(xhtml_ns, "pre"))
+ .textContent = html;
+ }
+ };
+
+ /*
+ * Template code
+ *
+ * A template is just a javascript structure. An element is represented as:
+ *
+ * [tag_name, {attr_name:attr_value}, child1, child2]
+ *
+ * the children can either be strings (which act like text nodes), other templates or
+ * functions (see below)
+ *
+ * A text node is represented as
+ *
+ * ["{text}", value]
+ *
+ * String values have a simple substitution syntax; ${foo} represents a variable foo.
+ *
+ * It is possible to embed logic in templates by using a function in a place where a
+ * node would usually go. The function must either return part of a template or null.
+ *
+ * In cases where a set of nodes are required as output rather than a single node
+ * with children it is possible to just use a list
+ * [node1, node2, node3]
+ *
+ * Usage:
+ *
+ * render(template, substitutions) - take a template and an object mapping
+ * variable names to parameters and return either a DOM node or a list of DOM nodes
+ *
+ * substitute(template, substitutions) - take a template and variable mapping object,
+ * make the variable substitutions and return the substituted template
+ *
+ */
+
+ function is_single_node(template)
+ {
+ return typeof template[0] === "string";
+ }
+
+ function substitute(template, substitutions)
+ {
+ if (typeof template === "function") {
+ var replacement = template(substitutions);
+ if (!replacement) {
+ return null;
+ }
+
+ return substitute(replacement, substitutions);
+ }
+
+ if (is_single_node(template)) {
+ return substitute_single(template, substitutions);
+ }
+
+ return filter(map(template, function(x) {
+ return substitute(x, substitutions);
+ }), function(x) {return x !== null;});
+ }
+
+ function substitute_single(template, substitutions)
+ {
+ var substitution_re = /\$\{([^ }]*)\}/g;
+
+ function do_substitution(input) {
+ var components = input.split(substitution_re);
+ var rv = [];
+ for (var i = 0; i < components.length; i += 2) {
+ rv.push(components[i]);
+ if (components[i + 1]) {
+ rv.push(String(substitutions[components[i + 1]]));
+ }
+ }
+ return rv;
+ }
+
+ function substitute_attrs(attrs, rv)
+ {
+ rv[1] = {};
+ for (var name in template[1]) {
+ if (attrs.hasOwnProperty(name)) {
+ var new_name = do_substitution(name).join("");
+ var new_value = do_substitution(attrs[name]).join("");
+ rv[1][new_name] = new_value;
+ }
+ }
+ }
+
+ function substitute_children(children, rv)
+ {
+ for (var i = 0; i < children.length; i++) {
+ if (children[i] instanceof Object) {
+ var replacement = substitute(children[i], substitutions);
+ if (replacement !== null) {
+ if (is_single_node(replacement)) {
+ rv.push(replacement);
+ } else {
+ extend(rv, replacement);
+ }
+ }
+ } else {
+ extend(rv, do_substitution(String(children[i])));
+ }
+ }
+ return rv;
+ }
+
+ var rv = [];
+ rv.push(do_substitution(String(template[0])).join(""));
+
+ if (template[0] === "{text}") {
+ substitute_children(template.slice(1), rv);
+ } else {
+ substitute_attrs(template[1], rv);
+ substitute_children(template.slice(2), rv);
+ }
+
+ return rv;
+ }
+
+ function make_dom_single(template, doc)
+ {
+ var output_document = doc || document;
+ var element;
+ if (template[0] === "{text}") {
+ element = output_document.createTextNode("");
+ for (var i = 1; i < template.length; i++) {
+ element.data += template[i];
+ }
+ } else {
+ element = output_document.createElementNS(xhtml_ns, template[0]);
+ for (var name in template[1]) {
+ if (template[1].hasOwnProperty(name)) {
+ element.setAttribute(name, template[1][name]);
+ }
+ }
+ for (var i = 2; i < template.length; i++) {
+ if (template[i] instanceof Object) {
+ var sub_element = make_dom(template[i]);
+ element.appendChild(sub_element);
+ } else {
+ var text_node = output_document.createTextNode(template[i]);
+ element.appendChild(text_node);
+ }
+ }
+ }
+
+ return element;
+ }
+
+ function make_dom(template, substitutions, output_document)
+ {
+ if (is_single_node(template)) {
+ return make_dom_single(template, output_document);
+ }
+
+ return map(template, function(x) {
+ return make_dom_single(x, output_document);
+ });
+ }
+
+ function render(template, substitutions, output_document)
+ {
+ return make_dom(substitute(template, substitutions), output_document);
+ }
+
+ /*
+ * Utility funcions
+ */
+ function assert(expected_true, function_name, description, error, substitutions)
+ {
+ if (tests.tests.length === 0) {
+ tests.set_file_is_test();
+ }
+ if (expected_true !== true) {
+ var msg = make_message(function_name, description,
+ error, substitutions);
+ throw new AssertionError(msg);
+ }
+ }
+
+ function AssertionError(message)
+ {
+ this.message = message;
+ this.stack = this.get_stack();
+ }
+ expose(AssertionError, "AssertionError");
+
+ AssertionError.prototype = Object.create(Error.prototype);
+
+ AssertionError.prototype.get_stack = function() {
+ var stack = new Error().stack;
+ // IE11 does not initialize 'Error.stack' until the object is thrown.
+ if (!stack) {
+ try {
+ throw new Error();
+ } catch (e) {
+ stack = e.stack;
+ }
+ }
+
+ // 'Error.stack' is not supported in all browsers/versions
+ if (!stack) {
+ return "(Stack trace unavailable)";
+ }
+
+ var lines = stack.split("\n");
+
+ // Create a pattern to match stack frames originating within testharness.js. These include the
+ // script URL, followed by the line/col (e.g., '/resources/testharness.js:120:21').
+ // Escape the URL per http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+ // in case it contains RegExp characters.
+ var script_url = get_script_url();
+ var re_text = script_url ? script_url.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : "\\btestharness.js";
+ var re = new RegExp(re_text + ":\\d+:\\d+");
+
+ // Some browsers include a preamble that specifies the type of the error object. Skip this by
+ // advancing until we find the first stack frame originating from testharness.js.
+ var i = 0;
+ while (!re.test(lines[i]) && i < lines.length) {
+ i++;
+ }
+
+ // Then skip the top frames originating from testharness.js to begin the stack at the test code.
+ while (re.test(lines[i]) && i < lines.length) {
+ i++;
+ }
+
+ // Paranoid check that we didn't skip all frames. If so, return the original stack unmodified.
+ if (i >= lines.length) {
+ return stack;
+ }
+
+ return lines.slice(i).join("\n");
+ }
+
+ function make_message(function_name, description, error, substitutions)
+ {
+ for (var p in substitutions) {
+ if (substitutions.hasOwnProperty(p)) {
+ substitutions[p] = format_value(substitutions[p]);
+ }
+ }
+ var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
+ merge({function_name:function_name,
+ description:(description?description + " ":"")},
+ substitutions));
+ return node_form.slice(1).join("");
+ }
+
+ function filter(array, callable, thisObj) {
+ var rv = [];
+ for (var i = 0; i < array.length; i++) {
+ if (array.hasOwnProperty(i)) {
+ var pass = callable.call(thisObj, array[i], i, array);
+ if (pass) {
+ rv.push(array[i]);
+ }
+ }
+ }
+ return rv;
+ }
+
+ function map(array, callable, thisObj)
+ {
+ var rv = [];
+ rv.length = array.length;
+ for (var i = 0; i < array.length; i++) {
+ if (array.hasOwnProperty(i)) {
+ rv[i] = callable.call(thisObj, array[i], i, array);
+ }
+ }
+ return rv;
+ }
+
+ function extend(array, items)
+ {
+ Array.prototype.push.apply(array, items);
+ }
+
+ function forEach(array, callback, thisObj)
+ {
+ for (var i = 0; i < array.length; i++) {
+ if (array.hasOwnProperty(i)) {
+ callback.call(thisObj, array[i], i, array);
+ }
+ }
+ }
+
+ /**
+ * Immediately invoke a "iteratee" function with a series of values in
+ * parallel and invoke a final "done" function when all of the "iteratee"
+ * invocations have signaled completion.
+ *
+ * If all callbacks complete synchronously (or if no callbacks are
+ * specified), the `done_callback` will be invoked synchronously. It is the
+ * responsibility of the caller to ensure asynchronicity in cases where
+ * that is desired.
+ *
+ * @param {array} value Zero or more values to use in the invocation of
+ * `iter_callback`
+ * @param {function} iter_callback A function that will be invoked once for
+ * each of the provided `values`. Two
+ * arguments will be available in each
+ * invocation: the value from `values` and
+ * a function that must be invoked to
+ * signal completion
+ * @param {function} done_callback A function that will be invoked after
+ * all operations initiated by the
+ * `iter_callback` function have signaled
+ * completion
+ */
+ function all_async(values, iter_callback, done_callback)
+ {
+ var remaining = values.length;
+
+ if (remaining === 0) {
+ done_callback();
+ }
+
+ forEach(values,
+ function(element) {
+ var invoked = false;
+ var elDone = function() {
+ if (invoked) {
+ return;
+ }
+
+ invoked = true;
+ remaining -= 1;
+
+ if (remaining === 0) {
+ done_callback();
+ }
+ };
+
+ iter_callback(element, elDone);
+ });
+ }
+
+ function merge(a,b)
+ {
+ var rv = {};
+ var p;
+ for (p in a) {
+ rv[p] = a[p];
+ }
+ for (p in b) {
+ rv[p] = b[p];
+ }
+ return rv;
+ }
+
+ function expose(object, name)
+ {
+ var components = name.split(".");
+ var target = global_scope;
+ for (var i = 0; i < components.length - 1; i++) {
+ if (!(components[i] in target)) {
+ target[components[i]] = {};
+ }
+ target = target[components[i]];
+ }
+ target[components[components.length - 1]] = object;
+ }
+
+ function is_same_origin(w) {
+ try {
+ 'random_prop' in w;
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ /** Returns the 'src' URL of the first
+```
+
+## Documentation
+
+The API to WebIDL2 is trivial: you parse a string of WebIDL and it returns a syntax tree.
+
+### Parsing
+
+In Node, that happens with:
+
+```JS
+var WebIDL2 = require("webidl2");
+var tree = WebIDL2.parse("string of WebIDL");
+```
+
+In the browser:
+```HTML
+
+
+```
+
+### Errors
+
+When there is a syntax error in the WebIDL, it throws an exception object with the following
+properties:
+
+* `message`: the error message
+* `line`: the line at which the error occurred.
+* `input`: a short peek at the text at the point where the error happened
+* `tokens`: the five tokens at the point of error, as understood by the tokeniser
+ (this is the same content as `input`, but seen from the tokeniser's point of view)
+
+The exception also has a `toString()` method that hopefully should produce a decent
+error message.
+
+### AST (Abstract Syntax Tree)
+
+The `parse()` method returns a tree object representing the parse tree of the IDL.
+Comment and white space are not represented in the AST.
+
+The root of this object is always an array of definitions (where definitions are
+any of interfaces, dictionaries, callbacks, etc. — anything that can occur at the root
+of the IDL).
+
+### IDL Type
+
+This structure is used in many other places (operation return types, argument types, etc.).
+It captures a WebIDL type with a number of options. Types look like this and are typically
+attached to a field called `idlType`:
+
+```JS
+{
+ "type": "attribute-type",
+ "generic": null,
+ "idlType": "unsigned short",
+ "nullable": false,
+ "union": false,
+ "extAttrs": [...]
+}
+```
+
+Where the fields are as follows:
+
+* `type`: String indicating where this type is used. Can be `null` if not applicable.
+* `generic`: String indicating the generic type (e.g. "Promise", "sequence"). `null`
+ otherwise.
+* `idlType`: Can be different things depending on context. In most cases, this will just
+ be a string with the type name. But the reason this field isn't called "typeName" is
+ because it can take more complex values. If the type is a union, then this contains an
+ array of the types it unites. If it is a generic type, it contains the IDL type
+ description for the type in the sequence, the eventual value of the promise, etc.
+* `nullable`: Boolean indicating whether this is nullable or not.
+* `union`: Boolean indicating whether this is a union type or not.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Interface
+
+Interfaces look like this:
+
+```JS
+{
+ "type": "interface",
+ "name": "Animal",
+ "partial": false,
+ "members": [...],
+ "inheritance": null,
+ "extAttrs": [...]
+}, {
+ "type": "interface",
+ "name": "Human",
+ "partial": false,
+ "members": [...],
+ "inheritance": "Animal",
+ "extAttrs": [...]
+}
+```
+
+The fields are as follows:
+
+* `type`: Always "interface".
+* `name`: The name of the interface.
+* `partial`: A boolean indicating whether it's a partial interface.
+* `members`: An array of interface members (attributes, operations, etc.). Empty if there are none.
+* `inheritance`: A string giving the name of an interface this one inherits from, `null` otherwise.
+ **NOTE**: In v1 this was an array, but multiple inheritance is no longer supported so this didn't make
+ sense.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Interface mixins
+
+Interfaces mixins look like this:
+
+```JS
+{
+ "type": "interface mixin",
+ "name": "Animal",
+ "partial": false,
+ "members": [...],
+ "extAttrs": [...]
+}, {
+ "type": "interface mixin",
+ "name": "Human",
+ "partial": false,
+ "members": [...],
+ "extAttrs": [...]
+}
+```
+
+The fields are as follows:
+
+* `type`: Always "interface mixin".
+* `name`: The name of the interface mixin.
+* `partial`: A boolean indicating whether it's a partial interface mixin.
+* `members`: An array of interface members (attributes, operations, etc.). Empty if there are none.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Namespace
+
+Namespaces look like this:
+
+```JS
+{
+ "type": "namespace",
+ "name": "Console",
+ "partial": false,
+ "members": [...],
+ "extAttrs": [...]
+}
+```
+
+The fields are as follows:
+
+* `type`: Always "namespace".
+* `name`: The name of the namespace.
+* `partial`: A boolean indicating whether it's a partial namespace.
+* `members`: An array of namespace members (attributes and operations). Empty if there are none.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Callback Interfaces
+
+These are captured by the same structure as [Interfaces](#interface) except that
+their `type` field is "callback interface".
+
+### Callback
+
+A callback looks like this:
+
+```JS
+{
+ "type": "callback",
+ "name": "AsyncOperationCallback",
+ "idlType": {
+ "type": "return-type",
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "arguments": [...],
+ "extAttrs": []
+}
+```
+
+The fields are as follows:
+
+* `type`: Always "callback".
+* `name`: The name of the callback.
+* `idlType`: An [IDL Type](#idl-type) describing what the callback returns.
+* `arguments`: A list of [arguments](#arguments), as in function paramters.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Dictionary
+
+A dictionary looks like this:
+
+```JS
+{
+ "type": "dictionary",
+ "name": "PaintOptions",
+ "partial": false,
+ "members": [{
+ "type": "field",
+ "name": "fillPattern",
+ "required": false,
+ "idlType": {
+ "type": "dictionary-type",
+ "sequence": false,
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": [...]
+ },
+ "extAttrs": [],
+ "default": {
+ "type": "string",
+ "value": "black"
+ }
+ }],
+ "inheritance": null,
+ "extAttrs": []
+}
+```
+
+The fields are as follows:
+
+* `type`: Always "dictionary".
+* `name`: The dictionary name.
+* `partial`: Boolean indicating whether it's a partial dictionary.
+* `members`: An array of members (see below).
+* `inheritance`: A string indicating which dictionary is being inherited from, `null` otherwise.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+All the members are fields as follows:
+
+* `type`: Always "field".
+* `name`: The name of the field.
+* `required`: Boolean indicating whether this is a [required](https://heycam.github.io/webidl/#required-dictionary-member) field.
+* `idlType`: An [IDL Type](#idl-type) describing what field's type.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+* `default`: A [default value](#default-and-const-values), absent if there is none.
+
+### Enum
+
+An enum looks like this:
+
+```JS
+{
+ "type": "enum",
+ "name": "MealType",
+ "values": [
+ { "type": "string", "value": "rice" },
+ { "type": "string", "value": "noodles" },
+ { "type": "string", "value": "other" }
+ ],
+ "extAttrs": []
+}
+```
+
+The fields are as follows:
+
+* `type`: Always "enum".
+* `name`: The enum's name.
+* `values`: An array of values.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Typedef
+
+A typedef looks like this:
+
+```JS
+{
+ "type": "typedef",
+ "idlType": {
+ "type": "typedef-type",
+ "sequence": true,
+ "generic": "sequence",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "typedef-type",
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Point",
+ "extAttrs": [...]
+ },
+ "extAttrs": [...]
+ },
+ "name": "PointSequence",
+ "extAttrs": []
+}
+```
+
+
+The fields are as follows:
+
+* `type`: Always "typedef".
+* `name`: The typedef's name.
+* `idlType`: An [IDL Type](#idl-type) describing what typedef's type.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Implements
+
+An implements definition looks like this:
+
+```JS
+{
+ "type": "implements",
+ "target": "Node",
+ "implements": "EventTarget",
+ "extAttrs": []
+}
+```
+
+The fields are as follows:
+
+* `type`: Always "implements".
+* `target`: The interface that implements another.
+* `implements`: The interface that is being implemented by the target.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Includes
+
+An includes definition looks like this:
+
+```JS
+{
+ "type": "includes",
+ "target": "Node",
+ "includes": "EventTarget",
+ "extAttrs": []
+}
+```
+
+The fields are as follows:
+
+* `type`: Always "includes".
+* `target`: The interface that includes an interface mixin.
+* `includes`: The interface mixin that is being included by the target.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Operation Member
+
+An operation looks like this:
+```JS
+{
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "intersection",
+ "arguments": [{
+ "optional": false,
+ "variadic": true,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": [...]
+ },
+ "name": "ints"
+ }],
+ "extAttrs": []
+}
+```
+
+The fields are as follows:
+
+* `type`: Always "operation".
+* `getter`: True if a getter operation.
+* `setter`: True if a setter operation.
+* `deleter`: True if a deleter operation.
+* `static`: True if a static operation.
+* `stringifier`: True if a stringifier operation.
+* `idlType`: An [IDL Type](#idl-type) of what the operation returns. If a stringifier, may be absent.
+* `name`: The name of the operation. If a stringifier, may be `null`.
+* `arguments`: An array of [arguments](#arguments) for the operation.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Attribute Member
+
+An attribute member looks like this:
+
+```JS
+{
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "RegExp",
+ "extAttrs": [...]
+ },
+ "name": "regexp",
+ "extAttrs": []
+}
+```
+
+The fields are as follows:
+
+* `type`: Always "attribute".
+* `name`: The attribute's name.
+* `static`: True if it's a static attribute.
+* `stringifier`: True if it's a stringifier attribute.
+* `inherit`: True if it's an inherit attribute.
+* `readonly`: True if it's a read-only attribute.
+* `idlType`: An [IDL Type](#idl-type) for the attribute.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Constant Member
+
+A constant member looks like this:
+
+```JS
+{
+ "type": "const",
+ "nullable": false,
+ "idlType": {
+ "type": "const-type",
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "boolean"
+ "extAttrs": []
+ },
+ "name": "DEBUG",
+ "value": {
+ "type": "boolean",
+ "value": false
+ },
+ "extAttrs": []
+}
+```
+
+The fields are as follows:
+
+* `type`: Always "const".
+* `nullable`: Whether its type is nullable.
+* `idlType`: An [IDL Type](#idl-type) of the constant that represents a simple type, the type name.
+* `name`: The name of the constant.
+* `value`: The constant value as described by [Const Values](#default-and-const-values)
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Arguments
+
+The arguments (e.g. for an operation) look like this:
+
+```JS
+{
+ "arguments": [{
+ "optional": false,
+ "variadic": true,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": [...]
+ },
+ "name": "ints"
+ }]
+}
+```
+
+The fields are as follows:
+
+* `optional`: True if the argument is optional.
+* `variadic`: True if the argument is variadic.
+* `idlType`: An [IDL Type](#idl-type) describing the type of the argument.
+* `name`: The argument's name.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+### Extended Attributes
+
+Extended attributes are arrays of items that look like this:
+
+```JS
+{
+ "extAttrs": [{
+ "name": "TreatNullAs",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": {
+ "type": "identifier",
+ "value": "EmptyString"
+ }
+ }]
+}
+```
+
+The fields are as follows:
+
+* `name`: The extended attribute's name.
+* `arguments`: If the extended attribute takes arguments (e.g. `[Foo()]`) or if
+ its right-hand side does (e.g. `[NamedConstructor=Name(DOMString blah)]`) they
+ are listed here. Note that an empty arguments list will produce an empty array,
+ whereas the lack thereof will yield a `null`. If there is an `rhs` field then
+ they are the right-hand side's arguments, otherwise they apply to the extended
+ attribute directly.
+* `type`: Always `"extended-attribute"`.
+* `rhs`: If there is a right-hand side, this will capture its `type` (which can be
+ "identifier" or "identifier-list") and its `value`.
+
+### Default and Const Values
+
+Dictionary fields and operation arguments can take default values, and constants take
+values, all of which have the following fields:
+
+* `type`: One of string, number, boolean, null, Infinity, NaN, or sequence.
+
+For string, number, boolean, and sequence:
+
+* `value`: The value of the given type, as a string. For sequence, the only possible value is `[]`.
+
+For Infinity:
+
+* `negative`: Boolean indicating whether this is negative Infinity or not.
+
+### `iterable<>`, `legacyiterable<>`, `maplike<>`, `setlike<>` declarations
+
+These appear as members of interfaces that look like this:
+
+```JS
+{
+ "type": "maplike", // or "legacyiterable" / "iterable" / "setlike"
+ "idlType": /* One or two types */ ,
+ "readonly": false, // only for maplike and setlike
+ "extAttrs": []
+}
+```
+
+The fields are as follows:
+
+* `type`: Always one of "iterable", "legacyiterable", "maplike" or "setlike".
+* `idlType`: An array with one or more [IDL Types](#idl-type) representing the declared type arguments.
+* `readonly`: Whether the maplike or setlike is declared as read only.
+* `extAttrs`: A list of [extended attributes](#extended-attributes).
+
+
+## Testing
+
+### Running
+
+The test runs with mocha and expect.js. Normally, running mocha in the root directory
+should be enough once you're set up.
+
+### Coverage
+
+Current test coverage, as documented in `coverage.html`, is 95%. You can run your own
+coverage analysis with:
+
+```Bash
+jscoverage lib lib-cov
+```
+
+That will create the lib-cov directory with instrumented code; the test suite knows
+to use that if needed. You can then run the tests with:
+
+```Bash
+JSCOV=1 mocha --reporter html-cov > coverage.html
+```
+
+Note that I've been getting weirdly overescaped results from the html-cov reporter,
+so you might wish to try this instead:
+
+```Bash
+JSCOV=1 mocha --reporter html-cov | sed "s/<//g" | sed "s/"/\"/g" > coverage.html
+```
+### Browser tests
+
+In order to test in the browser, get inside `test/web` and run `make-web-tests.js`. This
+will generate a `browser-tests.html` file that you can open in a browser. As of this
+writing tests pass in the latest Firefox, Chrome, Opera, and Safari. Testing on IE
+and older versions will happen progressively.
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/index.js b/test/fixtures/web-platform-tests/resources/webidl2/index.js
new file mode 100644
index 00000000000000..09f9eb46aa78f4
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/index.js
@@ -0,0 +1 @@
+module.exports = require("./lib/webidl2.js");
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/lib/webidl2.js b/test/fixtures/web-platform-tests/resources/webidl2/lib/webidl2.js
new file mode 100644
index 00000000000000..ef519c09df6d6d
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/lib/webidl2.js
@@ -0,0 +1,970 @@
+"use strict";
+
+(() => {
+ // These regular expressions use the sticky flag so they will only match at
+ // the current location (ie. the offset of lastIndex).
+ const tokenRe = {
+ // This expression uses a lookahead assertion to catch false matches
+ // against integers early.
+ "float": /-?(?=[0-9]*\.|[0-9]+[eE])(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][-+]?[0-9]+)?|[0-9]+[Ee][-+]?[0-9]+)/y,
+ "integer": /-?(0([Xx][0-9A-Fa-f]+|[0-7]*)|[1-9][0-9]*)/y,
+ "identifier": /_?[A-Za-z][0-9A-Z_a-z-]*/y,
+ "string": /"[^"]*"/y,
+ "whitespace": /[\t\n\r ]+/y,
+ "comment": /((\/(\/.*|\*([^*]|\*[^\/])*\*\/)[\t\n\r ]*)+)/y,
+ "other": /[^\t\n\r 0-9A-Za-z]/y
+ };
+
+ const stringTypes = [
+ "ByteString",
+ "DOMString",
+ "USVString"
+ ];
+
+ const argumentNameKeywords = [
+ "attribute",
+ "callback",
+ "const",
+ "deleter",
+ "dictionary",
+ "enum",
+ "getter",
+ "includes",
+ "inherit",
+ "interface",
+ "iterable",
+ "maplike",
+ "namespace",
+ "partial",
+ "required",
+ "setlike",
+ "setter",
+ "static",
+ "stringifier",
+ "typedef",
+ "unrestricted"
+ ];
+
+ const nonRegexTerminals = [
+ "FrozenArray",
+ "Infinity",
+ "NaN",
+ "Promise",
+ "boolean",
+ "byte",
+ "double",
+ "false",
+ "float",
+ "implements",
+ "legacyiterable",
+ "long",
+ "mixin",
+ "null",
+ "octet",
+ "optional",
+ "or",
+ "readonly",
+ "record",
+ "sequence",
+ "short",
+ "true",
+ "unsigned",
+ "void"
+ ].concat(argumentNameKeywords, stringTypes);
+
+ const punctuations = [
+ "(",
+ ")",
+ ",",
+ "-Infinity",
+ "...",
+ ":",
+ ";",
+ "<",
+ "=",
+ ">",
+ "?",
+ "[",
+ "]",
+ "{",
+ "}"
+ ];
+
+ function tokenise(str) {
+ const tokens = [];
+ let lastIndex = 0;
+ let trivia = "";
+ while (lastIndex < str.length) {
+ const nextChar = str.charAt(lastIndex);
+ let result = -1;
+
+ if (/[\t\n\r ]/.test(nextChar)) {
+ result = attemptTokenMatch("whitespace", { noFlushTrivia: true });
+ } else if (nextChar === '/') {
+ result = attemptTokenMatch("comment", { noFlushTrivia: true });
+ }
+
+ if (result !== -1) {
+ trivia += tokens.pop().value;
+ } else if (/[-0-9.]/.test(nextChar)) {
+ result = attemptTokenMatch("float");
+ if (result === -1) {
+ result = attemptTokenMatch("integer");
+ }
+ } else if (/[A-Z_a-z]/.test(nextChar)) {
+ result = attemptTokenMatch("identifier");
+ const token = tokens[tokens.length - 1];
+ if (result !== -1 && nonRegexTerminals.includes(token.value)) {
+ token.type = token.value;
+ }
+ } else if (nextChar === '"') {
+ result = attemptTokenMatch("string");
+ }
+
+ for (const punctuation of punctuations) {
+ if (str.startsWith(punctuation, lastIndex)) {
+ tokens.push({ type: punctuation, value: punctuation, trivia });
+ trivia = "";
+ lastIndex += punctuation.length;
+ result = lastIndex;
+ break;
+ }
+ }
+
+ // other as the last try
+ if (result === -1) {
+ result = attemptTokenMatch("other");
+ }
+ if (result === -1) {
+ throw new Error("Token stream not progressing");
+ }
+ lastIndex = result;
+ }
+ return tokens;
+
+ function attemptTokenMatch(type, { noFlushTrivia } = {}) {
+ const re = tokenRe[type];
+ re.lastIndex = lastIndex;
+ const result = re.exec(str);
+ if (result) {
+ tokens.push({ type, value: result[0], trivia });
+ if (!noFlushTrivia) {
+ trivia = "";
+ }
+ return re.lastIndex;
+ }
+ return -1;
+ }
+ }
+
+ class WebIDLParseError {
+ constructor(str, line, input, tokens) {
+ this.message = str;
+ this.line = line;
+ this.input = input;
+ this.tokens = tokens;
+ }
+
+ toString() {
+ const escapedInput = JSON.stringify(this.input);
+ const tokens = JSON.stringify(this.tokens, null, 4);
+ return `${this.message}, line ${this.line} (tokens: ${escapedInput})\n${tokens}`;
+ }
+ }
+
+ function parse(tokens) {
+ let line = 1;
+ tokens = tokens.slice();
+ const names = new Map();
+ let current = null;
+
+ const FLOAT = "float";
+ const INT = "integer";
+ const ID = "identifier";
+ const STR = "string";
+ const OTHER = "other";
+
+ const EMPTY_OPERATION = Object.freeze({
+ type: "operation",
+ getter: false,
+ setter: false,
+ deleter: false,
+ static: false,
+ stringifier: false
+ });
+
+ const EMPTY_IDLTYPE = Object.freeze({
+ generic: null,
+ nullable: false,
+ union: false,
+ idlType: null,
+ extAttrs: []
+ });
+
+ function error(str) {
+ const maxTokens = 5;
+ const tok = tokens
+ .slice(consume_position, consume_position + maxTokens)
+ .map(t => t.trivia + t.value).join("");
+ // Count newlines preceding the actual erroneous token
+ if (tokens.length) {
+ line += count(tokens[consume_position].trivia, "\n");
+ }
+
+ let message;
+ if (current) {
+ message = `Got an error during or right after parsing \`${current.partial ? "partial " : ""}${current.type} ${current.name}\`: ${str}`
+ }
+ else {
+ // throwing before any valid definition
+ message = `Got an error before parsing any named definition: ${str}`;
+ }
+
+ throw new WebIDLParseError(message, line, tok, tokens.slice(0, maxTokens));
+ }
+
+ function sanitize_name(name, type) {
+ if (names.has(name)) {
+ error(`The name "${name}" of type "${names.get(name)}" is already seen`);
+ }
+ names.set(name, type);
+ return name;
+ }
+
+ let consume_position = 0;
+
+ function probe(type) {
+ return tokens.length > consume_position && tokens[consume_position].type === type;
+ }
+
+ function consume(...candidates) {
+ // TODO: use const when Servo updates its JS engine
+ for (let type of candidates) {
+ if (!probe(type)) continue;
+ const token = tokens[consume_position];
+ consume_position++;
+ line += count(token.trivia, "\n");
+ return token;
+ }
+ }
+
+ function unescape(identifier) {
+ return identifier.startsWith('_') ? identifier.slice(1) : identifier;
+ }
+
+ function unconsume(position) {
+ while (consume_position > position) {
+ consume_position--;
+ line -= count(tokens[consume_position].trivia, "\n");
+ }
+ }
+
+ function count(str, char) {
+ let total = 0;
+ for (let i = str.indexOf(char); i !== -1; i = str.indexOf(char, i + 1)) {
+ ++total;
+ }
+ return total;
+ }
+
+ function integer_type() {
+ let ret = "";
+ if (consume("unsigned")) ret = "unsigned ";
+ if (consume("short")) return ret + "short";
+ if (consume("long")) {
+ ret += "long";
+ if (consume("long")) return ret + " long";
+ return ret;
+ }
+ if (ret) error("Failed to parse integer type");
+ }
+
+ function float_type() {
+ let ret = "";
+ if (consume("unrestricted")) ret = "unrestricted ";
+ if (consume("float")) return ret + "float";
+ if (consume("double")) return ret + "double";
+ if (ret) error("Failed to parse float type");
+ }
+
+ function primitive_type() {
+ const num_type = integer_type() || float_type();
+ if (num_type) return num_type;
+ if (consume("boolean")) return "boolean";
+ if (consume("byte")) return "byte";
+ if (consume("octet")) return "octet";
+ }
+
+ function const_value() {
+ if (consume("true")) return { type: "boolean", value: true };
+ if (consume("false")) return { type: "boolean", value: false };
+ if (consume("null")) return { type: "null" };
+ if (consume("Infinity")) return { type: "Infinity", negative: false };
+ if (consume("-Infinity")) return { type: "Infinity", negative: true };
+ if (consume("NaN")) return { type: "NaN" };
+ const ret = consume(FLOAT, INT);
+ if (ret) return { type: "number", value: ret.value };
+ }
+
+ function type_suffix(obj) {
+ obj.nullable = !!consume("?");
+ if (probe("?")) error("Can't nullable more than once");
+ }
+
+ function generic_type(typeName) {
+ const name = consume("FrozenArray", "Promise", "sequence", "record");
+ if (!name) {
+ return;
+ }
+ const ret = { generic: name.type };
+ consume("<") || error(`No opening bracket after ${name.type}`);
+ switch (name.type) {
+ case "Promise":
+ if (probe("[")) error("Promise type cannot have extended attribute");
+ ret.idlType = return_type(typeName);
+ break;
+ case "sequence":
+ case "FrozenArray":
+ ret.idlType = type_with_extended_attributes(typeName);
+ break;
+ case "record":
+ if (probe("[")) error("Record key cannot have extended attribute");
+ ret.idlType = [];
+ const keyType = consume(...stringTypes);
+ if (!keyType) error(`Record key must be a string type`);
+ ret.idlType.push(Object.assign({ type: typeName }, EMPTY_IDLTYPE, { idlType: keyType.value }));
+ consume(",") || error("Missing comma after record key type");
+ const valueType = type_with_extended_attributes(typeName) || error("Error parsing generic type record");
+ ret.idlType.push(valueType);
+ break;
+ }
+ if (!ret.idlType) error(`Error parsing generic type ${name.type}`);
+ consume(">") || error(`Missing closing bracket after ${name.type}`);
+ if (name.type === "Promise" && probe("?")) {
+ error("Promise type cannot be nullable");
+ }
+ type_suffix(ret);
+ return ret;
+ }
+
+ function single_type(typeName) {
+ const ret = Object.assign({ type: typeName || null }, EMPTY_IDLTYPE);
+ const generic = generic_type(typeName);
+ if (generic) {
+ return Object.assign(ret, generic);
+ }
+ const prim = primitive_type();
+ let name;
+ if (prim) {
+ ret.idlType = prim;
+ } else if (name = consume(ID, ...stringTypes)) {
+ ret.idlType = name.value;
+ if (probe("<")) error(`Unsupported generic type ${name.value}`);
+ } else {
+ return;
+ }
+ type_suffix(ret);
+ if (ret.nullable && ret.idlType === "any") error("Type any cannot be made nullable");
+ return ret;
+ }
+
+ function union_type(typeName) {
+ if (!consume("(")) return;
+ const ret = Object.assign({ type: typeName || null }, EMPTY_IDLTYPE, { union: true, idlType: [] });
+ do {
+ const typ = type_with_extended_attributes() || error("No type after open parenthesis or 'or' in union type");
+ ret.idlType.push(typ);
+ } while (consume("or"));
+ if (ret.idlType.length < 2) {
+ error("At least two types are expected in a union type but found less");
+ }
+ if (!consume(")")) error("Unterminated union type");
+ type_suffix(ret);
+ return ret;
+ }
+
+ function type(typeName) {
+ return single_type(typeName) || union_type(typeName);
+ }
+
+ function type_with_extended_attributes(typeName) {
+ const extAttrs = extended_attrs();
+ const ret = single_type(typeName) || union_type(typeName);
+ if (extAttrs.length && ret) ret.extAttrs = extAttrs;
+ return ret;
+ }
+
+ function argument() {
+ const start_position = consume_position;
+ const ret = { optional: false, variadic: false, default: null };
+ ret.extAttrs = extended_attrs();
+ const opt_token = consume("optional");
+ if (opt_token) {
+ ret.optional = true;
+ }
+ ret.idlType = type_with_extended_attributes("argument-type");
+ if (!ret.idlType) {
+ unconsume(start_position);
+ return;
+ }
+ if (!ret.optional && consume("...")) {
+ ret.variadic = true;
+ }
+ const name = consume(ID, ...argumentNameKeywords);
+ if (!name) {
+ unconsume(start_position);
+ return;
+ }
+ ret.name = unescape(name.value);
+ ret.escapedName = name.value;
+ if (ret.optional) {
+ ret.default = default_() || null;
+ }
+ return ret;
+ }
+
+ function argument_list() {
+ const ret = [];
+ const arg = argument();
+ if (!arg) return ret;
+ ret.push(arg);
+ while (true) {
+ if (!consume(",")) return ret;
+ const nxt = argument() || error("Trailing comma in arguments list");
+ ret.push(nxt);
+ }
+ }
+
+ function simple_extended_attr() {
+ const name = consume(ID);
+ if (!name) return;
+ const ret = {
+ name: name.value,
+ arguments: null,
+ type: "extended-attribute",
+ rhs: null
+ };
+ const eq = consume("=");
+ if (eq) {
+ ret.rhs = consume(ID, FLOAT, INT, STR);
+ if (ret.rhs) {
+ // No trivia exposure yet
+ ret.rhs.trivia = undefined;
+ }
+ }
+ if (consume("(")) {
+ if (eq && !ret.rhs) {
+ // [Exposed=(Window,Worker)]
+ ret.rhs = {
+ type: "identifier-list",
+ value: identifiers()
+ };
+ }
+ else {
+ // [NamedConstructor=Audio(DOMString src)] or [Constructor(DOMString str)]
+ ret.arguments = argument_list();
+ }
+ consume(")") || error("Unexpected token in extended attribute argument list");
+ }
+ if (eq && !ret.rhs) error("No right hand side to extended attribute assignment");
+ return ret;
+ }
+
+ // Note: we parse something simpler than the official syntax. It's all that ever
+ // seems to be used
+ function extended_attrs() {
+ const eas = [];
+ if (!consume("[")) return eas;
+ eas[0] = simple_extended_attr() || error("Extended attribute with not content");
+ while (consume(",")) {
+ eas.push(simple_extended_attr() || error("Trailing comma in extended attribute"));
+ }
+ consume("]") || error("No end of extended attribute");
+ return eas;
+ }
+
+ function default_() {
+ if (consume("=")) {
+ const def = const_value();
+ if (def) {
+ return def;
+ } else if (consume("[")) {
+ if (!consume("]")) error("Default sequence value must be empty");
+ return { type: "sequence", value: [] };
+ } else {
+ const str = consume(STR) || error("No value for default");
+ str.value = str.value.slice(1, -1);
+ // No trivia exposure yet
+ str.trivia = undefined;
+ return str;
+ }
+ }
+ }
+
+ function const_() {
+ if (!consume("const")) return;
+ const ret = { type: "const", nullable: false };
+ let typ = primitive_type();
+ if (!typ) {
+ typ = consume(ID) || error("No type for const");
+ typ = typ.value;
+ }
+ ret.idlType = Object.assign({ type: "const-type" }, EMPTY_IDLTYPE, { idlType: typ });
+ type_suffix(ret);
+ const name = consume(ID) || error("No name for const");
+ ret.name = name.value;
+ consume("=") || error("No value assignment for const");
+ const cnt = const_value();
+ if (cnt) ret.value = cnt;
+ else error("No value for const");
+ consume(";") || error("Unterminated const");
+ return ret;
+ }
+
+ function inheritance() {
+ if (consume(":")) {
+ const inh = consume(ID) || error("No type in inheritance");
+ return inh.value;
+ }
+ }
+
+ function operation_rest(ret) {
+ if (!ret) ret = {};
+ const name = consume(ID);
+ ret.name = name ? unescape(name.value) : null;
+ ret.escapedName = name ? name.value : null;
+ consume("(") || error("Invalid operation");
+ ret.arguments = argument_list();
+ consume(")") || error("Unterminated operation");
+ consume(";") || error("Unterminated operation");
+ return ret;
+ }
+
+ function callback() {
+ let ret;
+ if (!consume("callback")) return;
+ const tok = consume("interface");
+ if (tok) {
+ ret = interface_rest(false, "callback interface");
+ return ret;
+ }
+ const name = consume(ID) || error("No name for callback");
+ ret = current = { type: "callback", name: sanitize_name(name.value, "callback") };
+ consume("=") || error("No assignment in callback");
+ ret.idlType = return_type() || error("Missing return type");
+ consume("(") || error("No arguments in callback");
+ ret.arguments = argument_list();
+ consume(")") || error("Unterminated callback");
+ consume(";") || error("Unterminated callback");
+ return ret;
+ }
+
+ function attribute({ noInherit = false, readonly = false } = {}) {
+ const start_position = consume_position;
+ const ret = {
+ type: "attribute",
+ static: false,
+ stringifier: false,
+ inherit: false,
+ readonly: false
+ };
+ if (!noInherit && consume("inherit")) {
+ ret.inherit = true;
+ }
+ if (consume("readonly")) {
+ ret.readonly = true;
+ } else if (readonly && probe("attribute")) {
+ error("Attributes must be readonly in this context");
+ }
+ const rest = attribute_rest(ret);
+ if (!rest) {
+ unconsume(start_position);
+ }
+ return rest;
+ }
+
+ function attribute_rest(ret) {
+ if (!consume("attribute")) {
+ return;
+ }
+ ret.idlType = type_with_extended_attributes("attribute-type") || error("No type in attribute");
+ if (ret.idlType.generic === "sequence") error("Attributes cannot accept sequence types");
+ if (ret.idlType.generic === "record") error("Attributes cannot accept record types");
+ const name = consume(ID, "required") || error("No name in attribute");
+ ret.name = unescape(name.value);
+ ret.escapedName = name.value;
+ consume(";") || error("Unterminated attribute");
+ return ret;
+ }
+
+ function return_type(typeName) {
+ const typ = type(typeName || "return-type");
+ if (typ) {
+ return typ;
+ }
+ if (consume("void")) {
+ return Object.assign({ type: "return-type" }, EMPTY_IDLTYPE, { idlType: "void" });
+ }
+ }
+
+ function operation({ regular = false } = {}) {
+ const ret = Object.assign({}, EMPTY_OPERATION);
+ while (!regular) {
+ if (consume("getter")) ret.getter = true;
+ else if (consume("setter")) ret.setter = true;
+ else if (consume("deleter")) ret.deleter = true;
+ else break;
+ }
+ ret.idlType = return_type() || error("Missing return type");
+ operation_rest(ret);
+ return ret;
+ }
+
+ function static_member() {
+ if (!consume("static")) return;
+ const member = attribute({ noInherit: true }) ||
+ operation({ regular: true }) ||
+ error("No body in static member");
+ member.static = true;
+ return member;
+ }
+
+ function stringifier() {
+ if (!consume("stringifier")) return;
+ if (consume(";")) {
+ return Object.assign({}, EMPTY_OPERATION, { stringifier: true });
+ }
+ const member = attribute({ noInherit: true }) ||
+ operation({ regular: true }) ||
+ error("Unterminated stringifier");
+ member.stringifier = true;
+ return member;
+ }
+
+ function identifiers() {
+ const arr = [];
+ const id = consume(ID);
+ if (id) {
+ arr.push(id.value);
+ }
+ else error("Expected identifiers but not found");
+ while (true) {
+ if (consume(",")) {
+ const name = consume(ID) || error("Trailing comma in identifiers list");
+ arr.push(name.value);
+ } else break;
+ }
+ return arr;
+ }
+
+ function iterable_type() {
+ if (consume("iterable")) return "iterable";
+ else if (consume("legacyiterable")) return "legacyiterable";
+ else if (consume("maplike")) return "maplike";
+ else if (consume("setlike")) return "setlike";
+ else return;
+ }
+
+ function readonly_iterable_type() {
+ if (consume("maplike")) return "maplike";
+ else if (consume("setlike")) return "setlike";
+ else return;
+ }
+
+ function iterable() {
+ const start_position = consume_position;
+ const ret = { type: null, idlType: null, readonly: false };
+ if (consume("readonly")) {
+ ret.readonly = true;
+ }
+ const consumeItType = ret.readonly ? readonly_iterable_type : iterable_type;
+
+ const ittype = consumeItType();
+ if (!ittype) {
+ unconsume(start_position);
+ return;
+ }
+
+ const secondTypeRequired = ittype === "maplike";
+ const secondTypeAllowed = secondTypeRequired || ittype === "iterable";
+ ret.type = ittype;
+ if (ret.type !== 'maplike' && ret.type !== 'setlike')
+ delete ret.readonly;
+ if (consume("<")) {
+ ret.idlType = [type_with_extended_attributes()] || error(`Error parsing ${ittype} declaration`);
+ if (secondTypeAllowed) {
+ if (consume(",")) {
+ ret.idlType.push(type_with_extended_attributes());
+ }
+ else if (secondTypeRequired)
+ error(`Missing second type argument in ${ittype} declaration`);
+ }
+ if (!consume(">")) error(`Unterminated ${ittype} declaration`);
+ if (!consume(";")) error(`Missing semicolon after ${ittype} declaration`);
+ } else
+ error(`Error parsing ${ittype} declaration`);
+
+ return ret;
+ }
+
+ function interface_rest(isPartial, typeName = "interface") {
+ const name = consume(ID) || error("No name for interface");
+ const mems = [];
+ const ret = current = {
+ type: typeName,
+ name: isPartial ? name.value : sanitize_name(name.value, "interface"),
+ partial: isPartial,
+ members: mems
+ };
+ if (!isPartial) ret.inheritance = inheritance() || null;
+ consume("{") || error("Bodyless interface");
+ while (true) {
+ if (consume("}")) {
+ consume(";") || error("Missing semicolon after interface");
+ return ret;
+ }
+ const ea = extended_attrs();
+ const mem = const_() ||
+ static_member() ||
+ stringifier() ||
+ iterable() ||
+ attribute() ||
+ operation() ||
+ error("Unknown member");
+ mem.extAttrs = ea;
+ ret.members.push(mem);
+ }
+ }
+
+ function mixin_rest(isPartial) {
+ if (!consume("mixin")) return;
+ const name = consume(ID) || error("No name for interface mixin");
+ const mems = [];
+ const ret = current = {
+ type: "interface mixin",
+ name: isPartial ? name.value : sanitize_name(name.value, "interface mixin"),
+ partial: isPartial,
+ members: mems
+ };
+ consume("{") || error("Bodyless interface mixin");
+ while (true) {
+ if (consume("}")) {
+ consume(";") || error("Missing semicolon after interface mixin");
+ return ret;
+ }
+ const ea = extended_attrs();
+ const mem = const_() ||
+ stringifier() ||
+ attribute({ noInherit: true }) ||
+ operation({ regular: true }) ||
+ error("Unknown member");
+ mem.extAttrs = ea;
+ ret.members.push(mem);
+ }
+ }
+
+ function interface_(isPartial) {
+ if (!consume("interface")) return;
+ return mixin_rest(isPartial) ||
+ interface_rest(isPartial) ||
+ error("Interface has no proper body");
+ }
+
+ function namespace(isPartial) {
+ if (!consume("namespace")) return;
+ const name = consume(ID) || error("No name for namespace");
+ const mems = [];
+ const ret = current = {
+ type: "namespace",
+ name: isPartial ? name.value : sanitize_name(name.value, "namespace"),
+ partial: isPartial,
+ members: mems
+ };
+ consume("{") || error("Bodyless namespace");
+ while (true) {
+ if (consume("}")) {
+ consume(";") || error("Missing semicolon after namespace");
+ return ret;
+ }
+ const ea = extended_attrs();
+ const mem = attribute({ noInherit: true, readonly: true }) ||
+ operation({ regular: true }) ||
+ error("Unknown member");
+ mem.extAttrs = ea;
+ ret.members.push(mem);
+ }
+ }
+
+ function partial() {
+ if (!consume("partial")) return;
+ const thing = dictionary(true) ||
+ interface_(true) ||
+ namespace(true) ||
+ error("Partial doesn't apply to anything");
+ return thing;
+ }
+
+ function dictionary(isPartial) {
+ if (!consume("dictionary")) return;
+ const name = consume(ID) || error("No name for dictionary");
+ const mems = [];
+ const ret = current = {
+ type: "dictionary",
+ name: isPartial ? name.value : sanitize_name(name.value, "dictionary"),
+ partial: isPartial,
+ members: mems
+ };
+ if (!isPartial) ret.inheritance = inheritance() || null;
+ consume("{") || error("Bodyless dictionary");
+ while (true) {
+ if (consume("}")) {
+ consume(";") || error("Missing semicolon after dictionary");
+ return ret;
+ }
+ const ea = extended_attrs();
+ const required = consume("required");
+ const typ = type_with_extended_attributes("dictionary-type") || error("No type for dictionary member");
+ const name = consume(ID) || error("No name for dictionary member");
+ const dflt = default_() || null;
+ if (required && dflt) error("Required member must not have a default");
+ const member = {
+ type: "field",
+ name: unescape(name.value),
+ escapedName: name.value,
+ required: !!required,
+ idlType: typ,
+ extAttrs: ea,
+ default: dflt
+ };
+ ret.members.push(member);
+ consume(";") || error("Unterminated dictionary member");
+ }
+ }
+
+ function enum_() {
+ if (!consume("enum")) return;
+ const name = consume(ID) || error("No name for enum");
+ const vals = [];
+ const ret = current = {
+ type: "enum",
+ name: sanitize_name(name.value, "enum"),
+ values: vals
+ };
+ consume("{") || error("No curly for enum");
+ let value_expected = true;
+ while (true) {
+ if (consume("}")) {
+ if (!ret.values.length) error("No value in enum");
+ consume(";") || error("No semicolon after enum");
+ return ret;
+ }
+ else if (!value_expected) {
+ error("No comma between enum values");
+ }
+ const val = consume(STR) || error("Unexpected value in enum");
+ val.value = val.value.slice(1, -1);
+ // No trivia exposure yet
+ val.trivia = undefined;
+ ret.values.push(val);
+ value_expected = !!consume(",");
+ }
+ }
+
+ function typedef() {
+ if (!consume("typedef")) return;
+ const ret = {
+ type: "typedef"
+ };
+ ret.idlType = type_with_extended_attributes("typedef-type") || error("No type in typedef");
+ const name = consume(ID) || error("No name in typedef");
+ ret.name = sanitize_name(name.value, "typedef");
+ current = ret;
+ consume(";") || error("Unterminated typedef");
+ return ret;
+ }
+
+ function implements_() {
+ const start_position = consume_position;
+ const target = consume(ID);
+ if (!target) return;
+ if (consume("implements")) {
+ const ret = {
+ type: "implements",
+ target: target.value
+ };
+ const imp = consume(ID) || error("Incomplete implements statement");
+ ret.implements = imp.value;
+ consume(";") || error("No terminating ; for implements statement");
+ return ret;
+ } else {
+ // rollback
+ unconsume(start_position);
+ }
+ }
+
+ function includes() {
+ const start_position = consume_position;
+ const target = consume(ID);
+ if (!target) return;
+ if (consume("includes")) {
+ const ret = {
+ type: "includes",
+ target: target.value
+ };
+ const imp = consume(ID) || error("Incomplete includes statement");
+ ret.includes = imp.value;
+ consume(";") || error("No terminating ; for includes statement");
+ return ret;
+ } else {
+ // rollback
+ unconsume(start_position);
+ }
+ }
+
+ function definition() {
+ return callback() ||
+ interface_(false) ||
+ partial() ||
+ dictionary(false) ||
+ enum_() ||
+ typedef() ||
+ implements_() ||
+ includes() ||
+ namespace(false);
+ }
+
+ function definitions() {
+ if (!tokens.length) return [];
+ const defs = [];
+ while (true) {
+ const ea = extended_attrs();
+ const def = definition();
+ if (!def) {
+ if (ea.length) error("Stray extended attributes");
+ break;
+ }
+ def.extAttrs = ea;
+ defs.push(def);
+ }
+ return defs;
+ }
+ const res = definitions();
+ if (consume_position < tokens.length) error("Unrecognised tokens");
+ return res;
+ }
+
+ const obj = {
+ parse(str) {
+ const tokens = tokenise(str);
+ return parse(tokens);
+ }
+ };
+
+ if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
+ module.exports = obj;
+ } else if (typeof define === 'function' && define.amd) {
+ define([], () => obj);
+ } else {
+ (self || window).WebIDL2 = obj;
+ }
+})();
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/lib/webidl2.js.headers b/test/fixtures/web-platform-tests/resources/webidl2/lib/webidl2.js.headers
new file mode 100644
index 00000000000000..6805c323df5a97
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/lib/webidl2.js.headers
@@ -0,0 +1 @@
+Content-Type: text/javascript; charset=utf-8
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/lib/writer.js b/test/fixtures/web-platform-tests/resources/webidl2/lib/writer.js
new file mode 100644
index 00000000000000..b3097a6f8a74e4
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/lib/writer.js
@@ -0,0 +1,221 @@
+"use strict";
+
+(() => {
+ function write(ast, opt = {}) {
+ const noop = str => str;
+ const optNames = "type".split(" ");
+ const context = [];
+ for (const o of optNames) {
+ if (!opt[o]) opt[o] = noop;
+ }
+
+ function literal(it) {
+ return it.value;
+ };
+ function type(it) {
+ if (typeof it === "string") return opt.type(it); // XXX should maintain some context
+ let ret = extended_attributes(it.extAttrs);
+ if (it.union) ret += `(${it.idlType.map(type).join(" or ")})`;
+ else {
+ if (it.generic) ret += `${it.generic}<`;
+ if (Array.isArray(it.idlType)) ret += it.idlType.map(type).join(", ");
+ else ret += type(it.idlType);
+ if (it.generic) ret += ">";
+ }
+ if (it.nullable) ret += "?";
+
+ return ret;
+ };
+ function const_value(it) {
+ const tp = it.type;
+ if (tp === "boolean") return it.value ? "true" : "false";
+ else if (tp === "null") return "null";
+ else if (tp === "Infinity") return (it.negative ? "-" : "") + "Infinity";
+ else if (tp === "NaN") return "NaN";
+ else if (tp === "number") return it.value;
+ else if (tp === "sequence") return "[]";
+ else return `"${it.value}"`;
+ };
+ function argument(arg) {
+ let ret = extended_attributes(arg.extAttrs);
+ if (arg.optional) ret += "optional ";
+ ret += type(arg.idlType);
+ if (arg.variadic) ret += "...";
+ ret += ` ${arg.escapedName}`;
+ if (arg.default) ret += ` = ${const_value(arg.default)}`;
+ return ret;
+ };
+ function make_ext_at(it) {
+ context.unshift(it);
+ let ret = it.name;
+ if (it.rhs) {
+ if (it.rhs.type === "identifier-list") ret += `=(${it.rhs.value.join(",")})`;
+ else ret += `=${it.rhs.value}`;
+ }
+ if (it.arguments) ret += `(${it.arguments.length ? it.arguments.map(argument).join(",") : ""})`;
+ context.shift(); // XXX need to add more contexts, but not more than needed for ReSpec
+ return ret;
+ };
+ function extended_attributes(eats) {
+ if (!eats || !eats.length) return "";
+ return `[${eats.map(make_ext_at).join(", ")}]`;
+ };
+
+ const modifiers = "getter setter deleter stringifier static".split(" ");
+ function operation(it) {
+ let ret = extended_attributes(it.extAttrs);
+ if (it.stringifier && !it.idlType) return "stringifier;";
+ for (const mod of modifiers) {
+ if (it[mod]) ret += mod + " ";
+ }
+ ret += type(it.idlType) + " ";
+ if (it.name) ret += it.escapedName;
+ ret += `(${it.arguments.map(argument).join(",")});`;
+ return ret;
+ };
+
+ function attribute(it) {
+ let ret = extended_attributes(it.extAttrs);
+ if (it.static) ret += "static ";
+ if (it.stringifier) ret += "stringifier ";
+ if (it.inherit) ret += "inherit ";
+ if (it.readonly) ret += "readonly ";
+ ret += `attribute ${type(it.idlType)} ${it.escapedName};`;
+ return ret;
+ };
+
+ function interface_(it) {
+ let ret = extended_attributes(it.extAttrs);
+ if (it.partial) ret += "partial ";
+ ret += `interface ${it.name} `;
+ if (it.inheritance) ret += `: ${it.inheritance} `;
+ ret += `{${iterate(it.members)}};`;
+ return ret;
+ };
+
+ function interface_mixin(it) {
+ let ret = extended_attributes(it.extAttrs);
+ if (it.partial) ret += "partial ";
+ ret += `interface mixin ${it.name} `;
+ ret += `{${iterate(it.members)}};`;
+ return ret;
+ }
+
+ function namespace(it) {
+ let ret = extended_attributes(it.extAttrs);
+ if (it.partial) ret += "partial ";
+ ret += `namespace ${it.name} `;
+ ret += `{${iterate(it.members)}};`;
+ return ret;
+ }
+
+ function dictionary(it) {
+ let ret = extended_attributes(it.extAttrs);
+ if (it.partial) ret += "partial ";
+ ret += `dictionary ${it.name} `;
+ if (it.inheritance) ret += `: ${it.inheritance} `;
+ ret += `{${iterate(it.members)}};`;
+ return ret;
+ };
+ function field(it) {
+ let ret = extended_attributes(it.extAttrs);
+ if (it.required) ret += "required ";
+ ret += `${type(it.idlType)} ${it.escapedName}`;
+ if (it.default) ret += ` = ${const_value(it.default)}`;
+ ret += ";";
+ return ret;
+ };
+ function const_(it) {
+ const ret = extended_attributes(it.extAttrs);
+ return `${ret}const ${type(it.idlType)}${it.nullable ? "?" : ""} ${it.name} = ${const_value(it.value)};`;
+ };
+ function typedef(it) {
+ let ret = extended_attributes(it.extAttrs);
+ ret += `typedef ${extended_attributes(it.typeExtAttrs)}`;
+ return `${ret}${type(it.idlType)} ${it.name};`;
+ };
+ function implements_(it) {
+ const ret = extended_attributes(it.extAttrs);
+ return `${ret}${it.target} implements ${it.implements};`;
+ };
+ function includes(it) {
+ const ret = extended_attributes(it.extAttrs);
+ return `${ret}${it.target} includes ${it.includes};`;
+ };
+ function callback(it) {
+ const ret = extended_attributes(it.extAttrs);
+ return `${ret}callback ${it.name} = ${type(it.idlType)}(${it.arguments.map(argument).join(",")});`;
+ };
+ function enum_(it) {
+ let ret = extended_attributes(it.extAttrs);
+ ret += `enum ${it.name} {`;
+ for (const v of it.values) {
+ ret += `"${v.value}",`;
+ }
+ return ret + "};";
+ };
+ function iterable(it) {
+ return `iterable<${Array.isArray(it.idlType) ? it.idlType.map(type).join(", ") : type(it.idlType)}>;`;
+ };
+ function legacyiterable(it) {
+ return `legacyiterable<${Array.isArray(it.idlType) ? it.idlType.map(type).join(", ") : type(it.idlType)}>;`;
+ };
+ function maplike(it) {
+ return `${it.readonly ? "readonly " : ""}maplike<${it.idlType.map(type).join(", ")}>;`;
+ };
+ function setlike(it) {
+ return `${it.readonly ? "readonly " : ""}setlike<${type(it.idlType[0])}>;`;
+ };
+ function callbackInterface(it) {
+ return `callback ${interface_(it)}`;
+ };
+
+ const table = {
+ interface: interface_,
+ "interface mixin": interface_mixin,
+ namespace,
+ operation,
+ attribute,
+ dictionary,
+ field,
+ const: const_,
+ typedef,
+ implements: implements_,
+ includes,
+ callback,
+ enum: enum_,
+ iterable,
+ legacyiterable,
+ maplike,
+ setlike,
+ "callback interface": callbackInterface
+ };
+ function dispatch(it) {
+ const dispatcher = table[it.type];
+ if (!dispatcher) {
+ throw new Error(`Type "${it.type}" is unsupported`)
+ }
+ return table[it.type](it);
+ };
+ function iterate(things) {
+ if (!things) return;
+ let ret = "";
+ for (const thing of things) ret += dispatch(thing);
+ return ret;
+ };
+ return iterate(ast);
+ };
+
+
+ const obj = {
+ write
+ };
+
+ if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
+ module.exports = obj;
+ } else if (typeof define === 'function' && define.amd) {
+ define([], () => obj);
+ } else {
+ (self || window).WebIDL2Writer = obj;
+ }
+})();
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/package-lock.json b/test/fixtures/web-platform-tests/resources/webidl2/package-lock.json
new file mode 100644
index 00000000000000..b0581037fe9434
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/package-lock.json
@@ -0,0 +1,700 @@
+{
+ "name": "webidl2",
+ "version": "13.0.3",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.0.0-beta.40",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.40.tgz",
+ "integrity": "sha512-eVXQSbu/RimU6OKcK2/gDJVTFcxXJI4sHbIqw2mhwMZeQ2as/8AhS9DGkEDoHMBBNJZ5B0US63lF56x+KDcxiA==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "7.0.0-beta.40"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.0.0-beta.40",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.40.tgz",
+ "integrity": "sha512-mOhhTrzieV6VO7odgzFGFapiwRK0ei8RZRhfzHhb6cpX3QM8XXuCLXWjN8qBB7JReDdUR80V3LFfFrGUYevhNg==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.0",
+ "esutils": "^2.0.2",
+ "js-tokens": "^3.0.0"
+ }
+ },
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "arr-diff": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
+ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.0.1"
+ }
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
+ "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
+ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
+ "dev": true,
+ "requires": {
+ "expand-range": "^1.8.1",
+ "preserve": "^0.2.0",
+ "repeat-element": "^1.1.2"
+ }
+ },
+ "browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
+ "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
+ "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "^1.1.1"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "commander": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
+ "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "diff": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+ "dev": true
+ },
+ "diff-match-patch": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.0.tgz",
+ "integrity": "sha1-HMPIOkkNZ/ldkeOfatHy4Ia2MEg=",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+ "dev": true
+ },
+ "expand-brackets": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
+ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
+ "dev": true,
+ "requires": {
+ "is-posix-bracket": "^0.1.0"
+ }
+ },
+ "expand-range": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
+ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
+ "dev": true,
+ "requires": {
+ "fill-range": "^2.1.0"
+ }
+ },
+ "expect": {
+ "version": "22.4.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-22.4.0.tgz",
+ "integrity": "sha512-Fiy862jT3qc70hwIHwwCBNISmaqBrfWKKrtqyMJ6iwZr+6KXtcnHojZFtd63TPRvRl8EQTJ+YXYy2lK6/6u+Hw==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.0",
+ "jest-diff": "^22.4.0",
+ "jest-get-type": "^22.1.0",
+ "jest-matcher-utils": "^22.4.0",
+ "jest-message-util": "^22.4.0",
+ "jest-regex-util": "^22.1.0"
+ }
+ },
+ "extglob": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
+ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^1.0.0"
+ }
+ },
+ "filename-regex": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
+ "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=",
+ "dev": true
+ },
+ "fill-range": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz",
+ "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==",
+ "dev": true,
+ "requires": {
+ "is-number": "^2.1.0",
+ "isobject": "^2.0.0",
+ "randomatic": "^3.0.0",
+ "repeat-element": "^1.1.2",
+ "repeat-string": "^1.5.2"
+ }
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+ "dev": true
+ },
+ "for-own": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
+ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
+ "dev": true,
+ "requires": {
+ "for-in": "^1.0.1"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-base": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
+ "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
+ "dev": true,
+ "requires": {
+ "glob-parent": "^2.0.0",
+ "is-glob": "^2.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
+ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
+ "dev": true,
+ "requires": {
+ "is-glob": "^2.0.0"
+ }
+ },
+ "growl": {
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz",
+ "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "he": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
+ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "dev": true
+ },
+ "is-dotfile": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
+ "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
+ "dev": true
+ },
+ "is-equal-shallow": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
+ "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
+ "dev": true,
+ "requires": {
+ "is-primitive": "^2.0.0"
+ }
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+ "dev": true
+ },
+ "is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^1.0.0"
+ }
+ },
+ "is-number": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
+ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "is-posix-bracket": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
+ "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=",
+ "dev": true
+ },
+ "is-primitive": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
+ "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "dev": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ },
+ "jest-diff": {
+ "version": "22.4.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-22.4.0.tgz",
+ "integrity": "sha512-+/t20WmnkOkB8MOaGaPziI8zWKxquMvYw4Ub+wOzi7AUhmpFXz43buWSxVoZo4J5RnCozpGbX3/FssjJ5KV9Nw==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.1",
+ "diff": "^3.2.0",
+ "jest-get-type": "^22.1.0",
+ "pretty-format": "^22.4.0"
+ }
+ },
+ "jest-get-type": {
+ "version": "22.1.0",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.1.0.tgz",
+ "integrity": "sha512-nD97IVOlNP6fjIN5i7j5XRH+hFsHL7VlauBbzRvueaaUe70uohrkz7pL/N8lx/IAwZRTJ//wOdVgh85OgM7g3w==",
+ "dev": true
+ },
+ "jest-matcher-utils": {
+ "version": "22.4.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-22.4.0.tgz",
+ "integrity": "sha512-03m3issxUXpWMwDYTfmL8hRNewUB0yCRTeXPm+eq058rZxLHD9f5NtSSO98CWHqe4UyISIxd9Ao9iDVjHWd2qg==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.1",
+ "jest-get-type": "^22.1.0",
+ "pretty-format": "^22.4.0"
+ }
+ },
+ "jest-message-util": {
+ "version": "22.4.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.0.tgz",
+ "integrity": "sha512-eyCJB0T3hrlpFF2FqQoIB093OulP+1qvATQmD3IOgJgMGqPL6eYw8TbC5P/VCWPqKhGL51xvjIIhow5eZ2wHFw==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.0.0-beta.35",
+ "chalk": "^2.0.1",
+ "micromatch": "^2.3.11",
+ "slash": "^1.0.0",
+ "stack-utils": "^1.0.1"
+ }
+ },
+ "jest-regex-util": {
+ "version": "22.1.0",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-22.1.0.tgz",
+ "integrity": "sha512-on0LqVS6Xeh69sw3d1RukVnur+lVOl3zkmb0Q54FHj9wHoq6dbtWqb3TSlnVUyx36hqjJhjgs/QLqs07Bzu72Q==",
+ "dev": true
+ },
+ "js-tokens": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+ "dev": true
+ },
+ "jsondiffpatch": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.3.5.tgz",
+ "integrity": "sha512-v7eaGLDMCHXH+fsIaZhptEUJmS8EJpunq7IM4cc4vIT/kSRAkaZ6ZF4ebiNcyUelL0znbvj6o2B5Gh9v7Og0BQ==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.3.0",
+ "diff-match-patch": "^1.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ },
+ "math-random": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz",
+ "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "2.3.11",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
+ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^2.0.0",
+ "array-unique": "^0.2.1",
+ "braces": "^1.8.2",
+ "expand-brackets": "^0.1.4",
+ "extglob": "^0.3.1",
+ "filename-regex": "^2.0.0",
+ "is-extglob": "^1.0.0",
+ "is-glob": "^2.0.1",
+ "kind-of": "^3.0.2",
+ "normalize-path": "^2.0.1",
+ "object.omit": "^2.0.0",
+ "parse-glob": "^3.0.4",
+ "regex-cache": "^0.4.2"
+ }
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "mocha": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.4.tgz",
+ "integrity": "sha512-nMOpAPFosU1B4Ix1jdhx5e3q7XO55ic5a8cgYvW27CequcEY+BabS0kUVL1Cw1V5PuVHZWeNRWFLmEPexo79VA==",
+ "dev": true,
+ "requires": {
+ "browser-stdout": "1.3.1",
+ "commander": "2.11.0",
+ "debug": "3.1.0",
+ "diff": "3.5.0",
+ "escape-string-regexp": "1.0.5",
+ "glob": "7.1.2",
+ "growl": "1.10.3",
+ "he": "1.1.1",
+ "mkdirp": "0.5.1",
+ "supports-color": "4.4.0"
+ },
+ "dependencies": {
+ "has-flag": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
+ "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz",
+ "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^2.0.0"
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+ "dev": true,
+ "requires": {
+ "remove-trailing-separator": "^1.0.1"
+ }
+ },
+ "object.omit": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
+ "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
+ "dev": true,
+ "requires": {
+ "for-own": "^0.1.4",
+ "is-extendable": "^0.1.1"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "parse-glob": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
+ "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
+ "dev": true,
+ "requires": {
+ "glob-base": "^0.3.0",
+ "is-dotfile": "^1.0.0",
+ "is-extglob": "^1.0.0",
+ "is-glob": "^2.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "preserve": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
+ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
+ "dev": true
+ },
+ "pretty-format": {
+ "version": "22.4.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-22.4.0.tgz",
+ "integrity": "sha512-pvCxP2iODIIk9adXlo4S3GRj0BrJiil68kByAa1PrgG97c1tClh9dLMgp3Z6cHFZrclaABt0UH8PIhwHuFLqYA==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0",
+ "ansi-styles": "^3.2.0"
+ }
+ },
+ "randomatic": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz",
+ "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==",
+ "dev": true,
+ "requires": {
+ "is-number": "^4.0.0",
+ "kind-of": "^6.0.0",
+ "math-random": "^1.0.1"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
+ "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ }
+ }
+ },
+ "regex-cache": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
+ "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==",
+ "dev": true,
+ "requires": {
+ "is-equal-shallow": "^0.1.3"
+ }
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+ "dev": true
+ },
+ "repeat-element": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz",
+ "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=",
+ "dev": true
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+ "dev": true
+ },
+ "slash": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
+ "dev": true
+ },
+ "stack-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.1.tgz",
+ "integrity": "sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
+ "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ }
+ }
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/package.json b/test/fixtures/web-platform-tests/resources/webidl2/package.json
new file mode 100644
index 00000000000000..92faccafa813fb
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "webidl2",
+ "description": "A WebIDL Parser",
+ "version": "13.0.3",
+ "contributors": [
+ "Robin Berjon (https://berjon.com)",
+ "Marcos Caceres (https://marcosc.com)",
+ "Kagami Sascha Rosylight ",
+ "Timothy Gu "
+ ],
+ "license": "W3C",
+ "dependencies": {},
+ "devDependencies": {
+ "expect": "22.4.0",
+ "jsondiffpatch": "0.3.5",
+ "mocha": "5.0.4"
+ },
+ "scripts": {
+ "test": "mocha",
+ "acquire": "node test/util/acquire.js"
+ },
+ "repository": "git://github.com/w3c/webidl2.js",
+ "main": "index.js",
+ "files": [
+ "lib/*"
+ ]
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid.js b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid.js
new file mode 100644
index 00000000000000..19bbf006e579ef
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid.js
@@ -0,0 +1,20 @@
+// NOTES:
+// - the errors actually still need to be reviewed to check that they
+// are fully correct interpretations of the IDLs
+
+"use strict";
+
+const { collect } = require("./util/collect");
+const fs = require("fs");
+const expect = require("expect");
+
+describe("Parses all of the invalid IDLs to check that they blow up correctly", () => {
+ for (const test of collect("invalid", { expectError: true })) {
+ it(`should produce the right error for ${test.path}`, () => {
+ const err = test.readJSON();
+ expect(test.error).toBeTruthy();
+ expect(test.error.message).toEqual(err.message);
+ expect(test.error.line).toEqual(err.line);
+ });
+ }
+});
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/array.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/array.widl
new file mode 100644
index 00000000000000..58a8618ab64857
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/array.widl
@@ -0,0 +1,6 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+// T[] is removed by https://github.com/heycam/webidl/commit/079cbb861a99e9e857a3f2a169c0beeb49cd020a
+[Constructor]
+interface LotteryResults {
+ readonly attribute unsigned short[][] numbers;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/caller.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/caller.widl
new file mode 100644
index 00000000000000..26fedc33f9402c
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/caller.widl
@@ -0,0 +1,7 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+// legacycallers are removed by https://github.com/heycam/webidl/pull/412
+
+interface NumberQuadrupler {
+ // This operation simply returns four times the given number x.
+ legacycaller float compute(float x);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/dict-required-default.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/dict-required-default.widl
new file mode 100644
index 00000000000000..412448d2261f88
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/dict-required-default.widl
@@ -0,0 +1,5 @@
+// https://heycam.github.io/webidl/#required-dictionary-member
+// "A required dictionary member must not have a default value."
+dictionary Dict {
+ required long member = 0;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/duplicate.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/duplicate.widl
new file mode 100644
index 00000000000000..4916af34273d30
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/duplicate.widl
@@ -0,0 +1,5 @@
+typedef int Test;
+
+interface Test {
+ void foo();
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/enum-empty.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/enum-empty.widl
new file mode 100644
index 00000000000000..7f189eb62c1bd2
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/enum-empty.widl
@@ -0,0 +1 @@
+enum Empty {};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/enum-wo-comma.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/enum-wo-comma.widl
new file mode 100644
index 00000000000000..ebc53065ededed
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/enum-wo-comma.widl
@@ -0,0 +1 @@
+enum NoComma { "value1" "value2" };
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/enum.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/enum.widl
new file mode 100644
index 00000000000000..c355c3251c0084
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/enum.widl
@@ -0,0 +1 @@
+enum foo { 1, 2, 3};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/exception.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/exception.widl
new file mode 100644
index 00000000000000..a0ea2e47e20833
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/exception.widl
@@ -0,0 +1,6 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+// IDL exceptions are removed by https://github.com/heycam/webidl/commit/50e172ec079db073c3724c9beac1b576fb5dbc47
+
+exception SomeException {
+};
+
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/extattr-empty-ids.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/extattr-empty-ids.widl
new file mode 100644
index 00000000000000..93c48c3ade5fa4
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/extattr-empty-ids.widl
@@ -0,0 +1,2 @@
+[Exposed=()]
+interface Unexposed {};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/id-underscored-number.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/id-underscored-number.widl
new file mode 100644
index 00000000000000..d00121fd54f2f3
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/id-underscored-number.widl
@@ -0,0 +1 @@
+interface _0 {};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/implements_and_includes_ws.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/implements_and_includes_ws.widl
new file mode 100644
index 00000000000000..6666daed00c3e7
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/implements_and_includes_ws.widl
@@ -0,0 +1,4 @@
+// This hits the unshifting of whitespace in the "implements" and
+// "includes" productions. If there is a bug in that whitespace
+// rollback, the wrong exception will be produced.
+foobar;
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/iterator.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/iterator.widl
new file mode 100644
index 00000000000000..3bf1b36dec6756
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/iterator.widl
@@ -0,0 +1,35 @@
+interface SessionManager {
+ Session getSessionForUser(DOMString username);
+ readonly attribute unsigned long sessionCount;
+
+ Session iterator;
+};
+
+interface Session {
+ readonly attribute DOMString username;
+ // ...
+};
+
+interface SessionManager2 {
+ Session2 getSessionForUser(DOMString username);
+ readonly attribute unsigned long sessionCount;
+
+ Session2 iterator = SessionIterator;
+};
+
+interface Session2 {
+ readonly attribute DOMString username;
+ // ...
+};
+
+interface SessionIterator {
+ readonly attribute unsigned long remainingSessions;
+};
+
+ interface NodeList {
+ Node iterator = NodeIterator;
+ };
+
+ interface NodeIterator {
+ Node iterator object;
+ };
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/maplike-1type.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/maplike-1type.widl
new file mode 100644
index 00000000000000..efb5c14ffd626f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/maplike-1type.widl
@@ -0,0 +1,3 @@
+interface MapLikeOneType {
+ maplike;
+}
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/module.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/module.widl
new file mode 100644
index 00000000000000..a4c79fdf155ea5
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/module.widl
@@ -0,0 +1,25 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+module gfx {
+
+ module geom {
+ interface Shape { /* ... */ };
+ interface Rectangle : Shape { /* ... */ };
+ interface Path : Shape { /* ... */ };
+ };
+
+ interface GraphicsContext {
+ void fillShape(geom::Shape s);
+ void strokeShape(geom::Shape s);
+ };
+};
+
+module gui {
+
+ interface Widget { /* ... */ };
+
+ interface Window : Widget {
+ gfx::GraphicsContext getGraphicsContext();
+ };
+
+ interface Button : Widget { /* ... */ };
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/namespace-readwrite.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/namespace-readwrite.widl
new file mode 100644
index 00000000000000..e184133228458e
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/namespace-readwrite.widl
@@ -0,0 +1,3 @@
+namespace CSS {
+ attribute object readwrite;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/no-semicolon-callback.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/no-semicolon-callback.widl
new file mode 100644
index 00000000000000..cb2055718e5f04
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/no-semicolon-callback.widl
@@ -0,0 +1,7 @@
+callback interface NoSemicolon {
+ attribute boolean noSemiColon;
+}
+
+enum YouNeedOne {
+ "really"
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/no-semicolon.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/no-semicolon.widl
new file mode 100644
index 00000000000000..10bc716249b3e1
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/no-semicolon.widl
@@ -0,0 +1,7 @@
+partial interface NoSemicolon {
+ attribute boolean noSemiColon;
+}
+
+enum YouNeedOne {
+ "really"
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/nonnullableany.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/nonnullableany.widl
new file mode 100644
index 00000000000000..389576555236d9
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/nonnullableany.widl
@@ -0,0 +1,3 @@
+interface NonNullable {
+ attribute any? foo;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/nonnullableobjects.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/nonnullableobjects.widl
new file mode 100644
index 00000000000000..6c875ff04b5231
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/nonnullableobjects.widl
@@ -0,0 +1,6 @@
+interface Foo {};
+
+interface NonNullable {
+ attribute Foo??
+ foo;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/promise-nullable.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/promise-nullable.widl
new file mode 100644
index 00000000000000..894d7c044b2a5b
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/promise-nullable.widl
@@ -0,0 +1,4 @@
+interface X {
+ attribute Promise?
+ promise;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/promise-with-extended-attribute.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/promise-with-extended-attribute.widl
new file mode 100644
index 00000000000000..0ce171fec33e72
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/promise-with-extended-attribute.widl
@@ -0,0 +1,3 @@
+interface Foo {
+ Promise<[XAttr] DOMString> foo(any param);
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/raises.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/raises.widl
new file mode 100644
index 00000000000000..ff65522f2b3355
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/raises.widl
@@ -0,0 +1,18 @@
+// getraises and setraises are not longer valid Web IDL
+interface Person {
+
+ // An attribute that can raise an exception if it is set to an invalid value.
+ attribute DOMString name setraises (InvalidName);
+
+ // An attribute whose value cannot be assigned to, and which can raise an
+ // exception some circumstances.
+ readonly attribute DOMString petName getraises (NoSuchPet);
+};
+
+exception SomeException {
+};
+
+interface ExceptionThrower {
+ // This attribute always throws a SomeException and never returns a value.
+ attribute long valueOf getraises(SomeException);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/readonly-iterable.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/readonly-iterable.widl
new file mode 100644
index 00000000000000..6057aa1feba64e
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/readonly-iterable.widl
@@ -0,0 +1,3 @@
+interface ReadonlyIterable {
+ readonly iterable;
+}
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/record-key-with-extended-attribute.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/record-key-with-extended-attribute.widl
new file mode 100644
index 00000000000000..c11eb7414b0b8f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/record-key-with-extended-attribute.widl
@@ -0,0 +1,3 @@
+interface Foo {
+ void foo(record<[XAttr] DOMString, any> param);
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/record-key.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/record-key.widl
new file mode 100644
index 00000000000000..39dc386182f809
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/record-key.widl
@@ -0,0 +1,3 @@
+interface Foo {
+ void foo(record param);
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/record-single.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/record-single.widl
new file mode 100644
index 00000000000000..84db40282433b8
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/record-single.widl
@@ -0,0 +1,3 @@
+interface Foo {
+ void foo(record param);
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/scopedname.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/scopedname.widl
new file mode 100644
index 00000000000000..cfcb1ccc9395cd
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/scopedname.widl
@@ -0,0 +1,2 @@
+// scoped names are no longer valid in WebIDL
+ typedef gfx::geom::geom2d::Point Point;
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/sequenceAsAttribute.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/sequenceAsAttribute.widl
new file mode 100644
index 00000000000000..c23da82ac22198
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/sequenceAsAttribute.widl
@@ -0,0 +1,3 @@
+interface sequenceAsAttribute {
+ attribute sequence invalid;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/setlike-2types.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/setlike-2types.widl
new file mode 100644
index 00000000000000..c2681bc75f1e8b
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/setlike-2types.widl
@@ -0,0 +1,3 @@
+interface SetLikeTwoTypes {
+ setlike;
+}
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/setter-creator.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/setter-creator.widl
new file mode 100644
index 00000000000000..a70b26774d722f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/setter-creator.widl
@@ -0,0 +1,4 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface OrderedMap {
+ setter creator void set(DOMString name, any value);
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/spaced-negative-infinity.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/spaced-negative-infinity.widl
new file mode 100644
index 00000000000000..3d71222e54b592
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/spaced-negative-infinity.widl
@@ -0,0 +1,3 @@
+interface X {
+ const float infinity = - Infinity;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/spaced-variadic.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/spaced-variadic.widl
new file mode 100644
index 00000000000000..6d77e186d6670f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/spaced-variadic.widl
@@ -0,0 +1,3 @@
+interface X {
+ void operation(object . . . args);
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/special-omittable.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/special-omittable.widl
new file mode 100644
index 00000000000000..dd0c1b18589979
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/special-omittable.widl
@@ -0,0 +1,8 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+// omittable is no longer a recognized keyword as of 20110905
+interface Dictionary {
+ readonly attribute unsigned long propertyCount;
+
+ omittable getter float getProperty(DOMString propertyName);
+ omittable setter void setProperty(DOMString propertyName, float propertyValue);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/stray-slash.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/stray-slash.widl
new file mode 100644
index 00000000000000..b673aa94b01d65
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/stray-slash.widl
@@ -0,0 +1,2 @@
+// This is a comment.
+/ This is not.
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/stringconstants.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/stringconstants.widl
new file mode 100644
index 00000000000000..44fd3ff136ee56
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/stringconstants.widl
@@ -0,0 +1,3 @@
+interface Util {
+ const DOMString hello = "world";
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/typedef-nested.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/typedef-nested.widl
new file mode 100644
index 00000000000000..dfd377bf932b98
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/typedef-nested.widl
@@ -0,0 +1,22 @@
+
+ interface Point {
+ attribute float x;
+ attribute float y;
+ };
+
+
+ interface Rect {
+ attribute Point topleft;
+ attribute Point bottomright;
+ };
+
+ interface Widget {
+ typedef sequence PointSequence;
+
+ readonly attribute Rect bounds;
+
+ boolean pointWithinBounds(Point p);
+ boolean allPointsWithinBounds(PointSequence ps);
+ };
+
+ typedef [Clamp] octet value;
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/union-dangling-or.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/union-dangling-or.widl
new file mode 100644
index 00000000000000..0aa043e9aca958
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/union-dangling-or.widl
@@ -0,0 +1 @@
+typedef (One or Two or) UnionOr;
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/union-one.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/union-one.widl
new file mode 100644
index 00000000000000..86ee96f516d635
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/union-one.widl
@@ -0,0 +1 @@
+typedef (OnlyOne) UnionOne;
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/union-zero.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/union-zero.widl
new file mode 100644
index 00000000000000..177fc4c708f901
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/union-zero.widl
@@ -0,0 +1 @@
+typedef () UnionZero;
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/unknown-generic.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/unknown-generic.widl
new file mode 100644
index 00000000000000..ee4a2db74a63bd
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/unknown-generic.widl
@@ -0,0 +1,3 @@
+interface FetchEvent : Event {
+ ResponsePromise default();
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/array.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/array.json
new file mode 100644
index 00000000000000..898b2d836bff81
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/array.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface LotteryResults`: No name in attribute",
+ "line": 5
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/caller.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/caller.json
new file mode 100644
index 00000000000000..567fa3368129f9
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/caller.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface NumberQuadrupler`: Invalid operation",
+ "line": 6
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/dict-required-default.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/dict-required-default.json
new file mode 100644
index 00000000000000..82b6b2ae42a6f6
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/dict-required-default.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `dictionary Dict`: Required member must not have a default"
+, "line": 4
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/duplicate.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/duplicate.json
new file mode 100644
index 00000000000000..e88a7156fe8c1c
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/duplicate.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `typedef Test`: The name \"Test\" of type \"typedef\" is already seen",
+ "line": 3
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/enum-empty.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/enum-empty.json
new file mode 100644
index 00000000000000..734bc67de1fd67
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/enum-empty.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `enum Empty`: No value in enum",
+ "line": 1
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/enum-wo-comma.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/enum-wo-comma.json
new file mode 100644
index 00000000000000..bfd0b0951fce8d
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/enum-wo-comma.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `enum NoComma`: No comma between enum values",
+ "line": 1
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/enum.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/enum.json
new file mode 100644
index 00000000000000..073ff6c290cafd
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/enum.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `enum foo`: Unexpected value in enum"
+, "line": 1
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/exception.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/exception.json
new file mode 100644
index 00000000000000..ad9fac6ca2f64e
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/exception.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error before parsing any named definition: Unrecognised tokens",
+ "line": 4
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/extattr-empty-ids.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/extattr-empty-ids.json
new file mode 100644
index 00000000000000..4337f1e180c288
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/extattr-empty-ids.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error before parsing any named definition: Expected identifiers but not found",
+ "line": 1
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/id-underscored-number.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/id-underscored-number.json
new file mode 100644
index 00000000000000..419ed946fca05c
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/id-underscored-number.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error before parsing any named definition: No name for interface",
+ "line": 1
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/implements_and_includes_ws.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/implements_and_includes_ws.json
new file mode 100644
index 00000000000000..ad9fac6ca2f64e
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/implements_and_includes_ws.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error before parsing any named definition: Unrecognised tokens",
+ "line": 4
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/iterator.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/iterator.json
new file mode 100644
index 00000000000000..e46d653ae3c512
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/iterator.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface SessionManager`: Invalid operation",
+ "line": 5
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/maplike-1type.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/maplike-1type.json
new file mode 100644
index 00000000000000..75e7a35ee256e9
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/maplike-1type.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface MapLikeOneType`: Missing second type argument in maplike declaration",
+ "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/module.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/module.json
new file mode 100644
index 00000000000000..9c071cdd07a7a3
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/module.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error before parsing any named definition: Unrecognised tokens"
+, "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/namespace-readwrite.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/namespace-readwrite.json
new file mode 100644
index 00000000000000..d21215111f1ee2
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/namespace-readwrite.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `namespace CSS`: Attributes must be readonly in this context",
+ "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/no-semicolon-callback.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/no-semicolon-callback.json
new file mode 100644
index 00000000000000..1db9d14c8e2c51
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/no-semicolon-callback.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `callback interface NoSemicolon`: Missing semicolon after interface",
+ "line": 5
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/no-semicolon.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/no-semicolon.json
new file mode 100644
index 00000000000000..087532a012f592
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/no-semicolon.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `partial interface NoSemicolon`: Missing semicolon after interface",
+ "line": 5
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/nonnullableany.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/nonnullableany.json
new file mode 100644
index 00000000000000..8a1f90046ae4d9
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/nonnullableany.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface NonNullable`: Type any cannot be made nullable"
+, "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/nonnullableobjects.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/nonnullableobjects.json
new file mode 100644
index 00000000000000..d470ec94a606c6
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/nonnullableobjects.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface NonNullable`: Can't nullable more than once",
+ "line": 4
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/promise-nullable.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/promise-nullable.json
new file mode 100644
index 00000000000000..ced51faf1be4df
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/promise-nullable.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface X`: Promise type cannot be nullable",
+ "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/promise-with-extended-attribute.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/promise-with-extended-attribute.json
new file mode 100644
index 00000000000000..71212d46e3c011
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/promise-with-extended-attribute.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface Foo`: Promise type cannot have extended attribute",
+ "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/raises.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/raises.json
new file mode 100644
index 00000000000000..3165b874f0c189
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/raises.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface Person`: Unterminated attribute"
+, "line": 5
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/readonly-iterable.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/readonly-iterable.json
new file mode 100644
index 00000000000000..a571b22271b80e
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/readonly-iterable.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface ReadonlyIterable`: Missing return type",
+ "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/record-key-with-extended-attribute.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/record-key-with-extended-attribute.json
new file mode 100644
index 00000000000000..4002e7fe0155d5
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/record-key-with-extended-attribute.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface Foo`: Record key cannot have extended attribute",
+ "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/record-key.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/record-key.json
new file mode 100644
index 00000000000000..6f1bb99be90808
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/record-key.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface Foo`: Record key must be a string type",
+ "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/record-single.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/record-single.json
new file mode 100644
index 00000000000000..ece4fb2fee3c07
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/record-single.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface Foo`: Missing comma after record key type",
+ "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/scopedname.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/scopedname.json
new file mode 100644
index 00000000000000..4620d2df5fa0cc
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/scopedname.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error before parsing any named definition: No name in typedef"
+, "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/sequenceAsAttribute.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/sequenceAsAttribute.json
new file mode 100644
index 00000000000000..5b4314a6de128d
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/sequenceAsAttribute.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface sequenceAsAttribute`: Attributes cannot accept sequence types"
+, "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/setlike-2types.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/setlike-2types.json
new file mode 100644
index 00000000000000..2900e1bac30074
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/setlike-2types.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface SetLikeTwoTypes`: Unterminated setlike declaration",
+ "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/setter-creator.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/setter-creator.json
new file mode 100644
index 00000000000000..25decb374e12fe
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/setter-creator.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface OrderedMap`: Invalid operation",
+ "line": 3
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/spaced-negative-infinity.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/spaced-negative-infinity.json
new file mode 100644
index 00000000000000..9e5d61804990bb
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/spaced-negative-infinity.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface X`: No value for const",
+ "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/spaced-variadic.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/spaced-variadic.json
new file mode 100644
index 00000000000000..0090abeeb71b23
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/spaced-variadic.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface X`: Unterminated operation",
+ "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/special-omittable.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/special-omittable.json
new file mode 100644
index 00000000000000..c20b28e03c17d6
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/special-omittable.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface Dictionary`: Invalid operation"
+, "line": 6
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/stray-slash.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/stray-slash.json
new file mode 100644
index 00000000000000..9c071cdd07a7a3
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/stray-slash.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error before parsing any named definition: Unrecognised tokens"
+, "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/stringconstants.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/stringconstants.json
new file mode 100644
index 00000000000000..745d6e6e08ba22
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/stringconstants.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface Util`: No type for const"
+, "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/typedef-nested.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/typedef-nested.json
new file mode 100644
index 00000000000000..e1843cec7d93e4
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/typedef-nested.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface Widget`: Missing return type"
+, "line": 14
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/union-dangling-or.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/union-dangling-or.json
new file mode 100644
index 00000000000000..68dfd8b2ae6098
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/union-dangling-or.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error before parsing any named definition: No type after open parenthesis or 'or' in union type",
+ "line": 1
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/union-one.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/union-one.json
new file mode 100644
index 00000000000000..476403d6889be7
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/union-one.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error before parsing any named definition: At least two types are expected in a union type but found less",
+ "line": 1
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/union-zero.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/union-zero.json
new file mode 100644
index 00000000000000..68dfd8b2ae6098
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/union-zero.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error before parsing any named definition: No type after open parenthesis or 'or' in union type",
+ "line": 1
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/unknown-generic.json b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/unknown-generic.json
new file mode 100644
index 00000000000000..3703db3b2ab332
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/unknown-generic.json
@@ -0,0 +1,4 @@
+{
+ "message": "Got an error during or right after parsing `interface FetchEvent`: Unsupported generic type ResponsePromise",
+ "line": 2
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/mocha.opts b/test/fixtures/web-platform-tests/resources/webidl2/test/mocha.opts
new file mode 100644
index 00000000000000..5ada47be16bf47
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/mocha.opts
@@ -0,0 +1 @@
+--reporter spec
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax.js b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax.js
new file mode 100644
index 00000000000000..05d647eda10394
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax.js
@@ -0,0 +1,19 @@
+"use strict";
+
+const { collect } = require("./util/collect");
+const expect = require("expect");
+const debug = true;
+
+describe("Parses all of the IDLs to produce the correct ASTs", () => {
+ for (const test of collect("syntax")) {
+ it(`should produce the same AST for ${test.path}`, () => {
+ try {
+ expect(test.diff()).toBeFalsy();
+ }
+ catch (e) {
+ console.log(e.toString());
+ throw e;
+ }
+ });
+ }
+});
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/allowany.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/allowany.widl
new file mode 100644
index 00000000000000..2343bb96374f98
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/allowany.widl
@@ -0,0 +1,6 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface B {
+ void g();
+ void g(B b);
+ void g([AllowAny] DOMString s);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/attributes.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/attributes.widl
new file mode 100644
index 00000000000000..f665c1fc47276f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/attributes.widl
@@ -0,0 +1,11 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+
+interface Person {
+
+ // A simple attribute that can be set to any value the range an unsigned
+ // short can take.
+ attribute unsigned short age;
+
+ // required is an allowed attribute name
+ attribute any required;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/callback.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/callback.widl
new file mode 100644
index 00000000000000..adaf75e049c9cb
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/callback.widl
@@ -0,0 +1,7 @@
+callback AsyncOperationCallback = void (DOMString status);
+
+callback interface EventHandler {
+ void eventOccurred(DOMString details);
+};
+
+callback SortCallback = boolean (any a, any b);
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/constants.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/constants.widl
new file mode 100644
index 00000000000000..043b022b6462ee
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/constants.widl
@@ -0,0 +1,11 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface Util {
+ const boolean DEBUG = false;
+ const short negative = -1;
+ const octet LF = 10;
+ const unsigned long BIT_MASK = 0x0000fc00;
+ const float AVOGADRO = 6.022e23;
+ const unrestricted float sobig = Infinity;
+ const unrestricted double minusonedividedbyzero = -Infinity;
+ const short notanumber = NaN;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/constructor.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/constructor.widl
new file mode 100644
index 00000000000000..f93ec08a6e6f81
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/constructor.widl
@@ -0,0 +1,9 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+[Constructor,
+ Constructor(float radius)]
+interface Circle {
+ attribute float r;
+ attribute float cx;
+ attribute float cy;
+ readonly attribute float circumference;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/dictionary-inherits.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/dictionary-inherits.widl
new file mode 100644
index 00000000000000..48f8a0fdceb6c2
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/dictionary-inherits.widl
@@ -0,0 +1,9 @@
+dictionary PaintOptions {
+ DOMString? fillPattern = "black";
+ DOMString? strokePattern = null;
+ Point position;
+};
+
+dictionary WetPaintOptions : PaintOptions {
+ float hydrometry;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/dictionary.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/dictionary.widl
new file mode 100644
index 00000000000000..c64a14c8590987
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/dictionary.widl
@@ -0,0 +1,15 @@
+// Extracted from Web IDL editors draft May 31 2011
+dictionary PaintOptions {
+ DOMString? fillPattern = "black";
+ DOMString? strokePattern = null;
+ Point position;
+ // https://heycam.github.io/webidl/#dfn-optional-argument-default-value allows sequences to default to "[]".
+ sequence seq = [];
+ // https://heycam.github.io/webidl/#required-dictionary-member
+ required long reqSeq;
+};
+
+partial dictionary A {
+ long h;
+ long d;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/documentation-dos.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/documentation-dos.widl
new file mode 100644
index 00000000000000..fb801101f14910
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/documentation-dos.widl
@@ -0,0 +1,33 @@
+/**
+* \brief Testing documentation features
+*
+* This is a
+* single paragraph
+*
+* This is valid.
+* This is valid .
+* This is valid .
+* This is valid .
+*
+* This
+* is
+* valid
+*
+*
+* This
+* valid
+*
+*
+*
+* this
+* is
+*
+*
+* valid
+*
+*
+* This is valid.
+* This is valid.
+* This is valid.
+*/
+interface Documentation {};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/documentation.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/documentation.widl
new file mode 100644
index 00000000000000..003e9226f672c2
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/documentation.widl
@@ -0,0 +1,34 @@
+/**
+* \brief Testing documentation features
+*
+* This is a
+* single paragraph
+*
+* This is valid.
+* This is valid .
+* This is valid .
+* This is valid .
+*
+* This
+* is
+* valid
+*
+*
+* This
+* valid
+*
+*
+*
+* this
+* is
+*
+*
+* valid
+*
+*
+* This is valid.
+* This is valid.
+* This is valid.
+*
+*/
+interface Documentation {};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/enum.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/enum.widl
new file mode 100644
index 00000000000000..37c4ffddee31b6
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/enum.widl
@@ -0,0 +1,10 @@
+enum MealType { "rice", "noodles", "other" };
+
+interface Meal {
+ attribute MealType type;
+ attribute float size; // in grams
+
+ void initialize(MealType type, float size);
+};
+
+enum AltMealType { "rice", "noodles", "other", };
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/equivalent-decl.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/equivalent-decl.widl
new file mode 100644
index 00000000000000..6ffeb3c20a1a47
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/equivalent-decl.widl
@@ -0,0 +1,18 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface Dictionary {
+ readonly attribute unsigned long propertyCount;
+
+ getter float getProperty(DOMString propertyName);
+ setter void setProperty(DOMString propertyName, float propertyValue);
+};
+
+
+interface Dictionary2 {
+ readonly attribute unsigned long propertyCount;
+
+ float getProperty(DOMString propertyName);
+ void setProperty(DOMString propertyName, float propertyValue);
+
+ getter float (DOMString propertyName);
+ setter void (DOMString propertyName, float propertyValue);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/extended-attributes.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/extended-attributes.widl
new file mode 100644
index 00000000000000..57d4f97de7c317
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/extended-attributes.widl
@@ -0,0 +1,29 @@
+// Extracted from http://www.w3.org/TR/2015/WD-service-workers-20150205/
+
+[Global=(Worker,ServiceWorker), Exposed=ServiceWorker]
+interface ServiceWorkerGlobalScope : WorkerGlobalScope {
+
+};
+
+// Conformance with ExtendedAttributeList grammar in http://www.w3.org/TR/WebIDL/#idl-extended-attributes
+// Section 3.11
+[IntAttr=0, FloatAttr=3.14, StringAttr="abc"]
+interface IdInterface {};
+
+// Extracted from http://www.w3.org/TR/2016/REC-WebIDL-1-20161215/#Constructor on 2017-5-18 with whitespace differences
+[
+ Constructor,
+ Constructor(double radius)
+]
+interface Circle {
+ attribute double r;
+ attribute double cx;
+ attribute double cy;
+ readonly attribute double circumference;
+};
+
+// Extracted from https://heycam.github.io/webidl/#idl-annotated-types on 2017-12-15
+[Exposed=Window]
+interface I {
+ attribute [XAttr] (long or Node) attrib;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/generic.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/generic.widl
new file mode 100644
index 00000000000000..693cd324e96b85
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/generic.widl
@@ -0,0 +1,17 @@
+interface Foo {
+ Promise>> bar();
+ readonly attribute Promise baz;
+};
+
+// Extracted from https://slightlyoff.github.io/ServiceWorker/spec/service_worker/ on 2014-05-08
+
+interface ServiceWorkerClients {
+ Promise getServiced();
+ Promise reloadAll();
+};
+
+// Extracted from https://slightlyoff.github.io/ServiceWorker/spec/service_worker/ on 2014-05-13
+
+interface FetchEvent : Event {
+ Promise default();
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/getter-setter.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/getter-setter.widl
new file mode 100644
index 00000000000000..bdf87e1c7c72f2
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/getter-setter.widl
@@ -0,0 +1,7 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface Dictionary {
+ readonly attribute unsigned long propertyCount;
+
+ getter float (DOMString propertyName);
+ setter void (DOMString propertyName, float propertyValue);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/identifier-qualified-names.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/identifier-qualified-names.widl
new file mode 100644
index 00000000000000..c39f84b45a36cc
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/identifier-qualified-names.widl
@@ -0,0 +1,33 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+ // Typedef identifier: "number"
+ // Qualified name: "::framework::number"
+ typedef float number;
+
+ // Interface identifier: "System"
+ // Qualified name: "::framework::System"
+ interface System {
+
+ // Operation identifier: "createObject"
+ // Operation argument identifier: "interface"
+ object createObject(DOMString _interface);
+
+ // Operation has no identifier; it declares a getter.
+ getter DOMString (DOMString keyName);
+ };
+
+
+ // Interface identifier: "TextField"
+ // Qualified name: "::framework::gui::TextField"
+ interface TextField {
+
+ // Attribute identifier: "const"
+ attribute boolean _const;
+
+ // Attribute identifier: "value"
+ attribute DOMString? _value;
+ };
+
+interface FooEventTarget {
+ // Argument names allow some selected keywords
+ void addEventListener(EventListener? callback);
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/implements.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/implements.widl
new file mode 100644
index 00000000000000..7a310926f1c8d4
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/implements.widl
@@ -0,0 +1,14 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+ interface Node {
+ readonly attribute unsigned short nodeType;
+ // ...
+ };
+
+ interface EventTarget {
+ void addEventListener(DOMString type,
+ EventListener listener,
+ boolean useCapture);
+ // ...
+ };
+
+ Node implements EventTarget;
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/indexed-properties.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/indexed-properties.widl
new file mode 100644
index 00000000000000..4b8aa9e353fac8
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/indexed-properties.widl
@@ -0,0 +1,12 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface OrderedMap {
+ readonly attribute unsigned long size;
+
+ getter any getByIndex(unsigned long index);
+ setter void setByIndex(unsigned long index, any value);
+ deleter void removeByIndex(unsigned long index);
+
+ getter any get(DOMString name);
+ setter void set(DOMString name, any value);
+ deleter void remove(DOMString name);
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/inherits-getter.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/inherits-getter.widl
new file mode 100644
index 00000000000000..435b3ab3c8cd58
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/inherits-getter.widl
@@ -0,0 +1,22 @@
+interface Animal {
+
+ // A simple attribute that can be set to any string value.
+ readonly attribute DOMString name;
+};
+
+interface Person : Animal {
+
+ // An attribute whose value cannot be assigned to.
+ readonly attribute unsigned short age;
+
+ // An attribute that can raise an exception if it is set to an invalid value.
+ // Its getter behavior is inherited from Animal, and need not be specified
+ // the description of Person.
+ inherit attribute DOMString name;
+};
+
+interface Ghost : Person {
+
+ // An attribute that only inherits the getter behavior
+ inherit readonly attribute DOMString name;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/interface-inherits.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/interface-inherits.widl
new file mode 100644
index 00000000000000..7921def77279f7
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/interface-inherits.widl
@@ -0,0 +1,12 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface Animal {
+ attribute DOMString name;
+};
+
+interface Human : Animal {
+ attribute Dog pet;
+};
+
+interface Dog : Animal {
+ attribute Human owner;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/iterable.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/iterable.widl
new file mode 100644
index 00000000000000..7f726f926fdecd
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/iterable.widl
@@ -0,0 +1,11 @@
+interface IterableOne {
+ iterable;
+};
+
+interface IterableTwo {
+ iterable;
+};
+
+interface IterableThree {
+ iterable<[XAttr] long>;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/legacyiterable.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/legacyiterable.widl
new file mode 100644
index 00000000000000..9e1e9c527447a2
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/legacyiterable.widl
@@ -0,0 +1,3 @@
+interface LegacyIterable {
+ legacyiterable;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/maplike.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/maplike.widl
new file mode 100644
index 00000000000000..437e381fef7673
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/maplike.widl
@@ -0,0 +1,13 @@
+interface MapLike {
+ maplike;
+};
+
+interface ReadOnlyMapLike {
+ readonly maplike;
+};
+
+// Extracted from https://heycam.github.io/webidl/#idl-type-extended-attribute-associated-with on 2017-07-01
+
+interface I {
+ maplike<[XAttr2] DOMString, [XAttr3] long>;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/mixin.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/mixin.widl
new file mode 100644
index 00000000000000..7c37a6ee4207b3
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/mixin.widl
@@ -0,0 +1,12 @@
+// Extracted from https://heycam.github.io/webidl/#using-mixins-and-partials on 2017-11-02
+
+interface mixin GlobalCrypto {
+ readonly attribute Crypto crypto;
+};
+
+Window includes GlobalCrypto;
+WorkerGlobalScope includes GlobalCrypto;
+
+partial interface mixin WindowOrWorkerGlobalScope {
+ readonly attribute Crypto crypto;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/namedconstructor.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/namedconstructor.widl
new file mode 100644
index 00000000000000..c468b78f8e1814
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/namedconstructor.widl
@@ -0,0 +1,6 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+[NamedConstructor=Audio,
+ NamedConstructor=Audio(DOMString src)]
+interface HTMLAudioElement : HTMLMediaElement {
+ // ...
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/namespace.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/namespace.widl
new file mode 100644
index 00000000000000..d9610555e17ad2
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/namespace.widl
@@ -0,0 +1,10 @@
+// Extracted from Web IDL editors draft March 27 2017
+namespace VectorUtils {
+ readonly attribute Vector unit;
+ double dotProduct(Vector x, Vector y);
+ Vector crossProduct(Vector x, Vector y);
+};
+
+partial namespace SomeNamespace {
+ /* namespace_members... */
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/nointerfaceobject.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/nointerfaceobject.widl
new file mode 100644
index 00000000000000..c17d75ff8fd373
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/nointerfaceobject.widl
@@ -0,0 +1,5 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+[NoInterfaceObject]
+interface Query {
+ any lookupEntry(unsigned long key);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/nullable.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/nullable.widl
new file mode 100644
index 00000000000000..ccbf625ff8aea1
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/nullable.widl
@@ -0,0 +1,9 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface MyConstants {
+ const boolean? ARE_WE_THERE_YET = false;
+};
+
+interface Node {
+ readonly attribute DOMString? namespaceURI;
+ // ...
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/nullableobjects.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/nullableobjects.widl
new file mode 100644
index 00000000000000..83d1d40b2acfa3
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/nullableobjects.widl
@@ -0,0 +1,13 @@
+// Extracted from WebIDL spec 2011-05-23
+
+interface A {
+ // ...
+};
+interface B {
+ // ...
+};
+interface C {
+ void f(A? x);
+ void f(B? x);
+
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/operation-optional-arg.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/operation-optional-arg.widl
new file mode 100644
index 00000000000000..379053b45f14a1
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/operation-optional-arg.widl
@@ -0,0 +1,4 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface ColorCreator {
+ object createColor(float v1, float v2, float v3, optional float alpha = 3.5);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/overloading.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/overloading.widl
new file mode 100644
index 00000000000000..52d8d15c1a13c8
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/overloading.widl
@@ -0,0 +1,20 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface A {
+ // ...
+};
+
+interface B {
+ // ...
+};
+
+interface C {
+ void f(A x);
+ void f(B x);
+};
+
+interface D {
+ /* f1 */ void f(DOMString a);
+ /* f2 */ void f([AllowAny] DOMString a, DOMString b, float... c);
+ /* f3 */ void f();
+ /* f4 */ void f(long a, DOMString b, optional DOMString c, float... d);
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/overridebuiltins.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/overridebuiltins.widl
new file mode 100644
index 00000000000000..79211c29e8436d
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/overridebuiltins.widl
@@ -0,0 +1,6 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+[OverrideBuiltins]
+interface StringMap2 {
+ readonly attribute unsigned long length;
+ getter DOMString lookup(DOMString key);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/partial-interface.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/partial-interface.widl
new file mode 100644
index 00000000000000..90e7e0ea421b4f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/partial-interface.widl
@@ -0,0 +1,7 @@
+interface Foo {
+ attribute DOMString bar;
+};
+
+partial interface Foo {
+ attribute DOMString quux;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/primitives.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/primitives.widl
new file mode 100644
index 00000000000000..a91455ee192f19
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/primitives.widl
@@ -0,0 +1,19 @@
+interface Primitives {
+ attribute boolean truth;
+ attribute byte character;
+ attribute octet value;
+ attribute short number;
+ attribute unsigned short positive;
+ attribute long big;
+ attribute unsigned long bigpositive;
+ attribute long long bigbig;
+ attribute unsigned long long bigbigpositive;
+ attribute float real;
+ attribute double bigreal;
+ attribute unrestricted float realwithinfinity;
+ attribute unrestricted double bigrealwithinfinity;
+ attribute DOMString string;
+ attribute ByteString bytes;
+ attribute Date date;
+ attribute RegExp regexp;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/promise-void.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/promise-void.widl
new file mode 100644
index 00000000000000..c4eac3b75c46ac
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/promise-void.widl
@@ -0,0 +1,3 @@
+interface Cat {
+ attribute Promise meow;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/prototyperoot.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/prototyperoot.widl
new file mode 100644
index 00000000000000..30dd5cbca13be0
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/prototyperoot.widl
@@ -0,0 +1,5 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+[PrototypeRoot]
+interface Node {
+ readonly attribute unsigned short nodeType;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/putforwards.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/putforwards.widl
new file mode 100644
index 00000000000000..1e50a4ee394f5d
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/putforwards.widl
@@ -0,0 +1,5 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface Person {
+ [PutForwards=full] readonly attribute Name name;
+ attribute unsigned short age;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/record.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/record.widl
new file mode 100644
index 00000000000000..dbfad3afbfeb68
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/record.widl
@@ -0,0 +1,9 @@
+[Constructor(record init)]
+interface Foo {
+ void foo(sequence> param);
+ record bar();
+};
+
+interface Bar {
+ record bar();
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/reg-operations.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/reg-operations.widl
new file mode 100644
index 00000000000000..338c8d427636fb
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/reg-operations.widl
@@ -0,0 +1,15 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface Dimensions {
+ attribute unsigned long width;
+ attribute unsigned long height;
+};
+
+interface Button {
+
+ // An operation that takes no arguments, returns a boolean
+ boolean isMouseOver();
+
+ // Overloaded operations.
+ void setDimensions(Dimensions size);
+ void setDimensions(unsigned long width, unsigned long height);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/replaceable.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/replaceable.widl
new file mode 100644
index 00000000000000..c14d0c37689047
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/replaceable.widl
@@ -0,0 +1,5 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface Counter {
+ [Replaceable] readonly attribute unsigned long value;
+ void increment();
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/sequence.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/sequence.widl
new file mode 100644
index 00000000000000..b47c98225c7156
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/sequence.widl
@@ -0,0 +1,13 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+// edited to remove sequence as attributes, now invalid
+interface Canvas {
+ void drawPolygon(sequence coordinates);
+ sequence getInflectionPoints();
+ // ...
+};
+
+// Extracted from https://heycam.github.io/webidl/#idl-type-extended-attribute-associated-with on 2017-07-01
+
+interface I {
+ void f1(sequence<[XAttr] long> arg);
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/setlike.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/setlike.widl
new file mode 100644
index 00000000000000..4512f286b59a50
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/setlike.widl
@@ -0,0 +1,11 @@
+interface SetLike {
+ setlike;
+};
+
+interface ReadOnlySetLike {
+ readonly setlike;
+};
+
+interface SetLikeExt {
+ setlike<[XAttr] long>;
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/static.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/static.widl
new file mode 100644
index 00000000000000..5b2cd36590fd6f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/static.widl
@@ -0,0 +1,11 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface Point { /* ... */ };
+
+interface Circle {
+ attribute float cx;
+ attribute float cy;
+ attribute float radius;
+
+ static readonly attribute long triangulationCount;
+ static Point triangulate(Circle c1, Circle c2, Circle c3);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/stringifier-attribute.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/stringifier-attribute.widl
new file mode 100644
index 00000000000000..c964ecb93e601f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/stringifier-attribute.widl
@@ -0,0 +1,6 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+[Constructor]
+interface Student {
+ attribute unsigned long id;
+ stringifier attribute DOMString name;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/stringifier-custom.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/stringifier-custom.widl
new file mode 100644
index 00000000000000..b5d7c87e7f4572
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/stringifier-custom.widl
@@ -0,0 +1,9 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+[Constructor]
+interface Student {
+ attribute unsigned long id;
+ attribute DOMString? familyName;
+ attribute DOMString givenName;
+
+ stringifier DOMString ();
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/stringifier.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/stringifier.widl
new file mode 100644
index 00000000000000..c45277ea8db337
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/stringifier.widl
@@ -0,0 +1,8 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface A {
+ stringifier DOMString ();
+};
+
+interface B {
+ stringifier;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/treatasnull.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/treatasnull.widl
new file mode 100644
index 00000000000000..d3c55b008c0d86
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/treatasnull.widl
@@ -0,0 +1,7 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface Dog {
+ attribute DOMString name;
+ attribute DOMString owner;
+
+ boolean isMemberOfBreed([TreatNullAs=EmptyString] DOMString breedName);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/treatasundefined.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/treatasundefined.widl
new file mode 100644
index 00000000000000..e30050f8413403
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/treatasundefined.widl
@@ -0,0 +1,7 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface Cat {
+ attribute DOMString name;
+ attribute DOMString owner;
+
+ boolean isMemberOfBreed([TreatUndefinedAs=EmptyString] DOMString breedName);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/typedef-union.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/typedef-union.widl
new file mode 100644
index 00000000000000..3048703e0c5541
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/typedef-union.widl
@@ -0,0 +1,4 @@
+ typedef (ImageData or
+ HTMLImageElement or
+ HTMLCanvasElement or
+ HTMLVideoElement) TexImageSource;
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/typedef.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/typedef.widl
new file mode 100644
index 00000000000000..b4c17d8d36a89f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/typedef.widl
@@ -0,0 +1,22 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+ interface Point {
+ attribute float x;
+ attribute float y;
+ };
+
+ typedef sequence PointSequence;
+
+ interface Rect {
+ attribute Point topleft;
+ attribute Point bottomright;
+ };
+
+ interface Widget {
+
+ readonly attribute Rect bounds;
+
+ boolean pointWithinBounds(Point p);
+ boolean allPointsWithinBounds(PointSequence ps);
+ };
+
+ typedef [Clamp] octet value;
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/typesuffixes.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/typesuffixes.widl
new file mode 100644
index 00000000000000..beaaa8726009e6
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/typesuffixes.widl
@@ -0,0 +1,3 @@
+interface Suffixes {
+ void test(sequence? foo);
+};
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/uniontype.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/uniontype.widl
new file mode 100644
index 00000000000000..0d5fe9be428640
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/uniontype.widl
@@ -0,0 +1,4 @@
+interface Union {
+ attribute (float or (Date or Event) or (Node or DOMString)?) test;
+ attribute ([EnforceRange] long or Date) test2;
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/variadic-operations.widl b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/variadic-operations.widl
new file mode 100644
index 00000000000000..51fae4cc1ea1c4
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/variadic-operations.widl
@@ -0,0 +1,7 @@
+// Extracted from http://dev.w3.org/2006/webapi/WebIDL/ on 2011-05-06
+interface IntegerSet {
+ readonly attribute unsigned long cardinality;
+
+ void union(long... ints);
+ void intersection(long... ints);
+};
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/allowany.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/allowany.json
new file mode 100644
index 00000000000000..2a93518ed94637
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/allowany.json
@@ -0,0 +1,112 @@
+[
+ {
+ "type": "interface",
+ "name": "B",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "g",
+ "escapedName": "g",
+ "arguments": [],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "g",
+ "escapedName": "g",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "B",
+ "extAttrs": []
+ },
+ "name": "b",
+ "escapedName": "b"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "g",
+ "escapedName": "g",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [
+ {
+ "name": "AllowAny",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "s",
+ "escapedName": "s"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/attributes.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/attributes.json
new file mode 100644
index 00000000000000..f02cb2187a2af2
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/attributes.json
@@ -0,0 +1,47 @@
+[
+ {
+ "type": "interface",
+ "name": "Person",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned short",
+ "extAttrs": []
+ },
+ "name": "age",
+ "escapedName": "age",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "any",
+ "extAttrs": []
+ },
+ "name": "required",
+ "escapedName": "required",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/callback.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/callback.json
new file mode 100644
index 00000000000000..89d63aae53d02a
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/callback.json
@@ -0,0 +1,126 @@
+[
+ {
+ "type": "callback",
+ "name": "AsyncOperationCallback",
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "status",
+ "escapedName": "status"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "callback interface",
+ "name": "EventHandler",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "eventOccurred",
+ "escapedName": "eventOccurred",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "details",
+ "escapedName": "details"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "callback",
+ "name": "SortCallback",
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "boolean",
+ "extAttrs": []
+ },
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "any",
+ "extAttrs": []
+ },
+ "name": "a",
+ "escapedName": "a"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "any",
+ "extAttrs": []
+ },
+ "name": "b",
+ "escapedName": "b"
+ }
+ ],
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/constants.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/constants.json
new file mode 100644
index 00000000000000..ef2b8c44ca167c
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/constants.json
@@ -0,0 +1,154 @@
+[
+ {
+ "type": "interface",
+ "name": "Util",
+ "partial": false,
+ "members": [
+ {
+ "type": "const",
+ "nullable": false,
+ "idlType": {
+ "type": "const-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "boolean",
+ "extAttrs": []
+ },
+ "name": "DEBUG",
+ "value": {
+ "type": "boolean",
+ "value": false
+ },
+ "extAttrs": []
+ },
+ {
+ "type": "const",
+ "nullable": false,
+ "idlType": {
+ "type": "const-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "short",
+ "extAttrs": []
+ },
+ "name": "negative",
+ "value": {
+ "type": "number",
+ "value": "-1"
+ },
+ "extAttrs": []
+ },
+ {
+ "type": "const",
+ "nullable": false,
+ "idlType": {
+ "type": "const-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "octet",
+ "extAttrs": []
+ },
+ "name": "LF",
+ "value": {
+ "type": "number",
+ "value": "10"
+ },
+ "extAttrs": []
+ },
+ {
+ "type": "const",
+ "nullable": false,
+ "idlType": {
+ "type": "const-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "BIT_MASK",
+ "value": {
+ "type": "number",
+ "value": "0x0000fc00"
+ },
+ "extAttrs": []
+ },
+ {
+ "type": "const",
+ "nullable": false,
+ "idlType": {
+ "type": "const-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "AVOGADRO",
+ "value": {
+ "type": "number",
+ "value": "6.022e23"
+ },
+ "extAttrs": []
+ },
+ {
+ "type": "const",
+ "nullable": false,
+ "idlType": {
+ "type": "const-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unrestricted float",
+ "extAttrs": []
+ },
+ "name": "sobig",
+ "value": {
+ "type": "Infinity",
+ "negative": false
+ },
+ "extAttrs": []
+ },
+ {
+ "type": "const",
+ "nullable": false,
+ "idlType": {
+ "type": "const-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unrestricted double",
+ "extAttrs": []
+ },
+ "name": "minusonedividedbyzero",
+ "value": {
+ "type": "Infinity",
+ "negative": true
+ },
+ "extAttrs": []
+ },
+ {
+ "type": "const",
+ "nullable": false,
+ "idlType": {
+ "type": "const-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "short",
+ "extAttrs": []
+ },
+ "name": "notanumber",
+ "value": {
+ "type": "NaN"
+ },
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/constructor.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/constructor.json
new file mode 100644
index 00000000000000..efdd1b5a6bedc6
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/constructor.json
@@ -0,0 +1,113 @@
+[
+ {
+ "type": "interface",
+ "name": "Circle",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "r",
+ "escapedName": "r",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "cx",
+ "escapedName": "cx",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "cy",
+ "escapedName": "cy",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "circumference",
+ "escapedName": "circumference",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": [
+ {
+ "name": "Constructor",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ },
+ {
+ "name": "Constructor",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "radius",
+ "escapedName": "radius"
+ }
+ ],
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/dictionary-inherits.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/dictionary-inherits.json
new file mode 100644
index 00000000000000..595c35cf79748a
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/dictionary-inherits.json
@@ -0,0 +1,89 @@
+[
+ {
+ "type": "dictionary",
+ "name": "PaintOptions",
+ "partial": false,
+ "members": [
+ {
+ "type": "field",
+ "name": "fillPattern",
+ "escapedName": "fillPattern",
+ "required": false,
+ "idlType": {
+ "type": "dictionary-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "extAttrs": [],
+ "default": {
+ "type": "string",
+ "value": "black"
+ }
+ },
+ {
+ "type": "field",
+ "name": "strokePattern",
+ "escapedName": "strokePattern",
+ "required": false,
+ "idlType": {
+ "type": "dictionary-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "extAttrs": [],
+ "default": {
+ "type": "null"
+ }
+ },
+ {
+ "type": "field",
+ "name": "position",
+ "escapedName": "position",
+ "required": false,
+ "idlType": {
+ "type": "dictionary-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Point",
+ "extAttrs": []
+ },
+ "extAttrs": [],
+ "default": null
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "dictionary",
+ "name": "WetPaintOptions",
+ "partial": false,
+ "members": [
+ {
+ "type": "field",
+ "name": "hydrometry",
+ "escapedName": "hydrometry",
+ "required": false,
+ "idlType": {
+ "type": "dictionary-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "extAttrs": [],
+ "default": null
+ }
+ ],
+ "inheritance": "PaintOptions",
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/dictionary.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/dictionary.json
new file mode 100644
index 00000000000000..8bbc6b9dfabb4e
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/dictionary.json
@@ -0,0 +1,146 @@
+[
+ {
+ "type": "dictionary",
+ "name": "PaintOptions",
+ "partial": false,
+ "members": [
+ {
+ "type": "field",
+ "name": "fillPattern",
+ "escapedName": "fillPattern",
+ "required": false,
+ "idlType": {
+ "type": "dictionary-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "extAttrs": [],
+ "default": {
+ "type": "string",
+ "value": "black"
+ }
+ },
+ {
+ "type": "field",
+ "name": "strokePattern",
+ "escapedName": "strokePattern",
+ "required": false,
+ "idlType": {
+ "type": "dictionary-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "extAttrs": [],
+ "default": {
+ "type": "null"
+ }
+ },
+ {
+ "type": "field",
+ "name": "position",
+ "escapedName": "position",
+ "required": false,
+ "idlType": {
+ "type": "dictionary-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Point",
+ "extAttrs": []
+ },
+ "extAttrs": [],
+ "default": null
+ },
+ {
+ "type": "field",
+ "name": "seq",
+ "escapedName": "seq",
+ "required": false,
+ "idlType": {
+ "type": "dictionary-type",
+ "generic": "sequence",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "dictionary-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "extAttrs": [],
+ "default": {
+ "type": "sequence",
+ "value": []
+ }
+ },
+ {
+ "type": "field",
+ "name": "reqSeq",
+ "escapedName": "reqSeq",
+ "required": true,
+ "idlType": {
+ "type": "dictionary-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ },
+ "extAttrs": [],
+ "default": null
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "dictionary",
+ "name": "A",
+ "partial": true,
+ "members": [
+ {
+ "type": "field",
+ "name": "h",
+ "escapedName": "h",
+ "required": false,
+ "idlType": {
+ "type": "dictionary-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ },
+ "extAttrs": [],
+ "default": null
+ },
+ {
+ "type": "field",
+ "name": "d",
+ "escapedName": "d",
+ "required": false,
+ "idlType": {
+ "type": "dictionary-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ },
+ "extAttrs": [],
+ "default": null
+ }
+ ],
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/documentation-dos.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/documentation-dos.json
new file mode 100644
index 00000000000000..baa0b5a09b5ee1
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/documentation-dos.json
@@ -0,0 +1,10 @@
+[
+ {
+ "type": "interface",
+ "name": "Documentation",
+ "partial": false,
+ "members": [],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/documentation.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/documentation.json
new file mode 100644
index 00000000000000..baa0b5a09b5ee1
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/documentation.json
@@ -0,0 +1,10 @@
+[
+ {
+ "type": "interface",
+ "name": "Documentation",
+ "partial": false,
+ "members": [],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/enum.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/enum.json
new file mode 100644
index 00000000000000..11f5d09f54b62b
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/enum.json
@@ -0,0 +1,138 @@
+[
+ {
+ "type": "enum",
+ "name": "MealType",
+ "values": [
+ {
+ "type": "string",
+ "value": "rice"
+ },
+ {
+ "type": "string",
+ "value": "noodles"
+ },
+ {
+ "type": "string",
+ "value": "other"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Meal",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "MealType",
+ "extAttrs": []
+ },
+ "name": "type",
+ "escapedName": "type",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "size",
+ "escapedName": "size",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "initialize",
+ "escapedName": "initialize",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "MealType",
+ "extAttrs": []
+ },
+ "name": "type",
+ "escapedName": "type"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "size",
+ "escapedName": "size"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "enum",
+ "name": "AltMealType",
+ "values": [
+ {
+ "type": "string",
+ "value": "rice"
+ },
+ {
+ "type": "string",
+ "value": "noodles"
+ },
+ {
+ "type": "string",
+ "value": "other"
+ }
+ ],
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/equivalent-decl.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/equivalent-decl.json
new file mode 100644
index 00000000000000..ee079a77790531
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/equivalent-decl.json
@@ -0,0 +1,326 @@
+[
+ {
+ "type": "interface",
+ "name": "Dictionary",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "propertyCount",
+ "escapedName": "propertyCount",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": true,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "getProperty",
+ "escapedName": "getProperty",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "propertyName",
+ "escapedName": "propertyName"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": true,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "setProperty",
+ "escapedName": "setProperty",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "propertyName",
+ "escapedName": "propertyName"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "propertyValue",
+ "escapedName": "propertyValue"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Dictionary2",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "propertyCount",
+ "escapedName": "propertyCount",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "getProperty",
+ "escapedName": "getProperty",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "propertyName",
+ "escapedName": "propertyName"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "setProperty",
+ "escapedName": "setProperty",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "propertyName",
+ "escapedName": "propertyName"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "propertyValue",
+ "escapedName": "propertyValue"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": true,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": null,
+ "escapedName": null,
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "propertyName",
+ "escapedName": "propertyName"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": true,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": null,
+ "escapedName": null,
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "propertyName",
+ "escapedName": "propertyName"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "propertyValue",
+ "escapedName": "propertyValue"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/exception-inheritance.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/exception-inheritance.json
new file mode 100644
index 00000000000000..4a76b98285697e
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/exception-inheritance.json
@@ -0,0 +1,36 @@
+[
+ {
+ "type": "exception",
+ "name": "DOMException",
+ "members": [
+ {
+ "type": "field",
+ "name": "code",
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned short"
+ },
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "exception",
+ "name": "HierarchyRequestError",
+ "members": [],
+ "inheritance": "DOMException",
+ "extAttrs": []
+ },
+ {
+ "type": "exception",
+ "name": "NoModificationAllowedError",
+ "members": [],
+ "inheritance": "DOMException",
+ "extAttrs": []
+ }
+]
\ No newline at end of file
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/extended-attributes.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/extended-attributes.json
new file mode 100644
index 00000000000000..25f56f0340ac87
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/extended-attributes.json
@@ -0,0 +1,240 @@
+[
+ {
+ "type": "interface",
+ "name": "ServiceWorkerGlobalScope",
+ "partial": false,
+ "members": [],
+ "inheritance": "WorkerGlobalScope",
+ "extAttrs": [
+ {
+ "name": "Global",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": {
+ "type": "identifier-list",
+ "value": [
+ "Worker",
+ "ServiceWorker"
+ ]
+ }
+ },
+ {
+ "name": "Exposed",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": {
+ "type": "identifier",
+ "value": "ServiceWorker"
+ }
+ }
+ ]
+ },
+ {
+ "type": "interface",
+ "name": "IdInterface",
+ "partial": false,
+ "members": [],
+ "inheritance": null,
+ "extAttrs": [
+ {
+ "name": "IntAttr",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": {
+ "type": "integer",
+ "value": "0"
+ }
+ },
+ {
+ "name": "FloatAttr",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": {
+ "type": "float",
+ "value": "3.14"
+ }
+ },
+ {
+ "name": "StringAttr",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": {
+ "type": "string",
+ "value": "\"abc\""
+ }
+ }
+ ]
+ },
+ {
+ "type": "interface",
+ "name": "Circle",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "double",
+ "extAttrs": []
+ },
+ "name": "r",
+ "escapedName": "r",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "double",
+ "extAttrs": []
+ },
+ "name": "cx",
+ "escapedName": "cx",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "double",
+ "extAttrs": []
+ },
+ "name": "cy",
+ "escapedName": "cy",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "double",
+ "extAttrs": []
+ },
+ "name": "circumference",
+ "escapedName": "circumference",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": [
+ {
+ "name": "Constructor",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ },
+ {
+ "name": "Constructor",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "double",
+ "extAttrs": []
+ },
+ "name": "radius",
+ "escapedName": "radius"
+ }
+ ],
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ },
+ {
+ "type": "interface",
+ "name": "I",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": true,
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Node",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": [
+ {
+ "name": "XAttr",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ },
+ "name": "attrib",
+ "escapedName": "attrib",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": [
+ {
+ "name": "Exposed",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": {
+ "type": "identifier",
+ "value": "Window"
+ }
+ }
+ ]
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/generic.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/generic.json
new file mode 100644
index 00000000000000..d3c26ac7cd0ddd
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/generic.json
@@ -0,0 +1,176 @@
+[
+ {
+ "type": "interface",
+ "name": "Foo",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": "Promise",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": "Promise",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": "sequence",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "name": "bar",
+ "escapedName": "bar",
+ "arguments": [],
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": "Promise",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "name": "baz",
+ "escapedName": "baz",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "ServiceWorkerClients",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": "Promise",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "Client",
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "name": "getServiced",
+ "escapedName": "getServiced",
+ "arguments": [],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": "Promise",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "any",
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "name": "reloadAll",
+ "escapedName": "reloadAll",
+ "arguments": [],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "FetchEvent",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": "Promise",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "any",
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "name": "default",
+ "escapedName": "default",
+ "arguments": [],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": "Event",
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/getter-setter.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/getter-setter.json
new file mode 100644
index 00000000000000..6f8196fe56dd3d
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/getter-setter.json
@@ -0,0 +1,119 @@
+[
+ {
+ "type": "interface",
+ "name": "Dictionary",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "propertyCount",
+ "escapedName": "propertyCount",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": true,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": null,
+ "escapedName": null,
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "propertyName",
+ "escapedName": "propertyName"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": true,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": null,
+ "escapedName": null,
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "propertyName",
+ "escapedName": "propertyName"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "propertyValue",
+ "escapedName": "propertyValue"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/identifier-qualified-names.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/identifier-qualified-names.json
new file mode 100644
index 00000000000000..098cdcb1fbbc4f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/identifier-qualified-names.json
@@ -0,0 +1,189 @@
+[
+ {
+ "type": "typedef",
+ "idlType": {
+ "type": "typedef-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "number",
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "System",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "object",
+ "extAttrs": []
+ },
+ "name": "createObject",
+ "escapedName": "createObject",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "interface",
+ "escapedName": "_interface"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": true,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": null,
+ "escapedName": null,
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "keyName",
+ "escapedName": "keyName"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "TextField",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "boolean",
+ "extAttrs": []
+ },
+ "name": "const",
+ "escapedName": "_const",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "value",
+ "escapedName": "_value",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "FooEventTarget",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "addEventListener",
+ "escapedName": "addEventListener",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "EventListener",
+ "extAttrs": []
+ },
+ "name": "callback",
+ "escapedName": "callback"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/implements.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/implements.json
new file mode 100644
index 00000000000000..1736118cd902fb
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/implements.json
@@ -0,0 +1,113 @@
+[
+ {
+ "type": "interface",
+ "name": "Node",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned short",
+ "extAttrs": []
+ },
+ "name": "nodeType",
+ "escapedName": "nodeType",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "EventTarget",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "addEventListener",
+ "escapedName": "addEventListener",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "type",
+ "escapedName": "type"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "EventListener",
+ "extAttrs": []
+ },
+ "name": "listener",
+ "escapedName": "listener"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "boolean",
+ "extAttrs": []
+ },
+ "name": "useCapture",
+ "escapedName": "useCapture"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "implements",
+ "target": "Node",
+ "implements": "EventTarget",
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/indexed-properties.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/indexed-properties.json
new file mode 100644
index 00000000000000..dccd511af96bfd
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/indexed-properties.json
@@ -0,0 +1,283 @@
+[
+ {
+ "type": "interface",
+ "name": "OrderedMap",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "size",
+ "escapedName": "size",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": true,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "any",
+ "extAttrs": []
+ },
+ "name": "getByIndex",
+ "escapedName": "getByIndex",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "index",
+ "escapedName": "index"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": true,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "setByIndex",
+ "escapedName": "setByIndex",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "index",
+ "escapedName": "index"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "any",
+ "extAttrs": []
+ },
+ "name": "value",
+ "escapedName": "value"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": true,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "removeByIndex",
+ "escapedName": "removeByIndex",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "index",
+ "escapedName": "index"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": true,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "any",
+ "extAttrs": []
+ },
+ "name": "get",
+ "escapedName": "get",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "name",
+ "escapedName": "name"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": true,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "set",
+ "escapedName": "set",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "name",
+ "escapedName": "name"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "any",
+ "extAttrs": []
+ },
+ "name": "value",
+ "escapedName": "value"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": true,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "remove",
+ "escapedName": "remove",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "name",
+ "escapedName": "name"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/inherits-getter.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/inherits-getter.json
new file mode 100644
index 00000000000000..86eb68e270e46a
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/inherits-getter.json
@@ -0,0 +1,101 @@
+[
+ {
+ "type": "interface",
+ "name": "Animal",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "name",
+ "escapedName": "name",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Person",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned short",
+ "extAttrs": []
+ },
+ "name": "age",
+ "escapedName": "age",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": true,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "name",
+ "escapedName": "name",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": "Animal",
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Ghost",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": true,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "name",
+ "escapedName": "name",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": "Person",
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/interface-inherits.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/interface-inherits.json
new file mode 100644
index 00000000000000..02caf35f605ab3
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/interface-inherits.json
@@ -0,0 +1,83 @@
+[
+ {
+ "type": "interface",
+ "name": "Animal",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "name",
+ "escapedName": "name",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Human",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Dog",
+ "extAttrs": []
+ },
+ "name": "pet",
+ "escapedName": "pet",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": "Animal",
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Dog",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Human",
+ "extAttrs": []
+ },
+ "name": "owner",
+ "escapedName": "owner",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": "Animal",
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/iterable.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/iterable.json
new file mode 100644
index 00000000000000..ee906f75ea2eb5
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/iterable.json
@@ -0,0 +1,86 @@
+[
+ {
+ "type": "interface",
+ "name": "IterableOne",
+ "partial": false,
+ "members": [
+ {
+ "type": "iterable",
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "IterableTwo",
+ "partial": false,
+ "members": [
+ {
+ "type": "iterable",
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "short",
+ "extAttrs": []
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "double",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "IterableThree",
+ "partial": false,
+ "members": [
+ {
+ "type": "iterable",
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": [
+ {
+ "name": "XAttr",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/iterator.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/iterator.json
new file mode 100644
index 00000000000000..f9605b83450cb5
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/iterator.json
@@ -0,0 +1,276 @@
+[
+ {
+ "type": "interface",
+ "name": "SessionManager",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Session"
+ },
+ "name": "getSessionForUser",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "extAttrs": [],
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString"
+ },
+ "name": "username"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long"
+ },
+ "name": "sessionCount",
+ "extAttrs": []
+ },
+ {
+ "type": "iterator",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Session"
+ },
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Session",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString"
+ },
+ "name": "username",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "SessionManager2",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Session2"
+ },
+ "name": "getSessionForUser",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "extAttrs": [],
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString"
+ },
+ "name": "username"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long"
+ },
+ "name": "sessionCount",
+ "extAttrs": []
+ },
+ {
+ "type": "iterator",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Session2"
+ },
+ "iteratorObject": "SessionIterator",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Session2",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString"
+ },
+ "name": "username",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "SessionIterator",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long"
+ },
+ "name": "remainingSessions",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "NodeList",
+ "partial": false,
+ "members": [
+ {
+ "type": "iterator",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Node"
+ },
+ "iteratorObject": "NodeIterator",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "NodeIterator",
+ "partial": false,
+ "members": [
+ {
+ "type": "iterator",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "sequence": false,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Node"
+ },
+ "iteratorObject": "object",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/legacyiterable.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/legacyiterable.json
new file mode 100644
index 00000000000000..80fd000a849371
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/legacyiterable.json
@@ -0,0 +1,25 @@
+[
+ {
+ "type": "interface",
+ "name": "LegacyIterable",
+ "partial": false,
+ "members": [
+ {
+ "type": "legacyiterable",
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/maplike.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/maplike.json
new file mode 100644
index 00000000000000..2cb8c360ee3e2f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/maplike.json
@@ -0,0 +1,112 @@
+[
+ {
+ "type": "interface",
+ "name": "MapLike",
+ "partial": false,
+ "members": [
+ {
+ "type": "maplike",
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ }
+ ],
+ "readonly": false,
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "ReadOnlyMapLike",
+ "partial": false,
+ "members": [
+ {
+ "type": "maplike",
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ }
+ ],
+ "readonly": true,
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "I",
+ "partial": false,
+ "members": [
+ {
+ "type": "maplike",
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": [
+ {
+ "name": "XAttr2",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": [
+ {
+ "name": "XAttr3",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ }
+ ],
+ "readonly": false,
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/mixin.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/mixin.json
new file mode 100644
index 00000000000000..b037cb3577941a
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/mixin.json
@@ -0,0 +1,66 @@
+[
+ {
+ "type": "interface mixin",
+ "name": "GlobalCrypto",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Crypto",
+ "extAttrs": []
+ },
+ "name": "crypto",
+ "escapedName": "crypto",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "includes",
+ "target": "Window",
+ "includes": "GlobalCrypto",
+ "extAttrs": []
+ },
+ {
+ "type": "includes",
+ "target": "WorkerGlobalScope",
+ "includes": "GlobalCrypto",
+ "extAttrs": []
+ },
+ {
+ "type": "interface mixin",
+ "name": "WindowOrWorkerGlobalScope",
+ "partial": true,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Crypto",
+ "extAttrs": []
+ },
+ "name": "crypto",
+ "escapedName": "crypto",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/namedconstructor.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/namedconstructor.json
new file mode 100644
index 00000000000000..deb429e986eb10
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/namedconstructor.json
@@ -0,0 +1,46 @@
+[
+ {
+ "type": "interface",
+ "name": "HTMLAudioElement",
+ "partial": false,
+ "members": [],
+ "inheritance": "HTMLMediaElement",
+ "extAttrs": [
+ {
+ "name": "NamedConstructor",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": {
+ "type": "identifier",
+ "value": "Audio"
+ }
+ },
+ {
+ "name": "NamedConstructor",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "src",
+ "escapedName": "src"
+ }
+ ],
+ "type": "extended-attribute",
+ "rhs": {
+ "type": "identifier",
+ "value": "Audio"
+ }
+ }
+ ]
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/namespace.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/namespace.json
new file mode 100644
index 00000000000000..9e37b26fa6504f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/namespace.json
@@ -0,0 +1,141 @@
+[
+ {
+ "type": "namespace",
+ "name": "VectorUtils",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Vector",
+ "extAttrs": []
+ },
+ "name": "unit",
+ "escapedName": "unit",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "double",
+ "extAttrs": []
+ },
+ "name": "dotProduct",
+ "escapedName": "dotProduct",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Vector",
+ "extAttrs": []
+ },
+ "name": "x",
+ "escapedName": "x"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Vector",
+ "extAttrs": []
+ },
+ "name": "y",
+ "escapedName": "y"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Vector",
+ "extAttrs": []
+ },
+ "name": "crossProduct",
+ "escapedName": "crossProduct",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Vector",
+ "extAttrs": []
+ },
+ "name": "x",
+ "escapedName": "x"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Vector",
+ "extAttrs": []
+ },
+ "name": "y",
+ "escapedName": "y"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "namespace",
+ "name": "SomeNamespace",
+ "partial": true,
+ "members": [],
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/nointerfaceobject.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/nointerfaceobject.json
new file mode 100644
index 00000000000000..cafb5e0e156bd0
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/nointerfaceobject.json
@@ -0,0 +1,55 @@
+[
+ {
+ "type": "interface",
+ "name": "Query",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "any",
+ "extAttrs": []
+ },
+ "name": "lookupEntry",
+ "escapedName": "lookupEntry",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "key",
+ "escapedName": "key"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": [
+ {
+ "name": "NoInterfaceObject",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/nullable.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/nullable.json
new file mode 100644
index 00000000000000..f325e2eab7b8af
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/nullable.json
@@ -0,0 +1,56 @@
+[
+ {
+ "type": "interface",
+ "name": "MyConstants",
+ "partial": false,
+ "members": [
+ {
+ "type": "const",
+ "nullable": true,
+ "idlType": {
+ "type": "const-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "boolean",
+ "extAttrs": []
+ },
+ "name": "ARE_WE_THERE_YET",
+ "value": {
+ "type": "boolean",
+ "value": false
+ },
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Node",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "namespaceURI",
+ "escapedName": "namespaceURI",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/nullableobjects.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/nullableobjects.json
new file mode 100644
index 00000000000000..27ecd25d363bfb
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/nullableobjects.json
@@ -0,0 +1,101 @@
+[
+ {
+ "type": "interface",
+ "name": "A",
+ "partial": false,
+ "members": [],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "B",
+ "partial": false,
+ "members": [],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "C",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "f",
+ "escapedName": "f",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "A",
+ "extAttrs": []
+ },
+ "name": "x",
+ "escapedName": "x"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "f",
+ "escapedName": "f",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "B",
+ "extAttrs": []
+ },
+ "name": "x",
+ "escapedName": "x"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/operation-optional-arg.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/operation-optional-arg.json
new file mode 100644
index 00000000000000..4b7436ba5baf76
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/operation-optional-arg.json
@@ -0,0 +1,99 @@
+[
+ {
+ "type": "interface",
+ "name": "ColorCreator",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "object",
+ "extAttrs": []
+ },
+ "name": "createColor",
+ "escapedName": "createColor",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "v1",
+ "escapedName": "v1"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "v2",
+ "escapedName": "v2"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "v3",
+ "escapedName": "v3"
+ },
+ {
+ "optional": true,
+ "variadic": false,
+ "default": {
+ "type": "number",
+ "value": "3.5"
+ },
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "alpha",
+ "escapedName": "alpha"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/overloading.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/overloading.json
new file mode 100644
index 00000000000000..bf7aca6cde3808
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/overloading.json
@@ -0,0 +1,328 @@
+[
+ {
+ "type": "interface",
+ "name": "A",
+ "partial": false,
+ "members": [],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "B",
+ "partial": false,
+ "members": [],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "C",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "f",
+ "escapedName": "f",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "A",
+ "extAttrs": []
+ },
+ "name": "x",
+ "escapedName": "x"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "f",
+ "escapedName": "f",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "B",
+ "extAttrs": []
+ },
+ "name": "x",
+ "escapedName": "x"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "D",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "f",
+ "escapedName": "f",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "a",
+ "escapedName": "a"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "f",
+ "escapedName": "f",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [
+ {
+ "name": "AllowAny",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "a",
+ "escapedName": "a"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "b",
+ "escapedName": "b"
+ },
+ {
+ "optional": false,
+ "variadic": true,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "c",
+ "escapedName": "c"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "f",
+ "escapedName": "f",
+ "arguments": [],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "f",
+ "escapedName": "f",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ },
+ "name": "a",
+ "escapedName": "a"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "b",
+ "escapedName": "b"
+ },
+ {
+ "optional": true,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "c",
+ "escapedName": "c"
+ },
+ {
+ "optional": false,
+ "variadic": true,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "d",
+ "escapedName": "d"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/overridebuiltins.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/overridebuiltins.json
new file mode 100644
index 00000000000000..e54a468eb98e86
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/overridebuiltins.json
@@ -0,0 +1,73 @@
+[
+ {
+ "type": "interface",
+ "name": "StringMap2",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "length",
+ "escapedName": "length",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": true,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "lookup",
+ "escapedName": "lookup",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "key",
+ "escapedName": "key"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": [
+ {
+ "name": "OverrideBuiltins",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/partial-interface.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/partial-interface.json
new file mode 100644
index 00000000000000..d791262f465691
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/partial-interface.json
@@ -0,0 +1,55 @@
+[
+ {
+ "type": "interface",
+ "name": "Foo",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "bar",
+ "escapedName": "bar",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Foo",
+ "partial": true,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "quux",
+ "escapedName": "quux",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/primitives.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/primitives.json
new file mode 100644
index 00000000000000..a216221140c65a
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/primitives.json
@@ -0,0 +1,317 @@
+[
+ {
+ "type": "interface",
+ "name": "Primitives",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "boolean",
+ "extAttrs": []
+ },
+ "name": "truth",
+ "escapedName": "truth",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "byte",
+ "extAttrs": []
+ },
+ "name": "character",
+ "escapedName": "character",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "octet",
+ "extAttrs": []
+ },
+ "name": "value",
+ "escapedName": "value",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "short",
+ "extAttrs": []
+ },
+ "name": "number",
+ "escapedName": "number",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned short",
+ "extAttrs": []
+ },
+ "name": "positive",
+ "escapedName": "positive",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ },
+ "name": "big",
+ "escapedName": "big",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "bigpositive",
+ "escapedName": "bigpositive",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long long",
+ "extAttrs": []
+ },
+ "name": "bigbig",
+ "escapedName": "bigbig",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long long",
+ "extAttrs": []
+ },
+ "name": "bigbigpositive",
+ "escapedName": "bigbigpositive",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "real",
+ "escapedName": "real",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "double",
+ "extAttrs": []
+ },
+ "name": "bigreal",
+ "escapedName": "bigreal",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unrestricted float",
+ "extAttrs": []
+ },
+ "name": "realwithinfinity",
+ "escapedName": "realwithinfinity",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unrestricted double",
+ "extAttrs": []
+ },
+ "name": "bigrealwithinfinity",
+ "escapedName": "bigrealwithinfinity",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "string",
+ "escapedName": "string",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "ByteString",
+ "extAttrs": []
+ },
+ "name": "bytes",
+ "escapedName": "bytes",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Date",
+ "extAttrs": []
+ },
+ "name": "date",
+ "escapedName": "date",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "RegExp",
+ "extAttrs": []
+ },
+ "name": "regexp",
+ "escapedName": "regexp",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/promise-void.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/promise-void.json
new file mode 100644
index 00000000000000..7676838b8179d9
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/promise-void.json
@@ -0,0 +1,36 @@
+[
+ {
+ "type": "interface",
+ "name": "Cat",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": "Promise",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "name": "meow",
+ "escapedName": "meow",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/prototyperoot.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/prototyperoot.json
new file mode 100644
index 00000000000000..cec79ff66b6a49
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/prototyperoot.json
@@ -0,0 +1,36 @@
+[
+ {
+ "type": "interface",
+ "name": "Node",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned short",
+ "extAttrs": []
+ },
+ "name": "nodeType",
+ "escapedName": "nodeType",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": [
+ {
+ "name": "PrototypeRoot",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/putforwards.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/putforwards.json
new file mode 100644
index 00000000000000..951b3ef718ccc9
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/putforwards.json
@@ -0,0 +1,57 @@
+[
+ {
+ "type": "interface",
+ "name": "Person",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Name",
+ "extAttrs": []
+ },
+ "name": "name",
+ "escapedName": "name",
+ "extAttrs": [
+ {
+ "name": "PutForwards",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": {
+ "type": "identifier",
+ "value": "full"
+ }
+ }
+ ]
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned short",
+ "extAttrs": []
+ },
+ "name": "age",
+ "escapedName": "age",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/record.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/record.json
new file mode 100644
index 00000000000000..1be5427c74d6c8
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/record.json
@@ -0,0 +1,220 @@
+[
+ {
+ "type": "interface",
+ "name": "Foo",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "foo",
+ "escapedName": "foo",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": "sequence",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "argument-type",
+ "generic": "record",
+ "nullable": false,
+ "union": false,
+ "idlType": [
+ {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "ByteString",
+ "extAttrs": []
+ },
+ {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "any",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "name": "param",
+ "escapedName": "param"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": "record",
+ "nullable": false,
+ "union": false,
+ "idlType": [
+ {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ {
+ "type": "return-type",
+ "generic": null,
+ "nullable": true,
+ "union": true,
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ },
+ "name": "bar",
+ "escapedName": "bar",
+ "arguments": [],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": [
+ {
+ "name": "Constructor",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": "record",
+ "nullable": false,
+ "union": false,
+ "idlType": [
+ {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "USVString",
+ "extAttrs": []
+ },
+ {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "USVString",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ },
+ "name": "init",
+ "escapedName": "init"
+ }
+ ],
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ },
+ {
+ "type": "interface",
+ "name": "Bar",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": "record",
+ "nullable": false,
+ "union": false,
+ "idlType": [
+ {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": [
+ {
+ "name": "XAttr",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ }
+ ],
+ "extAttrs": []
+ },
+ "name": "bar",
+ "escapedName": "bar",
+ "arguments": [],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/reg-operations.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/reg-operations.json
new file mode 100644
index 00000000000000..8795c59ec87371
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/reg-operations.json
@@ -0,0 +1,166 @@
+[
+ {
+ "type": "interface",
+ "name": "Dimensions",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "width",
+ "escapedName": "width",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "height",
+ "escapedName": "height",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Button",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "boolean",
+ "extAttrs": []
+ },
+ "name": "isMouseOver",
+ "escapedName": "isMouseOver",
+ "arguments": [],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "setDimensions",
+ "escapedName": "setDimensions",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Dimensions",
+ "extAttrs": []
+ },
+ "name": "size",
+ "escapedName": "size"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "setDimensions",
+ "escapedName": "setDimensions",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "width",
+ "escapedName": "width"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "height",
+ "escapedName": "height"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/replaceable.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/replaceable.json
new file mode 100644
index 00000000000000..133891d461db31
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/replaceable.json
@@ -0,0 +1,56 @@
+[
+ {
+ "type": "interface",
+ "name": "Counter",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "value",
+ "escapedName": "value",
+ "extAttrs": [
+ {
+ "name": "Replaceable",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "increment",
+ "escapedName": "increment",
+ "arguments": [],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/sequence.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/sequence.json
new file mode 100644
index 00000000000000..ead0cdd862c786
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/sequence.json
@@ -0,0 +1,142 @@
+[
+ {
+ "type": "interface",
+ "name": "Canvas",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "drawPolygon",
+ "escapedName": "drawPolygon",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": "sequence",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "name": "coordinates",
+ "escapedName": "coordinates"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": "sequence",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "name": "getInflectionPoints",
+ "escapedName": "getInflectionPoints",
+ "arguments": [],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "I",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "f1",
+ "escapedName": "f1",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": "sequence",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": [
+ {
+ "name": "XAttr",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ },
+ "extAttrs": []
+ },
+ "name": "arg",
+ "escapedName": "arg"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/setlike.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/setlike.json
new file mode 100644
index 00000000000000..d8583538fd0f0a
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/setlike.json
@@ -0,0 +1,81 @@
+[
+ {
+ "type": "interface",
+ "name": "SetLike",
+ "partial": false,
+ "members": [
+ {
+ "type": "setlike",
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ }
+ ],
+ "readonly": false,
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "ReadOnlySetLike",
+ "partial": false,
+ "members": [
+ {
+ "type": "setlike",
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ }
+ ],
+ "readonly": true,
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "SetLikeExt",
+ "partial": false,
+ "members": [
+ {
+ "type": "setlike",
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": [
+ {
+ "name": "XAttr",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ }
+ ],
+ "readonly": false,
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/static.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/static.json
new file mode 100644
index 00000000000000..d0ddf35ec6e4b5
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/static.json
@@ -0,0 +1,160 @@
+[
+ {
+ "type": "interface",
+ "name": "Point",
+ "partial": false,
+ "members": [],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Circle",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "cx",
+ "escapedName": "cx",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "cy",
+ "escapedName": "cy",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "radius",
+ "escapedName": "radius",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": true,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ },
+ "name": "triangulationCount",
+ "escapedName": "triangulationCount",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": true,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Point",
+ "extAttrs": []
+ },
+ "name": "triangulate",
+ "escapedName": "triangulate",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Circle",
+ "extAttrs": []
+ },
+ "name": "c1",
+ "escapedName": "c1"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Circle",
+ "extAttrs": []
+ },
+ "name": "c2",
+ "escapedName": "c2"
+ },
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Circle",
+ "extAttrs": []
+ },
+ "name": "c3",
+ "escapedName": "c3"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/stringifier-attribute.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/stringifier-attribute.json
new file mode 100644
index 00000000000000..dbca7f17902c2b
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/stringifier-attribute.json
@@ -0,0 +1,54 @@
+[
+ {
+ "type": "interface",
+ "name": "Student",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "id",
+ "escapedName": "id",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": true,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "name",
+ "escapedName": "name",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": [
+ {
+ "name": "Constructor",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/stringifier-custom.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/stringifier-custom.json
new file mode 100644
index 00000000000000..c13df6ef10c0c5
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/stringifier-custom.json
@@ -0,0 +1,92 @@
+[
+ {
+ "type": "interface",
+ "name": "Student",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "id",
+ "escapedName": "id",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "familyName",
+ "escapedName": "familyName",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "givenName",
+ "escapedName": "givenName",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": true,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": null,
+ "escapedName": null,
+ "arguments": [],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": [
+ {
+ "name": "Constructor",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/stringifier.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/stringifier.json
new file mode 100644
index 00000000000000..a4f23e03f66d42
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/stringifier.json
@@ -0,0 +1,49 @@
+[
+ {
+ "type": "interface",
+ "name": "A",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": true,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": null,
+ "escapedName": null,
+ "arguments": [],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "B",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": true,
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/treatasnull.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/treatasnull.json
new file mode 100644
index 00000000000000..d5156bcb4f1b5d
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/treatasnull.json
@@ -0,0 +1,94 @@
+[
+ {
+ "type": "interface",
+ "name": "Dog",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "name",
+ "escapedName": "name",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "owner",
+ "escapedName": "owner",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "boolean",
+ "extAttrs": []
+ },
+ "name": "isMemberOfBreed",
+ "escapedName": "isMemberOfBreed",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [
+ {
+ "name": "TreatNullAs",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": {
+ "type": "identifier",
+ "value": "EmptyString"
+ }
+ }
+ ],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "breedName",
+ "escapedName": "breedName"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/treatasundefined.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/treatasundefined.json
new file mode 100644
index 00000000000000..1e98315d9f13bf
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/treatasundefined.json
@@ -0,0 +1,94 @@
+[
+ {
+ "type": "interface",
+ "name": "Cat",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "name",
+ "escapedName": "name",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "owner",
+ "escapedName": "owner",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "boolean",
+ "extAttrs": []
+ },
+ "name": "isMemberOfBreed",
+ "escapedName": "isMemberOfBreed",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [
+ {
+ "name": "TreatUndefinedAs",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": {
+ "type": "identifier",
+ "value": "EmptyString"
+ }
+ }
+ ],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "name": "breedName",
+ "escapedName": "breedName"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/typedef-union.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/typedef-union.json
new file mode 100644
index 00000000000000..31e24198ab130f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/typedef-union.json
@@ -0,0 +1,48 @@
+[
+ {
+ "type": "typedef",
+ "idlType": {
+ "type": "typedef-type",
+ "generic": null,
+ "nullable": false,
+ "union": true,
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "ImageData",
+ "extAttrs": []
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "HTMLImageElement",
+ "extAttrs": []
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "HTMLCanvasElement",
+ "extAttrs": []
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "HTMLVideoElement",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ },
+ "name": "TexImageSource",
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/typedef.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/typedef.json
new file mode 100644
index 00000000000000..35f988e721e75e
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/typedef.json
@@ -0,0 +1,233 @@
+[
+ {
+ "type": "interface",
+ "name": "Point",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "x",
+ "escapedName": "x",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ "name": "y",
+ "escapedName": "y",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "typedef",
+ "idlType": {
+ "type": "typedef-type",
+ "generic": "sequence",
+ "nullable": false,
+ "union": false,
+ "idlType": {
+ "type": "typedef-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Point",
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "name": "PointSequence",
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Rect",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Point",
+ "extAttrs": []
+ },
+ "name": "topleft",
+ "escapedName": "topleft",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Point",
+ "extAttrs": []
+ },
+ "name": "bottomright",
+ "escapedName": "bottomright",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "interface",
+ "name": "Widget",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Rect",
+ "extAttrs": []
+ },
+ "name": "bounds",
+ "escapedName": "bounds",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "boolean",
+ "extAttrs": []
+ },
+ "name": "pointWithinBounds",
+ "escapedName": "pointWithinBounds",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Point",
+ "extAttrs": []
+ },
+ "name": "p",
+ "escapedName": "p"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "boolean",
+ "extAttrs": []
+ },
+ "name": "allPointsWithinBounds",
+ "escapedName": "allPointsWithinBounds",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "PointSequence",
+ "extAttrs": []
+ },
+ "name": "ps",
+ "escapedName": "ps"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ },
+ {
+ "type": "typedef",
+ "idlType": {
+ "type": "typedef-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "octet",
+ "extAttrs": [
+ {
+ "name": "Clamp",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ },
+ "name": "value",
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/typesuffixes.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/typesuffixes.json
new file mode 100644
index 00000000000000..52870c22f4b476
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/typesuffixes.json
@@ -0,0 +1,55 @@
+[
+ {
+ "type": "interface",
+ "name": "Suffixes",
+ "partial": false,
+ "members": [
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "test",
+ "escapedName": "test",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": false,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": "sequence",
+ "nullable": true,
+ "union": false,
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": true,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ },
+ "extAttrs": []
+ },
+ "name": "foo",
+ "escapedName": "foo"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/uniontype.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/uniontype.json
new file mode 100644
index 00000000000000..90eb074ef3a0a7
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/uniontype.json
@@ -0,0 +1,130 @@
+[
+ {
+ "type": "interface",
+ "name": "Union",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": true,
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "float",
+ "extAttrs": []
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": true,
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Date",
+ "extAttrs": []
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Event",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": true,
+ "union": true,
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Node",
+ "extAttrs": []
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "DOMString",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ },
+ "name": "test",
+ "escapedName": "test",
+ "extAttrs": []
+ },
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": false,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": true,
+ "idlType": [
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": [
+ {
+ "name": "EnforceRange",
+ "arguments": null,
+ "type": "extended-attribute",
+ "rhs": null
+ }
+ ]
+ },
+ {
+ "type": null,
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "Date",
+ "extAttrs": []
+ }
+ ],
+ "extAttrs": []
+ },
+ "name": "test2",
+ "escapedName": "test2",
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/variadic-operations.json b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/variadic-operations.json
new file mode 100644
index 00000000000000..3280b7b6a78a94
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/variadic-operations.json
@@ -0,0 +1,103 @@
+[
+ {
+ "type": "interface",
+ "name": "IntegerSet",
+ "partial": false,
+ "members": [
+ {
+ "type": "attribute",
+ "static": false,
+ "stringifier": false,
+ "inherit": false,
+ "readonly": true,
+ "idlType": {
+ "type": "attribute-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "unsigned long",
+ "extAttrs": []
+ },
+ "name": "cardinality",
+ "escapedName": "cardinality",
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "union",
+ "escapedName": "union",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": true,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ },
+ "name": "ints",
+ "escapedName": "ints"
+ }
+ ],
+ "extAttrs": []
+ },
+ {
+ "type": "operation",
+ "getter": false,
+ "setter": false,
+ "deleter": false,
+ "static": false,
+ "stringifier": false,
+ "idlType": {
+ "type": "return-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "void",
+ "extAttrs": []
+ },
+ "name": "intersection",
+ "escapedName": "intersection",
+ "arguments": [
+ {
+ "optional": false,
+ "variadic": true,
+ "default": null,
+ "extAttrs": [],
+ "idlType": {
+ "type": "argument-type",
+ "generic": null,
+ "nullable": false,
+ "union": false,
+ "idlType": "long",
+ "extAttrs": []
+ },
+ "name": "ints",
+ "escapedName": "ints"
+ }
+ ],
+ "extAttrs": []
+ }
+ ],
+ "inheritance": null,
+ "extAttrs": []
+ }
+]
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/util/acquire.js b/test/fixtures/web-platform-tests/resources/webidl2/test/util/acquire.js
new file mode 100644
index 00000000000000..6f37dd6083c3c7
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/util/acquire.js
@@ -0,0 +1,8 @@
+"use strict";
+
+const { collect } = require("./collect");
+const fs = require("fs");
+
+for (const test of collect("syntax")) {
+ fs.writeFileSync(test.jsonPath, `${JSON.stringify(test.ast, null, 4)}\n`)
+}
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/util/collect.js b/test/fixtures/web-platform-tests/resources/webidl2/test/util/collect.js
new file mode 100644
index 00000000000000..7e3d9d3bf31267
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/util/collect.js
@@ -0,0 +1,59 @@
+"use strict";
+
+const wp = require("../../lib/webidl2");
+const pth = require("path");
+const fs = require("fs");
+const jdp = require("jsondiffpatch");
+
+/**
+ * Collects test items from the specified directory
+ * @param {string} base
+ */
+function* collect(base, { expectError } = {}) {
+ base = pth.join(__dirname, "..", base);
+ const dir = pth.join(base, "idl");
+ const idls = fs.readdirSync(dir)
+ .filter(it => (/\.widl$/).test(it))
+ .map(it => pth.join(dir, it));
+
+ for (const path of idls) {
+ const optFile = pth.join(base, "opt", pth.basename(path)).replace(".widl", ".json");
+ let opt;
+ if (fs.existsSync(optFile))
+ opt = JSON.parse(fs.readFileSync(optFile, "utf8"));
+
+ try {
+ const ast = wp.parse(fs.readFileSync(path, "utf8").replace(/\r\n/g, "\n"), opt);
+ yield new TestItem({ ast, path, opt });
+ }
+ catch (error) {
+ if (expectError) {
+ yield new TestItem({ path, error });
+ }
+ else {
+ throw error;
+ }
+ }
+ }
+};
+
+
+class TestItem {
+ constructor({ ast, path, error, opt }) {
+ this.ast = ast;
+ this.path = path;
+ this.error = error;
+ this.opt = opt;
+ this.jsonPath = pth.join(pth.dirname(path), "../json", pth.basename(path).replace(".widl", ".json"));
+ }
+
+ readJSON() {
+ return JSON.parse(fs.readFileSync(this.jsonPath, "utf8"));
+ }
+
+ diff(target = this.readJSON()) {
+ return jdp.diff(target, this.ast);
+ }
+}
+
+module.exports.collect = collect;
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/web/make-web-tests.js b/test/fixtures/web-platform-tests/resources/webidl2/test/web/make-web-tests.js
new file mode 100644
index 00000000000000..1774806994e0a7
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/web/make-web-tests.js
@@ -0,0 +1,52 @@
+
+// generates tests that work in a browser
+
+// XXX
+// have it run through valid and invalid properly
+
+var pth = require("path")
+, fs = require("fs")
+, dir = function (path) {
+ return pth.join(__dirname, "..", path);
+ }
+, allFromDir = function (dir, ext, asJSON) {
+ return fs.readdirSync(dir)
+ .filter(function (it) { return ext.test(it); })
+ .map(function (it) {
+ var cnt = fs.readFileSync(pth.join(dir, it), "utf8");
+ return asJSON ? JSON.parse(cnt) : cnt;
+ });
+ }
+, data = {
+ valid: {
+ json: allFromDir(dir("syntax/json"), /\.json$/, true)
+ , idl: allFromDir(dir("syntax/idl"), /\.w?idl$/, false)
+ }
+ , invalid:{
+ json: allFromDir(dir("invalid/json"), /\.json$/, true)
+ , idl: allFromDir(dir("invalid/idl"), /\.w?idl$/, false)
+ }
+ }
+, html = [
+ ""
+ , ""
+ , " "
+ , " WebIDL2 Browser Tests "
+ , " "
+ , " "
+ , "
"
+ , " "
+ , " "
+ , " "
+ , " "
+ , " "
+ , " "
+ , " "
+ , " "
+ , " "
+ , " "
+ , ""
+ ].join("\n")
+;
+
+fs.writeFileSync("browser-tests.html", html, "utf8");
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/web/run-tests.js b/test/fixtures/web-platform-tests/resources/webidl2/test/web/run-tests.js
new file mode 100644
index 00000000000000..452f799b2a6f03
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/web/run-tests.js
@@ -0,0 +1,48 @@
+
+describe("Parses all of the IDLs to produce the correct ASTs", function () {
+ for (var i = 0, n = data.valid.idl.length; i < n; i++) {
+ var idl = data.valid.idl[i], json = data.valid.json[i];
+ var func = (function (idl, json) {
+ return function () {
+ try {
+ // the AST contains NaN and +/-Infinity that cannot be serialised to JSON
+ // the stored JSON ASTs use the same replacement function as is used below
+ // so we compare based on that
+ var diff = jsondiffpatch.diff(json, WebIDL2.parse(idl));
+ if (diff && debug) console.log(JSON.stringify(diff, null, 4));
+ expect(diff).toBe(undefined);
+ }
+ catch (e) {
+ console.log(e.toString());
+ throw e;
+ }
+ };
+ }(idl, json));
+ it("should produce the same AST for " + i, func);
+ }
+});
+
+describe("Parses all of the invalid IDLs to check that they blow up correctly", function () {
+ for (var i = 0, n = data.invalid.idl.length; i < n; i++) {
+ var idl = data.invalid.idl[i], error = data.invalid.json[i];
+ var func = (function (idl, err) {
+ return function () {
+ var error;
+ try {
+ var ast = WebIDL2.parse(idl);
+ console.log(JSON.stringify(ast, null, 4));
+ }
+ catch (e) {
+ error = e;
+ }
+ finally {
+ expect(error).toExist();
+ expect(error.message).toEqual(err.message);
+ expect(error.line).toEqual(err.line);
+ }
+
+ };
+ }(idl, error));
+ it("should produce the right error for " + i, func);
+ }
+});
diff --git a/test/fixtures/web-platform-tests/resources/webidl2/test/writer.js b/test/fixtures/web-platform-tests/resources/webidl2/test/writer.js
new file mode 100644
index 00000000000000..e84076b4f28ce3
--- /dev/null
+++ b/test/fixtures/web-platform-tests/resources/webidl2/test/writer.js
@@ -0,0 +1,23 @@
+"use strict";
+
+const { collect } = require("./util/collect");
+const wp = require("../lib/webidl2");
+const writer = require("../lib/writer");
+const expect = require("expect");
+const debug = true;
+
+describe("Rewrite and parses all of the IDLs to produce the same ASTs", () => {
+ for (const test of collect("syntax")) {
+ it(`should produce the same AST for ${test.path}`, () => {
+ try {
+ const diff = test.diff(wp.parse(writer.write(test.ast), test.opt));
+ if (diff && debug) console.log(JSON.stringify(diff, null, 4));
+ expect(diff).toBe(undefined);
+ }
+ catch (e) {
+ console.log(e.toString());
+ throw e;
+ }
+ });
+ }
+});
diff --git a/test/fixtures/web-platform-tests/streams/META.yml b/test/fixtures/web-platform-tests/streams/META.yml
new file mode 100644
index 00000000000000..108c774fae6f0f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/META.yml
@@ -0,0 +1,8 @@
+spec: https://streams.spec.whatwg.org/
+suggested_reviewers:
+ - domenic
+ - yutakahirano
+ - youennf
+ - calvaris
+ - wanderview
+ - ricea
diff --git a/test/fixtures/web-platform-tests/streams/README.md b/test/fixtures/web-platform-tests/streams/README.md
new file mode 100644
index 00000000000000..8868a858e5f49a
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/README.md
@@ -0,0 +1,14 @@
+# Streams Tests
+
+The work on the streams tests is closely tracked by the specification authors, who maintain a reference implementation intended to match the spec line-by-line while passing all of these tests. See [the whatwg/streams repository for details](https://github.com/whatwg/streams/tree/master/reference-implementation). Some tests may be in that repository while the spec sections they test are still undergoing heavy churn.
+
+## Generating wrapper files
+
+Because the streams feature is supposed to work in all global contexts, each test is written as a `.js` file, and then four `.html` files are generated around it. So for example, for `count-queueing-strategy.js`, we have the wrapper files:
+
+- `count-queueing-strategy.https.html`
+- `count-queueing-strategy.dedicatedworker.html`
+- `count-queueing-strategy-sharedworker.html`
+- `count-queueing-strategy-serviceworker.html`
+
+These are generated automatically by the Node.js script in `generate-test-wrappers.js`. See it for details, and please remember to use it whenever adding new tests.
diff --git a/test/fixtures/web-platform-tests/streams/byte-length-queuing-strategy.js b/test/fixtures/web-platform-tests/streams/byte-length-queuing-strategy.js
new file mode 100644
index 00000000000000..e96e68ee406a56
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/byte-length-queuing-strategy.js
@@ -0,0 +1,114 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+test(() => {
+
+ new ByteLengthQueuingStrategy({ highWaterMark: 4 });
+
+}, 'Can construct a ByteLengthQueuingStrategy with a valid high water mark');
+
+test(() => {
+
+ for (const highWaterMark of [-Infinity, NaN, 'foo', {}, () => {}]) {
+ const strategy = new ByteLengthQueuingStrategy({ highWaterMark });
+ assert_equals(strategy.highWaterMark, highWaterMark, `${highWaterMark} gets set correctly`);
+ }
+
+}, 'Can construct a ByteLengthQueuingStrategy with any value as its high water mark');
+
+test(() => {
+
+ const highWaterMark = 1;
+ const highWaterMarkObjectGetter = {
+ get highWaterMark() { return highWaterMark; }
+ };
+ const error = new Error('wow!');
+ const highWaterMarkObjectGetterThrowing = {
+ get highWaterMark() { throw error; }
+ };
+
+ assert_throws({ name: 'TypeError' }, () => new ByteLengthQueuingStrategy(), 'construction fails with undefined');
+ assert_throws({ name: 'TypeError' }, () => new ByteLengthQueuingStrategy(null), 'construction fails with null');
+ assert_throws({ name: 'Error' }, () => new ByteLengthQueuingStrategy(highWaterMarkObjectGetterThrowing),
+ 'construction fails with an object with a throwing highWaterMark getter');
+
+ // Should not fail:
+ new ByteLengthQueuingStrategy('potato');
+ new ByteLengthQueuingStrategy({});
+ new ByteLengthQueuingStrategy(highWaterMarkObjectGetter);
+
+}, 'ByteLengthQueuingStrategy constructor behaves as expected with strange arguments');
+
+test(() => {
+
+ const size = 1024;
+ const chunk = { byteLength: size };
+ const chunkGetter = {
+ get byteLength() { return size; }
+ };
+ const error = new Error('wow!');
+ const chunkGetterThrowing = {
+ get byteLength() { throw error; }
+ };
+ assert_throws({ name: 'TypeError' }, () => ByteLengthQueuingStrategy.prototype.size(), 'size fails with undefined');
+ assert_throws({ name: 'TypeError' }, () => ByteLengthQueuingStrategy.prototype.size(null), 'size fails with null');
+ assert_equals(ByteLengthQueuingStrategy.prototype.size('potato'), undefined,
+ 'size succeeds with undefined with a random non-object type');
+ assert_equals(ByteLengthQueuingStrategy.prototype.size({}), undefined,
+ 'size succeeds with undefined with an object without hwm property');
+ assert_equals(ByteLengthQueuingStrategy.prototype.size(chunk), size,
+ 'size succeeds with the right amount with an object with a hwm');
+ assert_equals(ByteLengthQueuingStrategy.prototype.size(chunkGetter), size,
+ 'size succeeds with the right amount with an object with a hwm getter');
+ assert_throws({ name: 'Error' }, () => ByteLengthQueuingStrategy.prototype.size(chunkGetterThrowing),
+ 'size fails with the error thrown by the getter');
+
+}, 'ByteLengthQueuingStrategy size behaves as expected with strange arguments');
+
+test(() => {
+
+ const thisValue = null;
+ const returnValue = { 'returned from': 'byteLength getter' };
+ const chunk = {
+ get byteLength() { return returnValue; }
+ };
+
+ assert_equals(ByteLengthQueuingStrategy.prototype.size.call(thisValue, chunk), returnValue);
+
+}, 'ByteLengthQueuingStrategy.prototype.size should work generically on its this and its arguments');
+
+test(() => {
+
+ const strategy = new ByteLengthQueuingStrategy({ highWaterMark: 4 });
+
+ assert_object_equals(Object.getOwnPropertyDescriptor(strategy, 'highWaterMark'),
+ { value: 4, writable: true, enumerable: true, configurable: true },
+ 'highWaterMark property should be a data property with the value passed the constructor');
+ assert_equals(typeof strategy.size, 'function');
+
+}, 'ByteLengthQueuingStrategy instances have the correct properties');
+
+test(() => {
+
+ const strategy = new ByteLengthQueuingStrategy({ highWaterMark: 4 });
+ assert_equals(strategy.highWaterMark, 4);
+
+ strategy.highWaterMark = 10;
+ assert_equals(strategy.highWaterMark, 10);
+
+ strategy.highWaterMark = 'banana';
+ assert_equals(strategy.highWaterMark, 'banana');
+
+}, 'ByteLengthQueuingStrategy\'s highWaterMark property can be set to anything');
+
+test(() => {
+
+ assert_equals(ByteLengthQueuingStrategy.name, 'ByteLengthQueuingStrategy',
+ 'ByteLengthQueuingStrategy.name must be "ByteLengthQueuingStrategy"');
+
+}, 'ByteLengthQueuingStrategy.name is correct');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/count-queuing-strategy.js b/test/fixtures/web-platform-tests/streams/count-queuing-strategy.js
new file mode 100644
index 00000000000000..8da8eb679def31
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/count-queuing-strategy.js
@@ -0,0 +1,113 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+test(() => {
+
+ new CountQueuingStrategy({ highWaterMark: 4 });
+
+}, 'Can construct a CountQueuingStrategy with a valid high water mark');
+
+test(() => {
+
+ for (const highWaterMark of [-Infinity, NaN, 'foo', {}, () => {}]) {
+ const strategy = new CountQueuingStrategy({ highWaterMark });
+ assert_equals(strategy.highWaterMark, highWaterMark, `${highWaterMark} gets set correctly`);
+ }
+
+}, 'Can construct a CountQueuingStrategy with any value as its high water mark');
+
+test(() => {
+
+ const highWaterMark = 1;
+ const highWaterMarkObjectGetter = {
+ get highWaterMark() { return highWaterMark; }
+ };
+ const error = new Error('wow!');
+ const highWaterMarkObjectGetterThrowing = {
+ get highWaterMark() { throw error; }
+ };
+
+ assert_throws({ name: 'TypeError' }, () => new CountQueuingStrategy(), 'construction fails with undefined');
+ assert_throws({ name: 'TypeError' }, () => new CountQueuingStrategy(null), 'construction fails with null');
+ assert_throws({ name: 'Error' }, () => new CountQueuingStrategy(highWaterMarkObjectGetterThrowing),
+ 'construction fails with an object with a throwing highWaterMark getter');
+
+ // Should not fail:
+ new CountQueuingStrategy('potato');
+ new CountQueuingStrategy({});
+ new CountQueuingStrategy(highWaterMarkObjectGetter);
+
+}, 'CountQueuingStrategy constructor behaves as expected with strange arguments');
+
+
+test(() => {
+
+ const thisValue = null;
+ const chunk = {
+ get byteLength() {
+ throw new TypeError('shouldn\'t be called');
+ }
+ };
+
+ assert_equals(CountQueuingStrategy.prototype.size.call(thisValue, chunk), 1);
+
+}, 'CountQueuingStrategy.prototype.size should work generically on its this and its arguments');
+
+test(() => {
+
+ const size = 1024;
+ const chunk = { byteLength: size };
+ const chunkGetter = {
+ get byteLength() { return size; }
+ };
+ const error = new Error('wow!');
+ const chunkGetterThrowing = {
+ get byteLength() { throw error; }
+ };
+
+ assert_equals(CountQueuingStrategy.prototype.size(), 1, 'size returns 1 with undefined');
+ assert_equals(CountQueuingStrategy.prototype.size(null), 1, 'size returns 1 with null');
+ assert_equals(CountQueuingStrategy.prototype.size('potato'), 1, 'size returns 1 with non-object type');
+ assert_equals(CountQueuingStrategy.prototype.size({}), 1, 'size returns 1 with empty object');
+ assert_equals(CountQueuingStrategy.prototype.size(chunk), 1, 'size returns 1 with a chunk');
+ assert_equals(CountQueuingStrategy.prototype.size(chunkGetter), 1, 'size returns 1 with chunk getter');
+ assert_equals(CountQueuingStrategy.prototype.size(chunkGetterThrowing), 1,
+ 'size returns 1 with chunk getter that throws');
+
+}, 'CountQueuingStrategy size behaves as expected with strange arguments');
+
+test(() => {
+
+ const strategy = new CountQueuingStrategy({ highWaterMark: 4 });
+
+ assert_object_equals(Object.getOwnPropertyDescriptor(strategy, 'highWaterMark'),
+ { value: 4, writable: true, enumerable: true, configurable: true },
+ 'highWaterMark property should be a data property with the value passed the constructor');
+ assert_equals(typeof strategy.size, 'function');
+
+}, 'CountQueuingStrategy instances have the correct properties');
+
+test(() => {
+
+ const strategy = new CountQueuingStrategy({ highWaterMark: 4 });
+ assert_equals(strategy.highWaterMark, 4);
+
+ strategy.highWaterMark = 10;
+ assert_equals(strategy.highWaterMark, 10);
+
+ strategy.highWaterMark = 'banana';
+ assert_equals(strategy.highWaterMark, 'banana');
+
+}, 'CountQueuingStrategy\'s highWaterMark property can be set to anything');
+
+test(() => {
+
+ assert_equals(CountQueuingStrategy.name, 'CountQueuingStrategy',
+ 'CountQueuingStrategy.name must be "CountQueuingStrategy"');
+
+}, 'CountQueuingStrategy.name is correct');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/generate-test-wrappers.js b/test/fixtures/web-platform-tests/streams/generate-test-wrappers.js
new file mode 100644
index 00000000000000..22e5c786bbc4d7
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/generate-test-wrappers.js
@@ -0,0 +1,99 @@
+"use strict";
+// Usage: `node generate-test-wrappers.js js-filename1.js [js-filename2.js ...]` will generate:
+// - js-filename1.html
+// - js-filename1.sharedworker.html
+// - js-filename1.dedicatedworker.html
+// - js-filename1.serviceworker.https.html
+// (for each passed filename)
+//
+// It will turn any importScripts inside the .js file into `)
+ .join('\n');
+
+ const basename = path.basename(jsFilename);
+ const noExtension = path.basename(jsFilename, '.js');
+
+ const outputs = {
+ '.html': `
+
+${basename} browser context wrapper file
+
+
+
+
+${importedScriptTags}
+
+
+`,
+ '.dedicatedworker.html': `
+
+${basename} dedicated worker wrapper file
+
+
+
+
+
+`,
+ '.sharedworker.html': `
+
+${basename} shared worker wrapper file
+
+
+
+
+
+`,
+ '.serviceworker.https.html': `
+
+${basename} service worker wrapper file
+
+
+
+
+
+
+`
+ };
+
+ for (const [key, value] of Object.entries(outputs)) {
+ const destFilename = path.resolve(path.dirname(jsFilename), `${noExtension}${key}`);
+ fs.writeFileSync(destFilename, value, { encoding: 'utf-8' });
+ }
+}
+
+function findImportedScriptFilenames(inputFilename) {
+ const scriptContents = fs.readFileSync(inputFilename, { encoding: 'utf-8' });
+
+ const regExp = /self\.importScripts\('([^']+)'\);/g;
+
+ let result = [];
+ let match;
+ while (match = regExp.exec(scriptContents)) {
+ result.push(match[1]);
+ }
+
+ return result.filter(x => x !== '/resources/testharness.js');
+}
diff --git a/test/fixtures/web-platform-tests/streams/piping/close-propagation-backward.js b/test/fixtures/web-platform-tests/streams/piping/close-propagation-backward.js
new file mode 100644
index 00000000000000..31207e99672ba7
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/piping/close-propagation-backward.js
@@ -0,0 +1,158 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+const error1 = new Error('error1!');
+error1.name = 'error1';
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+ const writer = ws.getWriter();
+ writer.close();
+ writer.releaseLock();
+
+ return rs.pipeTo(ws).then(
+ () => assert_unreached('the promise must not fulfill'),
+ err => {
+ assert_equals(err.name, 'TypeError', 'the promise must reject with a TypeError');
+
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', err]);
+ assert_array_equals(ws.events, ['close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ ws.getWriter().closed
+ ]);
+ }
+ );
+
+}, 'Closing must be propagated backward: starts closed; preventCancel omitted; fulfilled cancel promise');
+
+promise_test(t => {
+
+ // Our recording streams do not deal well with errors generated by the system, so give them some help
+ let recordedError;
+ const rs = recordingReadableStream({
+ cancel(cancelErr) {
+ recordedError = cancelErr;
+ throw error1;
+ }
+ });
+
+ const ws = recordingWritableStream();
+ const writer = ws.getWriter();
+ writer.close();
+ writer.releaseLock();
+
+ return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error').then(() => {
+ assert_equals(recordedError.name, 'TypeError', 'the cancel reason must be a TypeError');
+
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', recordedError]);
+ assert_array_equals(ws.events, ['close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ ws.getWriter().closed
+ ]);
+ });
+
+}, 'Closing must be propagated backward: starts closed; preventCancel omitted; rejected cancel promise');
+
+for (const falsy of [undefined, null, false, +0, -0, NaN, '']) {
+ const stringVersion = Object.is(falsy, -0) ? '-0' : String(falsy);
+
+ promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+ const writer = ws.getWriter();
+ writer.close();
+ writer.releaseLock();
+
+ return rs.pipeTo(ws, { preventCancel: falsy }).then(
+ () => assert_unreached('the promise must not fulfill'),
+ err => {
+ assert_equals(err.name, 'TypeError', 'the promise must reject with a TypeError');
+
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', err]);
+ assert_array_equals(ws.events, ['close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ ws.getWriter().closed
+ ]);
+ }
+ );
+
+ }, `Closing must be propagated backward: starts closed; preventCancel = ${stringVersion} (falsy); fulfilled cancel ` +
+ `promise`);
+}
+
+for (const truthy of [true, 'a', 1, Symbol(), { }]) {
+ promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+ const writer = ws.getWriter();
+ writer.close();
+ writer.releaseLock();
+
+ return promise_rejects(t, new TypeError(), rs.pipeTo(ws, { preventCancel: truthy })).then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['close']);
+
+ return ws.getWriter().closed;
+ });
+
+ }, `Closing must be propagated backward: starts closed; preventCancel = ${String(truthy)} (truthy)`);
+}
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+ const writer = ws.getWriter();
+ writer.close();
+ writer.releaseLock();
+
+ return promise_rejects(t, new TypeError(), rs.pipeTo(ws, { preventCancel: true, preventAbort: true }))
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['close']);
+
+ return ws.getWriter().closed;
+ });
+
+}, 'Closing must be propagated backward: starts closed; preventCancel = true, preventAbort = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+ const writer = ws.getWriter();
+ writer.close();
+ writer.releaseLock();
+
+ return promise_rejects(t, new TypeError(),
+ rs.pipeTo(ws, { preventCancel: true, preventAbort: true, preventClose: true }))
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['close']);
+
+ return ws.getWriter().closed;
+ });
+
+}, 'Closing must be propagated backward: starts closed; preventCancel = true, preventAbort = true, preventClose ' +
+ '= true');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/piping/close-propagation-forward.js b/test/fixtures/web-platform-tests/streams/piping/close-propagation-forward.js
new file mode 100644
index 00000000000000..9b1546d80c5477
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/piping/close-propagation-forward.js
@@ -0,0 +1,594 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+const error1 = new Error('error1!');
+error1.name = 'error1';
+
+promise_test(() => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.close();
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ return rs.pipeTo(ws).then(value => {
+ assert_equals(value, undefined, 'the promise must fulfill with undefined');
+ })
+ .then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, ['close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ ws.getWriter().closed
+ ]);
+ });
+
+}, 'Closing must be propagated forward: starts closed; preventClose omitted; fulfilled close promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.close();
+ }
+ });
+
+ const ws = recordingWritableStream({
+ close() {
+ throw error1;
+ }
+ });
+
+ return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error').then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, ['close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ promise_rejects(t, error1, ws.getWriter().closed)
+ ]);
+ });
+
+}, 'Closing must be propagated forward: starts closed; preventClose omitted; rejected close promise');
+
+for (const falsy of [undefined, null, false, +0, -0, NaN, '']) {
+ const stringVersion = Object.is(falsy, -0) ? '-0' : String(falsy);
+
+ promise_test(() => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.close();
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ return rs.pipeTo(ws, { preventClose: falsy }).then(value => {
+ assert_equals(value, undefined, 'the promise must fulfill with undefined');
+ })
+ .then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, ['close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ ws.getWriter().closed
+ ]);
+ });
+
+ }, `Closing must be propagated forward: starts closed; preventClose = ${stringVersion} (falsy); fulfilled close ` +
+ `promise`);
+}
+
+for (const truthy of [true, 'a', 1, Symbol(), { }]) {
+ promise_test(() => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.close();
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ return rs.pipeTo(ws, { preventClose: truthy }).then(value => {
+ assert_equals(value, undefined, 'the promise must fulfill with undefined');
+ })
+ .then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, []);
+
+ return rs.getReader().closed;
+ });
+
+ }, `Closing must be propagated forward: starts closed; preventClose = ${String(truthy)} (truthy)`);
+}
+
+promise_test(() => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.close();
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ return rs.pipeTo(ws, { preventClose: true, preventAbort: true }).then(value => {
+ assert_equals(value, undefined, 'the promise must fulfill with undefined');
+ })
+ .then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, []);
+
+ return rs.getReader().closed;
+ });
+
+}, 'Closing must be propagated forward: starts closed; preventClose = true, preventAbort = true');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.close();
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ return rs.pipeTo(ws, { preventClose: true, preventAbort: true, preventCancel: true }).then(value => {
+ assert_equals(value, undefined, 'the promise must fulfill with undefined');
+ })
+ .then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, []);
+
+ return rs.getReader().closed;
+ });
+
+}, 'Closing must be propagated forward: starts closed; preventClose = true, preventAbort = true, preventCancel = true');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+
+ const pipePromise = rs.pipeTo(ws);
+
+ setTimeout(() => rs.controller.close());
+
+ return pipePromise.then(value => {
+ assert_equals(value, undefined, 'the promise must fulfill with undefined');
+ })
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ ws.getWriter().closed
+ ]);
+ });
+
+}, 'Closing must be propagated forward: becomes closed asynchronously; preventClose omitted; fulfilled close promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ close() {
+ throw error1;
+ }
+ });
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
+
+ setTimeout(() => rs.controller.close());
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ promise_rejects(t, error1, ws.getWriter().closed)
+ ]);
+ });
+
+}, 'Closing must be propagated forward: becomes closed asynchronously; preventClose omitted; rejected close promise');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+
+ const pipePromise = rs.pipeTo(ws, { preventClose: true });
+
+ setTimeout(() => rs.controller.close());
+
+ return pipePromise.then(value => {
+ assert_equals(value, undefined, 'the promise must fulfill with undefined');
+ })
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, []);
+
+ return rs.getReader().closed;
+ });
+
+}, 'Closing must be propagated forward: becomes closed asynchronously; preventClose = true');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = rs.pipeTo(ws);
+
+ setTimeout(() => rs.controller.close());
+
+ return pipePromise.then(value => {
+ assert_equals(value, undefined, 'the promise must fulfill with undefined');
+ })
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ ws.getWriter().closed
+ ]);
+ });
+
+}, 'Closing must be propagated forward: becomes closed asynchronously; dest never desires chunks; ' +
+ 'preventClose omitted; fulfilled close promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ close() {
+ throw error1;
+ }
+ }, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
+
+ setTimeout(() => rs.controller.close());
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ promise_rejects(t, error1, ws.getWriter().closed)
+ ]);
+ });
+
+}, 'Closing must be propagated forward: becomes closed asynchronously; dest never desires chunks; ' +
+ 'preventClose omitted; rejected close promise');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = rs.pipeTo(ws, { preventClose: true });
+
+ setTimeout(() => rs.controller.close());
+
+ return pipePromise.then(value => {
+ assert_equals(value, undefined, 'the promise must fulfill with undefined');
+ })
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, []);
+
+ return rs.getReader().closed;
+ });
+
+}, 'Closing must be propagated forward: becomes closed asynchronously; dest never desires chunks; ' +
+ 'preventClose = true');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+
+ const pipePromise = rs.pipeTo(ws);
+
+ setTimeout(() => {
+ rs.controller.enqueue('Hello');
+ setTimeout(() => rs.controller.close());
+ }, 10);
+
+ return pipePromise.then(value => {
+ assert_equals(value, undefined, 'the promise must fulfill with undefined');
+ })
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'Hello', 'close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ ws.getWriter().closed
+ ]);
+ });
+
+}, 'Closing must be propagated forward: becomes closed after one chunk; preventClose omitted; fulfilled close promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ close() {
+ throw error1;
+ }
+ });
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
+
+ setTimeout(() => {
+ rs.controller.enqueue('Hello');
+ setTimeout(() => rs.controller.close());
+ }, 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'Hello', 'close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ promise_rejects(t, error1, ws.getWriter().closed)
+ ]);
+ });
+
+}, 'Closing must be propagated forward: becomes closed after one chunk; preventClose omitted; rejected close promise');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+
+ const pipePromise = rs.pipeTo(ws, { preventClose: true });
+
+ setTimeout(() => {
+ rs.controller.enqueue('Hello');
+ setTimeout(() => rs.controller.close());
+ }, 10);
+
+ return pipePromise.then(value => {
+ assert_equals(value, undefined, 'the promise must fulfill with undefined');
+ })
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'Hello']);
+
+ return rs.getReader().closed;
+ });
+
+}, 'Closing must be propagated forward: becomes closed after one chunk; preventClose = true');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ let resolveWritePromise;
+ const ws = recordingWritableStream({
+ write() {
+ return new Promise(resolve => {
+ resolveWritePromise = resolve;
+ });
+ }
+ });
+
+ let pipeComplete = false;
+ const pipePromise = rs.pipeTo(ws).then(() => {
+ pipeComplete = true;
+ });
+
+ rs.controller.enqueue('a');
+ rs.controller.close();
+
+ // Flush async events and verify that no shutdown occurs.
+ return flushAsyncEvents().then(() => {
+ assert_array_equals(ws.events, ['write', 'a']); // no 'close'
+ assert_equals(pipeComplete, false, 'the pipe must not be complete');
+
+ resolveWritePromise();
+
+ return pipePromise.then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'close']);
+ });
+ });
+
+}, 'Closing must be propagated forward: shutdown must not occur until the final write completes');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ let resolveWritePromise;
+ const ws = recordingWritableStream({
+ write() {
+ return new Promise(resolve => {
+ resolveWritePromise = resolve;
+ });
+ }
+ });
+
+ let pipeComplete = false;
+ const pipePromise = rs.pipeTo(ws, { preventClose: true }).then(() => {
+ pipeComplete = true;
+ });
+
+ rs.controller.enqueue('a');
+ rs.controller.close();
+
+ // Flush async events and verify that no shutdown occurs.
+ return flushAsyncEvents().then(() => {
+ assert_array_equals(ws.events, ['write', 'a'],
+ 'the chunk must have been written, but close must not have happened');
+ assert_equals(pipeComplete, false, 'the pipe must not be complete');
+
+ resolveWritePromise();
+
+ return pipePromise;
+ }).then(() => flushAsyncEvents()).then(() => {
+ assert_array_equals(ws.events, ['write', 'a'],
+ 'the chunk must have been written, but close must not have happened');
+ });
+
+}, 'Closing must be propagated forward: shutdown must not occur until the final write completes; preventClose = true');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ let resolveWriteCalled;
+ const writeCalledPromise = new Promise(resolve => {
+ resolveWriteCalled = resolve;
+ });
+
+ let resolveWritePromise;
+ const ws = recordingWritableStream({
+ write() {
+ resolveWriteCalled();
+
+ return new Promise(resolve => {
+ resolveWritePromise = resolve;
+ });
+ }
+ }, new CountQueuingStrategy({ highWaterMark: 2 }));
+
+ let pipeComplete = false;
+ const pipePromise = rs.pipeTo(ws).then(() => {
+ pipeComplete = true;
+ });
+
+ rs.controller.enqueue('a');
+ rs.controller.enqueue('b');
+
+ return writeCalledPromise.then(() => flushAsyncEvents()).then(() => {
+ assert_array_equals(ws.events, ['write', 'a'],
+ 'the first chunk must have been written, but close must not have happened yet');
+ assert_false(pipeComplete, 'the pipe should not complete while the first write is pending');
+
+ rs.controller.close();
+ resolveWritePromise();
+ }).then(() => flushAsyncEvents()).then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+ 'the second chunk must have been written, but close must not have happened yet');
+ assert_false(pipeComplete, 'the pipe should not complete while the second write is pending');
+
+ resolveWritePromise();
+ return pipePromise;
+ }).then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b', 'close'],
+ 'all chunks must have been written and close must have happened');
+ });
+
+}, 'Closing must be propagated forward: shutdown must not occur until the final write completes; becomes closed after first write');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ let resolveWriteCalled;
+ const writeCalledPromise = new Promise(resolve => {
+ resolveWriteCalled = resolve;
+ });
+
+ let resolveWritePromise;
+ const ws = recordingWritableStream({
+ write() {
+ resolveWriteCalled();
+
+ return new Promise(resolve => {
+ resolveWritePromise = resolve;
+ });
+ }
+ }, new CountQueuingStrategy({ highWaterMark: 2 }));
+
+ let pipeComplete = false;
+ const pipePromise = rs.pipeTo(ws, { preventClose: true }).then(() => {
+ pipeComplete = true;
+ });
+
+ rs.controller.enqueue('a');
+ rs.controller.enqueue('b');
+
+ return writeCalledPromise.then(() => flushAsyncEvents()).then(() => {
+ assert_array_equals(ws.events, ['write', 'a'],
+ 'the first chunk must have been written, but close must not have happened');
+ assert_false(pipeComplete, 'the pipe should not complete while the first write is pending');
+
+ rs.controller.close();
+ resolveWritePromise();
+ }).then(() => flushAsyncEvents()).then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+ 'the second chunk must have been written, but close must not have happened');
+ assert_false(pipeComplete, 'the pipe should not complete while the second write is pending');
+
+ resolveWritePromise();
+ return pipePromise;
+ }).then(() => flushAsyncEvents()).then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+ 'all chunks must have been written, but close must not have happened');
+ });
+
+}, 'Closing must be propagated forward: shutdown must not occur until the final write completes; becomes closed after first write; preventClose = true');
+
+
+promise_test(t => {
+ const rs = recordingReadableStream({
+ start(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ c.close();
+ }
+ });
+ let rejectWritePromise;
+ const ws = recordingWritableStream({
+ write() {
+ return new Promise((resolve, reject) => {
+ rejectWritePromise = reject;
+ });
+ }
+ }, { highWaterMark: 3 });
+ const pipeToPromise = rs.pipeTo(ws);
+ return delay(0).then(() => {
+ rejectWritePromise(error1);
+ return promise_rejects(t, error1, pipeToPromise, 'pipeTo should reject');
+ }).then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, ['write', 'a']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ promise_rejects(t, error1, ws.getWriter().closed, 'ws should be errored')
+ ]);
+ });
+}, 'Closing must be propagated forward: erroring the writable while flushing pending writes should error pipeTo');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/piping/error-propagation-backward.js b/test/fixtures/web-platform-tests/streams/piping/error-propagation-backward.js
new file mode 100644
index 00000000000000..dda5774e1c9ae6
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/piping/error-propagation-backward.js
@@ -0,0 +1,635 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+const error1 = new Error('error1!');
+error1.name = 'error1';
+
+const error2 = new Error('error2!');
+error2.name = 'error2';
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ start() {
+ return Promise.reject(error1);
+ }
+ });
+
+ return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error')
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, []);
+ });
+
+}, 'Errors must be propagated backward: starts errored; preventCancel omitted; fulfilled cancel promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ write() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.write('Hello'), 'writer.write() must reject with the write error')
+ .then(() => promise_rejects(t, error1, writer.closed, 'writer.closed must reject with the write error'))
+ .then(() => {
+ writer.releaseLock();
+
+ return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the write error')
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, ['write', 'Hello']);
+ });
+ });
+
+}, 'Errors must be propagated backward: becomes errored before piping due to write; preventCancel omitted; ' +
+ 'fulfilled cancel promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ cancel() {
+ throw error2;
+ }
+ });
+
+ const ws = recordingWritableStream({
+ write() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.write('Hello'), 'writer.write() must reject with the write error')
+ .then(() => promise_rejects(t, error1, writer.closed, 'writer.closed must reject with the write error'))
+ .then(() => {
+ writer.releaseLock();
+
+ return promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the cancel error')
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, ['write', 'Hello']);
+ });
+ });
+
+}, 'Errors must be propagated backward: becomes errored before piping due to write; preventCancel omitted; rejected ' +
+ 'cancel promise');
+
+for (const falsy of [undefined, null, false, +0, -0, NaN, '']) {
+ const stringVersion = Object.is(falsy, -0) ? '-0' : String(falsy);
+
+ promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ write() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.write('Hello'), 'writer.write() must reject with the write error')
+ .then(() => promise_rejects(t, error1, writer.closed, 'writer.closed must reject with the write error'))
+ .then(() => {
+ writer.releaseLock();
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventCancel: falsy }),
+ 'pipeTo must reject with the write error')
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, ['write', 'Hello']);
+ });
+ });
+
+ }, `Errors must be propagated backward: becomes errored before piping due to write; preventCancel = ` +
+ `${stringVersion} (falsy); fulfilled cancel promise`);
+}
+
+for (const truthy of [true, 'a', 1, Symbol(), { }]) {
+ promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ write() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.write('Hello'), 'writer.write() must reject with the write error')
+ .then(() => promise_rejects(t, error1, writer.closed, 'writer.closed must reject with the write error'))
+ .then(() => {
+ writer.releaseLock();
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventCancel: truthy }),
+ 'pipeTo must reject with the write error')
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'Hello']);
+ });
+ });
+
+ }, `Errors must be propagated backward: becomes errored before piping due to write; preventCancel = ` +
+ `${String(truthy)} (truthy)`);
+}
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ write() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.write('Hello'), 'writer.write() must reject with the write error')
+ .then(() => promise_rejects(t, error1, writer.closed, 'writer.closed must reject with the write error'))
+ .then(() => {
+ writer.releaseLock();
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventCancel: true, preventAbort: true }),
+ 'pipeTo must reject with the write error')
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'Hello']);
+ });
+ });
+
+}, 'Errors must be propagated backward: becomes errored before piping due to write, preventCancel = true; ' +
+ 'preventAbort = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ write() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.write('Hello'), 'writer.write() must reject with the write error')
+ .then(() => promise_rejects(t, error1, writer.closed, 'writer.closed must reject with the write error'))
+ .then(() => {
+ writer.releaseLock();
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventCancel: true, preventAbort: true, preventClose: true }),
+ 'pipeTo must reject with the write error')
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'Hello']);
+ });
+ });
+
+}, 'Errors must be propagated backward: becomes errored before piping due to write; preventCancel = true, ' +
+ 'preventAbort = true, preventClose = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue('Hello');
+ }
+ });
+
+ const ws = recordingWritableStream({
+ write() {
+ throw error1;
+ }
+ });
+
+ return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error').then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, ['write', 'Hello']);
+ });
+
+}, 'Errors must be propagated backward: becomes errored during piping due to write; preventCancel omitted; fulfilled ' +
+ 'cancel promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue('Hello');
+ },
+ cancel() {
+ throw error2;
+ }
+ });
+
+ const ws = recordingWritableStream({
+ write() {
+ throw error1;
+ }
+ });
+
+ return promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the cancel error').then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, ['write', 'Hello']);
+ });
+
+}, 'Errors must be propagated backward: becomes errored during piping due to write; preventCancel omitted; rejected ' +
+ 'cancel promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue('Hello');
+ }
+ });
+
+ const ws = recordingWritableStream({
+ write() {
+ throw error1;
+ }
+ });
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventCancel: true }), 'pipeTo must reject with the same error')
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'Hello']);
+ });
+
+}, 'Errors must be propagated backward: becomes errored during piping due to write; preventCancel = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue('a');
+ controller.enqueue('b');
+ controller.enqueue('c');
+ }
+ });
+
+ const ws = recordingWritableStream({
+ write() {
+ if (ws.events.length > 2) {
+ return delay(0).then(() => {
+ throw error1;
+ });
+ }
+ return undefined;
+ }
+ });
+
+ return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error').then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b']);
+ });
+
+}, 'Errors must be propagated backward: becomes errored during piping due to write, but async; preventCancel = ' +
+ 'false; fulfilled cancel promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue('a');
+ controller.enqueue('b');
+ controller.enqueue('c');
+ },
+ cancel() {
+ throw error2;
+ }
+ });
+
+ const ws = recordingWritableStream({
+ write() {
+ if (ws.events.length > 2) {
+ return delay(0).then(() => {
+ throw error1;
+ });
+ }
+ return undefined;
+ }
+ });
+
+ return promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the cancel error').then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b']);
+ });
+
+}, 'Errors must be propagated backward: becomes errored during piping due to write, but async; preventCancel = ' +
+ 'false; rejected cancel promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue('a');
+ controller.enqueue('b');
+ controller.enqueue('c');
+ }
+ });
+
+ const ws = recordingWritableStream({
+ write() {
+ if (ws.events.length > 2) {
+ return delay(0).then(() => {
+ throw error1;
+ });
+ }
+ return undefined;
+ }
+ });
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventCancel: true }), 'pipeTo must reject with the same error')
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b']);
+ });
+
+}, 'Errors must be propagated backward: becomes errored during piping due to write, but async; preventCancel = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
+
+ setTimeout(() => ws.controller.error(error1), 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, []);
+ });
+
+}, 'Errors must be propagated backward: becomes errored after piping; preventCancel omitted; fulfilled cancel promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ cancel() {
+ throw error2;
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ const pipePromise = promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the cancel error');
+
+ setTimeout(() => ws.controller.error(error1), 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, []);
+ });
+
+}, 'Errors must be propagated backward: becomes errored after piping; preventCancel omitted; rejected cancel promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws, { preventCancel: true }),
+ 'pipeTo must reject with the same error');
+
+ setTimeout(() => ws.controller.error(error1), 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, []);
+ });
+
+}, 'Errors must be propagated backward: becomes errored after piping; preventCancel = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue('a');
+ controller.enqueue('b');
+ controller.enqueue('c');
+ controller.close();
+ }
+ });
+
+ const ws = recordingWritableStream({
+ write(chunk) {
+ if (chunk === 'c') {
+ return Promise.reject(error1);
+ }
+ return undefined;
+ }
+ });
+
+ return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error').then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b', 'write', 'c']);
+ });
+
+}, 'Errors must be propagated backward: becomes errored after piping due to last write; source is closed; ' +
+ 'preventCancel omitted (but cancel is never called)');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue('a');
+ controller.enqueue('b');
+ controller.enqueue('c');
+ controller.close();
+ }
+ });
+
+ const ws = recordingWritableStream({
+ write(chunk) {
+ if (chunk === 'c') {
+ return Promise.reject(error1);
+ }
+ return undefined;
+ }
+ });
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventCancel: true }), 'pipeTo must reject with the same error')
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b', 'write', 'c']);
+ });
+
+}, 'Errors must be propagated backward: becomes errored after piping due to last write; source is closed; ' +
+ 'preventCancel = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
+
+ setTimeout(() => ws.controller.error(error1), 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, []);
+ });
+
+}, 'Errors must be propagated backward: becomes errored after piping; dest never desires chunks; preventCancel = ' +
+ 'false; fulfilled cancel promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ cancel() {
+ throw error2;
+ }
+ });
+
+ const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the cancel error');
+
+ setTimeout(() => ws.controller.error(error1), 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, []);
+ });
+
+}, 'Errors must be propagated backward: becomes errored after piping; dest never desires chunks; preventCancel = ' +
+ 'false; rejected cancel promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws, { preventCancel: true }),
+ 'pipeTo must reject with the same error');
+
+ setTimeout(() => ws.controller.error(error1), 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, []);
+ });
+
+}, 'Errors must be propagated backward: becomes errored after piping; dest never desires chunks; preventCancel = ' +
+ 'true');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+
+ ws.abort(error1);
+
+ return rs.pipeTo(ws).then(
+ () => assert_unreached('the promise must not fulfill'),
+ err => {
+ assert_equals(err, error1, 'the promise must reject with error1');
+
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', err]);
+ assert_array_equals(ws.events, ['abort', error1]);
+ }
+ );
+
+}, 'Errors must be propagated backward: becomes errored before piping via abort; preventCancel omitted; fulfilled ' +
+ 'cancel promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ cancel() {
+ throw error2;
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ ws.abort(error1);
+
+ return promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the cancel error')
+ .then(() => {
+ return ws.getWriter().closed.then(
+ () => assert_unreached('the promise must not fulfill'),
+ err => {
+ assert_equals(err, error1, 'the promise must reject with error1');
+
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', err]);
+ assert_array_equals(ws.events, ['abort', error1]);
+ }
+ );
+ });
+
+}, 'Errors must be propagated backward: becomes errored before piping via abort; preventCancel omitted; rejected ' +
+ 'cancel promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+
+ ws.abort(error1);
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventCancel: true })).then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+
+}, 'Errors must be propagated backward: becomes errored before piping via abort; preventCancel = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ let resolveWriteCalled;
+ const writeCalledPromise = new Promise(resolve => {
+ resolveWriteCalled = resolve;
+ });
+
+ const ws = recordingWritableStream({
+ write() {
+ resolveWriteCalled();
+ return flushAsyncEvents();
+ }
+ });
+
+ const pipePromise = rs.pipeTo(ws);
+
+ rs.controller.enqueue('a');
+
+ return writeCalledPromise.then(() => {
+ ws.controller.error(error1);
+
+ return promise_rejects(t, error1, pipePromise);
+ }).then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, ['cancel', error1]);
+ assert_array_equals(ws.events, ['write', 'a']);
+ });
+
+}, 'Errors must be propagated backward: erroring via the controller errors once pending write completes');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/piping/error-propagation-forward.js b/test/fixtures/web-platform-tests/streams/piping/error-propagation-forward.js
new file mode 100644
index 00000000000000..983b92b9c3f8c7
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/piping/error-propagation-forward.js
@@ -0,0 +1,574 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+const error1 = new Error('error1!');
+error1.name = 'error1';
+
+const error2 = new Error('error2!');
+error2.name = 'error2';
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error')
+ .then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+
+}, 'Errors must be propagated forward: starts errored; preventAbort = false; fulfilled abort promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const ws = recordingWritableStream({
+ abort() {
+ throw error2;
+ }
+ });
+
+ return promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the abort error')
+ .then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+
+}, 'Errors must be propagated forward: starts errored; preventAbort = false; rejected abort promise');
+
+for (const falsy of [undefined, null, false, +0, -0, NaN, '']) {
+ const stringVersion = Object.is(falsy, -0) ? '-0' : String(falsy);
+
+ promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: falsy }), 'pipeTo must reject with the same error')
+ .then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+
+ }, `Errors must be propagated forward: starts errored; preventAbort = ${stringVersion} (falsy); fulfilled abort ` +
+ `promise`);
+}
+
+for (const truthy of [true, 'a', 1, Symbol(), { }]) {
+ promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: truthy }),
+ 'pipeTo must reject with the same error')
+ .then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, []);
+ });
+
+ }, `Errors must be propagated forward: starts errored; preventAbort = ${String(truthy)} (truthy)`);
+}
+
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: true, preventCancel: true }),
+ 'pipeTo must reject with the same error')
+ .then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, []);
+ });
+
+}, 'Errors must be propagated forward: starts errored; preventAbort = true, preventCancel = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: true, preventCancel: true, preventClose: true }),
+ 'pipeTo must reject with the same error')
+ .then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, []);
+ });
+
+}, 'Errors must be propagated forward: starts errored; preventAbort = true, preventCancel = true, preventClose = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
+
+ setTimeout(() => rs.controller.error(error1), 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+
+}, 'Errors must be propagated forward: becomes errored while empty; preventAbort = false; fulfilled abort promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ abort() {
+ throw error2;
+ }
+ });
+
+ const pipePromise = promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the abort error');
+
+ setTimeout(() => rs.controller.error(error1), 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+
+}, 'Errors must be propagated forward: becomes errored while empty; preventAbort = false; rejected abort promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: true }),
+ 'pipeTo must reject with the same error');
+
+ setTimeout(() => rs.controller.error(error1), 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, []);
+ });
+
+}, 'Errors must be propagated forward: becomes errored while empty; preventAbort = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
+
+ setTimeout(() => rs.controller.error(error1), 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+
+}, 'Errors must be propagated forward: becomes errored while empty; dest never desires chunks; ' +
+ 'preventAbort = false; fulfilled abort promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ abort() {
+ throw error2;
+ }
+ }, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the abort error');
+
+ setTimeout(() => rs.controller.error(error1), 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+
+}, 'Errors must be propagated forward: becomes errored while empty; dest never desires chunks; ' +
+ 'preventAbort = false; rejected abort promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: true }),
+ 'pipeTo must reject with the same error');
+
+ setTimeout(() => rs.controller.error(error1), 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, []);
+ });
+
+}, 'Errors must be propagated forward: becomes errored while empty; dest never desires chunks; ' +
+ 'preventAbort = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
+
+ setTimeout(() => {
+ rs.controller.enqueue('Hello');
+ setTimeout(() => rs.controller.error(error1), 10);
+ }, 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'Hello', 'abort', error1]);
+ });
+
+}, 'Errors must be propagated forward: becomes errored after one chunk; preventAbort = false; fulfilled abort promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ abort() {
+ throw error2;
+ }
+ });
+
+ const pipePromise = promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the abort error');
+
+ setTimeout(() => {
+ rs.controller.enqueue('Hello');
+ setTimeout(() => rs.controller.error(error1), 10);
+ }, 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'Hello', 'abort', error1]);
+ });
+
+}, 'Errors must be propagated forward: becomes errored after one chunk; preventAbort = false; rejected abort promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream();
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: true }),
+ 'pipeTo must reject with the same error');
+
+ setTimeout(() => {
+ rs.controller.enqueue('Hello');
+ setTimeout(() => rs.controller.error(error1), 10);
+ }, 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'Hello']);
+ });
+
+}, 'Errors must be propagated forward: becomes errored after one chunk; preventAbort = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the same error');
+
+ setTimeout(() => {
+ rs.controller.enqueue('Hello');
+ setTimeout(() => rs.controller.error(error1), 10);
+ }, 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+
+}, 'Errors must be propagated forward: becomes errored after one chunk; dest never desires chunks; ' +
+ 'preventAbort = false; fulfilled abort promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream({
+ abort() {
+ throw error2;
+ }
+ }, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the abort error');
+
+ setTimeout(() => {
+ rs.controller.enqueue('Hello');
+ setTimeout(() => rs.controller.error(error1), 10);
+ }, 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+
+}, 'Errors must be propagated forward: becomes errored after one chunk; dest never desires chunks; ' +
+ 'preventAbort = false; rejected abort promise');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: true }),
+ 'pipeTo must reject with the same error');
+
+ setTimeout(() => {
+ rs.controller.enqueue('Hello');
+ setTimeout(() => rs.controller.error(error1), 10);
+ }, 10);
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, []);
+ });
+
+}, 'Errors must be propagated forward: becomes errored after one chunk; dest never desires chunks; ' +
+ 'preventAbort = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ let resolveWriteCalled;
+ const writeCalledPromise = new Promise(resolve => {
+ resolveWriteCalled = resolve;
+ });
+
+ let resolveWritePromise;
+ const ws = recordingWritableStream({
+ write() {
+ resolveWriteCalled();
+
+ return new Promise(resolve => {
+ resolveWritePromise = resolve;
+ });
+ }
+ });
+
+ let pipeComplete = false;
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws)).then(() => {
+ pipeComplete = true;
+ });
+
+ rs.controller.enqueue('a');
+
+ return writeCalledPromise.then(() => {
+ rs.controller.error(error1);
+
+ // Flush async events and verify that no shutdown occurs.
+ return flushAsyncEvents();
+ }).then(() => {
+ assert_array_equals(ws.events, ['write', 'a']); // no 'abort'
+ assert_equals(pipeComplete, false, 'the pipe must not be complete');
+
+ resolveWritePromise();
+
+ return pipePromise.then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'abort', error1]);
+ });
+ });
+
+}, 'Errors must be propagated forward: shutdown must not occur until the final write completes');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ let resolveWriteCalled;
+ const writeCalledPromise = new Promise(resolve => {
+ resolveWriteCalled = resolve;
+ });
+
+ let resolveWritePromise;
+ const ws = recordingWritableStream({
+ write() {
+ resolveWriteCalled();
+
+ return new Promise(resolve => {
+ resolveWritePromise = resolve;
+ });
+ }
+ });
+
+ let pipeComplete = false;
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: true })).then(() => {
+ pipeComplete = true;
+ });
+
+ rs.controller.enqueue('a');
+
+ return writeCalledPromise.then(() => {
+ rs.controller.error(error1);
+
+ // Flush async events and verify that no shutdown occurs.
+ return flushAsyncEvents();
+ }).then(() => {
+ assert_array_equals(ws.events, ['write', 'a']); // no 'abort'
+ assert_equals(pipeComplete, false, 'the pipe must not be complete');
+
+ resolveWritePromise();
+ return pipePromise;
+ }).then(() => flushAsyncEvents()).then(() => {
+ assert_array_equals(ws.events, ['write', 'a']); // no 'abort'
+ });
+
+}, 'Errors must be propagated forward: shutdown must not occur until the final write completes; preventAbort = true');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ let resolveWriteCalled;
+ const writeCalledPromise = new Promise(resolve => {
+ resolveWriteCalled = resolve;
+ });
+
+ let resolveWritePromise;
+ const ws = recordingWritableStream({
+ write() {
+ resolveWriteCalled();
+
+ return new Promise(resolve => {
+ resolveWritePromise = resolve;
+ });
+ }
+ }, new CountQueuingStrategy({ highWaterMark: 2 }));
+
+ let pipeComplete = false;
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws)).then(() => {
+ pipeComplete = true;
+ });
+
+ rs.controller.enqueue('a');
+ rs.controller.enqueue('b');
+
+ return writeCalledPromise.then(() => flushAsyncEvents()).then(() => {
+ assert_array_equals(ws.events, ['write', 'a'],
+ 'the first chunk must have been written, but abort must not have happened yet');
+ assert_false(pipeComplete, 'the pipe should not complete while the first write is pending');
+
+ rs.controller.error(error1);
+ resolveWritePromise();
+ return flushAsyncEvents();
+ }).then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+ 'the second chunk must have been written, but abort must not have happened yet');
+ assert_false(pipeComplete, 'the pipe should not complete while the second write is pending');
+
+ resolveWritePromise();
+ return pipePromise;
+ }).then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b', 'abort', error1],
+ 'all chunks must have been written and abort must have happened');
+ });
+
+}, 'Errors must be propagated forward: shutdown must not occur until the final write completes; becomes errored after first write');
+
+promise_test(t => {
+
+ const rs = recordingReadableStream();
+
+ let resolveWriteCalled;
+ const writeCalledPromise = new Promise(resolve => {
+ resolveWriteCalled = resolve;
+ });
+
+ let resolveWritePromise;
+ const ws = recordingWritableStream({
+ write() {
+ resolveWriteCalled();
+
+ return new Promise(resolve => {
+ resolveWritePromise = resolve;
+ });
+ }
+ }, new CountQueuingStrategy({ highWaterMark: 2 }));
+
+ let pipeComplete = false;
+ const pipePromise = promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: true })).then(() => {
+ pipeComplete = true;
+ });
+
+ rs.controller.enqueue('a');
+ rs.controller.enqueue('b');
+
+ return writeCalledPromise.then(() => flushAsyncEvents()).then(() => {
+ assert_array_equals(ws.events, ['write', 'a'],
+ 'the first chunk must have been written, but abort must not have happened');
+ assert_false(pipeComplete, 'the pipe should not complete while the first write is pending');
+
+ rs.controller.error(error1);
+ resolveWritePromise();
+ }).then(() => flushAsyncEvents()).then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+ 'the second chunk must have been written, but abort must not have happened');
+ assert_false(pipeComplete, 'the pipe should not complete while the second write is pending');
+
+ resolveWritePromise();
+ return pipePromise;
+ }).then(() => flushAsyncEvents()).then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+ 'all chunks must have been written, but abort must not have happened');
+ });
+
+}, 'Errors must be propagated forward: shutdown must not occur until the final write completes; becomes errored after first write; preventAbort = true');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/piping/flow-control.js b/test/fixtures/web-platform-tests/streams/piping/flow-control.js
new file mode 100644
index 00000000000000..04c56ec40d0b44
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/piping/flow-control.js
@@ -0,0 +1,306 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/rs-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+const error1 = new Error('error1!');
+error1.name = 'error1';
+
+promise_test(t => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue('a');
+ controller.enqueue('b');
+ controller.close();
+ }
+ });
+
+ const ws = recordingWritableStream(undefined, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ const pipePromise = rs.pipeTo(ws, { preventCancel: true });
+
+ // Wait and make sure it doesn't do any reading.
+ return flushAsyncEvents().then(() => {
+ ws.controller.error(error1);
+ })
+ .then(() => promise_rejects(t, error1, pipePromise, 'pipeTo must reject with the same error'))
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, []);
+ })
+ .then(() => readableStreamToArray(rs))
+ .then(chunksNotPreviouslyRead => {
+ assert_array_equals(chunksNotPreviouslyRead, ['a', 'b']);
+ });
+
+}, 'Piping from a non-empty ReadableStream into a WritableStream that does not desire chunks');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream({
+ start(controller) {
+ controller.enqueue('b');
+ controller.close();
+ }
+ });
+
+ let resolveWritePromise;
+ const ws = recordingWritableStream({
+ write() {
+ if (!resolveWritePromise) {
+ // first write
+ return new Promise(resolve => {
+ resolveWritePromise = resolve;
+ });
+ }
+ return undefined;
+ }
+ });
+
+ const writer = ws.getWriter();
+ const firstWritePromise = writer.write('a');
+ assert_equals(writer.desiredSize, 0, 'after writing the writer\'s desiredSize must be 0');
+ writer.releaseLock();
+
+ // firstWritePromise won't settle until we call resolveWritePromise.
+
+ const pipePromise = rs.pipeTo(ws);
+
+ return flushAsyncEvents().then(() => resolveWritePromise())
+ .then(() => Promise.all([firstWritePromise, pipePromise]))
+ .then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b', 'close']);
+ });
+
+}, 'Piping from a non-empty ReadableStream into a WritableStream that does not desire chunks, but then does');
+
+promise_test(() => {
+
+ const rs = recordingReadableStream();
+
+ const startPromise = Promise.resolve();
+ let resolveWritePromise;
+ const ws = recordingWritableStream({
+ start() {
+ return startPromise;
+ },
+ write() {
+ if (!resolveWritePromise) {
+ // first write
+ return new Promise(resolve => {
+ resolveWritePromise = resolve;
+ });
+ }
+ return undefined;
+ }
+ });
+
+ const writer = ws.getWriter();
+ writer.write('a');
+
+ return startPromise.then(() => {
+ assert_array_equals(ws.events, ['write', 'a']);
+ assert_equals(writer.desiredSize, 0, 'after writing the writer\'s desiredSize must be 0');
+ writer.releaseLock();
+
+ const pipePromise = rs.pipeTo(ws);
+
+ rs.controller.enqueue('b');
+ resolveWritePromise();
+ rs.controller.close();
+
+ return pipePromise.then(() => {
+ assert_array_equals(rs.eventsWithoutPulls, []);
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b', 'close']);
+ });
+ });
+
+}, 'Piping from an empty ReadableStream into a WritableStream that does not desire chunks, but then the readable ' +
+ 'stream becomes non-empty and the writable stream starts desiring chunks');
+
+promise_test(() => {
+ const unreadChunks = ['b', 'c', 'd'];
+
+ const rs = recordingReadableStream({
+ pull(controller) {
+ controller.enqueue(unreadChunks.shift());
+ if (unreadChunks.length === 0) {
+ controller.close();
+ }
+ }
+ }, new CountQueuingStrategy({ highWaterMark: 0 }));
+
+ let resolveWritePromise;
+ const ws = recordingWritableStream({
+ write() {
+ if (!resolveWritePromise) {
+ // first write
+ return new Promise(resolve => {
+ resolveWritePromise = resolve;
+ });
+ }
+ return undefined;
+ }
+ }, new CountQueuingStrategy({ highWaterMark: 3 }));
+
+ const writer = ws.getWriter();
+ const firstWritePromise = writer.write('a');
+ assert_equals(writer.desiredSize, 2, 'after writing the writer\'s desiredSize must be 2');
+ writer.releaseLock();
+
+ // firstWritePromise won't settle until we call resolveWritePromise.
+
+ const pipePromise = rs.pipeTo(ws);
+
+ return flushAsyncEvents().then(() => {
+ assert_array_equals(ws.events, ['write', 'a']);
+ assert_equals(unreadChunks.length, 1, 'chunks should continue to be enqueued until the HWM is reached');
+ }).then(() => resolveWritePromise())
+ .then(() => Promise.all([firstWritePromise, pipePromise]))
+ .then(() => {
+ assert_array_equals(rs.events, ['pull', 'pull', 'pull']);
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b','write', 'c','write', 'd', 'close']);
+ });
+
+}, 'Piping from a ReadableStream to a WritableStream that desires more chunks before finishing with previous ones');
+
+class StepTracker {
+ constructor() {
+ this.waiters = [];
+ this.wakers = [];
+ }
+
+ // Returns promise which resolves when step `n` is reached. Also schedules step n + 1 to happen shortly after the
+ // promise is resolved.
+ waitThenAdvance(n) {
+ if (this.waiters[n] === undefined) {
+ this.waiters[n] = new Promise(resolve => {
+ this.wakers[n] = resolve;
+ });
+ this.waiters[n]
+ .then(() => flushAsyncEvents())
+ .then(() => {
+ if (this.wakers[n + 1] !== undefined) {
+ this.wakers[n + 1]();
+ }
+ });
+ }
+ if (n == 0) {
+ this.wakers[0]();
+ }
+ return this.waiters[n];
+ }
+}
+
+promise_test(() => {
+ const steps = new StepTracker();
+ const desiredSizes = [];
+ const rs = recordingReadableStream({
+ start(controller) {
+ steps.waitThenAdvance(1).then(() => enqueue('a'));
+ steps.waitThenAdvance(3).then(() => enqueue('b'));
+ steps.waitThenAdvance(5).then(() => enqueue('c'));
+ steps.waitThenAdvance(7).then(() => enqueue('d'));
+ steps.waitThenAdvance(11).then(() => controller.close());
+
+ function enqueue(chunk) {
+ controller.enqueue(chunk);
+ desiredSizes.push(controller.desiredSize);
+ }
+ }
+ });
+
+ const chunksFinishedWriting = [];
+ const writableStartPromise = Promise.resolve();
+ let writeCalled = false;
+ const ws = recordingWritableStream({
+ start() {
+ return writableStartPromise;
+ },
+ write(chunk) {
+ const waitForStep = writeCalled ? 12 : 9;
+ writeCalled = true;
+ return steps.waitThenAdvance(waitForStep).then(() => {
+ chunksFinishedWriting.push(chunk);
+ });
+ }
+ });
+
+ return writableStartPromise.then(() => {
+ const pipePromise = rs.pipeTo(ws);
+ steps.waitThenAdvance(0);
+
+ return Promise.all([
+ steps.waitThenAdvance(2).then(() => {
+ assert_array_equals(chunksFinishedWriting, [], 'at step 2, zero chunks must have finished writing');
+ assert_array_equals(ws.events, ['write', 'a'], 'at step 2, one chunk must have been written');
+
+ // When 'a' (the very first chunk) was enqueued, it was immediately used to fulfill the outstanding read request
+ // promise, leaving the queue empty.
+ assert_array_equals(desiredSizes, [1],
+ 'at step 2, the desiredSize at the last enqueue (step 1) must have been 1');
+ assert_equals(rs.controller.desiredSize, 1, 'at step 2, the current desiredSize must be 1');
+ }),
+
+ steps.waitThenAdvance(4).then(() => {
+ assert_array_equals(chunksFinishedWriting, [], 'at step 4, zero chunks must have finished writing');
+ assert_array_equals(ws.events, ['write', 'a'], 'at step 4, one chunk must have been written');
+
+ // When 'b' was enqueued at step 3, the queue was also empty, since immediately after enqueuing 'a' at
+ // step 1, it was dequeued in order to fulfill the read() call that was made at step 0. Thus the queue
+ // had size 1 (thus desiredSize of 0).
+ assert_array_equals(desiredSizes, [1, 0],
+ 'at step 4, the desiredSize at the last enqueue (step 3) must have been 0');
+ assert_equals(rs.controller.desiredSize, 0, 'at step 4, the current desiredSize must be 0');
+ }),
+
+ steps.waitThenAdvance(6).then(() => {
+ assert_array_equals(chunksFinishedWriting, [], 'at step 6, zero chunks must have finished writing');
+ assert_array_equals(ws.events, ['write', 'a'], 'at step 6, one chunk must have been written');
+
+ // When 'c' was enqueued at step 5, the queue was not empty; it had 'b' in it, since 'b' will not be read until
+ // the first write completes at step 9. Thus, the queue size is 2 after enqueuing 'c', giving a desiredSize of
+ // -1.
+ assert_array_equals(desiredSizes, [1, 0, -1],
+ 'at step 6, the desiredSize at the last enqueue (step 5) must have been -1');
+ assert_equals(rs.controller.desiredSize, -1, 'at step 6, the current desiredSize must be -1');
+ }),
+
+ steps.waitThenAdvance(8).then(() => {
+ assert_array_equals(chunksFinishedWriting, [], 'at step 8, zero chunks must have finished writing');
+ assert_array_equals(ws.events, ['write', 'a'], 'at step 8, one chunk must have been written');
+
+ // When 'd' was enqueued at step 7, the situation is the same as before, leading to a queue containing 'b', 'c',
+ // and 'd'.
+ assert_array_equals(desiredSizes, [1, 0, -1, -2],
+ 'at step 8, the desiredSize at the last enqueue (step 7) must have been -2');
+ assert_equals(rs.controller.desiredSize, -2, 'at step 8, the current desiredSize must be -2');
+ }),
+
+ steps.waitThenAdvance(10).then(() => {
+ assert_array_equals(chunksFinishedWriting, ['a'], 'at step 10, one chunk must have finished writing');
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b'],
+ 'at step 10, two chunks must have been written');
+
+ assert_equals(rs.controller.desiredSize, -1, 'at step 10, the current desiredSize must be -1');
+ }),
+
+ pipePromise.then(() => {
+ assert_array_equals(desiredSizes, [1, 0, -1, -2], 'backpressure must have been exerted at the source');
+ assert_array_equals(chunksFinishedWriting, ['a', 'b', 'c', 'd'], 'all chunks finished writing');
+
+ assert_array_equals(rs.eventsWithoutPulls, [], 'nothing unexpected should happen to the ReadableStream');
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'b', 'write', 'c', 'write', 'd', 'close'],
+ 'all chunks were written (and the WritableStream closed)');
+ })
+ ]);
+ });
+}, 'Piping to a WritableStream that does not consume the writes fast enough exerts backpressure on the ReadableStream');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/piping/general.js b/test/fixtures/web-platform-tests/streams/piping/general.js
new file mode 100644
index 00000000000000..6bc6e9be58bd86
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/piping/general.js
@@ -0,0 +1,195 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+test(() => {
+
+ const rs = new ReadableStream();
+ const ws = new WritableStream();
+
+ assert_false(rs.locked, 'sanity check: the ReadableStream must not start locked');
+ assert_false(ws.locked, 'sanity check: the WritableStream must not start locked');
+
+ rs.pipeTo(ws);
+
+ assert_true(rs.locked, 'the ReadableStream must become locked');
+ assert_true(ws.locked, 'the WritableStream must become locked');
+
+}, 'Piping must lock both the ReadableStream and WritableStream');
+
+promise_test(() => {
+
+ const rs = new ReadableStream({
+ start(controller) {
+ controller.close();
+ }
+ });
+ const ws = new WritableStream();
+
+ return rs.pipeTo(ws).then(() => {
+ assert_false(rs.locked, 'the ReadableStream must become unlocked');
+ assert_false(ws.locked, 'the WritableStream must become unlocked');
+ });
+
+}, 'Piping finishing must unlock both the ReadableStream and WritableStream');
+
+promise_test(t => {
+
+ const fakeRS = Object.create(ReadableStream.prototype);
+ const ws = new WritableStream();
+
+ return methodRejects(t, ReadableStream.prototype, 'pipeTo', fakeRS, [ws]);
+
+}, 'pipeTo must check the brand of its ReadableStream this value');
+
+promise_test(t => {
+
+ const rs = new ReadableStream();
+ const fakeWS = Object.create(WritableStream.prototype);
+
+ return methodRejects(t, ReadableStream.prototype, 'pipeTo', rs, [fakeWS]);
+
+}, 'pipeTo must check the brand of its WritableStream argument');
+
+promise_test(t => {
+
+ const rs = new ReadableStream();
+ const ws = new WritableStream();
+
+ rs.getReader();
+
+ assert_true(rs.locked, 'sanity check: the ReadableStream starts locked');
+ assert_false(ws.locked, 'sanity check: the WritableStream does not start locked');
+
+ return promise_rejects(t, new TypeError(), rs.pipeTo(ws)).then(() => {
+ assert_false(ws.locked, 'the WritableStream must still be unlocked');
+ });
+
+}, 'pipeTo must fail if the ReadableStream is locked, and not lock the WritableStream');
+
+promise_test(t => {
+
+ const rs = new ReadableStream();
+ const ws = new WritableStream();
+
+ ws.getWriter();
+
+ assert_false(rs.locked, 'sanity check: the ReadableStream does not start locked');
+ assert_true(ws.locked, 'sanity check: the WritableStream starts locked');
+
+ return promise_rejects(t, new TypeError(), rs.pipeTo(ws)).then(() => {
+ assert_false(rs.locked, 'the ReadableStream must still be unlocked');
+ });
+
+}, 'pipeTo must fail if the WritableStream is locked, and not lock the ReadableStream');
+
+promise_test(() => {
+
+ const CHUNKS = 10;
+
+ const rs = new ReadableStream({
+ start(c) {
+ for (let i = 0; i < CHUNKS; ++i) {
+ c.enqueue(i);
+ }
+ c.close();
+ }
+ });
+
+ const written = [];
+ const ws = new WritableStream({
+ write(chunk) {
+ written.push(chunk);
+ },
+ close() {
+ written.push('closed');
+ }
+ }, new CountQueuingStrategy({ highWaterMark: CHUNKS }));
+
+ return rs.pipeTo(ws).then(() => {
+ const targetValues = [];
+ for (let i = 0; i < CHUNKS; ++i) {
+ targetValues.push(i);
+ }
+ targetValues.push('closed');
+
+ assert_array_equals(written, targetValues, 'the correct values must be written');
+
+ // Ensure both readable and writable are closed by the time the pipe finishes.
+ return Promise.all([
+ rs.getReader().closed,
+ ws.getWriter().closed
+ ]);
+ });
+
+ // NOTE: no requirement on *when* the pipe finishes; that is left to implementations.
+
+}, 'Piping from a ReadableStream from which lots of chunks are synchronously readable');
+
+promise_test(() => {
+
+ let controller;
+ const rs = recordingReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const ws = recordingWritableStream();
+
+ const pipePromise = rs.pipeTo(ws).then(() => {
+ assert_array_equals(ws.events, ['write', 'Hello', 'close']);
+ });
+
+ setTimeout(() => {
+ controller.enqueue('Hello');
+ setTimeout(() => controller.close(), 10);
+ }, 10);
+
+ return pipePromise;
+
+}, 'Piping from a ReadableStream for which a chunk becomes asynchronously readable after the pipeTo');
+
+for (const preventAbort of [true, false]) {
+ promise_test(() => {
+
+ const rs = new ReadableStream({
+ pull() {
+ return Promise.reject(undefined);
+ }
+ });
+
+ return rs.pipeTo(new WritableStream(), { preventAbort }).then(
+ () => assert_unreached('pipeTo promise should be rejected'),
+ value => assert_equals(value, undefined, 'rejection value should be undefined'));
+
+ }, `an undefined rejection from pull should cause pipeTo() to reject when preventAbort is ${preventAbort}`);
+}
+
+for (const preventCancel of [true, false]) {
+ promise_test(() => {
+
+ const rs = new ReadableStream({
+ pull(controller) {
+ controller.enqueue(0);
+ }
+ });
+
+ const ws = new WritableStream({
+ write() {
+ return Promise.reject(undefined);
+ }
+ });
+
+ return rs.pipeTo(ws, { preventCancel }).then(
+ () => assert_unreached('pipeTo promise should be rejected'),
+ value => assert_equals(value, undefined, 'rejection value should be undefined'));
+
+ }, `an undefined rejection from write should cause pipeTo() to reject when preventCancel is ${preventCancel}`);
+}
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/piping/multiple-propagation.js b/test/fixtures/web-platform-tests/streams/piping/multiple-propagation.js
new file mode 100644
index 00000000000000..87119ebc65f6d0
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/piping/multiple-propagation.js
@@ -0,0 +1,232 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+const error1 = new Error('error1!');
+error1.name = 'error1';
+
+const error2 = new Error('error2!');
+error2.name = 'error2';
+
+function createErroredWritableStream(t) {
+ return Promise.resolve().then(() => {
+ const ws = recordingWritableStream({
+ start(c) {
+ c.error(error2);
+ }
+ });
+
+ const writer = ws.getWriter();
+ return promise_rejects(t, error2, writer.closed, 'the writable stream must be errored with error2')
+ .then(() => {
+ writer.releaseLock();
+ assert_array_equals(ws.events, []);
+ return ws;
+ });
+ });
+}
+
+promise_test(t => {
+ const rs = recordingReadableStream({
+ start(c) {
+ c.error(error1);
+ }
+ });
+ const ws = recordingWritableStream({
+ start(c) {
+ c.error(error2);
+ }
+ });
+
+ // Trying to abort a stream that is erroring will give the writable's error
+ return promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the writable stream\'s error').then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, []);
+
+ return Promise.all([
+ promise_rejects(t, error1, rs.getReader().closed, 'the readable stream must be errored with error1'),
+ promise_rejects(t, error2, ws.getWriter().closed, 'the writable stream must be errored with error2')
+ ]);
+ });
+
+}, 'Piping from an errored readable stream to an erroring writable stream');
+
+promise_test(t => {
+ const rs = recordingReadableStream({
+ start(c) {
+ c.error(error1);
+ }
+ });
+
+ return createErroredWritableStream(t)
+ .then(ws => promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the readable stream\'s error'))
+ .then(() => {
+ assert_array_equals(rs.events, []);
+
+ return promise_rejects(t, error1, rs.getReader().closed, 'the readable stream must be errored with error1');
+ });
+}, 'Piping from an errored readable stream to an errored writable stream');
+
+promise_test(t => {
+ const rs = recordingReadableStream({
+ start(c) {
+ c.error(error1);
+ }
+ });
+ const ws = recordingWritableStream({
+ start(c) {
+ c.error(error2);
+ }
+ });
+
+ return promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: true }),
+ 'pipeTo must reject with the readable stream\'s error')
+ .then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, []);
+
+ return Promise.all([
+ promise_rejects(t, error1, rs.getReader().closed, 'the readable stream must be errored with error1'),
+ promise_rejects(t, error2, ws.getWriter().closed, 'the writable stream must be errored with error2')
+ ]);
+ });
+
+}, 'Piping from an errored readable stream to an erroring writable stream; preventAbort = true');
+
+promise_test(t => {
+ const rs = recordingReadableStream({
+ start(c) {
+ c.error(error1);
+ }
+ });
+ return createErroredWritableStream(t)
+ .then(ws => promise_rejects(t, error1, rs.pipeTo(ws, { preventAbort: true }),
+ 'pipeTo must reject with the readable stream\'s error'))
+ .then(() => {
+ assert_array_equals(rs.events, []);
+
+ return promise_rejects(t, error1, rs.getReader().closed, 'the readable stream must be errored with error1');
+ });
+
+}, 'Piping from an errored readable stream to an errored writable stream; preventAbort = true');
+
+promise_test(t => {
+ const rs = recordingReadableStream({
+ start(c) {
+ c.error(error1);
+ }
+ });
+ const ws = recordingWritableStream();
+ const writer = ws.getWriter();
+ const closePromise = writer.close();
+ writer.releaseLock();
+
+ return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the readable stream\'s error').then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, ['abort', error1]);
+
+ return Promise.all([
+ promise_rejects(t, error1, rs.getReader().closed, 'the readable stream must be errored with error1'),
+ promise_rejects(t, error1, ws.getWriter().closed,
+ 'closed must reject with error1'),
+ promise_rejects(t, error1, closePromise,
+ 'close() must reject with error1')
+ ]);
+ });
+
+}, 'Piping from an errored readable stream to a closing writable stream');
+
+promise_test(t => {
+ const rs = recordingReadableStream({
+ start(c) {
+ c.error(error1);
+ }
+ });
+ const ws = recordingWritableStream();
+ const writer = ws.getWriter();
+ const closePromise = writer.close();
+ writer.releaseLock();
+
+ return flushAsyncEvents().then(() => {
+ return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the readable stream\'s error').then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, ['close']);
+
+ return Promise.all([
+ promise_rejects(t, error1, rs.getReader().closed, 'the readable stream must be errored with error1'),
+ ws.getWriter().closed,
+ closePromise
+ ]);
+ });
+ });
+
+}, 'Piping from an errored readable stream to a closed writable stream');
+
+promise_test(t => {
+ const rs = recordingReadableStream({
+ start(c) {
+ c.close();
+ }
+ });
+ const ws = recordingWritableStream({
+ start(c) {
+ c.error(error1);
+ }
+ });
+
+ return promise_rejects(t, error1, rs.pipeTo(ws), 'pipeTo must reject with the writable stream\'s error').then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, []);
+
+ return Promise.all([
+ rs.getReader().closed,
+ promise_rejects(t, error1, ws.getWriter().closed, 'the writable stream must be errored with error1')
+ ]);
+ });
+
+}, 'Piping from a closed readable stream to an erroring writable stream');
+
+promise_test(t => {
+ const rs = recordingReadableStream({
+ start(c) {
+ c.close();
+ }
+ });
+ return createErroredWritableStream(t)
+ .then(ws => promise_rejects(t, error2, rs.pipeTo(ws), 'pipeTo must reject with the writable stream\'s error'))
+ .then(() => {
+ assert_array_equals(rs.events, []);
+
+ return rs.getReader().closed;
+ });
+
+}, 'Piping from a closed readable stream to an errored writable stream');
+
+promise_test(() => {
+ const rs = recordingReadableStream({
+ start(c) {
+ c.close();
+ }
+ });
+ const ws = recordingWritableStream();
+ const writer = ws.getWriter();
+ writer.close();
+ writer.releaseLock();
+
+ return rs.pipeTo(ws).then(() => {
+ assert_array_equals(rs.events, []);
+ assert_array_equals(ws.events, ['close']);
+
+ return Promise.all([
+ rs.getReader().closed,
+ ws.getWriter().closed
+ ]);
+ });
+
+}, 'Piping from a closed readable stream to a closed writable stream');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/piping/pipe-through.js b/test/fixtures/web-platform-tests/streams/piping/pipe-through.js
new file mode 100644
index 00000000000000..7ba5607ae5dbb5
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/piping/pipe-through.js
@@ -0,0 +1,261 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/rs-utils.js');
+ self.importScripts('../resources/test-utils.js');
+}
+
+function duckTypedPassThroughTransform() {
+ let enqueueInReadable;
+ let closeReadable;
+
+ return {
+ writable: new WritableStream({
+ write(chunk) {
+ enqueueInReadable(chunk);
+ },
+
+ close() {
+ closeReadable();
+ }
+ }),
+
+ readable: new ReadableStream({
+ start(c) {
+ enqueueInReadable = c.enqueue.bind(c);
+ closeReadable = c.close.bind(c);
+ }
+ })
+ };
+}
+
+function uninterestingReadableWritablePair() {
+ return { writable: new WritableStream(), readable: new ReadableStream() };
+}
+
+promise_test(() => {
+ const readableEnd = sequentialReadableStream(5).pipeThrough(duckTypedPassThroughTransform());
+
+ return readableStreamToArray(readableEnd).then(chunks =>
+ assert_array_equals(chunks, [1, 2, 3, 4, 5]), 'chunks should match');
+}, 'Piping through a duck-typed pass-through transform stream should work');
+
+promise_test(() => {
+ const transform = {
+ writable: new WritableStream({
+ start(c) {
+ c.error(new Error('this rejection should not be reported as unhandled'));
+ }
+ }),
+ readable: new ReadableStream()
+ };
+
+ sequentialReadableStream(5).pipeThrough(transform);
+
+ // The test harness should complain about unhandled rejections by then.
+ return flushAsyncEvents();
+
+}, 'Piping through a transform errored on the writable end does not cause an unhandled promise rejection');
+
+test(() => {
+ let calledWithArgs;
+ const dummy = {
+ pipeTo(...args) {
+ calledWithArgs = args;
+
+ // Does not return anything, testing the spec's guard against trying to mark [[PromiseIsHandled]] on undefined.
+ }
+ };
+
+ const fakeWritable = { fake: 'writable' };
+ const fakeReadable = { fake: 'readable' };
+ const arg2 = { arg: 'arg2' };
+ const arg3 = { arg: 'arg3' };
+ const result =
+ ReadableStream.prototype.pipeThrough.call(dummy, { writable: fakeWritable, readable: fakeReadable }, arg2, arg3);
+
+ assert_array_equals(calledWithArgs, [fakeWritable, arg2],
+ 'The this value\'s pipeTo method should be called with the appropriate arguments');
+ assert_equals(result, fakeReadable, 'return value should be the passed readable property');
+
+}, 'pipeThrough generically calls pipeTo with the appropriate args');
+
+test(() => {
+ const dummy = {
+ pipeTo() {
+ return { not: 'a promise' };
+ }
+ };
+
+ ReadableStream.prototype.pipeThrough.call(dummy, uninterestingReadableWritablePair());
+
+ // Test passes if this doesn't throw or crash.
+
+}, 'pipeThrough can handle calling a pipeTo that returns a non-promise object');
+
+test(() => {
+ const dummy = {
+ pipeTo() {
+ return {
+ then() {},
+ this: 'is not a real promise'
+ };
+ }
+ };
+
+ ReadableStream.prototype.pipeThrough.call(dummy, uninterestingReadableWritablePair());
+
+ // Test passes if this doesn't throw or crash.
+
+}, 'pipeThrough can handle calling a pipeTo that returns a non-promise thenable object');
+
+promise_test(() => {
+ const dummy = {
+ pipeTo() {
+ return Promise.reject(new Error('this rejection should not be reported as unhandled'));
+ }
+ };
+
+ ReadableStream.prototype.pipeThrough.call(dummy, uninterestingReadableWritablePair());
+
+ // The test harness should complain about unhandled rejections by then.
+ return flushAsyncEvents();
+
+}, 'pipeThrough should mark a real promise from a fake readable as handled');
+
+test(() => {
+ let thenCalled = false;
+ let catchCalled = false;
+ const dummy = {
+ pipeTo() {
+ const fakePromise = Object.create(Promise.prototype);
+ fakePromise.then = () => {
+ thenCalled = true;
+ };
+ fakePromise.catch = () => {
+ catchCalled = true;
+ };
+ assert_true(fakePromise instanceof Promise, 'fakePromise fools instanceof');
+ return fakePromise;
+ }
+ };
+
+ // An incorrect implementation which uses an internal method to mark the promise as handled will throw or crash here.
+ ReadableStream.prototype.pipeThrough.call(dummy, uninterestingReadableWritablePair());
+
+ // An incorrect implementation that tries to mark the promise as handled by calling .then() or .catch() on the object
+ // will fail these tests.
+ assert_false(thenCalled, 'then should not be called');
+ assert_false(catchCalled, 'catch should not be called');
+}, 'pipeThrough should not be fooled by an object whose instanceof Promise returns true');
+
+test(() => {
+ const pairs = [
+ {},
+ { readable: undefined, writable: undefined },
+ { readable: 'readable' },
+ { readable: 'readable', writable: undefined },
+ { writable: 'writable' },
+ { readable: undefined, writable: 'writable' }
+ ];
+ for (let i = 0; i < pairs.length; ++i) {
+ const pair = pairs[i];
+ const rs = new ReadableStream();
+ assert_throws(new TypeError(), () => rs.pipeThrough(pair),
+ `pipeThrough should throw for argument ${JSON.stringify(pair)} (index ${i});`);
+ }
+}, 'undefined readable or writable arguments should cause pipeThrough to throw');
+
+test(() => {
+ const invalidArguments = [null, 0, NaN, '', [], {}, false, () => {}];
+ for (const arg of invalidArguments) {
+ const rs = new ReadableStream();
+ assert_equals(arg, rs.pipeThrough({ writable: new WritableStream(), readable: arg }),
+ 'pipeThrough() should not throw for readable: ' + JSON.stringify(arg));
+ const rs2 = new ReadableStream();
+ assert_equals(rs2, rs.pipeThrough({ writable: arg, readable: rs2 }),
+ 'pipeThrough() should not throw for writable: ' + JSON.stringify(arg));
+ }
+}, 'invalid but not undefined arguments should not cause pipeThrough to throw');
+
+test(() => {
+
+ const thisValue = {
+ pipeTo() {
+ assert_unreached('pipeTo should not be called');
+ }
+ };
+
+ methodThrows(ReadableStream.prototype, 'pipeThrough', thisValue, [undefined, {}]);
+ methodThrows(ReadableStream.prototype, 'pipeThrough', thisValue, [null, {}]);
+
+}, 'pipeThrough should throw when its first argument is not convertible to an object');
+
+test(() => {
+
+ const args = [{ readable: {}, writable: {} }, {}];
+
+ methodThrows(ReadableStream.prototype, 'pipeThrough', undefined, args);
+ methodThrows(ReadableStream.prototype, 'pipeThrough', null, args);
+ methodThrows(ReadableStream.prototype, 'pipeThrough', 1, args);
+ methodThrows(ReadableStream.prototype, 'pipeThrough', { pipeTo: 'test' }, args);
+
+}, 'pipeThrough should throw when "this" has no pipeTo method');
+
+test(() => {
+ const error = new Error('potato');
+
+ const throwingPipeTo = {
+ get pipeTo() {
+ throw error;
+ }
+ };
+ assert_throws(error,
+ () => ReadableStream.prototype.pipeThrough.call(throwingPipeTo, { readable: { }, writable: { } }, {}),
+ 'pipeThrough should rethrow the error thrown by pipeTo');
+
+ const thisValue = {
+ pipeTo() {
+ assert_unreached('pipeTo should not be called');
+ }
+ };
+
+ const throwingWritable = {
+ readable: {},
+ get writable() {
+ throw error;
+ }
+ };
+ assert_throws(error,
+ () => ReadableStream.prototype.pipeThrough.call(thisValue, throwingWritable, {}),
+ 'pipeThrough should rethrow the error thrown by the writable getter');
+
+ const throwingReadable = {
+ get readable() {
+ throw error;
+ },
+ writable: {}
+ };
+ assert_throws(error,
+ () => ReadableStream.prototype.pipeThrough.call(thisValue, throwingReadable, {}),
+ 'pipeThrough should rethrow the error thrown by the readable getter');
+
+}, 'pipeThrough should rethrow errors from accessing pipeTo, readable, or writable');
+
+test(() => {
+
+ let count = 0;
+ const thisValue = {
+ pipeTo() {
+ ++count;
+ }
+ };
+
+ ReadableStream.prototype.pipeThrough.call(thisValue, { readable: {}, writable: {} });
+
+ assert_equals(count, 1, 'pipeTo was called once');
+
+}, 'pipeThrough should work with no options argument');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/piping/then-interception.js b/test/fixtures/web-platform-tests/streams/piping/then-interception.js
new file mode 100644
index 00000000000000..e7f8d94b859547
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/piping/then-interception.js
@@ -0,0 +1,67 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+function interceptThen() {
+ const intercepted = [];
+ const callCount = 0;
+ Object.prototype.then = function(resolver) {
+ if (!this.done) {
+ intercepted.push(this.value);
+ }
+ const retval = Object.create(null);
+ retval.done = ++callCount === 3;
+ retval.value = callCount;
+ resolver(retval);
+ if (retval.done) {
+ delete Object.prototype.then;
+ }
+ }
+ return intercepted;
+}
+
+promise_test(async () => {
+ const rs = new ReadableStream({
+ start(controller) {
+ controller.enqueue('a');
+ controller.close();
+ }
+ });
+ const ws = recordingWritableStream();
+
+ const intercepted = interceptThen();
+
+ await rs.pipeTo(ws);
+ delete Object.prototype.then;
+
+
+ assert_array_equals(intercepted, [], 'nothing should have been intercepted');
+ assert_array_equals(ws.events, ['write', 'a', 'close'], 'written chunk should be "a"');
+}, 'piping should not be observable');
+
+promise_test(async () => {
+ const rs = new ReadableStream({
+ start(controller) {
+ controller.enqueue('a');
+ controller.close();
+ }
+ });
+ const ws = recordingWritableStream();
+
+ const [ branch1, branch2 ] = rs.tee();
+
+ const intercepted = interceptThen();
+
+ await branch1.pipeTo(ws);
+ delete Object.prototype.then;
+ branch2.cancel();
+
+ assert_array_equals(intercepted, [], 'nothing should have been intercepted');
+ assert_array_equals(ws.events, ['write', 'a', 'close'], 'written chunk should be "a"');
+}, 'tee should not be observable');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/piping/transform-streams.js b/test/fixtures/web-platform-tests/streams/piping/transform-streams.js
new file mode 100644
index 00000000000000..8f6804a2227d7a
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/piping/transform-streams.js
@@ -0,0 +1,27 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+promise_test(() => {
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ c.enqueue('c');
+ c.close();
+ }
+ });
+
+ const ts = new TransformStream();
+
+ const ws = new WritableStream();
+
+ return rs.pipeThrough(ts).pipeTo(ws).then(() => {
+ const writer = ws.getWriter();
+ return writer.closed;
+ });
+}, 'Piping through an identity transform stream should close the destination when the source closes');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-byte-streams/brand-checks.js b/test/fixtures/web-platform-tests/streams/readable-byte-streams/brand-checks.js
new file mode 100644
index 00000000000000..702c6530165c2b
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-byte-streams/brand-checks.js
@@ -0,0 +1,194 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('/resources/testharness.js');
+}
+
+let ReadableStreamBYOBReader;
+let ReadableByteStreamController;
+
+test(() => {
+
+ // It's not exposed globally, but we test a few of its properties here.
+ ReadableStreamBYOBReader = realRSBYOBReader().constructor;
+
+ assert_equals(ReadableStreamBYOBReader.name, 'ReadableStreamBYOBReader', 'ReadableStreamBYOBReader should be set');
+
+}, 'Can get the ReadableStreamBYOBReader constructor indirectly');
+
+test(() => {
+
+ // It's not exposed globally, but we test a few of its properties here.
+ ReadableByteStreamController = realRBSController().constructor;
+
+ assert_equals(ReadableByteStreamController.name, 'ReadableByteStreamController',
+ 'ReadableByteStreamController should be set');
+
+}, 'Can get the ReadableByteStreamController constructor indirectly');
+
+function fakeRS() {
+ return Object.setPrototypeOf({
+ cancel() { return Promise.resolve(); },
+ getReader() { return realRSBYOBReader(); },
+ pipeThrough(obj) { return obj.readable; },
+ pipeTo() { return Promise.resolve(); },
+ tee() { return [realRS(), realRS()]; }
+ }, ReadableStream.prototype);
+}
+
+function realRS() {
+ return new ReadableStream();
+}
+
+function fakeRSBYOBReader() {
+ return Object.setPrototypeOf({
+ get closed() { return Promise.resolve(); },
+ cancel() { return Promise.resolve(); },
+ read() { return Promise.resolve({ value: undefined, done: true }); },
+ releaseLock() { return; }
+ }, ReadableStreamBYOBReader.prototype);
+}
+
+function realRSBYOBReader() {
+ return new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' });
+}
+
+function fakeRBSController() {
+ return Object.setPrototypeOf({
+ close() { },
+ enqueue() { },
+ error() { }
+ }, ReadableByteStreamController.prototype);
+}
+
+function realRBSController() {
+ let controller;
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+ return controller;
+}
+
+test(() => {
+
+ assert_throws(new TypeError(), () => new ReadableStreamBYOBReader(fakeRS()), 'constructor should throw');
+
+}, 'ReadableStreamBYOBReader enforces a brand check on its argument');
+
+promise_test(t => {
+
+ return getterRejectsForAll(t, ReadableStreamBYOBReader.prototype, 'closed',
+ [fakeRSBYOBReader(), realRS(), realRBSController(), undefined, null]);
+
+}, 'ReadableStreamBYOBReader.prototype.closed enforces a brand check');
+
+promise_test(t => {
+
+ return methodRejectsForAll(t, ReadableStreamBYOBReader.prototype, 'cancel',
+ [fakeRSBYOBReader(), realRS(), realRBSController(), undefined, null]);
+
+}, 'ReadableStreamBYOBReader.prototype.cancel enforces a brand check');
+
+promise_test(t => {
+
+ return methodRejectsForAll(t, ReadableStreamBYOBReader.prototype, 'read',
+ [fakeRSBYOBReader(), realRS(), realRBSController(), undefined, null], [new Uint8Array(1)]);
+
+}, 'ReadableStreamBYOBReader.prototype.read enforces a brand check');
+
+test(() => {
+
+ methodThrowsForAll(ReadableStreamBYOBReader.prototype, 'releaseLock',
+ [fakeRSBYOBReader(), realRS(), realRBSController(), undefined, null]);
+
+}, 'ReadableStreamBYOBReader.prototype.releaseLock enforces a brand check');
+
+test(() => {
+
+ assert_throws(new TypeError(), () => new ReadableByteStreamController(fakeRS()),
+ 'Constructing a ReadableByteStreamController should throw');
+
+}, 'ReadableByteStreamController enforces a brand check on its arguments');
+
+test(() => {
+
+ assert_throws(new TypeError(), () => new ReadableByteStreamController(realRS()),
+ 'Constructing a ReadableByteStreamController should throw');
+
+}, 'ReadableByteStreamController can\'t be given a fully-constructed ReadableStream');
+
+test(() => {
+
+ getterThrowsForAll(ReadableByteStreamController.prototype, 'byobRequest',
+ [fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null]);
+
+}, 'ReadableByteStreamController.prototype.byobRequest enforces a brand check');
+
+test(() => {
+
+ methodThrowsForAll(ReadableByteStreamController.prototype, 'close',
+ [fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null]);
+
+}, 'ReadableByteStreamController.prototype.close enforces a brand check');
+
+test(() => {
+
+ methodThrowsForAll(ReadableByteStreamController.prototype, 'enqueue',
+ [fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null], [new Uint8Array(1)]);
+
+}, 'ReadableByteStreamController.prototype.enqueue enforces a brand check');
+
+test(() => {
+
+ methodThrowsForAll(ReadableByteStreamController.prototype, 'error',
+ [fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null]);
+
+}, 'ReadableByteStreamController.prototype.error enforces a brand check');
+
+// ReadableStreamBYOBRequest can only be accessed asynchronously, so cram everything into one test.
+promise_test(t => {
+
+ let ReadableStreamBYOBRequest;
+ const rs = new ReadableStream({
+ pull(controller) {
+ return t.step(() => {
+ const byobRequest = controller.byobRequest;
+ ReadableStreamBYOBRequest = byobRequest.constructor;
+ brandChecks();
+ byobRequest.respond(1);
+ });
+ },
+ type: 'bytes'
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ return reader.read(new Uint8Array(1));
+
+ function fakeRSBYOBRequest() {
+ return Object.setPrototypeOf({
+ get view() {},
+ respond() {},
+ respondWithNewView() {}
+ }, ReadableStreamBYOBRequest.prototype);
+ }
+
+ function brandChecks() {
+ for (const badController of [fakeRBSController(), realRS(), realRSBYOBReader(), undefined, null]) {
+ assert_throws(new TypeError(), () => new ReadableStreamBYOBRequest(badController, new Uint8Array(1)),
+ 'ReadableStreamBYOBRequest constructor must throw for an invalid controller argument');
+ }
+ getterThrowsForAll(ReadableStreamBYOBRequest.prototype, 'view',
+ [fakeRSBYOBRequest(), realRS(), realRSBYOBReader(), realRBSController(), undefined, null]);
+ methodThrowsForAll(ReadableStreamBYOBRequest.prototype, 'respond',
+ [fakeRSBYOBRequest(), realRS(), realRSBYOBReader(), realRBSController(), undefined, null], [1]);
+ methodThrowsForAll(ReadableStreamBYOBRequest.prototype, 'respondWithNewView',
+ [fakeRSBYOBRequest(), realRS(), realRSBYOBReader(), realRBSController(), undefined, null],
+ [new Uint8Array(1)]);
+ }
+
+}, 'ReadableStreamBYOBRequest enforces brand checks');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-byte-streams/construct-byob-request.js b/test/fixtures/web-platform-tests/streams/readable-byte-streams/construct-byob-request.js
new file mode 100644
index 00000000000000..29fdac5baa9c87
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-byte-streams/construct-byob-request.js
@@ -0,0 +1,82 @@
+'use strict';
+
+// Prior to whatwg/stream#870 it was possible to construct a ReadableStreamBYOBRequest directly. This made it possible
+// to construct requests that were out-of-sync with the state of the ReadableStream. They could then be used to call
+// internal operations, resulting in asserts or bad behaviour. This file contains regression tests for the change.
+
+if (self.importScripts) {
+ self.importScripts('../resources/rs-utils.js');
+ self.importScripts('/resources/testharness.js');
+}
+
+function getRealByteStreamController() {
+ let controller;
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+ return controller;
+}
+
+const ReadableByteStreamController = getRealByteStreamController().constructor;
+
+// Create an object pretending to have prototype |prototype|, of type |type|. |type| is one of "undefined", "null",
+// "fake", or "real". "real" will call the realObjectCreator function to get a real instance of the object.
+function createDummyObject(prototype, type, realObjectCreator) {
+ switch (type) {
+ case 'undefined':
+ return undefined;
+
+ case 'null':
+ return null;
+
+ case 'fake':
+ return Object.create(prototype);
+
+ case 'real':
+ return realObjectCreator();
+ }
+
+ throw new Error('not reached');
+}
+
+const dummyTypes = ['undefined', 'null', 'fake', 'real'];
+
+function runTests(ReadableStreamBYOBRequest) {
+ for (const controllerType of dummyTypes) {
+ const controller = createDummyObject(ReadableByteStreamController.prototype, controllerType,
+ getRealByteStreamController);
+ for (const viewType of dummyTypes) {
+ const view = createDummyObject(Uint8Array.prototype, viewType, () => new Uint8Array(16));
+ test(() => {
+ assert_throws(new TypeError(), () => new ReadableStreamBYOBRequest(controller, view),
+ 'constructor should throw');
+ }, `ReadableStreamBYOBRequest constructor should throw when passed a ${controllerType} ` +
+ `ReadableByteStreamController and a ${viewType} view`);
+ }
+ }
+}
+
+function getConstructorAndRunTests() {
+ let ReadableStreamBYOBRequest;
+ const rs = new ReadableStream({
+ pull(controller) {
+ const byobRequest = controller.byobRequest;
+ ReadableStreamBYOBRequest = byobRequest.constructor;
+ byobRequest.respond(4);
+ },
+ type: 'bytes'
+ });
+ rs.getReader({ mode: 'byob' }).read(new Uint8Array(8)).then(() => {
+ runTests(ReadableStreamBYOBRequest);
+ done();
+ });
+}
+
+// We can only get at the ReadableStreamBYOBRequest constructor asynchronously, so we need to make the test harness wait
+// for us to explicitly tell it all our tests have run.
+setup({ explicit_done: true });
+
+getConstructorAndRunTests();
diff --git a/test/fixtures/web-platform-tests/streams/readable-byte-streams/constructor.js b/test/fixtures/web-platform-tests/streams/readable-byte-streams/constructor.js
new file mode 100644
index 00000000000000..3405e23878c9ad
--- /dev/null
+++ b/test/fixtures/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();
diff --git a/test/fixtures/web-platform-tests/streams/readable-byte-streams/detached-buffers.js b/test/fixtures/web-platform-tests/streams/readable-byte-streams/detached-buffers.js
new file mode 100644
index 00000000000000..b1b47f01959084
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-byte-streams/detached-buffers.js
@@ -0,0 +1,156 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+promise_test(() => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.close();
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ const view = new Uint8Array([1, 2, 3]);
+ return reader.read(view).then(({ value, done }) => {
+ // Sanity checks
+ assert_true(value instanceof Uint8Array, 'The value read must be a Uint8Array');
+ assert_not_equals(value, view, 'The value read must not be the *same* Uint8Array');
+ assert_array_equals(value, [], 'The value read must be an empty Uint8Array, since the stream is closed');
+ assert_true(done, 'done must be true, since the stream is closed');
+
+ // The important assertions
+ assert_not_equals(value.buffer, view.buffer, 'a different ArrayBuffer must underlie the value');
+ assert_equals(view.buffer.byteLength, 0, 'the original buffer must be detached');
+ });
+}, 'ReadableStream with byte source: read()ing from a closed stream still transfers the buffer');
+
+promise_test(() => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array([1, 2, 3]));
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ const view = new Uint8Array([4, 5, 6]);
+ return reader.read(view).then(({ value, done }) => {
+ // Sanity checks
+ assert_true(value instanceof Uint8Array, 'The value read must be a Uint8Array');
+ assert_not_equals(value, view, 'The value read must not be the *same* Uint8Array');
+ assert_array_equals(value, [1, 2, 3], 'The value read must be the enqueued Uint8Array, not the original values');
+ assert_false(done, 'done must be false, since the stream is not closed');
+
+ // The important assertions
+ assert_not_equals(value.buffer, view.buffer, 'a different ArrayBuffer must underlie the value');
+ assert_equals(view.buffer.byteLength, 0, 'the original buffer must be detached');
+ });
+}, 'ReadableStream with byte source: read()ing from a stream with queued chunks still transfers the buffer');
+
+test(() => {
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array([1, 2, 3]);
+ c.enqueue(view);
+ assert_throws(new TypeError(), () => c.enqueue(view), 'enqueuing an already-detached buffer must throw');
+ },
+ type: 'bytes'
+ });
+}, 'ReadableStream with byte source: enqueuing an already-detached buffer throws');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array([1, 2, 3]));
+ },
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const view = new Uint8Array([4, 5, 6]);
+ return reader.read(view).then(() => {
+ // view is now detached
+ return promise_rejects(t, new TypeError(), reader.read(view),
+ 'read(view) must reject when given an already-detached buffer');
+ });
+}, 'ReadableStream with byte source: reading into an already-detached buffer rejects');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ // Detach it by reading into it
+ reader.read(c.byobRequest.view);
+
+ assert_throws(new TypeError(), () => c.byobRequest.respond(1),
+ 'respond() must throw if the corresponding view has become detached');
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respond() throws if the BYOB request\'s buffer has been detached (in the ' +
+ 'readable state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ // Detach it by reading into it
+ reader.read(c.byobRequest.view);
+
+ c.close();
+
+ assert_throws(new TypeError(), () => c.byobRequest.respond(0),
+ 'respond() must throw if the corresponding view has become detached');
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respond() throws if the BYOB request\'s buffer has been detached (in the ' +
+ 'closed state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ // Detach it by reading into it
+ const view = new Uint8Array([1, 2, 3]);
+ reader.read(view);
+
+ assert_throws(new TypeError(), () => c.byobRequest.respondWithNewView(view),
+ 'respondWithNewView() must throw if passed a detached view');
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has been detached ' +
+ '(in the readable state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ // Detach it by reading into it
+ const view = new Uint8Array([1, 2, 3]);
+ reader.read(view);
+
+ c.close();
+
+ const zeroLengthView = new Uint8Array(view.buffer, 0, 0);
+ assert_throws(new TypeError(), () => c.byobRequest.respondWithNewView(zeroLengthView),
+ 'respondWithNewView() must throw if passed a (zero-length) view whose buffer has been detached');
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has been detached ' +
+ '(in the closed state)');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-byte-streams/general.js b/test/fixtures/web-platform-tests/streams/readable-byte-streams/general.js
new file mode 100644
index 00000000000000..39dd7080bc7a11
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-byte-streams/general.js
@@ -0,0 +1,2126 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('../resources/rs-utils.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('/resources/testharness.js');
+}
+
+const error1 = new Error('error1');
+error1.name = 'error1';
+
+test(() => {
+ assert_throws(new TypeError(), () => new ReadableStream().getReader({ mode: 'byob' }));
+}, 'getReader({mode: "byob"}) throws on non-bytes streams');
+
+
+test(() => {
+ // Constructing ReadableStream with an empty underlying byte source object as parameter shouldn't throw.
+ new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' });
+ // Constructor must perform ToString(type).
+ new ReadableStream({ type: { toString() {return 'bytes';} } })
+ .getReader({ mode: 'byob' });
+ new ReadableStream({ type: { toString: null, valueOf() {return 'bytes';} } })
+ .getReader({ mode: 'byob' });
+}, 'ReadableStream with byte source can be constructed with no errors');
+
+test(() => {
+ const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
+ const rs = new ReadableStream({ type: 'bytes' });
+
+ let reader = rs.getReader({ mode: { toString() { return 'byob'; } } });
+ assert_true(reader instanceof ReadableStreamBYOBReader, 'must give a BYOB reader');
+ reader.releaseLock();
+
+ reader = rs.getReader({ mode: { toString: null, valueOf() {return 'byob';} } });
+ assert_true(reader instanceof ReadableStreamBYOBReader, 'must give a BYOB reader');
+ reader.releaseLock();
+
+ reader = rs.getReader({ mode: 'byob', notmode: 'ignored' });
+ assert_true(reader instanceof ReadableStreamBYOBReader, 'must give a BYOB reader');
+}, 'getReader({mode}) must perform ToString()');
+
+promise_test(() => {
+ let startCalled = false;
+ let startCalledBeforePull = false;
+ let desiredSize;
+ let controller;
+
+ let resolveTestPromise;
+ const testPromise = new Promise(resolve => {
+ resolveTestPromise = resolve;
+ });
+
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ startCalled = true;
+ },
+ pull() {
+ startCalledBeforePull = startCalled;
+ desiredSize = controller.desiredSize;
+ resolveTestPromise();
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 256
+ });
+
+ return testPromise.then(() => {
+ assert_true(startCalledBeforePull, 'start should be called before pull');
+ assert_equals(desiredSize, 256, 'desiredSize should equal highWaterMark');
+ });
+
+}, 'ReadableStream with byte source: Construct and expect start and pull being called');
+
+promise_test(() => {
+ let pullCount = 0;
+ let checkedNoPull = false;
+
+ let resolveTestPromise;
+ const testPromise = new Promise(resolve => {
+ resolveTestPromise = resolve;
+ });
+ let resolveStartPromise;
+
+ new ReadableStream({
+ start() {
+ return new Promise(resolve => {
+ resolveStartPromise = resolve;
+ });
+ },
+ pull() {
+ if (checkedNoPull) {
+ resolveTestPromise();
+ }
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 256
+ });
+
+ Promise.resolve().then(() => {
+ assert_equals(pullCount, 0);
+ checkedNoPull = true;
+ resolveStartPromise();
+ });
+
+ return testPromise;
+
+}, 'ReadableStream with byte source: No automatic pull call if start doesn\'t finish');
+
+promise_test(t => {
+ new ReadableStream({
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ }, {
+ highWaterMark: 0
+ });
+
+ return Promise.resolve();
+}, 'ReadableStream with byte source: Construct with highWaterMark of 0');
+
+test(() => {
+ new ReadableStream({
+ start(c) {
+ assert_equals(c.desiredSize, 10, 'desiredSize must start at the highWaterMark');
+ c.close();
+ assert_equals(c.desiredSize, 0, 'after closing, desiredSize must be 0');
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 10
+ });
+}, 'ReadableStream with byte source: desiredSize when closed');
+
+test(() => {
+ new ReadableStream({
+ start(c) {
+ assert_equals(c.desiredSize, 10, 'desiredSize must start at the highWaterMark');
+ c.error();
+ assert_equals(c.desiredSize, null, 'after erroring, desiredSize must be null');
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 10
+ });
+}, 'ReadableStream with byte source: desiredSize when errored');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+ reader.releaseLock();
+
+ return promise_rejects(t, new TypeError(), reader.closed, 'closed must reject');
+}, 'ReadableStream with byte source: getReader(), then releaseLock()');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ reader.releaseLock();
+
+ return promise_rejects(t, new TypeError(), reader.closed, 'closed must reject');
+}, 'ReadableStream with byte source: getReader() with mode set to byob, then releaseLock()');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.close();
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return reader.closed.then(() => {
+ assert_throws(new TypeError(), () => stream.getReader(), 'getReader() must throw');
+ });
+}, 'ReadableStream with byte source: Test that closing a stream does not release a reader automatically');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.close();
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.closed.then(() => {
+ assert_throws(new TypeError(), () => stream.getReader({ mode: 'byob' }), 'getReader() must throw');
+ });
+}, 'ReadableStream with byte source: Test that closing a stream does not release a BYOB reader automatically');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.error(error1);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return promise_rejects(t, error1, reader.closed, 'closed must reject').then(() => {
+ assert_throws(new TypeError(), () => stream.getReader(), 'getReader() must throw');
+ });
+}, 'ReadableStream with byte source: Test that erroring a stream does not release a reader automatically');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.error(error1);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects(t, error1, reader.closed, 'closed must reject').then(() => {
+ assert_throws(new TypeError(), () => stream.getReader({ mode: 'byob' }), 'getReader() must throw');
+ });
+}, 'ReadableStream with byte source: Test that erroring a stream does not release a BYOB reader automatically');
+
+test(() => {
+ const stream = new ReadableStream({
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+ reader.read();
+ assert_throws(new TypeError(), () => reader.releaseLock(), 'reader.releaseLock() must throw');
+}, 'ReadableStream with byte source: releaseLock() on ReadableStreamReader with pending read() must throw');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ const stream = new ReadableStream({
+ pull() {
+ ++pullCount;
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 8
+ });
+
+ stream.getReader();
+
+ assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');
+
+ return Promise.resolve().then(() => {
+ assert_equals(pullCount, 1, 'pull must be invoked');
+ });
+}, 'ReadableStream with byte source: Automatic pull() after start()');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ const stream = new ReadableStream({
+ pull() {
+ ++pullCount;
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 0
+ });
+
+ const reader = stream.getReader();
+ reader.read();
+
+ assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');
+
+ return Promise.resolve().then(() => {
+ assert_equals(pullCount, 1, 'pull must be invoked');
+ });
+}, 'ReadableStream with byte source: Automatic pull() after start() and read()');
+
+// View buffers are detached after pull() returns, so record the information at the time that pull() was called.
+function extractViewInfo(view) {
+ return {
+ constructor: view.constructor,
+ bufferByteLength: view.buffer.byteLength,
+ byteOffset: view.byteOffset,
+ byteLength: view.byteLength
+ };
+}
+
+promise_test(() => {
+ let pullCount = 0;
+ let controller;
+ const byobRequests = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ const byobRequest = controller.byobRequest;
+ const view = byobRequest.view;
+ byobRequests[pullCount] = {
+ defined: byobRequest !== undefined,
+ viewDefined: view !== undefined,
+ viewInfo: extractViewInfo(view)
+ };
+ if (pullCount === 0) {
+ view[0] = 0x01;
+ byobRequest.respond(1);
+ } else if (pullCount === 1) {
+ view[0] = 0x02;
+ view[1] = 0x03;
+ byobRequest.respond(2);
+ }
+
+ ++pullCount;
+ },
+ type: 'bytes',
+ autoAllocateChunkSize: 16
+ }, {
+ highWaterMark: 0
+ });
+
+ const reader = stream.getReader();
+ const p0 = reader.read();
+ const p1 = reader.read();
+
+ assert_equals(pullCount, 0, 'No pull() as start() just finished and is not yet reflected to the state of the stream');
+
+ return Promise.resolve().then(() => {
+ assert_equals(pullCount, 1, 'pull() must have been invoked once');
+ const byobRequest = byobRequests[0];
+ assert_true(byobRequest.defined, 'first byobRequest must not be undefined');
+ assert_true(byobRequest.viewDefined, 'first byobRequest.view must not be undefined');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 16, 'first view.buffer.byteLength should be 16');
+ assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 16, 'first view.byteLength should be 16');
+
+ return p0;
+ }).then(result => {
+ assert_equals(pullCount, 2, 'pull() must have been invoked twice');
+ const value = result.value;
+ assert_not_equals(value, undefined, 'first read should have a value');
+ assert_equals(value.constructor, Uint8Array, 'first value should be a Uint8Array');
+ assert_equals(value.buffer.byteLength, 16, 'first value.buffer.byteLength should be 16');
+ assert_equals(value.byteOffset, 0, 'first value.byteOffset should be 0');
+ assert_equals(value.byteLength, 1, 'first value.byteLength should be 1');
+ assert_equals(value[0], 0x01, 'first value[0] should be 0x01');
+ const byobRequest = byobRequests[1];
+ assert_true(byobRequest.defined, 'second byobRequest must not be undefined');
+ assert_true(byobRequest.viewDefined, 'second byobRequest.view must not be undefined');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 16, 'second view.buffer.byteLength should be 16');
+ assert_equals(viewInfo.byteOffset, 0, 'second view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 16, 'second view.byteLength should be 16');
+
+ return p1;
+ }).then(result => {
+ assert_equals(pullCount, 2, 'pull() should only be invoked twice');
+ const value = result.value;
+ assert_not_equals(value, undefined, 'second read should have a value');
+ assert_equals(value.constructor, Uint8Array, 'second value should be a Uint8Array');
+ assert_equals(value.buffer.byteLength, 16, 'second value.buffer.byteLength should be 16');
+ assert_equals(value.byteOffset, 0, 'second value.byteOffset should be 0');
+ assert_equals(value.byteLength, 2, 'second value.byteLength should be 2');
+ assert_equals(value[0], 0x02, 'second value[0] should be 0x02');
+ assert_equals(value[1], 0x03, 'second value[1] should be 0x03');
+ });
+}, 'ReadableStream with byte source: autoAllocateChunkSize');
+
+promise_test(() => {
+ let pullCount = 0;
+ let controller;
+ const byobRequests = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ const byobRequest = controller.byobRequest;
+ const view = byobRequest.view;
+ byobRequests[pullCount] = {
+ defined: byobRequest !== undefined,
+ viewDefined: view !== undefined,
+ viewInfo: extractViewInfo(view)
+ };
+ if (pullCount === 0) {
+ view[0] = 0x01;
+ byobRequest.respond(1);
+ } else if (pullCount === 1) {
+ view[0] = 0x02;
+ view[1] = 0x03;
+ byobRequest.respond(2);
+ }
+
+ ++pullCount;
+ },
+ type: 'bytes',
+ autoAllocateChunkSize: 16
+ }, {
+ highWaterMark: 0
+ });
+
+ const reader = stream.getReader();
+ return reader.read().then(result => {
+ const value = result.value;
+ assert_not_equals(value, undefined, 'first read should have a value');
+ assert_equals(value.constructor, Uint8Array, 'first value should be a Uint8Array');
+ assert_equals(value.buffer.byteLength, 16, 'first value.buffer.byteLength should be 16');
+ assert_equals(value.byteOffset, 0, 'first value.byteOffset should be 0');
+ assert_equals(value.byteLength, 1, 'first value.byteLength should be 1');
+ assert_equals(value[0], 0x01, 'first value[0] should be 0x01');
+ const byobRequest = byobRequests[0];
+ assert_true(byobRequest.defined, 'first byobRequest must not be undefined');
+ assert_true(byobRequest.viewDefined, 'first byobRequest.view must not be undefined');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 16, 'first view.buffer.byteLength should be 16');
+ assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 16, 'first view.byteLength should be 16');
+
+ reader.releaseLock();
+ const byobReader = stream.getReader({ mode: 'byob' });
+ return byobReader.read(new Uint8Array(32));
+ }).then(result => {
+ const value = result.value;
+ assert_not_equals(value, undefined, 'second read should have a value');
+ assert_equals(value.constructor, Uint8Array, 'second value should be a Uint8Array');
+ assert_equals(value.buffer.byteLength, 32, 'second value.buffer.byteLength should be 32');
+ assert_equals(value.byteOffset, 0, 'second value.byteOffset should be 0');
+ assert_equals(value.byteLength, 2, 'second value.byteLength should be 2');
+ assert_equals(value[0], 0x02, 'second value[0] should be 0x02');
+ assert_equals(value[1], 0x03, 'second value[1] should be 0x03');
+ const byobRequest = byobRequests[1];
+ assert_true(byobRequest.defined, 'second byobRequest must not be undefined');
+ assert_true(byobRequest.viewDefined, 'second byobRequest.view must not be undefined');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 32, 'second view.buffer.byteLength should be 32');
+ assert_equals(viewInfo.byteOffset, 0, 'second view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 32, 'second view.byteLength should be 32');
+ assert_equals(pullCount, 2, 'pullCount should be 2');
+ });
+}, 'ReadableStream with byte source: Mix of auto allocate and BYOB');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ const stream = new ReadableStream({
+ pull() {
+ ++pullCount;
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 0
+ });
+
+ const reader = stream.getReader();
+ reader.read(new Uint8Array(8));
+
+ assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');
+
+ return Promise.resolve().then(() => {
+ assert_equals(pullCount, 1, 'pull must be invoked');
+ });
+}, 'ReadableStream with byte source: Automatic pull() after start() and read(view)');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ let desiredSizeInStart;
+ let desiredSizeInPull;
+
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array(16));
+ desiredSizeInStart = c.desiredSize;
+ controller = c;
+ },
+ pull() {
+ ++pullCount;
+
+ if (pullCount === 1) {
+ desiredSizeInPull = controller.desiredSize;
+ }
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 8
+ });
+
+ return Promise.resolve().then(() => {
+ assert_equals(pullCount, 0, 'No pull as the queue was filled by start()');
+ assert_equals(desiredSizeInStart, -8, 'desiredSize after enqueue() in start()');
+
+ const reader = stream.getReader();
+
+ const promise = reader.read();
+ assert_equals(pullCount, 1, 'The first pull() should be made on read()');
+ assert_equals(desiredSizeInPull, 8, 'desiredSize in pull()');
+
+ return promise.then(result => {
+ assert_equals(result.done, false, 'result.done');
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array, 'view.constructor');
+ assert_equals(view.buffer.byteLength, 16, 'view.buffer');
+ assert_equals(view.byteOffset, 0, 'view.byteOffset');
+ assert_equals(view.byteLength, 16, 'view.byteLength');
+ });
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then read()');
+
+promise_test(() => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ const promise = reader.read().then(result => {
+ assert_equals(result.done, false);
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array);
+ assert_equals(view.buffer.byteLength, 1);
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 1);
+ });
+
+ controller.enqueue(new Uint8Array(1));
+
+ return promise;
+}, 'ReadableStream with byte source: Push source that doesn\'t understand pull signal');
+
+test(() => {
+ assert_throws(new TypeError(), () => new ReadableStream({
+ pull: 'foo',
+ type: 'bytes'
+ }), 'constructor should throw');
+}, 'ReadableStream with byte source: pull() function is not callable');
+
+promise_test(() => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint16Array(16));
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return reader.read().then(result => {
+ assert_equals(result.done, false);
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array);
+ assert_equals(view.buffer.byteLength, 32);
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 32);
+ });
+}, 'ReadableStream with byte source: enqueue() with Uint16Array, getReader(), then read()');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(16);
+ view[0] = 0x01;
+ view[8] = 0x02;
+ c.enqueue(view);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const byobReader = stream.getReader({ mode: 'byob' });
+
+ return byobReader.read(new Uint8Array(8)).then(result => {
+ assert_equals(result.done, false, 'done');
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array, 'value.constructor');
+ assert_equals(view.buffer.byteLength, 8, 'value.buffer.byteLength');
+ assert_equals(view.byteOffset, 0, 'value.byteOffset');
+ assert_equals(view.byteLength, 8, 'value.byteLength');
+ assert_equals(view[0], 0x01);
+
+ byobReader.releaseLock();
+
+ const reader = stream.getReader();
+
+ return reader.read();
+ }).then(result => {
+ assert_equals(result.done, false, 'done');
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array, 'value.constructor');
+ assert_equals(view.buffer.byteLength, 16, 'value.buffer.byteLength');
+ assert_equals(view.byteOffset, 8, 'value.byteOffset');
+ assert_equals(view.byteLength, 8, 'value.byteLength');
+ assert_equals(view[0], 0x02);
+ });
+}, 'ReadableStream with byte source: enqueue(), read(view) partially, then read()');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ controller.enqueue(new Uint8Array(16));
+ controller.close();
+
+ return reader.read().then(result => {
+ assert_equals(result.done, false, 'done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 16, 'byteLength');
+
+ return reader.read();
+ }).then(result => {
+ assert_equals(result.done, true, 'done');
+ assert_equals(result.value, undefined, 'value');
+ });
+}, 'ReadableStream with byte source: getReader(), enqueue(), close(), then read()');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array(16));
+ c.close();
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return reader.read().then(result => {
+ assert_equals(result.done, false, 'done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 16, 'byteLength');
+
+ return reader.read();
+ }).then(result => {
+ assert_equals(result.done, true, 'done');
+ assert_equals(result.value, undefined, 'value');
+ });
+}, 'ReadableStream with byte source: enqueue(), close(), getReader(), then read()');
+
+promise_test(() => {
+ let controller;
+ let byobRequest;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ controller.enqueue(new Uint8Array(16));
+ byobRequest = controller.byobRequest;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return reader.read().then(result => {
+ assert_equals(result.done, false, 'done');
+ assert_equals(result.value.byteLength, 16, 'byteLength');
+ assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
+ });
+}, 'ReadableStream with byte source: Respond to pull() by enqueue()');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ let byobRequest;
+ const desiredSizes = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+ desiredSizes.push(controller.desiredSize);
+ controller.enqueue(new Uint8Array(1));
+ desiredSizes.push(controller.desiredSize);
+ controller.enqueue(new Uint8Array(1));
+ desiredSizes.push(controller.desiredSize);
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 0
+ });
+
+ const reader = stream.getReader();
+
+ const p0 = reader.read();
+ const p1 = reader.read();
+ const p2 = reader.read();
+
+ // Respond to the first pull call.
+ controller.enqueue(new Uint8Array(1));
+
+ assert_equals(pullCount, 0, 'pullCount after the enqueue() outside pull');
+
+ return Promise.all([p0, p1, p2]).then(result => {
+ assert_equals(pullCount, 1, 'pullCount after completion of all read()s');
+
+ assert_equals(result[0].done, false, 'result[0].done');
+ assert_equals(result[0].value.byteLength, 1, 'result[0].value.byteLength');
+ assert_equals(result[1].done, false, 'result[1].done');
+ assert_equals(result[1].value.byteLength, 1, 'result[1].value.byteLength');
+ assert_equals(result[2].done, false, 'result[2].done');
+ assert_equals(result[2].value.byteLength, 1, 'result[2].value.byteLength');
+ assert_equals(byobRequest, undefined, 'byobRequest should be undefined');
+ assert_equals(desiredSizes[0], 0, 'desiredSize on pull should be 0');
+ assert_equals(desiredSizes[1], 0, 'desiredSize after 1st enqueue() should be 0');
+ assert_equals(desiredSizes[2], 0, 'desiredSize after 2nd enqueue() should be 0');
+ assert_equals(pullCount, 1, 'pull() should only be called once');
+ });
+}, 'ReadableStream with byte source: Respond to pull() by enqueue() asynchronously');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let byobRequest;
+ const desiredSizes = [];
+
+ const stream = new ReadableStream({
+ pull(c) {
+ byobRequest = c.byobRequest;
+ desiredSizes.push(c.desiredSize);
+
+ if (pullCount < 3) {
+ c.enqueue(new Uint8Array(1));
+ } else {
+ c.close();
+ }
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 256
+ });
+
+ const reader = stream.getReader();
+
+ const p0 = reader.read();
+ const p1 = reader.read();
+ const p2 = reader.read();
+
+ assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');
+
+ return Promise.all([p0, p1, p2]).then(result => {
+ assert_equals(pullCount, 4, 'pullCount after completion of all read()s');
+
+ assert_equals(result[0].done, false, 'result[0].done');
+ assert_equals(result[0].value.byteLength, 1, 'result[0].value.byteLength');
+ assert_equals(result[1].done, false, 'result[1].done');
+ assert_equals(result[1].value.byteLength, 1, 'result[1].value.byteLength');
+ assert_equals(result[2].done, false, 'result[2].done');
+ assert_equals(result[2].value.byteLength, 1, 'result[2].value.byteLength');
+ assert_equals(byobRequest, undefined, 'byobRequest should be undefined');
+ assert_equals(desiredSizes[0], 256, 'desiredSize on pull should be 256');
+ assert_equals(desiredSizes[1], 256, 'desiredSize after 1st enqueue() should be 256');
+ assert_equals(desiredSizes[2], 256, 'desiredSize after 2nd enqueue() should be 256');
+ assert_equals(desiredSizes[3], 256, 'desiredSize after 3rd enqueue() should be 256');
+ });
+}, 'ReadableStream with byte source: Respond to multiple pull() by separate enqueue()');
+
+promise_test(() => {
+ let controller;
+
+ let pullCount = 0;
+ const byobRequestDefined = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequestDefined.push(controller.byobRequest !== undefined);
+
+ const view = controller.byobRequest.view;
+ view[0] = 0x01;
+ controller.byobRequest.respond(1);
+
+ byobRequestDefined.push(controller.byobRequest !== undefined);
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(1)).then(result => {
+ assert_equals(result.done, false, 'result.done');
+ assert_equals(result.value.byteLength, 1, 'result.value.byteLength');
+ assert_equals(result.value[0], 0x01, 'result.value[0]');
+ assert_equals(pullCount, 1, 'pull() should be called only once');
+ assert_true(byobRequestDefined[0], 'byobRequest must not be undefined before respond()');
+ assert_false(byobRequestDefined[1], 'byobRequest must be undefined after respond()');
+ });
+}, 'ReadableStream with byte source: read(view), then respond()');
+
+promise_test(() => {
+ let controller;
+
+ let pullCount = 0;
+ const byobRequestDefined = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequestDefined.push(controller.byobRequest !== undefined);
+
+ // Emulate ArrayBuffer transfer by just creating a new ArrayBuffer and pass it. By checking the result of
+ // read(view), we test that the respond()'s buffer argument is working correctly.
+ //
+ // A real implementation of the underlying byte source would transfer controller.byobRequest.view.buffer into
+ // a new ArrayBuffer, then construct a view around it and write to it.
+ const transferredView = new Uint8Array(1);
+ transferredView[0] = 0x01;
+ controller.byobRequest.respondWithNewView(transferredView);
+
+ byobRequestDefined.push(controller.byobRequest !== undefined);
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(1)).then(result => {
+ assert_equals(result.done, false, 'result.done');
+ assert_equals(result.value.byteLength, 1, 'result.value.byteLength');
+ assert_equals(result.value[0], 0x01, 'result.value[0]');
+ assert_equals(pullCount, 1, 'pull() should be called only once');
+ assert_true(byobRequestDefined[0], 'byobRequest must not be undefined before respond()');
+ assert_false(byobRequestDefined[1], 'byobRequest must be undefined after respond()');
+ });
+}, 'ReadableStream with byte source: read(view), then respond() with a transferred ArrayBuffer');
+
+promise_test(() => {
+ let controller;
+ let byobRequestWasDefined;
+ let incorrectRespondException;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequestWasDefined = controller.byobRequest !== undefined;
+
+ try {
+ controller.byobRequest.respond(2);
+ } catch (e) {
+ incorrectRespondException = e;
+ }
+
+ controller.byobRequest.respond(1);
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(1)).then(() => {
+ assert_true(byobRequestWasDefined, 'byobRequest should be defined');
+ assert_not_equals(incorrectRespondException, undefined, 'respond() must throw');
+ assert_equals(incorrectRespondException.name, 'RangeError', 'respond() must throw a RangeError');
+ });
+}, 'ReadableStream with byte source: read(view), then respond() with too big value');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ let byobRequest;
+ let viewInfo;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ ++pullCount;
+
+ byobRequest = controller.byobRequest;
+ const view = byobRequest.view;
+ viewInfo = extractViewInfo(view);
+
+ view[0] = 0x01;
+ view[1] = 0x02;
+ view[2] = 0x03;
+
+ controller.byobRequest.respond(3);
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint16Array(2)).then(result => {
+ assert_equals(pullCount, 1);
+
+ assert_equals(result.done, false, 'done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 2, 'byteLength');
+
+ assert_equals(view[0], 0x0201);
+
+ return reader.read(new Uint8Array(1));
+ }).then(result => {
+ assert_equals(pullCount, 1);
+ assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined');
+ assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 4, 'view.buffer.byteLength should be 4');
+ assert_equals(viewInfo.byteOffset, 0, 'view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 4, 'view.byteLength should be 4');
+
+ assert_equals(result.done, false, 'done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 1, 'byteLength');
+
+ assert_equals(view[0], 0x03);
+ });
+}, 'ReadableStream with byte source: respond(3) to read(view) with 2 element Uint16Array enqueues the 1 byte ' +
+ 'remainder');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(controller) {
+ const view = new Uint8Array(16);
+ view[15] = 0x01;
+ controller.enqueue(view);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(16)).then(result => {
+ assert_equals(result.done, false);
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 16);
+ assert_equals(view[15], 0x01);
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view)');
+
+promise_test(t => {
+ let cancelCount = 0;
+ let reason;
+
+ const passedReason = new TypeError('foo');
+
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array(16));
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ cancel(r) {
+ if (cancelCount === 0) {
+ reason = r;
+ }
+
+ ++cancelCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return reader.cancel(passedReason).then(result => {
+ assert_equals(result, undefined);
+ assert_equals(cancelCount, 1);
+ assert_equals(reason, passedReason, 'reason should equal the passed reason');
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then cancel() (mode = not BYOB)');
+
+promise_test(t => {
+ let cancelCount = 0;
+ let reason;
+
+ const passedReason = new TypeError('foo');
+
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array(16));
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ cancel(r) {
+ if (cancelCount === 0) {
+ reason = r;
+ }
+
+ ++cancelCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.cancel(passedReason).then(result => {
+ assert_equals(result, undefined);
+ assert_equals(cancelCount, 1);
+ assert_equals(reason, passedReason, 'reason should equal the passed reason');
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then cancel() (mode = BYOB)');
+
+promise_test(t => {
+ let cancelCount = 0;
+ let reason;
+
+ const passedReason = new TypeError('foo');
+
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ cancel(r) {
+ if (cancelCount === 0) {
+ reason = r;
+ controller.byobRequest.respond(0);
+ }
+
+ ++cancelCount;
+
+ return 'bar';
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const readPromise = reader.read(new Uint8Array(1)).then(result => {
+ assert_equals(result.done, true);
+ });
+
+ const cancelPromise = reader.cancel(passedReason).then(result => {
+ assert_equals(result, undefined);
+ assert_equals(cancelCount, 1);
+ assert_equals(reason, passedReason, 'reason should equal the passed reason');
+ });
+
+ return Promise.all([readPromise, cancelPromise]);
+}, 'ReadableStream with byte source: getReader(), read(view), then cancel()');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ let byobRequest;
+ const viewInfos = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+
+ viewInfos.push(extractViewInfo(controller.byobRequest.view));
+ controller.enqueue(new Uint8Array(1));
+ viewInfos.push(extractViewInfo(controller.byobRequest.view));
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ return Promise.resolve().then(() => {
+ assert_equals(pullCount, 0, 'No pull() as no read(view) yet');
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const promise = reader.read(new Uint16Array(1)).then(result => {
+ assert_equals(result.done, true, 'result.done');
+ assert_equals(result.value.constructor, Uint16Array, 'result.value');
+ });
+
+ assert_equals(pullCount, 1, '1 pull() should have been made in response to partial fill by enqueue()');
+ assert_not_equals(byobRequest, undefined, 'byobRequest should not be undefined');
+ assert_equals(viewInfos[0].byteLength, 2, 'byteLength before enqueue() shouild be 2');
+ assert_equals(viewInfos[1].byteLength, 1, 'byteLength after enqueue() should be 1');
+
+
+ reader.cancel();
+
+ // Tell that the buffer given via pull() is returned.
+ controller.byobRequest.respond(0);
+
+ assert_equals(pullCount, 1, 'pull() should only be called once');
+ return promise;
+ });
+}, 'ReadableStream with byte source: cancel() with partially filled pending pull() request');
+
+promise_test(() => {
+ let controller;
+ let byobRequest;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(8);
+ view[7] = 0x01;
+ c.enqueue(view);
+
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const buffer = new ArrayBuffer(16);
+
+ return reader.read(new Uint8Array(buffer, 8, 8)).then(result => {
+ assert_equals(result.done, false);
+
+ assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array);
+ assert_equals(view.buffer.byteLength, 16);
+ assert_equals(view.byteOffset, 8);
+ assert_equals(view.byteLength, 8);
+ assert_equals(view[7], 0x01);
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) where view.buffer is not fully ' +
+ 'covered by view');
+
+promise_test(() => {
+ let controller;
+ let byobRequest;
+
+ const stream = new ReadableStream({
+ start(c) {
+ let view;
+
+ view = new Uint8Array(16);
+ view[15] = 123;
+ c.enqueue(view);
+
+ view = new Uint8Array(8);
+ view[7] = 111;
+ c.enqueue(view);
+
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(24)).then(result => {
+ assert_equals(result.done, false, 'done');
+
+ assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 24, 'byteLength');
+ assert_equals(view[15], 123, 'Contents are set from the first chunk');
+ assert_equals(view[23], 111, 'Contents are set from the second chunk');
+ });
+}, 'ReadableStream with byte source: Multiple enqueue(), getReader(), then read(view)');
+
+promise_test(() => {
+ let byobRequest;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(16);
+ view[15] = 0x01;
+ c.enqueue(view);
+ },
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(24)).then(result => {
+ assert_equals(result.done, false);
+
+ assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 16);
+ assert_equals(view[15], 0x01);
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) with a bigger view');
+
+promise_test(() => {
+ let byobRequest;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(16);
+ view[7] = 0x01;
+ view[15] = 0x02;
+ c.enqueue(view);
+ },
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(8)).then(result => {
+ assert_equals(result.done, false, 'done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 8);
+ assert_equals(view[7], 0x01);
+
+ return reader.read(new Uint8Array(8));
+ }).then(result => {
+ assert_equals(result.done, false, 'done');
+
+ assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 8);
+ assert_equals(view[7], 0x02);
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) with a smaller views');
+
+promise_test(() => {
+ let controller;
+ let viewInfo;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(1);
+ view[0] = 0xff;
+ c.enqueue(view);
+
+ controller = c;
+ },
+ pull() {
+ if (controller.byobRequest === undefined) {
+ return;
+ }
+
+ const view = controller.byobRequest.view;
+ viewInfo = extractViewInfo(view);
+
+ view[0] = 0xaa;
+ controller.byobRequest.respond(1);
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint16Array(1)).then(result => {
+ assert_equals(result.done, false);
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 2);
+ assert_equals(view[0], 0xaaff);
+
+ assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 2, 'view.buffer.byteLength should be 2');
+ assert_equals(viewInfo.byteOffset, 1, 'view.byteOffset should be 1');
+ assert_equals(viewInfo.byteLength, 1, 'view.byteLength should be 1');
+ });
+}, 'ReadableStream with byte source: enqueue() 1 byte, getReader(), then read(view) with Uint16Array');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ let byobRequest;
+ let viewInfo;
+ let desiredSize;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(3);
+ view[0] = 0x01;
+ view[2] = 0x02;
+ c.enqueue(view);
+
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+
+ const view = controller.byobRequest.view;
+
+ viewInfo = extractViewInfo(view);
+
+ view[0] = 0x03;
+ controller.byobRequest.respond(1);
+
+ desiredSize = controller.desiredSize;
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ // Wait for completion of the start method to be reflected.
+ return Promise.resolve().then(() => {
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const promise = reader.read(new Uint16Array(2)).then(result => {
+ assert_equals(result.done, false, 'done');
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint16Array, 'constructor');
+ assert_equals(view.buffer.byteLength, 4, 'buffer.byteLength');
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 2, 'byteLength');
+ assert_equals(view[0], 0x0001, 'Contents are set');
+
+ const p = reader.read(new Uint16Array(1));
+
+ assert_equals(pullCount, 1);
+
+ return p;
+ }).then(result => {
+ assert_equals(result.done, false, 'done');
+
+ const view = result.value;
+ assert_equals(view.buffer.byteLength, 2, 'buffer.byteLength');
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 2, 'byteLength');
+ assert_equals(view[0], 0x0302, 'Contents are set');
+
+ assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined');
+ assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 2, 'view.buffer.byteLength should be 2');
+ assert_equals(viewInfo.byteOffset, 1, 'view.byteOffset should be 1');
+ assert_equals(viewInfo.byteLength, 1, 'view.byteLength should be 1');
+ assert_equals(desiredSize, 0, 'desiredSize should be zero');
+ });
+
+ assert_equals(pullCount, 0);
+
+ return promise;
+ });
+}, 'ReadableStream with byte source: enqueue() 3 byte, getReader(), then read(view) with 2-element Uint16Array');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(1);
+ view[0] = 0xff;
+ c.enqueue(view);
+ c.close();
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+
+ return promise_rejects(t, new TypeError(), reader.read(new Uint16Array(1)), 'read(view) must fail')
+ .then(() => promise_rejects(t, new TypeError(), reader.closed, 'reader.closed should reject'));
+}, 'ReadableStream with byte source: read(view) with Uint16Array on close()-d stream with 1 byte enqueue()-d must ' +
+ 'fail');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(1);
+ view[0] = 0xff;
+ c.enqueue(view);
+
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const readPromise = reader.read(new Uint16Array(1));
+
+ assert_throws(new TypeError(), () => controller.close(), 'controller.close() must throw');
+
+ return promise_rejects(t, new TypeError(), readPromise, 'read(view) must fail')
+ .then(() => promise_rejects(t, new TypeError(), reader.closed, 'reader.closed must reject'));
+}, 'ReadableStream with byte source: A stream must be errored if close()-d before fulfilling read(view) with ' +
+ 'Uint16Array');
+
+test(() => {
+ let controller;
+
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ // Enqueue a chunk so that the stream doesn't get closed. This is to check duplicate close() calls are rejected
+ // even if the stream has not yet entered the closed state.
+ const view = new Uint8Array(1);
+ controller.enqueue(view);
+ controller.close();
+
+ assert_throws(new TypeError(), () => controller.close(), 'controller.close() must throw');
+}, 'ReadableStream with byte source: Throw if close()-ed more than once');
+
+test(() => {
+ let controller;
+
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ // Enqueue a chunk so that the stream doesn't get closed. This is to check enqueue() after close() is rejected
+ // even if the stream has not yet entered the closed state.
+ const view = new Uint8Array(1);
+ controller.enqueue(view);
+ controller.close();
+
+ assert_throws(new TypeError(), () => controller.enqueue(view), 'controller.close() must throw');
+}, 'ReadableStream with byte source: Throw on enqueue() after close()');
+
+promise_test(() => {
+ let controller;
+ let byobRequest;
+ let viewInfo;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+ const view = controller.byobRequest.view;
+ viewInfo = extractViewInfo(view);
+
+ view[15] = 0x01;
+ controller.byobRequest.respond(16);
+ controller.close();
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(16)).then(result => {
+ assert_equals(result.done, false);
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 16);
+ assert_equals(view[15], 0x01);
+
+ return reader.read(new Uint8Array(16));
+ }).then(result => {
+ assert_equals(result.done, true);
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 0);
+
+ assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined');
+ assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 16, 'view.buffer.byteLength should be 16');
+ assert_equals(viewInfo.byteOffset, 0, 'view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 16, 'view.byteLength should be 16');
+ });
+}, 'ReadableStream with byte source: read(view), then respond() and close() in pull()');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ const viewInfos = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ if (controller.byobRequest === undefined) {
+ return;
+ }
+
+ for (let i = 0; i < 4; ++i) {
+ const view = controller.byobRequest.view;
+ viewInfos.push(extractViewInfo(view));
+
+ view[0] = 0x01;
+ controller.byobRequest.respond(1);
+ }
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint32Array(1)).then(result => {
+ assert_equals(result.done, false);
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 4);
+ assert_equals(view[0], 0x01010101);
+
+ assert_equals(pullCount, 1, 'pull() should only be called once');
+
+ for (let i = 0; i < 4; ++i) {
+ assert_equals(viewInfos[i].constructor, Uint8Array, 'view.constructor should be Uint8Array');
+ assert_equals(viewInfos[i].bufferByteLength, 4, 'view.buffer.byteLength should be 4');
+
+ assert_equals(viewInfos[i].byteOffset, i, 'view.byteOffset should be i');
+ assert_equals(viewInfos[i].byteLength, 4 - i, 'view.byteLength should be 4 - i');
+ }
+ });
+}, 'ReadableStream with byte source: read(view) with Uint32Array, then fill it by multiple respond() calls');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ let byobRequest;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ const p0 = reader.read().then(result => {
+ assert_equals(pullCount, 1);
+
+ controller.enqueue(new Uint8Array(2));
+
+ // Since the queue has data no less than HWM, no more pull.
+ assert_equals(pullCount, 1);
+
+ assert_equals(result.done, false);
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array);
+ assert_equals(view.buffer.byteLength, 1);
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 1);
+ });
+
+ assert_equals(pullCount, 0, 'No pull should have been made since the startPromise has not yet been handled');
+
+ const p1 = reader.read().then(result => {
+ assert_equals(pullCount, 1);
+
+ assert_equals(result.done, false);
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array);
+ assert_equals(view.buffer.byteLength, 2);
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 2);
+
+ assert_equals(byobRequest, undefined, 'byobRequest must be undefined');
+ });
+
+ assert_equals(pullCount, 0, 'No pull should have been made since the startPromise has not yet been handled');
+
+ controller.enqueue(new Uint8Array(1));
+
+ assert_equals(pullCount, 0, 'No pull should have been made since the startPromise has not yet been handled');
+
+ return Promise.all([p0, p1]);
+}, 'ReadableStream with byte source: read() twice, then enqueue() twice');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const p0 = reader.read(new Uint8Array(16)).then(result => {
+ assert_equals(result.done, true, '1st read: done');
+
+ const view = result.value;
+ assert_equals(view.buffer.byteLength, 16, '1st read: buffer.byteLength');
+ assert_equals(view.byteOffset, 0, '1st read: byteOffset');
+ assert_equals(view.byteLength, 0, '1st read: byteLength');
+ });
+
+ const p1 = reader.read(new Uint8Array(32)).then(result => {
+ assert_equals(result.done, true, '2nd read: done');
+
+ const view = result.value;
+ assert_equals(view.buffer.byteLength, 32, '2nd read: buffer.byteLength');
+ assert_equals(view.byteOffset, 0, '2nd read: byteOffset');
+ assert_equals(view.byteLength, 0, '2nd read: byteLength');
+ });
+
+ controller.close();
+ controller.byobRequest.respond(0);
+
+ return Promise.all([p0, p1]);
+}, 'ReadableStream with byte source: Multiple read(view), close() and respond()');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const p0 = reader.read(new Uint8Array(16)).then(result => {
+ assert_equals(result.done, false, '1st read: done');
+
+ const view = result.value;
+ assert_equals(view.buffer.byteLength, 16, '1st read: buffer.byteLength');
+ assert_equals(view.byteOffset, 0, '1st read: byteOffset');
+ assert_equals(view.byteLength, 16, '1st read: byteLength');
+ });
+
+ const p1 = reader.read(new Uint8Array(16)).then(result => {
+ assert_equals(result.done, false, '2nd read: done');
+
+ const view = result.value;
+ assert_equals(view.buffer.byteLength, 16, '2nd read: buffer.byteLength');
+ assert_equals(view.byteOffset, 0, '2nd read: byteOffset');
+ assert_equals(view.byteLength, 8, '2nd read: byteLength');
+ });
+
+ controller.enqueue(new Uint8Array(24));
+
+ return Promise.all([p0, p1]);
+}, 'ReadableStream with byte source: Multiple read(view), big enqueue()');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ let bytesRead = 0;
+
+ function pump() {
+ return reader.read(new Uint8Array(7)).then(result => {
+ if (result.done) {
+ assert_equals(bytesRead, 1024);
+ return undefined;
+ }
+
+ bytesRead += result.value.byteLength;
+
+ return pump();
+ });
+ }
+ const promise = pump();
+
+ controller.enqueue(new Uint8Array(512));
+ controller.enqueue(new Uint8Array(512));
+ controller.close();
+
+ return promise;
+}, 'ReadableStream with byte source: Multiple read(view) and multiple enqueue()');
+
+promise_test(t => {
+ let byobRequest;
+ const stream = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects(t, new TypeError(), reader.read(), 'read() must fail')
+ .then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined'));
+}, 'ReadableStream with byte source: read(view) with passing undefined as view must fail');
+
+promise_test(t => {
+ let byobRequest;
+ const stream = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects(t, new TypeError(), reader.read(new Uint8Array(0)), 'read(view) must fail')
+ .then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined'));
+}, 'ReadableStream with byte source: read(view) with zero-length view must fail');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects(t, new TypeError(), reader.read({}), 'read(view) must fail');
+}, 'ReadableStream with byte source: read(view) with passing an empty object as view must fail');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects(t, new TypeError(),
+ reader.read({ buffer: new ArrayBuffer(10), byteOffset: 0, byteLength: 10 }),
+ 'read(view) must fail');
+}, 'ReadableStream with byte source: Even read(view) with passing ArrayBufferView like object as view must fail');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.error(error1);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return promise_rejects(t, error1, reader.read(), 'read() must fail');
+}, 'ReadableStream with byte source: read() on an errored stream');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ const promise = promise_rejects(t, error1, reader.read(), 'read() must fail');
+
+ controller.error(error1);
+
+ return promise;
+}, 'ReadableStream with byte source: read(), then error()');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.error(error1);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects(t, error1, reader.read(new Uint8Array(1)), 'read() must fail');
+}, 'ReadableStream with byte source: read(view) on an errored stream');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const promise = promise_rejects(t, error1, reader.read(new Uint8Array(1)), 'read() must fail');
+
+ controller.error(error1);
+
+ return promise;
+}, 'ReadableStream with byte source: read(view), then error()');
+
+promise_test(t => {
+ let controller;
+ let byobRequest;
+
+ const testError = new TypeError('foo');
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+ throw testError;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ const promise = promise_rejects(t, testError, reader.read(), 'read() must fail');
+ return promise_rejects(t, testError, promise.then(() => reader.closed))
+ .then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined'));
+}, 'ReadableStream with byte source: Throwing in pull function must error the stream');
+
+promise_test(t => {
+ let byobRequest;
+
+ const stream = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ controller.error(error1);
+ throw new TypeError('foo');
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return promise_rejects(t, error1, reader.read(), 'read() must fail')
+ .then(() => promise_rejects(t, error1, reader.closed, 'closed must fail'))
+ .then(() => assert_equals(byobRequest, undefined, 'byobRequest must be undefined'));
+}, 'ReadableStream with byte source: Throwing in pull in response to read() must be ignored if the stream is ' +
+ 'errored in it');
+
+promise_test(t => {
+ let byobRequest;
+
+ const testError = new TypeError('foo');
+
+ const stream = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ throw testError;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects(t, testError, reader.read(new Uint8Array(1)), 'read(view) must fail')
+ .then(() => promise_rejects(t, testError, reader.closed, 'reader.closed must reject'))
+ .then(() => assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined'));
+}, 'ReadableStream with byte source: Throwing in pull in response to read(view) function must error the stream');
+
+promise_test(t => {
+ let byobRequest;
+
+ const stream = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ controller.error(error1);
+ throw new TypeError('foo');
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects(t, error1, reader.read(new Uint8Array(1)), 'read(view) must fail')
+ .then(() => promise_rejects(t, error1, reader.closed, 'closed must fail'))
+ .then(() => assert_not_equals(byobRequest, undefined, 'byobRequest must not be undefined'));
+}, 'ReadableStream with byte source: Throwing in pull in response to read(view) must be ignored if the stream is ' +
+ 'errored in it');
+
+promise_test(() => {
+ let byobRequest;
+ const rs = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ byobRequest.respond(4);
+ },
+ type: 'bytes'
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ const view = new Uint8Array(16);
+ return reader.read(view).then(() => {
+ assert_throws(new TypeError(), () => byobRequest.respond(4), 'respond() should throw a TypeError');
+ });
+}, 'calling respond() twice on the same byobRequest should throw');
+
+promise_test(() => {
+ let byobRequest;
+ const newView = () => new Uint8Array(16);
+ const rs = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ byobRequest.respondWithNewView(newView());
+ },
+ type: 'bytes'
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ return reader.read(newView()).then(() => {
+ assert_throws(new TypeError(), () => byobRequest.respondWithNewView(newView()),
+ 'respondWithNewView() should throw a TypeError');
+ });
+}, 'calling respondWithNewView() twice on the same byobRequest should throw');
+
+promise_test(() => {
+ let byobRequest;
+ let resolvePullCalledPromise;
+ const pullCalledPromise = new Promise(resolve => {
+ resolvePullCalledPromise = resolve;
+ });
+ let resolvePull;
+ const rs = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ resolvePullCalledPromise();
+ return new Promise(resolve => {
+ resolvePull = resolve;
+ });
+ },
+ type: 'bytes'
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ const readPromise = reader.read(new Uint8Array(16));
+ return pullCalledPromise.then(() => {
+ const cancelPromise = reader.cancel('meh');
+ resolvePull();
+ byobRequest.respond(0);
+ return Promise.all([readPromise, cancelPromise]).then(() => {
+ assert_throws(new TypeError(), () => byobRequest.respond(0), 'respond() should throw');
+ });
+ });
+}, 'calling respond(0) twice on the same byobRequest should throw even when closed');
+
+promise_test(() => {
+ let resolvePullCalledPromise;
+ const pullCalledPromise = new Promise(resolve => {
+ resolvePullCalledPromise = resolve;
+ });
+ let resolvePull;
+ const rs = new ReadableStream({
+ pull() {
+ resolvePullCalledPromise();
+ return new Promise(resolve => {
+ resolvePull = resolve;
+ });
+ },
+ type: 'bytes'
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ reader.read(new Uint8Array(16));
+ return pullCalledPromise.then(() => {
+ resolvePull();
+ return delay(0).then(() => {
+ assert_throws(new TypeError(), () => reader.releaseLock(), 'releaseLock() should throw');
+ });
+ });
+}, 'pull() resolving should not make releaseLock() possible');
+
+promise_test(() => {
+ // Tests https://github.com/whatwg/streams/issues/686
+
+ let controller;
+ const rs = new ReadableStream({
+ autoAllocateChunkSize: 128,
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ const readPromise = rs.getReader().read();
+
+ const br = controller.byobRequest;
+ controller.close();
+
+ br.respond(0);
+
+ return readPromise;
+}, 'ReadableStream with byte source: default reader + autoAllocateChunkSize + byobRequest interaction');
+
+test(() => {
+ const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
+ const stream = new ReadableStream({ type: 'bytes' });
+ new ReadableStreamBYOBReader(stream);
+}, 'ReadableStreamBYOBReader can be constructed directly');
+
+test(() => {
+ const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
+ assert_throws(new TypeError(), () => new ReadableStreamBYOBReader({}), 'constructor must throw');
+}, 'ReadableStreamBYOBReader constructor requires a ReadableStream argument');
+
+test(() => {
+ const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
+ const stream = new ReadableStream({ type: 'bytes' });
+ stream.getReader();
+ assert_throws(new TypeError(), () => new ReadableStreamBYOBReader(stream), 'constructor must throw');
+}, 'ReadableStreamBYOBReader constructor requires an unlocked ReadableStream');
+
+test(() => {
+ const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
+ const stream = new ReadableStream();
+ assert_throws(new TypeError(), () => new ReadableStreamBYOBReader(stream), 'constructor must throw');
+}, 'ReadableStreamBYOBReader constructor requires a ReadableStream with type "bytes"');
+
+test(() => {
+ assert_throws(new RangeError(), () => new ReadableStream({ type: 'bytes' }, {
+ size() {
+ return 1;
+ }
+ }), 'constructor should throw for size function');
+
+ assert_throws(new RangeError(), () => new ReadableStream({ type: 'bytes' }, { size: null }),
+ 'constructor should throw for size defined');
+
+ assert_throws(new RangeError(),
+ () => new ReadableStream({ type: 'bytes' }, new CountQueuingStrategy({ highWaterMark: 1 })),
+ 'constructor should throw when strategy is CountQueuingStrategy');
+
+ assert_throws(new RangeError(),
+ () => new ReadableStream({ type: 'bytes' }, new ByteLengthQueuingStrategy({ highWaterMark: 512 })),
+ 'constructor should throw when strategy is ByteLengthQueuingStrategy');
+
+ class HasSizeMethod {
+ size() {}
+ }
+
+ assert_throws(new RangeError(), () => new ReadableStream({ type: 'bytes' }, new HasSizeMethod()),
+ 'constructor should throw when size on the prototype chain');
+}, 'ReadableStream constructor should not accept a strategy with a size defined if type is "bytes"');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-byte-streams/properties.js b/test/fixtures/web-platform-tests/streams/readable-byte-streams/properties.js
new file mode 100644
index 00000000000000..975fba7109b064
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-byte-streams/properties.js
@@ -0,0 +1,147 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('../resources/rs-utils.js');
+ self.importScripts('/resources/testharness.js');
+}
+
+let ReadableStreamBYOBReader;
+
+test(() => {
+
+ // It's not exposed globally, but we test a few of its properties here.
+ ReadableStreamBYOBReader = (new ReadableStream({ type: 'bytes' }))
+ .getReader({ mode: 'byob' }).constructor;
+
+}, 'Can get the ReadableStreamBYOBReader constructor indirectly');
+
+test(() => {
+
+ assert_throws(new TypeError(), () => new ReadableStreamBYOBReader('potato'));
+ assert_throws(new TypeError(), () => new ReadableStreamBYOBReader({}));
+ assert_throws(new TypeError(), () => new ReadableStreamBYOBReader());
+
+}, 'ReadableStreamBYOBReader constructor should get a ReadableStream object as argument');
+
+test(() => {
+
+ const methods = ['cancel', 'constructor', 'read', 'releaseLock'];
+ const properties = methods.concat(['closed']).sort();
+
+ const rsReader = new ReadableStreamBYOBReader(new ReadableStream({ type: 'bytes' }));
+ const proto = Object.getPrototypeOf(rsReader);
+
+ assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
+
+ for (const m of methods) {
+ const propDesc = Object.getOwnPropertyDescriptor(proto, m);
+ assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
+ assert_equals(propDesc.configurable, true, 'method should be configurable');
+ assert_equals(propDesc.writable, true, 'method should be writable');
+ assert_equals(typeof rsReader[m], 'function', 'should have be a method');
+ const expectedName = m === 'constructor' ? 'ReadableStreamBYOBReader' : m;
+ assert_equals(rsReader[m].name, expectedName, 'method should have the correct name');
+ }
+
+ const closedPropDesc = Object.getOwnPropertyDescriptor(proto, 'closed');
+ assert_equals(closedPropDesc.enumerable, false, 'closed should be non-enumerable');
+ assert_equals(closedPropDesc.configurable, true, 'closed should be configurable');
+ assert_not_equals(closedPropDesc.get, undefined, 'closed should have a getter');
+ assert_equals(closedPropDesc.set, undefined, 'closed should not have a setter');
+
+ assert_equals(rsReader.cancel.length, 1, 'cancel has 1 parameter');
+ assert_not_equals(rsReader.closed, undefined, 'has a non-undefined closed property');
+ assert_equals(typeof rsReader.closed.then, 'function', 'closed property is thenable');
+ assert_equals(rsReader.constructor.length, 1, 'constructor has 1 parameter');
+ assert_equals(rsReader.read.length, 1, 'read has 1 parameter');
+ assert_equals(rsReader.releaseLock.length, 0, 'releaseLock has no parameters');
+
+}, 'ReadableStreamBYOBReader instances should have the correct list of properties');
+
+promise_test(t => {
+
+ const rs = new ReadableStream({
+ pull(controller) {
+ return t.step(() => {
+ const byobRequest = controller.byobRequest;
+
+ const methods = ['constructor', 'respond', 'respondWithNewView'];
+ const properties = methods.concat(['view']).sort();
+
+ const proto = Object.getPrototypeOf(byobRequest);
+
+ assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
+
+ for (const m of methods) {
+ const propDesc = Object.getOwnPropertyDescriptor(proto, m);
+ assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
+ assert_equals(propDesc.configurable, true, 'method should be configurable');
+ assert_equals(propDesc.writable, true, 'method should be writable');
+ assert_equals(typeof byobRequest[m], 'function', 'should have a method');
+ const expectedName = m === 'constructor' ? 'ReadableStreamBYOBRequest' : m;
+ assert_equals(byobRequest[m].name, expectedName, 'method should have the correct name');
+ }
+
+ const viewPropDesc = Object.getOwnPropertyDescriptor(proto, 'view');
+ assert_equals(viewPropDesc.enumerable, false, 'view should be non-enumerable');
+ assert_equals(viewPropDesc.configurable, true, 'view should be configurable');
+ assert_not_equals(viewPropDesc.get, undefined, 'view should have a getter');
+ assert_equals(viewPropDesc.set, undefined, 'view should not have a setter');
+ assert_not_equals(byobRequest.view, undefined, 'has a non-undefined view property');
+ assert_equals(byobRequest.constructor.length, 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');
+
+ byobRequest.respond(1);
+
+ });
+ },
+ type: 'bytes' });
+ const reader = rs.getReader({ mode: 'byob' });
+ return reader.read(new Uint8Array(1));
+
+}, 'ReadableStreamBYOBRequest instances should have the correct list of properties');
+
+test(() => {
+
+ const methods = ['close', 'constructor', 'enqueue', 'error'];
+ const accessors = ['byobRequest', 'desiredSize'];
+ const properties = methods.concat(accessors).sort();
+
+ let controller;
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+ const proto = Object.getPrototypeOf(controller);
+
+ assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
+
+ for (const m of methods) {
+ const propDesc = Object.getOwnPropertyDescriptor(proto, m);
+ assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
+ assert_equals(propDesc.configurable, true, 'method should be configurable');
+ assert_equals(propDesc.writable, true, 'method should be writable');
+ assert_equals(typeof controller[m], 'function', 'should have be a method');
+ const expectedName = m === 'constructor' ? 'ReadableByteStreamController' : m;
+ assert_equals(controller[m].name, expectedName, 'method should have the correct name');
+ }
+
+ for (const a of accessors) {
+ const propDesc = Object.getOwnPropertyDescriptor(proto, a);
+ assert_equals(propDesc.enumerable, false, `${a} should be non-enumerable`);
+ assert_equals(propDesc.configurable, true, `${a} should be configurable`);
+ assert_not_equals(propDesc.get, undefined, `${a} should have a getter`);
+ assert_equals(propDesc.set, undefined, `${a} should not have a setter`);
+ }
+
+ assert_equals(controller.close.length, 0, 'cancel has no parameters');
+ assert_equals(controller.constructor.length, 0, 'constructor has no parameters');
+ assert_equals(controller.enqueue.length, 1, 'enqueue has 1 parameter');
+ assert_equals(controller.error.length, 1, 'releaseLock has 1 parameter');
+
+}, 'ReadableByteStreamController instances should have the correct list of properties');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/bad-strategies.js b/test/fixtures/web-platform-tests/streams/readable-streams/bad-strategies.js
new file mode 100644
index 00000000000000..5a52d60d842a6d
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/bad-strategies.js
@@ -0,0 +1,164 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+test(() => {
+
+ const theError = new Error('a unique string');
+
+ assert_throws(theError, () => {
+ new ReadableStream({}, {
+ get size() {
+ throw theError;
+ },
+ highWaterMark: 5
+ });
+ }, 'construction should re-throw the error');
+
+}, 'Readable stream: throwing strategy.size getter');
+
+promise_test(t => {
+
+ const controllerError = { name: 'controller error' };
+ const thrownError = { name: 'thrown error' };
+
+ let controller;
+ const rs = new ReadableStream(
+ {
+ start(c) {
+ controller = c;
+ }
+ },
+ {
+ size() {
+ controller.error(controllerError);
+ throw thrownError;
+ },
+ highWaterMark: 5
+ }
+ );
+
+ assert_throws(thrownError, () => controller.enqueue('a'), 'enqueue should re-throw the error');
+
+ return promise_rejects(t, controllerError, rs.getReader().closed);
+
+}, 'Readable stream: strategy.size errors the stream and then throws');
+
+promise_test(t => {
+
+ const theError = { name: 'my error' };
+
+ let controller;
+ const rs = new ReadableStream(
+ {
+ start(c) {
+ controller = c;
+ }
+ },
+ {
+ size() {
+ controller.error(theError);
+ return Infinity;
+ },
+ highWaterMark: 5
+ }
+ );
+
+ assert_throws(new RangeError(), () => controller.enqueue('a'), 'enqueue should throw a RangeError');
+
+ return promise_rejects(t, theError, rs.getReader().closed, 'closed should reject with the error');
+
+}, 'Readable stream: strategy.size errors the stream and then returns Infinity');
+
+promise_test(() => {
+
+ const theError = new Error('a unique string');
+ const rs = new ReadableStream(
+ {
+ start(c) {
+ assert_throws(theError, () => c.enqueue('a'), 'enqueue should throw the error');
+ }
+ },
+ {
+ size() {
+ throw theError;
+ },
+ highWaterMark: 5
+ }
+ );
+
+ return rs.getReader().closed.catch(e => {
+ assert_equals(e, theError, 'closed should reject with the error');
+ });
+
+}, 'Readable stream: throwing strategy.size method');
+
+test(() => {
+
+ const theError = new Error('a unique string');
+
+ assert_throws(theError, () => {
+ new ReadableStream({}, {
+ size() {
+ return 1;
+ },
+ get highWaterMark() {
+ throw theError;
+ }
+ });
+ }, 'construction should re-throw the error');
+
+}, 'Readable stream: throwing strategy.highWaterMark getter');
+
+test(() => {
+
+ for (const highWaterMark of [-1, -Infinity, NaN, 'foo', {}]) {
+ assert_throws(new RangeError(), () => {
+ new ReadableStream({}, {
+ size() {
+ return 1;
+ },
+ highWaterMark
+ });
+ }, 'construction should throw a RangeError for ' + highWaterMark);
+ }
+
+}, 'Readable stream: invalid strategy.highWaterMark');
+
+promise_test(() => {
+
+ const promises = [];
+ for (const size of [NaN, -Infinity, Infinity, -1]) {
+ let theError;
+ const rs = new ReadableStream(
+ {
+ start(c) {
+ try {
+ c.enqueue('hi');
+ assert_unreached('enqueue didn\'t throw');
+ } catch (error) {
+ assert_equals(error.name, 'RangeError', 'enqueue should throw a RangeError for ' + size);
+ theError = error;
+ }
+ }
+ },
+ {
+ size() {
+ return size;
+ },
+ highWaterMark: 5
+ }
+ );
+
+ promises.push(rs.getReader().closed.catch(e => {
+ assert_equals(e, theError, 'closed should reject with the error for ' + size);
+ }));
+ }
+
+ return Promise.all(promises);
+
+}, 'Readable stream: invalid strategy.size return value');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/bad-underlying-sources.js b/test/fixtures/web-platform-tests/streams/readable-streams/bad-underlying-sources.js
new file mode 100644
index 00000000000000..6fce7b191b22b1
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/bad-underlying-sources.js
@@ -0,0 +1,405 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+
+test(() => {
+
+ const theError = new Error('a unique string');
+
+ assert_throws(theError, () => {
+ new ReadableStream({
+ get start() {
+ throw theError;
+ }
+ });
+ }, 'constructing the stream should re-throw the error');
+
+}, 'Underlying source start: throwing getter');
+
+
+test(() => {
+
+ const theError = new Error('a unique string');
+
+ assert_throws(theError, () => {
+ new ReadableStream({
+ start() {
+ throw theError;
+ }
+ });
+ }, 'constructing the stream should re-throw the error');
+
+}, 'Underlying source start: throwing method');
+
+
+test(() => {
+
+ const theError = new Error('a unique string');
+ assert_throws(theError, () => new ReadableStream({
+ get pull() {
+ throw theError;
+ }
+ }), 'constructor should throw');
+
+}, 'Underlying source: throwing pull getter (initial pull)');
+
+
+promise_test(t => {
+
+ const theError = new Error('a unique string');
+ const rs = new ReadableStream({
+ pull() {
+ throw theError;
+ }
+ });
+
+ return promise_rejects(t, theError, rs.getReader().closed);
+
+}, 'Underlying source: throwing pull method (initial pull)');
+
+
+promise_test(t => {
+
+ const theError = new Error('a unique string');
+
+ let counter = 0;
+ const rs = new ReadableStream({
+ get pull() {
+ ++counter;
+ if (counter === 1) {
+ return c => c.enqueue('a');
+ }
+
+ throw theError;
+ }
+ });
+ const reader = rs.getReader();
+
+ return Promise.all([
+ reader.read().then(r => {
+ assert_object_equals(r, { value: 'a', done: false }, 'the first chunk read should be correct');
+ }),
+ reader.read().then(r => {
+ assert_object_equals(r, { value: 'a', done: false }, 'the second chunk read should be correct');
+ assert_equals(counter, 1, 'counter should be 1');
+ })
+ ]);
+
+}, 'Underlying source pull: throwing getter (second pull does not result in a second get)');
+
+promise_test(t => {
+
+ const theError = new Error('a unique string');
+
+ let counter = 0;
+ const rs = new ReadableStream({
+ pull(c) {
+ ++counter;
+ if (counter === 1) {
+ c.enqueue('a');
+ return;
+ }
+
+ throw theError;
+ }
+ });
+ const reader = rs.getReader();
+
+ return Promise.all([
+ reader.read().then(r => {
+ assert_object_equals(r, { value: 'a', done: false }, 'the chunk read should be correct');
+ }),
+ promise_rejects(t, theError, reader.closed)
+ ]);
+
+}, 'Underlying source pull: throwing method (second pull)');
+
+test(() => {
+
+ const theError = new Error('a unique string');
+ assert_throws(theError, () => new ReadableStream({
+ get cancel() {
+ throw theError;
+ }
+ }), 'constructor should throw');
+
+}, 'Underlying source cancel: throwing getter');
+
+promise_test(t => {
+
+ const theError = new Error('a unique string');
+ const rs = new ReadableStream({
+ cancel() {
+ throw theError;
+ }
+ });
+
+ return promise_rejects(t, theError, rs.cancel());
+
+}, 'Underlying source cancel: throwing method');
+
+promise_test(() => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ rs.cancel();
+ assert_throws(new TypeError(), () => controller.enqueue('a'), 'Calling enqueue after canceling should throw');
+
+ return rs.getReader().closed;
+
+}, 'Underlying source: calling enqueue on an empty canceled stream should throw');
+
+promise_test(() => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ controller = c;
+ }
+ });
+
+ rs.cancel();
+ assert_throws(new TypeError(), () => controller.enqueue('c'), 'Calling enqueue after canceling should throw');
+
+ return rs.getReader().closed;
+
+}, 'Underlying source: calling enqueue on a non-empty canceled stream should throw');
+
+promise_test(() => {
+
+ return new ReadableStream({
+ start(c) {
+ c.close();
+ assert_throws(new TypeError(), () => c.enqueue('a'), 'call to enqueue should throw a TypeError');
+ }
+ }).getReader().closed;
+
+}, 'Underlying source: calling enqueue on a closed stream should throw');
+
+promise_test(t => {
+
+ const theError = new Error('boo');
+ const closed = new ReadableStream({
+ start(c) {
+ c.error(theError);
+ assert_throws(new TypeError(), () => c.enqueue('a'), 'call to enqueue should throw the error');
+ }
+ }).getReader().closed;
+
+ return promise_rejects(t, theError, closed);
+
+}, 'Underlying source: calling enqueue on an errored stream should throw');
+
+promise_test(() => {
+
+ return new ReadableStream({
+ start(c) {
+ c.close();
+ assert_throws(new TypeError(), () => c.close(), 'second call to close should throw a TypeError');
+ }
+ }).getReader().closed;
+
+}, 'Underlying source: calling close twice on an empty stream should throw the second time');
+
+promise_test(() => {
+
+ let startCalled = false;
+ let readCalled = false;
+ const reader = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ c.close();
+ assert_throws(new TypeError(), () => c.close(), 'second call to close should throw a TypeError');
+ startCalled = true;
+ }
+ }).getReader();
+
+ return Promise.all([
+ reader.read().then(r => {
+ assert_object_equals(r, { value: 'a', done: false }, 'read() should read the enqueued chunk');
+ readCalled = true;
+ }),
+ reader.closed.then(() => {
+ assert_true(startCalled);
+ assert_true(readCalled);
+ })
+ ]);
+
+}, 'Underlying source: calling close twice on a non-empty stream should throw the second time');
+
+promise_test(() => {
+
+ let controller;
+ let startCalled = false;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ startCalled = true;
+ }
+ });
+
+ rs.cancel();
+ assert_throws(new TypeError(), () => controller.close(), 'Calling close after canceling should throw');
+
+ return rs.getReader().closed.then(() => {
+ assert_true(startCalled);
+ });
+
+}, 'Underlying source: calling close on an empty canceled stream should throw');
+
+promise_test(() => {
+
+ let controller;
+ let startCalled = false;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ c.enqueue('a');
+ startCalled = true;
+ }
+ });
+
+ rs.cancel();
+ assert_throws(new TypeError(), () => controller.close(), 'Calling close after canceling should throw');
+
+ return rs.getReader().closed.then(() => {
+ assert_true(startCalled);
+ });
+
+}, 'Underlying source: calling close on a non-empty canceled stream should throw');
+
+promise_test(() => {
+
+ const theError = new Error('boo');
+ let startCalled = false;
+
+ const closed = new ReadableStream({
+ start(c) {
+ c.error(theError);
+ assert_throws(new TypeError(), () => c.close(), 'call to close should throw a TypeError');
+ startCalled = true;
+ }
+ }).getReader().closed;
+
+ return closed.catch(e => {
+ assert_true(startCalled);
+ assert_equals(e, theError, 'closed should reject with the error');
+ });
+
+}, 'Underlying source: calling close after error should throw');
+
+promise_test(() => {
+
+ const theError = new Error('boo');
+ let startCalled = false;
+
+ const closed = new ReadableStream({
+ start(c) {
+ c.error(theError);
+ c.error();
+ startCalled = true;
+ }
+ }).getReader().closed;
+
+ return closed.catch(e => {
+ assert_true(startCalled);
+ assert_equals(e, theError, 'closed should reject with the error');
+ });
+
+}, 'Underlying source: calling error twice should not throw');
+
+promise_test(() => {
+
+ let startCalled = false;
+
+ const closed = new ReadableStream({
+ start(c) {
+ c.close();
+ c.error();
+ startCalled = true;
+ }
+ }).getReader().closed;
+
+ return closed.then(() => assert_true(startCalled));
+
+}, 'Underlying source: calling error after close should not throw');
+
+promise_test(() => {
+
+ let startCalled = false;
+ const firstError = new Error('1');
+ const secondError = new Error('2');
+
+ const closed = new ReadableStream({
+ start(c) {
+ c.error(firstError);
+ startCalled = true;
+ return Promise.reject(secondError);
+ }
+ }).getReader().closed;
+
+ return closed.catch(e => {
+ assert_true(startCalled);
+ assert_equals(e, firstError, 'closed should reject with the first error');
+ });
+
+}, 'Underlying source: calling error and returning a rejected promise from start should cause the stream to error ' +
+ 'with the first error');
+
+promise_test(() => {
+
+ let startCalled = false;
+ const firstError = new Error('1');
+ const secondError = new Error('2');
+
+ const closed = new ReadableStream({
+ pull(c) {
+ c.error(firstError);
+ startCalled = true;
+ return Promise.reject(secondError);
+ }
+ }).getReader().closed;
+
+ return closed.catch(e => {
+ assert_true(startCalled);
+ assert_equals(e, firstError, 'closed should reject with the first error');
+ });
+
+}, 'Underlying source: calling error and returning a rejected promise from pull should cause the stream to error ' +
+ 'with the first error');
+
+const error1 = { name: 'error1' };
+
+promise_test(t => {
+
+ let pullShouldThrow = false;
+ const rs = new ReadableStream({
+ pull(controller) {
+ if (pullShouldThrow) {
+ throw error1;
+ }
+ controller.enqueue(0);
+ }
+ }, new CountQueuingStrategy({highWaterMark: 1}));
+ const reader = rs.getReader();
+ return Promise.resolve().then(() => {
+ pullShouldThrow = true;
+ return Promise.all([
+ reader.read(),
+ promise_rejects(t, error1, reader.closed, '.closed promise should reject')
+ ]);
+ });
+
+}, 'read should not error if it dequeues and pull() throws');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/brand-checks.js b/test/fixtures/web-platform-tests/streams/readable-streams/brand-checks.js
new file mode 100644
index 00000000000000..1b39d1ad6616cc
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/brand-checks.js
@@ -0,0 +1,164 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('/resources/testharness.js');
+}
+
+let ReadableStreamDefaultReader;
+let ReadableStreamDefaultController;
+
+test(() => {
+
+ // It's not exposed globally, but we test a few of its properties here.
+ ReadableStreamDefaultReader = (new ReadableStream()).getReader().constructor;
+
+}, 'Can get the ReadableStreamDefaultReader constructor indirectly');
+
+test(() => {
+
+ // It's not exposed globally, but we test a few of its properties here.
+ new ReadableStream({
+ start(c) {
+ ReadableStreamDefaultController = c.constructor;
+ }
+ });
+
+}, 'Can get the ReadableStreamDefaultController constructor indirectly');
+
+function fakeRS() {
+ return Object.setPrototypeOf({
+ cancel() { return Promise.resolve(); },
+ getReader() { return new ReadableStreamDefaultReader(new ReadableStream()); },
+ pipeThrough(obj) { return obj.readable; },
+ pipeTo() { return Promise.resolve(); },
+ tee() { return [realRS(), realRS()]; }
+ }, ReadableStream.prototype);
+}
+
+function realRS() {
+ return new ReadableStream();
+}
+
+function fakeRSDefaultReader() {
+ return Object.setPrototypeOf({
+ get closed() { return Promise.resolve(); },
+ cancel() { return Promise.resolve(); },
+ read() { return Promise.resolve({ value: undefined, done: true }); },
+ releaseLock() { return; }
+ }, ReadableStreamDefaultReader.prototype);
+}
+
+function realRSDefaultReader() {
+ return new ReadableStream().getReader();
+}
+
+function fakeRSDefaultController() {
+ return Object.setPrototypeOf({
+ close() { },
+ enqueue() { },
+ error() { }
+ }, ReadableStreamDefaultController.prototype);
+}
+
+function realRSDefaultController() {
+ let controller;
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ return controller;
+}
+
+promise_test(t => {
+
+ return methodRejectsForAll(t, ReadableStream.prototype, 'cancel',
+ [fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]);
+
+}, 'ReadableStream.prototype.cancel enforces a brand check');
+
+test(() => {
+
+ methodThrowsForAll(ReadableStream.prototype, 'getReader',
+ [fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]);
+
+}, 'ReadableStream.prototype.getReader enforces a brand check');
+
+test(() => {
+
+ methodThrowsForAll(ReadableStream.prototype, 'tee', [fakeRS(), realRSDefaultReader(), realRSDefaultController(), undefined, null]);
+
+}, 'ReadableStream.prototype.tee enforces a brand check');
+
+test(() => {
+
+ assert_throws(new TypeError(), () => new ReadableStreamDefaultReader(fakeRS()),
+ 'Constructing a ReadableStreamDefaultReader should throw');
+
+}, 'ReadableStreamDefaultReader enforces a brand check on its argument');
+
+promise_test(t => {
+
+ return getterRejectsForAll(t, ReadableStreamDefaultReader.prototype, 'closed',
+ [fakeRSDefaultReader(), realRS(), realRSDefaultController(), undefined, null]);
+
+}, 'ReadableStreamDefaultReader.prototype.closed enforces a brand check');
+
+promise_test(t => {
+
+ return methodRejectsForAll(t, ReadableStreamDefaultReader.prototype, 'cancel',
+ [fakeRSDefaultReader(), realRS(), realRSDefaultController(), undefined, null]);
+
+}, 'ReadableStreamDefaultReader.prototype.cancel enforces a brand check');
+
+promise_test(t => {
+
+ return methodRejectsForAll(t, ReadableStreamDefaultReader.prototype, 'read',
+ [fakeRSDefaultReader(), realRS(), realRSDefaultController(), undefined, null]);
+
+}, 'ReadableStreamDefaultReader.prototype.read enforces a brand check');
+
+test(() => {
+
+ methodThrowsForAll(ReadableStreamDefaultReader.prototype, 'releaseLock',
+ [fakeRSDefaultReader(), realRS(), realRSDefaultController(), undefined, null]);
+
+}, 'ReadableStreamDefaultReader.prototype.releaseLock enforces a brand check');
+
+test(() => {
+
+ assert_throws(new TypeError(), () => new ReadableStreamDefaultController(fakeRS()),
+ 'Constructing a ReadableStreamDefaultController should throw');
+
+}, 'ReadableStreamDefaultController enforces a brand check on its argument');
+
+test(() => {
+
+ assert_throws(new TypeError(), () => new ReadableStreamDefaultController(realRS()),
+ 'Constructing a ReadableStreamDefaultController should throw');
+
+}, 'ReadableStreamDefaultController can\'t be given a fully-constructed ReadableStream');
+
+test(() => {
+
+ methodThrowsForAll(ReadableStreamDefaultController.prototype, 'close',
+ [fakeRSDefaultController(), realRS(), realRSDefaultReader(), undefined, null]);
+
+}, 'ReadableStreamDefaultController.prototype.close enforces a brand check');
+
+test(() => {
+
+ methodThrowsForAll(ReadableStreamDefaultController.prototype, 'enqueue',
+ [fakeRSDefaultController(), realRS(), realRSDefaultReader(), undefined, null]);
+
+}, 'ReadableStreamDefaultController.prototype.enqueue enforces a brand check');
+
+test(() => {
+
+ methodThrowsForAll(ReadableStreamDefaultController.prototype, 'error',
+ [fakeRSDefaultController(), realRS(), realRSDefaultReader(), undefined, null]);
+
+}, 'ReadableStreamDefaultController.prototype.error enforces a brand check');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/cancel.js b/test/fixtures/web-platform-tests/streams/readable-streams/cancel.js
new file mode 100644
index 00000000000000..f8f7eec77cc412
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/cancel.js
@@ -0,0 +1,241 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/rs-utils.js');
+ self.importScripts('/resources/testharness.js');
+}
+
+promise_test(() => {
+
+ const randomSource = new RandomPushSource();
+
+ let cancellationFinished = false;
+ const rs = new ReadableStream({
+ start(c) {
+ randomSource.ondata = c.enqueue.bind(c);
+ randomSource.onend = c.close.bind(c);
+ randomSource.onerror = c.error.bind(c);
+ },
+
+ pull() {
+ randomSource.readStart();
+ },
+
+ cancel() {
+ randomSource.readStop();
+
+ return new Promise(resolve => {
+ setTimeout(() => {
+ cancellationFinished = true;
+ resolve();
+ }, 1);
+ });
+ }
+ });
+
+ const reader = rs.getReader();
+
+ // We call delay multiple times to avoid cancelling too early for the
+ // source to enqueue at least one chunk.
+ const cancel = delay(5).then(() => delay(5)).then(() => delay(5)).then(() => {
+ const cancelPromise = reader.cancel();
+ assert_false(cancellationFinished, 'cancellation in source should happen later');
+ return cancelPromise;
+ });
+
+ return readableStreamToArray(rs, reader).then(chunks => {
+ assert_greater_than(chunks.length, 0, 'at least one chunk should be read');
+ for (let i = 0; i < chunks.length; i++) {
+ assert_equals(chunks[i].length, 128, 'chunk ' + i + ' should have 128 bytes');
+ }
+ return cancel;
+ }).then(() => {
+ assert_true(cancellationFinished, 'it returns a promise that is fulfilled when the cancellation finishes');
+ });
+
+}, 'ReadableStream cancellation: integration test on an infinite stream derived from a random push source');
+
+test(() => {
+
+ let recordedReason;
+ const rs = new ReadableStream({
+ cancel(reason) {
+ recordedReason = reason;
+ }
+ });
+
+ const passedReason = new Error('Sorry, it just wasn\'t meant to be.');
+ rs.cancel(passedReason);
+
+ assert_equals(recordedReason, passedReason,
+ 'the error passed to the underlying source\'s cancel method should equal the one passed to the stream\'s cancel');
+
+}, 'ReadableStream cancellation: cancel(reason) should pass through the given reason to the underlying source');
+
+promise_test(() => {
+
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ c.close();
+ },
+ cancel() {
+ assert_unreached('underlying source cancel() should not have been called');
+ }
+ });
+
+ const reader = rs.getReader();
+
+ return rs.cancel().then(() => {
+ assert_unreached('cancel() should be rejected');
+ }, e => {
+ assert_equals(e.name, 'TypeError', 'cancel() should be rejected with a TypeError');
+ }).then(() => {
+ return reader.read();
+ }).then(result => {
+ assert_object_equals(result, { value: 'a', done: false }, 'read() should still work after the attempted cancel');
+ return reader.closed;
+ });
+
+}, 'ReadableStream cancellation: cancel() on a locked stream should fail and not call the underlying source cancel');
+
+promise_test(() => {
+
+ let cancelReceived = false;
+ const cancelReason = new Error('I am tired of this stream, I prefer to cancel it');
+ const rs = new ReadableStream({
+ cancel(reason) {
+ cancelReceived = true;
+ assert_equals(reason, cancelReason, 'cancellation reason given to the underlying source should be equal to the one passed');
+ }
+ });
+
+ return rs.cancel(cancelReason).then(() => {
+ assert_true(cancelReceived);
+ });
+
+}, 'ReadableStream cancellation: should fulfill promise when cancel callback went fine');
+
+promise_test(() => {
+
+ const rs = new ReadableStream({
+ cancel() {
+ return 'Hello';
+ }
+ });
+
+ return rs.cancel().then(v => {
+ assert_equals(v, undefined, 'cancel() return value should be fulfilled with undefined');
+ });
+
+}, 'ReadableStream cancellation: returning a value from the underlying source\'s cancel should not affect the fulfillment value of the promise returned by the stream\'s cancel');
+
+promise_test(() => {
+
+ const thrownError = new Error('test');
+ let cancelCalled = false;
+
+ const rs = new ReadableStream({
+ cancel() {
+ cancelCalled = true;
+ throw thrownError;
+ }
+ });
+
+ return rs.cancel('test').then(() => {
+ assert_unreached('cancel should reject');
+ }, e => {
+ assert_true(cancelCalled);
+ assert_equals(e, thrownError);
+ });
+
+}, 'ReadableStream cancellation: should reject promise when cancel callback raises an exception');
+
+promise_test(() => {
+
+ const cancelReason = new Error('test');
+
+ const rs = new ReadableStream({
+ cancel(error) {
+ assert_equals(error, cancelReason);
+ return delay(1);
+ }
+ });
+
+ return rs.cancel(cancelReason);
+
+}, 'ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should fulfill when that one does (1)');
+
+promise_test(() => {
+
+ let resolveSourceCancelPromise;
+ let sourceCancelPromiseHasFulfilled = false;
+
+ const rs = new ReadableStream({
+ cancel() {
+ const sourceCancelPromise = new Promise(resolve => resolveSourceCancelPromise = resolve);
+
+ sourceCancelPromise.then(() => {
+ sourceCancelPromiseHasFulfilled = true;
+ });
+
+ return sourceCancelPromise;
+ }
+ });
+
+ setTimeout(() => resolveSourceCancelPromise('Hello'), 1);
+
+ return rs.cancel().then(value => {
+ assert_true(sourceCancelPromiseHasFulfilled, 'cancel() return value should be fulfilled only after the promise returned by the underlying source\'s cancel');
+ assert_equals(value, undefined, 'cancel() return value should be fulfilled with undefined');
+ });
+
+}, 'ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should fulfill when that one does (2)');
+
+promise_test(() => {
+
+ let rejectSourceCancelPromise;
+ let sourceCancelPromiseHasRejected = false;
+
+ const rs = new ReadableStream({
+ cancel() {
+ const sourceCancelPromise = new Promise((resolve, reject) => rejectSourceCancelPromise = reject);
+
+ sourceCancelPromise.catch(() => {
+ sourceCancelPromiseHasRejected = true;
+ });
+
+ return sourceCancelPromise;
+ }
+ });
+
+ const errorInCancel = new Error('Sorry, it just wasn\'t meant to be.');
+
+ setTimeout(() => rejectSourceCancelPromise(errorInCancel), 1);
+
+ return rs.cancel().then(() => {
+ assert_unreached('cancel() return value should be rejected');
+ }, r => {
+ assert_true(sourceCancelPromiseHasRejected, 'cancel() return value should be rejected only after the promise returned by the underlying source\'s cancel');
+ assert_equals(r, errorInCancel, 'cancel() return value should be rejected with the underlying source\'s rejection reason');
+ });
+
+}, 'ReadableStream cancellation: if the underlying source\'s cancel method returns a promise, the promise returned by the stream\'s cancel should reject when that one does');
+
+promise_test(() => {
+
+ const rs = new ReadableStream({
+ start() {
+ return new Promise(() => {});
+ },
+ pull() {
+ assert_unreached('pull should not have been called');
+ }
+ });
+
+ return Promise.all([rs.cancel(), rs.getReader().closed]);
+
+}, 'ReadableStream cancellation: cancelling before start finishes should prevent pull() from being called');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/constructor.js b/test/fixtures/web-platform-tests/streams/readable-streams/constructor.js
new file mode 100644
index 00000000000000..c202f3b082c51b
--- /dev/null
+++ b/test/fixtures/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();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/count-queuing-strategy-integration.js b/test/fixtures/web-platform-tests/streams/readable-streams/count-queuing-strategy-integration.js
new file mode 100644
index 00000000000000..65c8b8cf0028b4
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/count-queuing-strategy-integration.js
@@ -0,0 +1,213 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+test(() => {
+
+ new ReadableStream({}, new CountQueuingStrategy({ highWaterMark: 4 }));
+
+}, 'Can construct a readable stream with a valid CountQueuingStrategy');
+
+promise_test(() => {
+
+ let controller;
+ const rs = new ReadableStream(
+ {
+ start(c) {
+ controller = c;
+ }
+ },
+ new CountQueuingStrategy({ highWaterMark: 0 })
+ );
+ const reader = rs.getReader();
+
+ assert_equals(controller.desiredSize, 0, '0 reads, 0 enqueues: desiredSize should be 0');
+ controller.enqueue('a');
+ assert_equals(controller.desiredSize, -1, '0 reads, 1 enqueue: desiredSize should be -1');
+ controller.enqueue('b');
+ assert_equals(controller.desiredSize, -2, '0 reads, 2 enqueues: desiredSize should be -2');
+ controller.enqueue('c');
+ assert_equals(controller.desiredSize, -3, '0 reads, 3 enqueues: desiredSize should be -3');
+ controller.enqueue('d');
+ assert_equals(controller.desiredSize, -4, '0 reads, 4 enqueues: desiredSize should be -4');
+
+ return reader.read()
+ .then(result => {
+ assert_object_equals(result, { value: 'a', done: false },
+ '1st read gives back the 1st chunk enqueued (queue now contains 3 chunks)');
+ return reader.read();
+ })
+ .then(result => {
+ assert_object_equals(result, { value: 'b', done: false },
+ '2nd read gives back the 2nd chunk enqueued (queue now contains 2 chunks)');
+ return reader.read();
+ })
+ .then(result => {
+ assert_object_equals(result, { value: 'c', done: false },
+ '3rd read gives back the 3rd chunk enqueued (queue now contains 1 chunk)');
+
+ assert_equals(controller.desiredSize, -1, '3 reads, 4 enqueues: desiredSize should be -1');
+ controller.enqueue('e');
+ assert_equals(controller.desiredSize, -2, '3 reads, 5 enqueues: desiredSize should be -2');
+
+ return reader.read();
+ })
+ .then(result => {
+ assert_object_equals(result, { value: 'd', done: false },
+ '4th read gives back the 4th chunk enqueued (queue now contains 1 chunks)');
+ return reader.read();
+
+ }).then(result => {
+ assert_object_equals(result, { value: 'e', done: false },
+ '5th read gives back the 5th chunk enqueued (queue now contains 0 chunks)');
+
+ assert_equals(controller.desiredSize, 0, '5 reads, 5 enqueues: desiredSize should be 0');
+ controller.enqueue('f');
+ assert_equals(controller.desiredSize, -1, '5 reads, 6 enqueues: desiredSize should be -1');
+ controller.enqueue('g');
+ assert_equals(controller.desiredSize, -2, '5 reads, 7 enqueues: desiredSize should be -2');
+ });
+
+}, 'Correctly governs a ReadableStreamController\'s desiredSize property (HWM = 0)');
+
+promise_test(() => {
+
+ let controller;
+ const rs = new ReadableStream(
+ {
+ start(c) {
+ controller = c;
+ }
+ },
+ new CountQueuingStrategy({ highWaterMark: 1 })
+ );
+ const reader = rs.getReader();
+
+ assert_equals(controller.desiredSize, 1, '0 reads, 0 enqueues: desiredSize should be 1');
+ controller.enqueue('a');
+ assert_equals(controller.desiredSize, 0, '0 reads, 1 enqueue: desiredSize should be 0');
+ controller.enqueue('b');
+ assert_equals(controller.desiredSize, -1, '0 reads, 2 enqueues: desiredSize should be -1');
+ controller.enqueue('c');
+ assert_equals(controller.desiredSize, -2, '0 reads, 3 enqueues: desiredSize should be -2');
+ controller.enqueue('d');
+ assert_equals(controller.desiredSize, -3, '0 reads, 4 enqueues: desiredSize should be -3');
+
+ return reader.read()
+ .then(result => {
+ assert_object_equals(result, { value: 'a', done: false },
+ '1st read gives back the 1st chunk enqueued (queue now contains 3 chunks)');
+ return reader.read();
+ })
+ .then(result => {
+ assert_object_equals(result, { value: 'b', done: false },
+ '2nd read gives back the 2nd chunk enqueued (queue now contains 2 chunks)');
+ return reader.read();
+ })
+ .then(result => {
+ assert_object_equals(result, { value: 'c', done: false },
+ '3rd read gives back the 3rd chunk enqueued (queue now contains 1 chunk)');
+
+ assert_equals(controller.desiredSize, 0, '3 reads, 4 enqueues: desiredSize should be 0');
+ controller.enqueue('e');
+ assert_equals(controller.desiredSize, -1, '3 reads, 5 enqueues: desiredSize should be -1');
+
+ return reader.read();
+ })
+ .then(result => {
+ assert_object_equals(result, { value: 'd', done: false },
+ '4th read gives back the 4th chunk enqueued (queue now contains 1 chunks)');
+ return reader.read();
+ })
+ .then(result => {
+ assert_object_equals(result, { value: 'e', done: false },
+ '5th read gives back the 5th chunk enqueued (queue now contains 0 chunks)');
+
+ assert_equals(controller.desiredSize, 1, '5 reads, 5 enqueues: desiredSize should be 1');
+ controller.enqueue('f');
+ assert_equals(controller.desiredSize, 0, '5 reads, 6 enqueues: desiredSize should be 0');
+ controller.enqueue('g');
+ assert_equals(controller.desiredSize, -1, '5 reads, 7 enqueues: desiredSize should be -1');
+ });
+
+}, 'Correctly governs a ReadableStreamController\'s desiredSize property (HWM = 1)');
+
+promise_test(() => {
+
+ let controller;
+ const rs = new ReadableStream(
+ {
+ start(c) {
+ controller = c;
+ }
+ },
+ new CountQueuingStrategy({ highWaterMark: 4 })
+ );
+ const reader = rs.getReader();
+
+ assert_equals(controller.desiredSize, 4, '0 reads, 0 enqueues: desiredSize should be 4');
+ controller.enqueue('a');
+ assert_equals(controller.desiredSize, 3, '0 reads, 1 enqueue: desiredSize should be 3');
+ controller.enqueue('b');
+ assert_equals(controller.desiredSize, 2, '0 reads, 2 enqueues: desiredSize should be 2');
+ controller.enqueue('c');
+ assert_equals(controller.desiredSize, 1, '0 reads, 3 enqueues: desiredSize should be 1');
+ controller.enqueue('d');
+ assert_equals(controller.desiredSize, 0, '0 reads, 4 enqueues: desiredSize should be 0');
+ controller.enqueue('e');
+ assert_equals(controller.desiredSize, -1, '0 reads, 5 enqueues: desiredSize should be -1');
+ controller.enqueue('f');
+ assert_equals(controller.desiredSize, -2, '0 reads, 6 enqueues: desiredSize should be -2');
+
+
+ return reader.read()
+ .then(result => {
+ assert_object_equals(result, { value: 'a', done: false },
+ '1st read gives back the 1st chunk enqueued (queue now contains 5 chunks)');
+ return reader.read();
+ })
+ .then(result => {
+ assert_object_equals(result, { value: 'b', done: false },
+ '2nd read gives back the 2nd chunk enqueued (queue now contains 4 chunks)');
+
+ assert_equals(controller.desiredSize, 0, '2 reads, 6 enqueues: desiredSize should be 0');
+ controller.enqueue('g');
+ assert_equals(controller.desiredSize, -1, '2 reads, 7 enqueues: desiredSize should be -1');
+
+ return reader.read();
+ })
+ .then(result => {
+ assert_object_equals(result, { value: 'c', done: false },
+ '3rd read gives back the 3rd chunk enqueued (queue now contains 4 chunks)');
+ return reader.read();
+ })
+ .then(result => {
+ assert_object_equals(result, { value: 'd', done: false },
+ '4th read gives back the 4th chunk enqueued (queue now contains 3 chunks)');
+ return reader.read();
+ })
+ .then(result => {
+ assert_object_equals(result, { value: 'e', done: false },
+ '5th read gives back the 5th chunk enqueued (queue now contains 2 chunks)');
+ return reader.read();
+ })
+ .then(result => {
+ assert_object_equals(result, { value: 'f', done: false },
+ '6th read gives back the 6th chunk enqueued (queue now contains 0 chunks)');
+
+ assert_equals(controller.desiredSize, 3, '6 reads, 7 enqueues: desiredSize should be 3');
+ controller.enqueue('h');
+ assert_equals(controller.desiredSize, 2, '6 reads, 8 enqueues: desiredSize should be 2');
+ controller.enqueue('i');
+ assert_equals(controller.desiredSize, 1, '6 reads, 9 enqueues: desiredSize should be 1');
+ controller.enqueue('j');
+ assert_equals(controller.desiredSize, 0, '6 reads, 10 enqueues: desiredSize should be 0');
+ controller.enqueue('k');
+ assert_equals(controller.desiredSize, -1, '6 reads, 11 enqueues: desiredSize should be -1');
+ });
+
+}, 'Correctly governs a ReadableStreamController\'s desiredSize property (HWM = 4)');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/default-reader.js b/test/fixtures/web-platform-tests/streams/readable-streams/default-reader.js
new file mode 100644
index 00000000000000..9b645e2472582f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/default-reader.js
@@ -0,0 +1,501 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('../resources/rs-utils.js');
+ self.importScripts('/resources/testharness.js');
+}
+
+let ReadableStreamDefaultReader;
+
+test(() => {
+
+ // It's not exposed globally, but we test a few of its properties here.
+ ReadableStreamDefaultReader = (new ReadableStream()).getReader().constructor;
+
+}, 'Can get the ReadableStreamDefaultReader constructor indirectly');
+
+test(() => {
+
+ assert_throws(new TypeError(), () => new ReadableStreamDefaultReader('potato'));
+ assert_throws(new TypeError(), () => new ReadableStreamDefaultReader({}));
+ assert_throws(new TypeError(), () => new ReadableStreamDefaultReader());
+
+}, 'ReadableStreamDefaultReader constructor should get a ReadableStream object as argument');
+
+test(() => {
+
+ const methods = ['cancel', 'constructor', 'read', 'releaseLock'];
+ const properties = methods.concat(['closed']).sort();
+
+ const rsReader = new ReadableStreamDefaultReader(new ReadableStream());
+ const proto = Object.getPrototypeOf(rsReader);
+
+ assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties);
+
+ for (const m of methods) {
+ const propDesc = Object.getOwnPropertyDescriptor(proto, m);
+ assert_equals(propDesc.enumerable, false, 'method should be non-enumerable');
+ assert_equals(propDesc.configurable, true, 'method should be configurable');
+ assert_equals(propDesc.writable, true, 'method should be writable');
+ assert_equals(typeof rsReader[m], 'function', 'should have be a method');
+ const expectedName = m === 'constructor' ? 'ReadableStreamDefaultReader' : m;
+ assert_equals(rsReader[m].name, expectedName, 'method should have the correct name');
+ }
+
+ const closedPropDesc = Object.getOwnPropertyDescriptor(proto, 'closed');
+ assert_equals(closedPropDesc.enumerable, false, 'closed should be non-enumerable');
+ assert_equals(closedPropDesc.configurable, true, 'closed should be configurable');
+ assert_not_equals(closedPropDesc.get, undefined, 'closed should have a getter');
+ assert_equals(closedPropDesc.set, undefined, 'closed should not have a setter');
+
+ assert_equals(rsReader.cancel.length, 1, 'cancel has 1 parameter');
+ assert_not_equals(rsReader.closed, undefined, 'has a non-undefined closed property');
+ assert_equals(typeof rsReader.closed.then, 'function', 'closed property is thenable');
+ assert_equals(typeof rsReader.constructor, 'function', 'has a constructor method');
+ assert_equals(rsReader.constructor.length, 1, 'constructor has 1 parameter');
+ assert_equals(typeof rsReader.read, 'function', 'has a getReader method');
+ assert_equals(rsReader.read.length, 0, 'read has no parameters');
+ assert_equals(typeof rsReader.releaseLock, 'function', 'has a releaseLock method');
+ assert_equals(rsReader.releaseLock.length, 0, 'releaseLock has no parameters');
+
+}, 'ReadableStreamDefaultReader instances should have the correct list of properties');
+
+test(() => {
+
+ const rsReader = new ReadableStreamDefaultReader(new ReadableStream());
+ assert_equals(rsReader.closed, rsReader.closed, 'closed should return the same promise');
+
+}, 'ReadableStreamDefaultReader closed should always return the same promise object');
+
+test(() => {
+
+ const rs = new ReadableStream();
+ new ReadableStreamDefaultReader(rs); // Constructing directly the first time should be fine.
+ assert_throws(new TypeError(), () => new ReadableStreamDefaultReader(rs),
+ 'constructing directly the second time should fail');
+
+}, 'Constructing a ReadableStreamDefaultReader directly should fail if the stream is already locked (via direct ' +
+ 'construction)');
+
+test(() => {
+
+ const rs = new ReadableStream();
+ new ReadableStreamDefaultReader(rs); // Constructing directly should be fine.
+ assert_throws(new TypeError(), () => rs.getReader(), 'getReader() should fail');
+
+}, 'Getting a ReadableStreamDefaultReader via getReader should fail if the stream is already locked (via direct ' +
+ 'construction)');
+
+test(() => {
+
+ const rs = new ReadableStream();
+ rs.getReader(); // getReader() should be fine.
+ assert_throws(new TypeError(), () => new ReadableStreamDefaultReader(rs), 'constructing directly should fail');
+
+}, 'Constructing a ReadableStreamDefaultReader directly should fail if the stream is already locked (via getReader)');
+
+test(() => {
+
+ const rs = new ReadableStream();
+ rs.getReader(); // getReader() should be fine.
+ assert_throws(new TypeError(), () => rs.getReader(), 'getReader() should fail');
+
+}, 'Getting a ReadableStreamDefaultReader via getReader should fail if the stream is already locked (via getReader)');
+
+test(() => {
+
+ const rs = new ReadableStream({
+ start(c) {
+ c.close();
+ }
+ });
+
+ new ReadableStreamDefaultReader(rs); // Constructing directly should not throw.
+
+}, 'Constructing a ReadableStreamDefaultReader directly should be OK if the stream is closed');
+
+test(() => {
+
+ const theError = new Error('don\'t say i didn\'t warn ya');
+ const rs = new ReadableStream({
+ start(c) {
+ c.error(theError);
+ }
+ });
+
+ new ReadableStreamDefaultReader(rs); // Constructing directly should not throw.
+
+}, 'Constructing a ReadableStreamDefaultReader directly should be OK if the stream is errored');
+
+promise_test(() => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ const reader = rs.getReader();
+
+ const promise = reader.read().then(result => {
+ assert_object_equals(result, { value: 'a', done: false }, 'read() should fulfill with the enqueued chunk');
+ });
+
+ controller.enqueue('a');
+ return promise;
+
+}, 'Reading from a reader for an empty stream will wait until a chunk is available');
+
+promise_test(() => {
+
+ let cancelCalled = false;
+ const passedReason = new Error('it wasn\'t the right time, sorry');
+ const rs = new ReadableStream({
+ cancel(reason) {
+ assert_true(rs.locked, 'the stream should still be locked');
+ assert_throws(new TypeError(), () => rs.getReader(), 'should not be able to get another reader');
+ assert_equals(reason, passedReason, 'the cancellation reason is passed through to the underlying source');
+ cancelCalled = true;
+ }
+ });
+
+ const reader = rs.getReader();
+ return reader.cancel(passedReason).then(() => assert_true(cancelCalled));
+
+}, 'cancel() on a reader does not release the reader');
+
+promise_test(() => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const reader = rs.getReader();
+ const promise = reader.closed;
+
+ controller.close();
+ return promise;
+
+}, 'closed should be fulfilled after stream is closed (.closed access before acquiring)');
+
+promise_test(t => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const reader1 = rs.getReader();
+
+ reader1.releaseLock();
+
+ const reader2 = rs.getReader();
+ controller.close();
+
+ return Promise.all([
+ promise_rejects(t, new TypeError(), reader1.closed),
+ reader2.closed
+ ]);
+
+}, 'closed should be rejected after reader releases its lock (multiple stream locks)');
+
+promise_test(() => {
+
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ c.close();
+ }
+ });
+
+ const reader1 = rs.getReader();
+ const promise1 = reader1.read().then(r => {
+ assert_object_equals(r, { value: 'a', done: false }, 'reading the first chunk from reader1 works');
+ });
+ reader1.releaseLock();
+
+ const reader2 = rs.getReader();
+ const promise2 = reader2.read().then(r => {
+ assert_object_equals(r, { value: 'b', done: false }, 'reading the second chunk from reader2 works');
+ });
+ reader2.releaseLock();
+
+ return Promise.all([promise1, promise2]);
+
+}, 'Multiple readers can access the stream in sequence');
+
+promise_test(() => {
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ }
+ });
+
+ const reader1 = rs.getReader();
+ reader1.releaseLock();
+
+ const reader2 = rs.getReader();
+
+ // Should be a no-op
+ reader1.releaseLock();
+
+ return reader2.read().then(result => {
+ assert_object_equals(result, { value: 'a', done: false },
+ 'read() should still work on reader2 even after reader1 is released');
+ });
+
+}, 'Cannot use an already-released reader to unlock a stream again');
+
+promise_test(t => {
+
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ },
+ cancel() {
+ assert_unreached('underlying source cancel should not be called');
+ }
+ });
+
+ const reader = rs.getReader();
+ reader.releaseLock();
+ const cancelPromise = reader.cancel();
+
+ const reader2 = rs.getReader();
+ const readPromise = reader2.read().then(r => {
+ assert_object_equals(r, { value: 'a', done: false }, 'a new reader should be able to read a chunk');
+ });
+
+ return Promise.all([
+ promise_rejects(t, new TypeError(), cancelPromise),
+ readPromise
+ ]);
+
+}, 'cancel() on a released reader is a no-op and does not pass through');
+
+promise_test(t => {
+
+ const promiseAsserts = [];
+
+ let controller;
+ const theError = { name: 'unique error' };
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const reader1 = rs.getReader();
+
+ promiseAsserts.push(
+ promise_rejects(t, theError, reader1.closed),
+ promise_rejects(t, theError, reader1.read())
+ );
+
+ assert_throws(new TypeError(), () => rs.getReader(), 'trying to get another reader before erroring should throw');
+
+ controller.error(theError);
+
+ reader1.releaseLock();
+
+ const reader2 = rs.getReader();
+
+ promiseAsserts.push(
+ promise_rejects(t, theError, reader2.closed),
+ promise_rejects(t, theError, reader2.read())
+ );
+
+ return Promise.all(promiseAsserts);
+
+}, 'Getting a second reader after erroring the stream and releasing the reader should succeed');
+
+promise_test(t => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const promise = rs.getReader().closed.then(
+ t.unreached_func('closed promise should not be fulfilled when stream is errored'),
+ err => {
+ assert_equals(err, undefined, 'passed error should be undefined as it was');
+ }
+ );
+
+ controller.error();
+ return promise;
+
+}, 'ReadableStreamDefaultReader closed promise should be rejected with undefined if that is the error');
+
+
+promise_test(t => {
+
+ const rs = new ReadableStream({
+ start() {
+ return Promise.reject();
+ }
+ });
+
+ return rs.getReader().read().then(
+ t.unreached_func('read promise should not be fulfilled when stream is errored'),
+ err => {
+ assert_equals(err, undefined, 'passed error should be undefined as it was');
+ }
+ );
+
+}, 'ReadableStreamDefaultReader: if start rejects with no parameter, it should error the stream with an undefined ' +
+ 'error');
+
+promise_test(t => {
+
+ const theError = { name: 'unique string' };
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const promise = promise_rejects(t, theError, rs.getReader().closed);
+
+ controller.error(theError);
+ return promise;
+
+}, 'Erroring a ReadableStream after checking closed should reject ReadableStreamDefaultReader closed promise');
+
+promise_test(t => {
+
+ const theError = { name: 'unique string' };
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ controller.error(theError);
+
+ // Let's call getReader twice for extra test coverage of this code path.
+ rs.getReader().releaseLock();
+
+ return promise_rejects(t, theError, rs.getReader().closed);
+
+}, 'Erroring a ReadableStream before checking closed should reject ReadableStreamDefaultReader closed promise');
+
+promise_test(() => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ const reader = rs.getReader();
+
+ const promise = Promise.all([
+ reader.read().then(result => {
+ assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (1)');
+ }),
+ reader.read().then(result => {
+ assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (2)');
+ }),
+ reader.closed
+ ]);
+
+ controller.close();
+ return promise;
+
+}, 'Reading twice on a stream that gets closed');
+
+promise_test(() => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ controller.close();
+ const reader = rs.getReader();
+
+ return Promise.all([
+ reader.read().then(result => {
+ assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (1)');
+ }),
+ reader.read().then(result => {
+ assert_object_equals(result, { value: undefined, done: true }, 'read() should fulfill with close (2)');
+ }),
+ reader.closed
+ ]);
+
+}, 'Reading twice on a closed stream');
+
+promise_test(t => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const myError = { name: 'mashed potatoes' };
+ controller.error(myError);
+
+ const reader = rs.getReader();
+
+ return Promise.all([
+ promise_rejects(t, myError, reader.read()),
+ promise_rejects(t, myError, reader.read()),
+ promise_rejects(t, myError, reader.closed)
+ ]);
+
+}, 'Reading twice on an errored stream');
+
+promise_test(t => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const myError = { name: 'mashed potatoes' };
+ const reader = rs.getReader();
+
+ const promise = Promise.all([
+ promise_rejects(t, myError, reader.read()),
+ promise_rejects(t, myError, reader.read()),
+ promise_rejects(t, myError, reader.closed)
+ ]);
+
+ controller.error(myError);
+ return promise;
+
+}, 'Reading twice on a stream that gets errored');
+
+test(() => {
+ const rs = new ReadableStream();
+ let toStringCalled = false;
+ const mode = {
+ toString() {
+ toStringCalled = true;
+ return '';
+ }
+ };
+ assert_throws(new RangeError(), () => rs.getReader({ mode }), 'getReader() should throw');
+ assert_true(toStringCalled, 'toString() should be called');
+}, 'getReader() should call ToString() on mode');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/floating-point-total-queue-size.js b/test/fixtures/web-platform-tests/streams/readable-streams/floating-point-total-queue-size.js
new file mode 100644
index 00000000000000..f7c76248b0b208
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/floating-point-total-queue-size.js
@@ -0,0 +1,121 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+// Due to the limitations of floating-point precision, the calculation of desiredSize sometimes gives different answers
+// than adding up the items in the queue would. It is important that implementations give the same result in these edge
+// cases so that developers do not come to depend on non-standard behaviour. See
+// https://github.com/whatwg/streams/issues/582 and linked issues for further discussion.
+
+promise_test(() => {
+ const { reader, controller } = setupTestStream();
+
+ controller.enqueue(2);
+ assert_equals(controller.desiredSize, 0 - 2, 'desiredSize must be -2 after enqueueing such a chunk');
+
+ controller.enqueue(Number.MAX_SAFE_INTEGER);
+ assert_equals(controller.desiredSize, 0 - Number.MAX_SAFE_INTEGER - 2,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (adding a second chunk)');
+
+ return reader.read().then(() => {
+ assert_equals(controller.desiredSize, 0 - Number.MAX_SAFE_INTEGER - 2 + 2,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (subtracting a chunk)');
+
+ return reader.read();
+ }).then(() => {
+ assert_equals(controller.desiredSize, 0, '[[queueTotalSize]] must clamp to 0 if it becomes negative');
+ });
+}, 'Floating point arithmetic must manifest near NUMBER.MAX_SAFE_INTEGER (total ends up positive)');
+
+promise_test(() => {
+ const { reader, controller } = setupTestStream();
+
+ controller.enqueue(1e-16);
+ assert_equals(controller.desiredSize, 0 - 1e-16, 'desiredSize must be -1e16 after enqueueing such a chunk');
+
+ controller.enqueue(1);
+ assert_equals(controller.desiredSize, 0 - 1e-16 - 1,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (adding a second chunk)');
+
+ return reader.read().then(() => {
+ assert_equals(controller.desiredSize, 0 - 1e-16 - 1 + 1e-16,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (subtracting a chunk)');
+
+ return reader.read();
+ }).then(() => {
+ assert_equals(controller.desiredSize, 0, '[[queueTotalSize]] must clamp to 0 if it becomes negative');
+ });
+}, 'Floating point arithmetic must manifest near 0 (total ends up positive, but clamped)');
+
+promise_test(() => {
+ const { reader, controller } = setupTestStream();
+
+ controller.enqueue(1e-16);
+ assert_equals(controller.desiredSize, 0 - 1e-16, 'desiredSize must be -2e16 after enqueueing such a chunk');
+
+ controller.enqueue(1);
+ assert_equals(controller.desiredSize, 0 - 1e-16 - 1,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (adding a second chunk)');
+
+ controller.enqueue(2e-16);
+ assert_equals(controller.desiredSize, 0 - 1e-16 - 1 - 2e-16,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (adding a third chunk)');
+
+ return reader.read().then(() => {
+ assert_equals(controller.desiredSize, 0 - 1e-16 - 1 - 2e-16 + 1e-16,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (subtracting a chunk)');
+
+ return reader.read();
+ }).then(() => {
+ assert_equals(controller.desiredSize, 0 - 1e-16 - 1 - 2e-16 + 1e-16 + 1,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (subtracting a second chunk)');
+
+ return reader.read();
+ }).then(() => {
+ assert_equals(controller.desiredSize, 0 - 1e-16 - 1 - 2e-16 + 1e-16 + 1 + 2e-16,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (subtracting a third chunk)');
+ });
+}, 'Floating point arithmetic must manifest near 0 (total ends up positive, and not clamped)');
+
+promise_test(() => {
+ const { reader, controller } = setupTestStream();
+
+ controller.enqueue(2e-16);
+ assert_equals(controller.desiredSize, 0 - 2e-16, 'desiredSize must be -2e16 after enqueueing such a chunk');
+
+ controller.enqueue(1);
+ assert_equals(controller.desiredSize, 0 - 2e-16 - 1,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (adding a second chunk)');
+
+ return reader.read().then(() => {
+ assert_equals(controller.desiredSize, 0 - 2e-16 - 1 + 2e-16,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (subtracting a chunk)');
+
+ return reader.read();
+ }).then(() => {
+ assert_equals(controller.desiredSize, 0,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (subtracting a second chunk)');
+ });
+}, 'Floating point arithmetic must manifest near 0 (total ends up zero)');
+
+function setupTestStream() {
+ const strategy = {
+ size(x) {
+ return x;
+ },
+ highWaterMark: 0
+ };
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }, strategy);
+
+ return { reader: rs.getReader(), controller };
+}
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/garbage-collection.js b/test/fixtures/web-platform-tests/streams/readable-streams/garbage-collection.js
new file mode 100644
index 00000000000000..2d16526e5a85b2
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/garbage-collection.js
@@ -0,0 +1,75 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('/resources/testharness.js');
+}
+
+promise_test(() => {
+
+ let controller;
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ garbageCollect();
+
+ return delay(50).then(() => {
+ controller.close();
+ assert_throws(new TypeError(), () => controller.close(), 'close should throw a TypeError the second time');
+ controller.error();
+ });
+
+}, 'ReadableStreamController methods should continue working properly when scripts lose their reference to the ' +
+ 'readable stream');
+
+promise_test(() => {
+
+ let controller;
+
+ const closedPromise = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }).getReader().closed;
+
+ garbageCollect();
+
+ return delay(50).then(() => controller.close()).then(() => closedPromise);
+
+}, 'ReadableStream closed promise should fulfill even if the stream and reader JS references are lost');
+
+promise_test(t => {
+
+ const theError = new Error('boo');
+ let controller;
+
+ const closedPromise = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }).getReader().closed;
+
+ garbageCollect();
+
+ return delay(50).then(() => controller.error(theError))
+ .then(() => promise_rejects(t, theError, closedPromise));
+
+}, 'ReadableStream closed promise should reject even if stream and reader JS references are lost');
+
+promise_test(() => {
+
+ const rs = new ReadableStream({});
+
+ rs.getReader();
+
+ garbageCollect();
+
+ return delay(50).then(() => assert_throws(new TypeError(), () => rs.getReader(),
+ 'old reader should still be locking the stream even after garbage collection'));
+
+}, 'Garbage-collecting a ReadableStreamDefaultReader should not unlock its stream');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/general.js b/test/fixtures/web-platform-tests/streams/readable-streams/general.js
new file mode 100644
index 00000000000000..05382d47f7eb61
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/general.js
@@ -0,0 +1,901 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/rs-utils.js');
+ self.importScripts('/resources/testharness.js');
+}
+
+const error1 = new Error('error1');
+error1.name = 'error1';
+
+test(() => {
+
+ new ReadableStream(); // ReadableStream constructed with no parameters
+ new ReadableStream({ }); // ReadableStream constructed with an empty object as parameter
+ new ReadableStream({ type: undefined }); // ReadableStream constructed with undefined type
+ new ReadableStream(undefined); // ReadableStream constructed with undefined as parameter
+
+ let x;
+ new ReadableStream(x); // ReadableStream constructed with an undefined variable as parameter
+
+}, 'ReadableStream can be constructed with no errors');
+
+test(() => {
+
+ assert_throws(new TypeError(), () => new ReadableStream(null), 'constructor should throw when the source is null');
+
+}, 'ReadableStream can\'t be constructed with garbage');
+
+test(() => {
+
+ assert_throws(new RangeError(), () => new ReadableStream({ type: null }),
+ 'constructor should throw when the type is null');
+ assert_throws(new RangeError(), () => new ReadableStream({ type: '' }),
+ 'constructor should throw when the type is empty string');
+ assert_throws(new RangeError(), () => new ReadableStream({ type: 'asdf' }),
+ 'constructor should throw when the type is asdf');
+ assert_throws(error1, () => new ReadableStream({ type: { get toString() {throw error1;} } }), 'constructor should throw when ToString() throws');
+ assert_throws(error1, () => new ReadableStream({ type: { toString() {throw error1;} } }), 'constructor should throw when ToString() throws');
+
+}, 'ReadableStream can\'t be constructed with an invalid type');
+
+test(() => {
+
+ const methods = ['cancel', 'constructor', 'getReader', 'pipeThrough', 'pipeTo', 'tee'];
+ const properties = methods.concat(['locked']).sort();
+
+ const rs = new ReadableStream();
+ const proto = Object.getPrototypeOf(rs);
+
+ assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties, 'should have all the correct methods');
+
+ for (const m of methods) {
+ const propDesc = Object.getOwnPropertyDescriptor(proto, m);
+ assert_false(propDesc.enumerable, 'method should be non-enumerable');
+ assert_true(propDesc.configurable, 'method should be configurable');
+ assert_true(propDesc.writable, 'method should be writable');
+ assert_equals(typeof rs[m], 'function', 'method should be a function');
+ const expectedName = m === 'constructor' ? 'ReadableStream' : m;
+ assert_equals(rs[m].name, expectedName, 'method should have the correct name');
+ }
+
+ const lockedPropDesc = Object.getOwnPropertyDescriptor(proto, 'locked');
+ assert_false(lockedPropDesc.enumerable, 'locked should be non-enumerable');
+ assert_equals(lockedPropDesc.writable, undefined, 'locked should not be a data property');
+ assert_equals(typeof lockedPropDesc.get, 'function', 'locked should have a getter');
+ assert_equals(lockedPropDesc.set, undefined, 'locked should not have a setter');
+ assert_true(lockedPropDesc.configurable, 'locked should be configurable');
+
+ assert_equals(rs.cancel.length, 1, 'cancel should have 1 parameter');
+ assert_equals(rs.constructor.length, 0, 'constructor should have no parameters');
+ assert_equals(rs.getReader.length, 0, 'getReader should have no parameters');
+ assert_equals(rs.pipeThrough.length, 2, 'pipeThrough should have 2 parameters');
+ assert_equals(rs.pipeTo.length, 1, 'pipeTo should have 1 parameter');
+ assert_equals(rs.tee.length, 0, 'tee should have no parameters');
+
+}, 'ReadableStream instances should have the correct list of properties');
+
+test(() => {
+
+ assert_throws(new TypeError(), () => {
+ new ReadableStream({ start: 'potato' });
+ }, 'constructor should throw when start is not a function');
+
+}, 'ReadableStream constructor should throw for non-function start arguments');
+
+test(() => {
+
+ assert_throws(new TypeError(), () => new ReadableStream({ cancel: '2' }), 'constructor should throw');
+
+}, 'ReadableStream constructor will not tolerate initial garbage as cancel argument');
+
+test(() => {
+
+ assert_throws(new TypeError(), () => new ReadableStream({ pull: { } }), 'constructor should throw');
+
+}, 'ReadableStream constructor will not tolerate initial garbage as pull argument');
+
+test(() => {
+
+ let startCalled = false;
+
+ const source = {
+ start(controller) {
+ assert_equals(this, source, 'source is this during start');
+
+ const methods = ['close', 'enqueue', 'error', 'constructor'];
+ const properties = ['desiredSize'].concat(methods).sort();
+ const proto = Object.getPrototypeOf(controller);
+
+ assert_array_equals(Object.getOwnPropertyNames(proto).sort(), properties,
+ 'the controller should have the right properties');
+
+ for (const m of methods) {
+ const propDesc = Object.getOwnPropertyDescriptor(proto, m);
+ assert_equals(typeof controller[m], 'function', `should have a ${m} method`);
+ assert_false(propDesc.enumerable, m + ' should be non-enumerable');
+ assert_true(propDesc.configurable, m + ' should be configurable');
+ assert_true(propDesc.writable, m + ' should be writable');
+ const expectedName = m === 'constructor' ? 'ReadableStreamDefaultController' : m;
+ assert_equals(controller[m].name, expectedName, 'method should have the correct name');
+ }
+
+ const desiredSizePropDesc = Object.getOwnPropertyDescriptor(proto, 'desiredSize');
+ assert_false(desiredSizePropDesc.enumerable, 'desiredSize should be non-enumerable');
+ assert_equals(desiredSizePropDesc.writable, undefined, 'desiredSize should not be a data property');
+ assert_equals(typeof desiredSizePropDesc.get, 'function', 'desiredSize should have a getter');
+ assert_equals(desiredSizePropDesc.set, undefined, 'desiredSize should not have a setter');
+ assert_true(desiredSizePropDesc.configurable, 'desiredSize should be configurable');
+
+ assert_equals(controller.close.length, 0, 'close should have no parameters');
+ assert_equals(controller.constructor.length, 0, 'constructor should have no parameters');
+ assert_equals(controller.enqueue.length, 1, 'enqueue should have 1 parameter');
+ assert_equals(controller.error.length, 1, 'error should have 1 parameter');
+
+ startCalled = true;
+ }
+ };
+
+ new ReadableStream(source);
+ assert_true(startCalled);
+
+}, 'ReadableStream start should be called with the proper parameters');
+
+test(() => {
+
+ let startCalled = false;
+ const source = {
+ start(controller) {
+ const properties = ['close', 'constructor', 'desiredSize', 'enqueue', 'error'];
+ assert_array_equals(Object.getOwnPropertyNames(Object.getPrototypeOf(controller)).sort(), properties,
+ 'prototype should have the right properties');
+
+ controller.test = '';
+ assert_array_equals(Object.getOwnPropertyNames(Object.getPrototypeOf(controller)).sort(), properties,
+ 'prototype should still have the right properties');
+ assert_not_equals(Object.getOwnPropertyNames(controller).indexOf('test'), -1,
+ '"test" should be a property of the controller');
+
+ startCalled = true;
+ }
+ };
+
+ new ReadableStream(source);
+ assert_true(startCalled);
+
+}, 'ReadableStream start controller parameter should be extensible');
+
+test(() => {
+ (new ReadableStream()).getReader(undefined);
+ (new ReadableStream()).getReader({});
+ (new ReadableStream()).getReader({ mode: undefined, notmode: 'ignored' });
+ assert_throws(new RangeError(), () => (new ReadableStream()).getReader({ mode: 'potato' }));
+}, 'default ReadableStream getReader() should only accept mode:undefined');
+
+promise_test(() => {
+
+ function SimpleStreamSource() {}
+ let resolve;
+ const promise = new Promise(r => resolve = r);
+ SimpleStreamSource.prototype = {
+ start: resolve
+ };
+
+ new ReadableStream(new SimpleStreamSource());
+ return promise;
+
+}, 'ReadableStream should be able to call start method within prototype chain of its source');
+
+promise_test(() => {
+
+ const rs = new ReadableStream({
+ start(c) {
+ return delay(5).then(() => {
+ c.enqueue('a');
+ c.close();
+ });
+ }
+ });
+
+ const reader = rs.getReader();
+ return reader.read().then(r => {
+ assert_object_equals(r, { value: 'a', done: false }, 'value read should be the one enqueued');
+ return reader.closed;
+ });
+
+}, 'ReadableStream start should be able to return a promise');
+
+promise_test(() => {
+
+ const theError = new Error('rejected!');
+ const rs = new ReadableStream({
+ start() {
+ return delay(1).then(() => {
+ throw theError;
+ });
+ }
+ });
+
+ return rs.getReader().closed.then(() => {
+ assert_unreached('closed promise should be rejected');
+ }, e => {
+ assert_equals(e, theError, 'promise should be rejected with the same error');
+ });
+
+}, 'ReadableStream start should be able to return a promise and reject it');
+
+promise_test(() => {
+
+ const objects = [
+ { potato: 'Give me more!' },
+ 'test',
+ 1
+ ];
+
+ const rs = new ReadableStream({
+ start(c) {
+ for (const o of objects) {
+ c.enqueue(o);
+ }
+ c.close();
+ }
+ });
+
+ const reader = rs.getReader();
+
+ return Promise.all([reader.read(), reader.read(), reader.read(), reader.closed]).then(r => {
+ assert_object_equals(r[0], { value: objects[0], done: false }, 'value read should be the one enqueued');
+ assert_object_equals(r[1], { value: objects[1], done: false }, 'value read should be the one enqueued');
+ assert_object_equals(r[2], { value: objects[2], done: false }, 'value read should be the one enqueued');
+ });
+
+}, 'ReadableStream should be able to enqueue different objects.');
+
+promise_test(() => {
+
+ const error = new Error('pull failure');
+ const rs = new ReadableStream({
+ pull() {
+ return Promise.reject(error);
+ }
+ });
+
+ const reader = rs.getReader();
+
+ let closed = false;
+ let read = false;
+
+ return Promise.all([
+ reader.closed.then(() => {
+ assert_unreached('closed should be rejected');
+ }, e => {
+ closed = true;
+ assert_true(read);
+ assert_equals(e, error, 'closed should be rejected with the thrown error');
+ }),
+ reader.read().then(() => {
+ assert_unreached('read() should be rejected');
+ }, e => {
+ read = true;
+ assert_false(closed);
+ assert_equals(e, error, 'read() should be rejected with the thrown error');
+ })
+ ]);
+
+}, 'ReadableStream: if pull rejects, it should error the stream');
+
+promise_test(() => {
+
+ let pullCount = 0;
+ const startPromise = Promise.resolve();
+
+ new ReadableStream({
+ start() {
+ return startPromise;
+ },
+ pull() {
+ pullCount++;
+ }
+ });
+
+ return startPromise.then(() => {
+ assert_equals(pullCount, 1, 'pull should be called once start finishes');
+ return delay(10);
+ }).then(() => {
+ assert_equals(pullCount, 1, 'pull should be called exactly once');
+ });
+
+}, 'ReadableStream: should only call pull once upon starting the stream');
+
+promise_test(() => {
+
+ let pullCount = 0;
+ const startPromise = Promise.resolve();
+
+ const rs = new ReadableStream({
+ start() {
+ return startPromise;
+ },
+ pull(c) {
+ // Don't enqueue immediately after start. We want the stream to be empty when we call .read() on it.
+ if (pullCount > 0) {
+ c.enqueue(pullCount);
+ }
+ ++pullCount;
+ }
+ });
+
+ return startPromise.then(() => {
+ assert_equals(pullCount, 1, 'pull should be called once start finishes');
+ }).then(() => {
+ const reader = rs.getReader();
+ const read = reader.read();
+ assert_equals(pullCount, 2, 'pull should be called when read is called');
+ return read;
+ }).then(result => {
+ assert_equals(pullCount, 3, 'pull should be called again in reaction to calling read');
+ assert_object_equals(result, { value: 1, done: false }, 'the result read should be the one enqueued');
+ });
+
+}, 'ReadableStream: should call pull when trying to read from a started, empty stream');
+
+promise_test(() => {
+
+ let pullCount = 0;
+ const startPromise = Promise.resolve();
+
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ return startPromise;
+ },
+ pull() {
+ pullCount++;
+ }
+ });
+
+ const read = rs.getReader().read();
+ assert_equals(pullCount, 0, 'calling read() should not cause pull to be called yet');
+
+ return startPromise.then(() => {
+ assert_equals(pullCount, 1, 'pull should be called once start finishes');
+ return read;
+ }).then(r => {
+ assert_object_equals(r, { value: 'a', done: false }, 'first read() should return first chunk');
+ assert_equals(pullCount, 1, 'pull should not have been called again');
+ return delay(10);
+ }).then(() => {
+ assert_equals(pullCount, 1, 'pull should be called exactly once');
+ });
+
+}, 'ReadableStream: should only call pull once on a non-empty stream read from before start fulfills');
+
+promise_test(() => {
+
+ let pullCount = 0;
+ const startPromise = Promise.resolve();
+
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ return startPromise;
+ },
+ pull() {
+ pullCount++;
+ }
+ });
+
+ return startPromise.then(() => {
+ assert_equals(pullCount, 0, 'pull should not be called once start finishes, since the queue is full');
+
+ const read = rs.getReader().read();
+ assert_equals(pullCount, 1, 'calling read() should cause pull to be called immediately');
+ return read;
+ }).then(r => {
+ assert_object_equals(r, { value: 'a', done: false }, 'first read() should return first chunk');
+ return delay(10);
+ }).then(() => {
+ assert_equals(pullCount, 1, 'pull should be called exactly once');
+ });
+
+}, 'ReadableStream: should only call pull once on a non-empty stream read from after start fulfills');
+
+promise_test(() => {
+
+ let pullCount = 0;
+ let controller;
+ const startPromise = Promise.resolve();
+
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ return startPromise;
+ },
+ pull() {
+ ++pullCount;
+ }
+ });
+
+ const reader = rs.getReader();
+ return startPromise.then(() => {
+ assert_equals(pullCount, 1, 'pull should have been called once by the time the stream starts');
+
+ controller.enqueue('a');
+ assert_equals(pullCount, 1, 'pull should not have been called again after enqueue');
+
+ return reader.read();
+ }).then(() => {
+ assert_equals(pullCount, 2, 'pull should have been called again after read');
+
+ return delay(10);
+ }).then(() => {
+ assert_equals(pullCount, 2, 'pull should be called exactly twice');
+ });
+}, 'ReadableStream: should call pull in reaction to read()ing the last chunk, if not draining');
+
+promise_test(() => {
+
+ let pullCount = 0;
+ let controller;
+ const startPromise = Promise.resolve();
+
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ return startPromise;
+ },
+ pull() {
+ ++pullCount;
+ }
+ });
+
+ const reader = rs.getReader();
+
+ return startPromise.then(() => {
+ assert_equals(pullCount, 1, 'pull should have been called once by the time the stream starts');
+
+ controller.enqueue('a');
+ assert_equals(pullCount, 1, 'pull should not have been called again after enqueue');
+
+ controller.close();
+
+ return reader.read();
+ }).then(() => {
+ assert_equals(pullCount, 1, 'pull should not have been called a second time after read');
+
+ return delay(10);
+ }).then(() => {
+ assert_equals(pullCount, 1, 'pull should be called exactly once');
+ });
+
+}, 'ReadableStream: should not call pull() in reaction to read()ing the last chunk, if draining');
+
+promise_test(() => {
+
+ let resolve;
+ let returnedPromise;
+ let timesCalled = 0;
+ const startPromise = Promise.resolve();
+
+ const rs = new ReadableStream({
+ start() {
+ return startPromise;
+ },
+ pull(c) {
+ c.enqueue(++timesCalled);
+ returnedPromise = new Promise(r => resolve = r);
+ return returnedPromise;
+ }
+ });
+ const reader = rs.getReader();
+
+ return startPromise.then(() => {
+ return reader.read();
+ }).then(result1 => {
+ assert_equals(timesCalled, 1,
+ 'pull should have been called once after start, but not yet have been called a second time');
+ assert_object_equals(result1, { value: 1, done: false }, 'read() should fulfill with the enqueued value');
+
+ return delay(10);
+ }).then(() => {
+ assert_equals(timesCalled, 1, 'after 10 ms, pull should still only have been called once');
+
+ resolve();
+ return returnedPromise;
+ }).then(() => {
+ assert_equals(timesCalled, 2,
+ 'after the promise returned by pull is fulfilled, pull should be called a second time');
+ });
+
+}, 'ReadableStream: should not call pull until the previous pull call\'s promise fulfills');
+
+promise_test(() => {
+
+ let timesCalled = 0;
+ const startPromise = Promise.resolve();
+
+ const rs = new ReadableStream(
+ {
+ start(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ c.enqueue('c');
+ return startPromise;
+ },
+ pull() {
+ ++timesCalled;
+ }
+ },
+ {
+ size() {
+ return 1;
+ },
+ highWaterMark: Infinity
+ }
+ );
+ const reader = rs.getReader();
+
+ return startPromise.then(() => {
+ return reader.read();
+ }).then(result1 => {
+ assert_object_equals(result1, { value: 'a', done: false }, 'first chunk should be as expected');
+
+ return reader.read();
+ }).then(result2 => {
+ assert_object_equals(result2, { value: 'b', done: false }, 'second chunk should be as expected');
+
+ return reader.read();
+ }).then(result3 => {
+ assert_object_equals(result3, { value: 'c', done: false }, 'third chunk should be as expected');
+
+ return delay(10);
+ }).then(() => {
+ // Once for after start, and once for every read.
+ assert_equals(timesCalled, 4, 'pull() should be called exactly four times');
+ });
+
+}, 'ReadableStream: should pull after start, and after every read');
+
+promise_test(() => {
+
+ let timesCalled = 0;
+ const startPromise = Promise.resolve();
+
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ c.close();
+ return startPromise;
+ },
+ pull() {
+ ++timesCalled;
+ }
+ });
+
+ const reader = rs.getReader();
+ return startPromise.then(() => {
+ assert_equals(timesCalled, 0, 'after start finishes, pull should not have been called');
+
+ return reader.read();
+ }).then(() => {
+ assert_equals(timesCalled, 0, 'reading should not have triggered a pull call');
+
+ return reader.closed;
+ }).then(() => {
+ assert_equals(timesCalled, 0, 'stream should have closed with still no calls to pull');
+ });
+
+}, 'ReadableStream: should not call pull after start if the stream is now closed');
+
+promise_test(() => {
+
+ let timesCalled = 0;
+ let resolve;
+ const ready = new Promise(r => resolve = r);
+
+ new ReadableStream(
+ {
+ start() {},
+ pull(c) {
+ c.enqueue(++timesCalled);
+
+ if (timesCalled === 4) {
+ resolve();
+ }
+ }
+ },
+ {
+ size() {
+ return 1;
+ },
+ highWaterMark: 4
+ }
+ );
+
+ return ready.then(() => {
+ // after start: size = 0, pull()
+ // after enqueue(1): size = 1, pull()
+ // after enqueue(2): size = 2, pull()
+ // after enqueue(3): size = 3, pull()
+ // after enqueue(4): size = 4, do not pull
+ assert_equals(timesCalled, 4, 'pull() should have been called four times');
+ });
+
+}, 'ReadableStream: should call pull after enqueueing from inside pull (with no read requests), if strategy allows');
+
+promise_test(() => {
+
+ let pullCalled = false;
+
+ const rs = new ReadableStream({
+ pull(c) {
+ pullCalled = true;
+ c.close();
+ }
+ });
+
+ const reader = rs.getReader();
+ return reader.closed.then(() => {
+ assert_true(pullCalled);
+ });
+
+}, 'ReadableStream pull should be able to close a stream.');
+
+promise_test(t => {
+
+ const controllerError = { name: 'controller error' };
+
+ const rs = new ReadableStream({
+ pull(c) {
+ c.error(controllerError);
+ }
+ });
+
+ return promise_rejects(t, controllerError, rs.getReader().closed);
+
+}, 'ReadableStream pull should be able to error a stream.');
+
+promise_test(t => {
+
+ const controllerError = { name: 'controller error' };
+ const thrownError = { name: 'thrown error' };
+
+ const rs = new ReadableStream({
+ pull(c) {
+ c.error(controllerError);
+ throw thrownError;
+ }
+ });
+
+ return promise_rejects(t, controllerError, rs.getReader().closed);
+
+}, 'ReadableStream pull should be able to error a stream and throw.');
+
+test(() => {
+
+ let startCalled = false;
+
+ new ReadableStream({
+ start(c) {
+ assert_equals(c.enqueue('a'), undefined, 'the first enqueue should return undefined');
+ c.close();
+
+ assert_throws(new TypeError(), () => c.enqueue('b'), 'enqueue after close should throw a TypeError');
+ startCalled = true;
+ }
+ });
+
+ assert_true(startCalled);
+
+}, 'ReadableStream: enqueue should throw when the stream is readable but draining');
+
+test(() => {
+
+ let startCalled = false;
+
+ new ReadableStream({
+ start(c) {
+ c.close();
+
+ assert_throws(new TypeError(), () => c.enqueue('a'), 'enqueue after close should throw a TypeError');
+ startCalled = true;
+ }
+ });
+
+ assert_true(startCalled);
+
+}, 'ReadableStream: enqueue should throw when the stream is closed');
+
+promise_test(() => {
+
+ let startCalled = 0;
+ let pullCalled = 0;
+ let cancelCalled = 0;
+
+ /* eslint-disable no-use-before-define */
+ class Source {
+ start(c) {
+ startCalled++;
+ assert_equals(this, theSource, 'start() should be called with the correct this');
+ c.enqueue('a');
+ }
+
+ pull() {
+ pullCalled++;
+ assert_equals(this, theSource, 'pull() should be called with the correct this');
+ }
+
+ cancel() {
+ cancelCalled++;
+ assert_equals(this, theSource, 'cancel() should be called with the correct this');
+ }
+ }
+ /* eslint-enable no-use-before-define */
+
+ const theSource = new Source();
+ theSource.debugName = 'the source object passed to the constructor'; // makes test failures easier to diagnose
+
+ const rs = new ReadableStream(theSource);
+ const reader = rs.getReader();
+
+ return reader.read().then(() => {
+ reader.releaseLock();
+ rs.cancel();
+ assert_equals(startCalled, 1);
+ assert_equals(pullCalled, 1);
+ assert_equals(cancelCalled, 1);
+ return rs.getReader().closed;
+ });
+
+}, 'ReadableStream: should call underlying source methods as methods');
+
+test(() => {
+ new ReadableStream({
+ start(c) {
+ assert_equals(c.desiredSize, 10, 'desiredSize must start at highWaterMark');
+ c.close();
+ assert_equals(c.desiredSize, 0, 'after closing, desiredSize must be 0');
+ }
+ }, {
+ highWaterMark: 10
+ });
+}, 'ReadableStream: desiredSize when closed');
+
+test(() => {
+ new ReadableStream({
+ start(c) {
+ assert_equals(c.desiredSize, 10, 'desiredSize must start at highWaterMark');
+ c.error();
+ assert_equals(c.desiredSize, null, 'after erroring, desiredSize must be null');
+ }
+ }, {
+ highWaterMark: 10
+ });
+}, 'ReadableStream: desiredSize when errored');
+
+test(() => {
+
+ let startCalled = false;
+ new ReadableStream({
+ start(c) {
+ assert_equals(c.desiredSize, 1);
+ c.enqueue('a');
+ assert_equals(c.desiredSize, 0);
+ c.enqueue('b');
+ assert_equals(c.desiredSize, -1);
+ c.enqueue('c');
+ assert_equals(c.desiredSize, -2);
+ c.enqueue('d');
+ assert_equals(c.desiredSize, -3);
+ c.enqueue('e');
+ startCalled = true;
+ }
+ });
+
+ assert_true(startCalled);
+
+}, 'ReadableStream strategies: the default strategy should give desiredSize of 1 to start, decreasing by 1 per enqueue');
+
+promise_test(() => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ const reader = rs.getReader();
+
+ assert_equals(controller.desiredSize, 1, 'desiredSize should start at 1');
+ controller.enqueue('a');
+ assert_equals(controller.desiredSize, 0, 'desiredSize should decrease to 0 after first enqueue');
+
+ return reader.read().then(result1 => {
+ assert_object_equals(result1, { value: 'a', done: false }, 'first chunk read should be correct');
+
+ assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 after the first read');
+ controller.enqueue('b');
+ assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 after the second enqueue');
+
+ return reader.read();
+ }).then(result2 => {
+ assert_object_equals(result2, { value: 'b', done: false }, 'second chunk read should be correct');
+
+ assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 after the second read');
+ controller.enqueue('c');
+ assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 after the third enqueue');
+
+ return reader.read();
+ }).then(result3 => {
+ assert_object_equals(result3, { value: 'c', done: false }, 'third chunk read should be correct');
+
+ assert_equals(controller.desiredSize, 1, 'desiredSize should go up to 1 after the third read');
+ controller.enqueue('d');
+ assert_equals(controller.desiredSize, 0, 'desiredSize should go down to 0 after the fourth enqueue');
+ });
+
+}, 'ReadableStream strategies: the default strategy should continue giving desiredSize of 1 if the chunks are read immediately');
+
+promise_test(t => {
+
+ const randomSource = new RandomPushSource(8);
+
+ const rs = new ReadableStream({
+ start(c) {
+ assert_equals(typeof c, 'object', 'c should be an object in start');
+ assert_equals(typeof c.enqueue, 'function', 'enqueue should be a function in start');
+ assert_equals(typeof c.close, 'function', 'close should be a function in start');
+ assert_equals(typeof c.error, 'function', 'error should be a function in start');
+
+ randomSource.ondata = t.step_func(chunk => {
+ if (!c.enqueue(chunk) <= 0) {
+ randomSource.readStop();
+ }
+ });
+
+ randomSource.onend = c.close.bind(c);
+ randomSource.onerror = c.error.bind(c);
+ },
+
+ pull(c) {
+ assert_equals(typeof c, 'object', 'c should be an object in pull');
+ assert_equals(typeof c.enqueue, 'function', 'enqueue should be a function in pull');
+ assert_equals(typeof c.close, 'function', 'close should be a function in pull');
+
+ randomSource.readStart();
+ }
+ });
+
+ return readableStreamToArray(rs).then(chunks => {
+ assert_equals(chunks.length, 8, '8 chunks should be read');
+ for (const chunk of chunks) {
+ assert_equals(chunk.length, 128, 'chunk should have 128 bytes');
+ }
+ });
+
+}, 'ReadableStream integration test: adapting a random push source');
+
+promise_test(() => {
+
+ const rs = sequentialReadableStream(10);
+
+ return readableStreamToArray(rs).then(chunks => {
+ assert_true(rs.source.closed, 'source should be closed after all chunks are read');
+ assert_array_equals(chunks, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'the expected 10 chunks should be read');
+ });
+
+}, 'ReadableStream integration test: adapting a sync pull source');
+
+promise_test(() => {
+
+ const rs = sequentialReadableStream(10, { async: true });
+
+ return readableStreamToArray(rs).then(chunks => {
+ assert_true(rs.source.closed, 'source should be closed after all chunks are read');
+ assert_array_equals(chunks, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'the expected 10 chunks should be read');
+ });
+
+}, 'ReadableStream integration test: adapting an async pull source');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/patched-global.js b/test/fixtures/web-platform-tests/streams/readable-streams/patched-global.js
new file mode 100644
index 00000000000000..e8117c480484a3
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/patched-global.js
@@ -0,0 +1,67 @@
+'use strict';
+
+// Tests which patch the global environment are kept separate to avoid
+// interfering with other tests.
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+const ReadableStream_prototype_locked_get =
+ Object.getOwnPropertyDescriptor(ReadableStream.prototype, 'locked').get;
+
+// Verify that |rs| passes the brand check as a readable stream.
+function isReadableStream(rs) {
+ try {
+ ReadableStream_prototype_locked_get.call(rs);
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+test(t => {
+ const rs = new ReadableStream();
+
+ const trappedProperties = ['highWaterMark', 'size', 'start', 'type', 'mode'];
+ for (const property of trappedProperties) {
+ // eslint-disable-next-line no-extend-native, accessor-pairs
+ Object.defineProperty(Object.prototype, property, {
+ get() { throw new Error(`${property} getter called`); },
+ configurable: true
+ });
+ }
+ t.add_cleanup(() => {
+ for (const property of trappedProperties) {
+ delete Object.prototype[property];
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ assert_true(isReadableStream(branch1), 'branch1 should be a ReadableStream');
+ assert_true(isReadableStream(branch2), 'branch2 should be a ReadableStream');
+}, 'ReadableStream tee() should not touch Object.prototype properties');
+
+test(t => {
+ const rs = new ReadableStream();
+
+ const oldReadableStream = self.ReadableStream;
+
+ /* eslint-disable no-native-reassign */
+ self.ReadableStream = function() {
+ throw new Error('ReadableStream called on global object');
+ };
+
+ t.add_cleanup(() => {
+ self.ReadableStream = oldReadableStream;
+ });
+
+ const [branch1, branch2] = rs.tee();
+
+ assert_true(isReadableStream(branch1), 'branch1 should be a ReadableStream');
+ assert_true(isReadableStream(branch2), 'branch2 should be a ReadableStream');
+
+ /* eslint-enable no-native-reassign */
+}, 'ReadableStream tee() should not call the global ReadableStream');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/reentrant-strategies.js b/test/fixtures/web-platform-tests/streams/readable-streams/reentrant-strategies.js
new file mode 100644
index 00000000000000..47dd3bf3c7debf
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/reentrant-strategies.js
@@ -0,0 +1,269 @@
+'use strict';
+
+// The size() function of the readable strategy can re-entrantly call back into the ReadableStream implementation. This
+// makes it risky to cache state across the call to ReadableStreamDefaultControllerEnqueue. These tests attempt to catch
+// such errors. They are separated from the other strategy tests because no real user code should ever do anything like
+// this.
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/recording-streams.js');
+ self.importScripts('../resources/rs-utils.js');
+ self.importScripts('../resources/test-utils.js');
+}
+
+const error1 = new Error('error1');
+error1.name = 'error1';
+
+promise_test(() => {
+ let controller;
+ let calls = 0;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }, {
+ size() {
+ ++calls;
+ if (calls < 2) {
+ controller.enqueue('b');
+ }
+ return 1;
+ }
+ });
+ controller.enqueue('a');
+ controller.close();
+ return readableStreamToArray(rs)
+ .then(array => assert_array_equals(array, ['b', 'a'], 'array should contain two chunks'));
+}, 'enqueue() inside size() should work');
+
+promise_test(() => {
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }, {
+ size() {
+ // The queue is empty.
+ controller.close();
+ // The state has gone from "readable" to "closed".
+ return 1;
+ // This chunk will be enqueued, but will be impossible to read because the state is already "closed".
+ }
+ });
+ controller.enqueue('a');
+ return readableStreamToArray(rs)
+ .then(array => assert_array_equals(array, [], 'array should contain no chunks'));
+ // The chunk 'a' is still in rs's queue. It is closed so 'a' cannot be read.
+}, 'close() inside size() should not crash');
+
+promise_test(() => {
+ let controller;
+ let calls = 0;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }, {
+ size() {
+ ++calls;
+ if (calls === 2) {
+ // The queue contains one chunk.
+ controller.close();
+ // The state is still "readable", but closeRequest is now true.
+ }
+ return 1;
+ }
+ });
+ controller.enqueue('a');
+ controller.enqueue('b');
+ return readableStreamToArray(rs)
+ .then(array => assert_array_equals(array, ['a', 'b'], 'array should contain two chunks'));
+}, 'close request inside size() should work');
+
+promise_test(t => {
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }, {
+ size() {
+ controller.error(error1);
+ return 1;
+ }
+ });
+ controller.enqueue('a');
+ return promise_rejects(t, error1, rs.getReader().read(), 'read() should reject');
+}, 'error() inside size() should work');
+
+promise_test(() => {
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }, {
+ size() {
+ assert_equals(controller.desiredSize, 1, 'desiredSize should be 1');
+ return 1;
+ },
+ highWaterMark: 1
+ });
+ controller.enqueue('a');
+ controller.close();
+ return readableStreamToArray(rs)
+ .then(array => assert_array_equals(array, ['a'], 'array should contain one chunk'));
+}, 'desiredSize inside size() should work');
+
+promise_test(t => {
+ let cancelPromise;
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ cancel: t.step_func(reason => {
+ assert_equals(reason, error1, 'reason should be error1');
+ assert_throws(new TypeError(), () => controller.enqueue(), 'enqueue() should throw');
+ })
+ }, {
+ size() {
+ cancelPromise = rs.cancel(error1);
+ return 1;
+ },
+ highWaterMark: Infinity
+ });
+ controller.enqueue('a');
+ const reader = rs.getReader();
+ return Promise.all([
+ reader.closed,
+ cancelPromise
+ ]);
+}, 'cancel() inside size() should work');
+
+promise_test(() => {
+ let controller;
+ let pipeToPromise;
+ const ws = recordingWritableStream();
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }, {
+ size() {
+ if (!pipeToPromise) {
+ pipeToPromise = rs.pipeTo(ws);
+ }
+ return 1;
+ },
+ highWaterMark: 1
+ });
+ controller.enqueue('a');
+ assert_not_equals(pipeToPromise, undefined);
+
+ // Some pipeTo() implementations need an additional chunk enqueued in order for the first one to be processed. See
+ // https://github.com/whatwg/streams/issues/794 for background.
+ controller.enqueue('a');
+
+ // Give pipeTo() a chance to process the queued chunks.
+ return delay(0).then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'a'], 'ws should contain two chunks');
+ controller.close();
+ return pipeToPromise;
+ }).then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'a', 'close'], 'target should have been closed');
+ });
+}, 'pipeTo() inside size() should behave as expected');
+
+promise_test(() => {
+ let controller;
+ let readPromise;
+ let calls = 0;
+ let readResolved = false;
+ let reader;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }, {
+ size() {
+ // This is triggered by controller.enqueue(). The queue is empty and there are no pending reads. This read is
+ // added to the list of pending reads.
+ readPromise = reader.read();
+ ++calls;
+ return 1;
+ },
+ highWaterMark: 0
+ });
+ reader = rs.getReader();
+ controller.enqueue('a');
+ readPromise.then(() => {
+ readResolved = true;
+ });
+ return flushAsyncEvents().then(() => {
+ assert_false(readResolved);
+ controller.enqueue('b');
+ assert_equals(calls, 1, 'size() should have been called once');
+ return delay(0);
+ }).then(() => {
+ assert_true(readResolved);
+ assert_equals(calls, 1, 'size() should only be called once');
+ return readPromise;
+ }).then(({ value, done }) => {
+ assert_false(done, 'done should be false');
+ // See https://github.com/whatwg/streams/issues/794 for why this chunk is not 'a'.
+ assert_equals(value, 'b', 'chunk should have been read');
+ assert_equals(calls, 1, 'calls should still be 1');
+ return reader.read();
+ }).then(({ value, done }) => {
+ assert_false(done, 'done should be false again');
+ assert_equals(value, 'a', 'chunk a should come after b');
+ });
+}, 'read() inside of size() should behave as expected');
+
+promise_test(() => {
+ let controller;
+ let reader;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }, {
+ size() {
+ reader = rs.getReader();
+ return 1;
+ }
+ });
+ controller.enqueue('a');
+ return reader.read().then(({ value, done }) => {
+ assert_false(done, 'done should be false');
+ assert_equals(value, 'a', 'value should be a');
+ });
+}, 'getReader() inside size() should work');
+
+promise_test(() => {
+ let controller;
+ let branch1;
+ let branch2;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }, {
+ size() {
+ [branch1, branch2] = rs.tee();
+ return 1;
+ }
+ });
+ controller.enqueue('a');
+ assert_true(rs.locked, 'rs should be locked');
+ controller.close();
+ return Promise.all([
+ readableStreamToArray(branch1).then(array => assert_array_equals(array, ['a'], 'branch1 should have one chunk')),
+ readableStreamToArray(branch2).then(array => assert_array_equals(array, ['a'], 'branch2 should have one chunk'))
+ ]);
+}, 'tee() inside size() should work');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/tee.js b/test/fixtures/web-platform-tests/streams/readable-streams/tee.js
new file mode 100644
index 00000000000000..df76877eff4a17
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/tee.js
@@ -0,0 +1,293 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('../resources/rs-utils.js');
+ self.importScripts('/resources/testharness.js');
+}
+
+test(() => {
+
+ const rs = new ReadableStream();
+ const result = rs.tee();
+
+ assert_true(Array.isArray(result), 'return value should be an array');
+ assert_equals(result.length, 2, 'array should have length 2');
+ assert_equals(result[0].constructor, ReadableStream, '0th element should be a ReadableStream');
+ assert_equals(result[1].constructor, ReadableStream, '1st element should be a ReadableStream');
+
+}, 'ReadableStream teeing: rs.tee() returns an array of two ReadableStreams');
+
+promise_test(t => {
+
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ c.close();
+ }
+ });
+
+ const branch = rs.tee();
+ const branch1 = branch[0];
+ const branch2 = branch[1];
+ const reader1 = branch1.getReader();
+ const reader2 = branch2.getReader();
+
+ reader2.closed.then(t.unreached_func('branch2 should not be closed'));
+
+ return Promise.all([
+ reader1.closed,
+ reader1.read().then(r => {
+ assert_object_equals(r, { value: 'a', done: false }, 'first chunk from branch1 should be correct');
+ }),
+ reader1.read().then(r => {
+ assert_object_equals(r, { value: 'b', done: false }, 'second chunk from branch1 should be correct');
+ }),
+ reader1.read().then(r => {
+ assert_object_equals(r, { value: undefined, done: true }, 'third read() from branch1 should be done');
+ }),
+ reader2.read().then(r => {
+ assert_object_equals(r, { value: 'a', done: false }, 'first chunk from branch2 should be correct');
+ })
+ ]);
+
+}, 'ReadableStream teeing: should be able to read one branch to the end without affecting the other');
+
+promise_test(() => {
+
+ const theObject = { the: 'test object' };
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue(theObject);
+ }
+ });
+
+ const branch = rs.tee();
+ const branch1 = branch[0];
+ const branch2 = branch[1];
+ const reader1 = branch1.getReader();
+ const reader2 = branch2.getReader();
+
+ return Promise.all([reader1.read(), reader2.read()]).then(values => {
+ assert_object_equals(values[0], values[1], 'the values should be equal');
+ });
+
+}, 'ReadableStream teeing: values should be equal across each branch');
+
+promise_test(t => {
+
+ const theError = { name: 'boo!' };
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ },
+ pull() {
+ throw theError;
+ }
+ });
+
+ const branches = rs.tee();
+ const reader1 = branches[0].getReader();
+ const reader2 = branches[1].getReader();
+
+ reader1.label = 'reader1';
+ reader2.label = 'reader2';
+
+ return Promise.all([
+ promise_rejects(t, theError, reader1.closed),
+ promise_rejects(t, theError, reader2.closed),
+ reader1.read().then(r => {
+ assert_object_equals(r, { value: 'a', done: false }, 'should be able to read the first chunk in branch1');
+ }),
+ reader1.read().then(r => {
+ assert_object_equals(r, { value: 'b', done: false }, 'should be able to read the second chunk in branch1');
+
+ return promise_rejects(t, theError, reader2.read());
+ })
+ .then(() => promise_rejects(t, theError, reader1.read()))
+ ]);
+
+}, 'ReadableStream teeing: errors in the source should propagate to both branches');
+
+promise_test(() => {
+
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ c.close();
+ }
+ });
+
+ const branches = rs.tee();
+ const branch1 = branches[0];
+ const branch2 = branches[1];
+ branch1.cancel();
+
+ return Promise.all([
+ readableStreamToArray(branch1).then(chunks => {
+ assert_array_equals(chunks, [], 'branch1 should have no chunks');
+ }),
+ readableStreamToArray(branch2).then(chunks => {
+ assert_array_equals(chunks, ['a', 'b'], 'branch2 should have two chunks');
+ })
+ ]);
+
+}, 'ReadableStream teeing: canceling branch1 should not impact branch2');
+
+promise_test(() => {
+
+ const rs = new ReadableStream({
+ start(c) {
+ c.enqueue('a');
+ c.enqueue('b');
+ c.close();
+ }
+ });
+
+ const branches = rs.tee();
+ const branch1 = branches[0];
+ const branch2 = branches[1];
+ branch2.cancel();
+
+ return Promise.all([
+ readableStreamToArray(branch1).then(chunks => {
+ assert_array_equals(chunks, ['a', 'b'], 'branch1 should have two chunks');
+ }),
+ readableStreamToArray(branch2).then(chunks => {
+ assert_array_equals(chunks, [], 'branch2 should have no chunks');
+ })
+ ]);
+
+}, 'ReadableStream teeing: canceling branch2 should not impact branch2');
+
+promise_test(() => {
+
+ const reason1 = new Error('We\'re wanted men.');
+ const reason2 = new Error('I have the death sentence on twelve systems.');
+
+ let resolve;
+ const promise = new Promise(r => resolve = r);
+ const rs = new ReadableStream({
+ cancel(reason) {
+ assert_array_equals(reason, [reason1, reason2],
+ 'the cancel reason should be an array containing those from the branches');
+ resolve();
+ }
+ });
+
+ const branch = rs.tee();
+ const branch1 = branch[0];
+ const branch2 = branch[1];
+ branch1.cancel(reason1);
+ branch2.cancel(reason2);
+
+ return promise;
+
+}, 'ReadableStream teeing: canceling both branches should aggregate the cancel reasons into an array');
+
+promise_test(t => {
+
+ const theError = { name: 'I\'ll be careful.' };
+ const rs = new ReadableStream({
+ cancel() {
+ throw theError;
+ }
+ });
+
+ const branch = rs.tee();
+ const branch1 = branch[0];
+ const branch2 = branch[1];
+
+ return Promise.all([
+ promise_rejects(t, theError, branch1.cancel()),
+ promise_rejects(t, theError, branch2.cancel())
+ ]);
+
+}, 'ReadableStream teeing: failing to cancel the original stream should cause cancel() to reject on branches');
+
+test(t => {
+
+ let controller;
+ const stream = new ReadableStream({ start(c) { controller = c; } });
+ const [branch1, branch2] = stream.tee();
+
+ const promise = controller.error("error");
+
+ branch1.cancel().catch(_=>_);
+ branch2.cancel().catch(_=>_);
+
+ return promise;
+}, 'ReadableStream teeing: erroring a teed stream should properly handle canceled branches');
+
+promise_test(() => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const branches = rs.tee();
+ const reader1 = branches[0].getReader();
+ const reader2 = branches[1].getReader();
+
+ const promise = Promise.all([reader1.closed, reader2.closed]);
+
+ controller.close();
+ return promise;
+
+}, 'ReadableStream teeing: closing the original should immediately close the branches');
+
+promise_test(t => {
+
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const branches = rs.tee();
+ const reader1 = branches[0].getReader();
+ const reader2 = branches[1].getReader();
+
+ const theError = { name: 'boo!' };
+ const promise = Promise.all([
+ promise_rejects(t, theError, reader1.closed),
+ promise_rejects(t, theError, reader2.closed)
+ ]);
+
+ controller.error(theError);
+ return promise;
+
+}, 'ReadableStream teeing: erroring the original should immediately error the branches');
+
+test(t => {
+
+ // Copy original global.
+ const oldReadableStream = ReadableStream;
+ const getReader = ReadableStream.prototype.getReader;
+
+ const origRS = new ReadableStream();
+
+ // Replace the global ReadableStream constructor with one that doesn't work.
+ ReadableStream = function() {
+ throw new Error('global ReadableStream constructor called');
+ };
+ t.add_cleanup(() => {
+ ReadableStream = oldReadableStream;
+ });
+
+ // This will probably fail if the global ReadableStream constructor was used.
+ const [rs1, rs2] = origRS.tee();
+
+ // These will definitely fail if the global ReadableStream constructor was used.
+ assert_not_equals(getReader.call(rs1), undefined, 'getReader should work on rs1');
+ assert_not_equals(getReader.call(rs2), undefined, 'getReader should work on rs2');
+
+}, 'ReadableStreamTee should not use a modified ReadableStream constructor from the global object');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/readable-streams/templated.js b/test/fixtures/web-platform-tests/streams/readable-streams/templated.js
new file mode 100644
index 00000000000000..6db0429994d453
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/readable-streams/templated.js
@@ -0,0 +1,148 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/rs-test-templates.js');
+}
+
+// Run the readable stream test templates against readable streams created directly using the constructor
+
+const theError = { name: 'boo!' };
+const chunks = ['a', 'b'];
+
+templatedRSEmpty('ReadableStream (empty)', () => {
+ return new ReadableStream();
+});
+
+templatedRSEmptyReader('ReadableStream (empty) reader', () => {
+ return streamAndDefaultReader(new ReadableStream());
+});
+
+templatedRSClosed('ReadableStream (closed via call in start)', () => {
+ return new ReadableStream({
+ start(c) {
+ c.close();
+ }
+ });
+});
+
+templatedRSClosedReader('ReadableStream reader (closed before getting reader)', () => {
+ let controller;
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ controller.close();
+ const result = streamAndDefaultReader(stream);
+ return result;
+});
+
+templatedRSClosedReader('ReadableStream reader (closed after getting reader)', () => {
+ let controller;
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ const result = streamAndDefaultReader(stream);
+ controller.close();
+ return result;
+});
+
+templatedRSClosed('ReadableStream (closed via cancel)', () => {
+ const stream = new ReadableStream();
+ stream.cancel();
+ return stream;
+});
+
+templatedRSClosedReader('ReadableStream reader (closed via cancel after getting reader)', () => {
+ const stream = new ReadableStream();
+ const result = streamAndDefaultReader(stream);
+ result.reader.cancel();
+ return result;
+});
+
+templatedRSErrored('ReadableStream (errored via call in start)', () => {
+ return new ReadableStream({
+ start(c) {
+ c.error(theError);
+ }
+ });
+}, theError);
+
+templatedRSErroredSyncOnly('ReadableStream (errored via call in start)', () => {
+ return new ReadableStream({
+ start(c) {
+ c.error(theError);
+ }
+ });
+}, theError);
+
+templatedRSErrored('ReadableStream (errored via returning a rejected promise in start)', () => {
+ return new ReadableStream({
+ start() {
+ return Promise.reject(theError);
+ }
+ });
+}, theError);
+
+templatedRSErroredReader('ReadableStream (errored via returning a rejected promise in start) reader', () => {
+ return streamAndDefaultReader(new ReadableStream({
+ start() {
+ return Promise.reject(theError);
+ }
+ }));
+}, theError);
+
+templatedRSErroredReader('ReadableStream reader (errored before getting reader)', () => {
+ let controller;
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ controller.error(theError);
+ return streamAndDefaultReader(stream);
+}, theError);
+
+templatedRSErroredReader('ReadableStream reader (errored after getting reader)', () => {
+ let controller;
+ const result = streamAndDefaultReader(new ReadableStream({
+ start(c) {
+ controller = c;
+ }
+ }));
+ controller.error(theError);
+ return result;
+}, theError);
+
+templatedRSTwoChunksOpenReader('ReadableStream (two chunks enqueued, still open) reader', () => {
+ return streamAndDefaultReader(new ReadableStream({
+ start(c) {
+ c.enqueue(chunks[0]);
+ c.enqueue(chunks[1]);
+ }
+ }));
+}, chunks);
+
+templatedRSTwoChunksClosedReader('ReadableStream (two chunks enqueued, then closed) reader', () => {
+ let doClose;
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(chunks[0]);
+ c.enqueue(chunks[1]);
+ doClose = c.close.bind(c);
+ }
+ });
+ const result = streamAndDefaultReader(stream);
+ doClose();
+ return result;
+}, chunks);
+
+function streamAndDefaultReader(stream) {
+ return { stream, reader: stream.getReader() };
+}
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/resources/constructor-ordering.js b/test/fixtures/web-platform-tests/streams/resources/constructor-ordering.js
new file mode 100644
index 00000000000000..79862e044c079f
--- /dev/null
+++ b/test/fixtures/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();
+}
diff --git a/test/fixtures/web-platform-tests/streams/resources/recording-streams.js b/test/fixtures/web-platform-tests/streams/resources/recording-streams.js
new file mode 100644
index 00000000000000..34d02a143dccdb
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/resources/recording-streams.js
@@ -0,0 +1,130 @@
+'use strict';
+
+self.recordingReadableStream = (extras = {}, strategy) => {
+ let controllerToCopyOver;
+ const stream = new ReadableStream({
+ start(controller) {
+ controllerToCopyOver = controller;
+
+ if (extras.start) {
+ return extras.start(controller);
+ }
+
+ return undefined;
+ },
+ pull(controller) {
+ stream.events.push('pull');
+
+ if (extras.pull) {
+ return extras.pull(controller);
+ }
+
+ return undefined;
+ },
+ cancel(reason) {
+ stream.events.push('cancel', reason);
+ stream.eventsWithoutPulls.push('cancel', reason);
+
+ if (extras.cancel) {
+ return extras.cancel(reason);
+ }
+
+ return undefined;
+ }
+ }, strategy);
+
+ stream.controller = controllerToCopyOver;
+ stream.events = [];
+ stream.eventsWithoutPulls = [];
+
+ return stream;
+};
+
+self.recordingWritableStream = (extras = {}, strategy) => {
+ let controllerToCopyOver;
+ const stream = new WritableStream({
+ start(controller) {
+ controllerToCopyOver = controller;
+
+ if (extras.start) {
+ return extras.start(controller);
+ }
+
+ return undefined;
+ },
+ write(chunk, controller) {
+ stream.events.push('write', chunk);
+
+ if (extras.write) {
+ return extras.write(chunk, controller);
+ }
+
+ return undefined;
+ },
+ close() {
+ stream.events.push('close');
+
+ if (extras.close) {
+ return extras.close();
+ }
+
+ return undefined;
+ },
+ abort(e) {
+ stream.events.push('abort', e);
+
+ if (extras.abort) {
+ return extras.abort(e);
+ }
+
+ return undefined;
+ }
+ }, strategy);
+
+ stream.controller = controllerToCopyOver;
+ stream.events = [];
+
+ return stream;
+};
+
+self.recordingTransformStream = (extras = {}, writableStrategy, readableStrategy) => {
+ let controllerToCopyOver;
+ const stream = new TransformStream({
+ start(controller) {
+ controllerToCopyOver = controller;
+
+ if (extras.start) {
+ return extras.start(controller);
+ }
+
+ return undefined;
+ },
+
+ transform(chunk, controller) {
+ stream.events.push('transform', chunk);
+
+ if (extras.transform) {
+ return extras.transform(chunk, controller);
+ }
+
+ controller.enqueue(chunk);
+
+ return undefined;
+ },
+
+ flush(controller) {
+ stream.events.push('flush');
+
+ if (extras.flush) {
+ return extras.flush(controller);
+ }
+
+ return undefined;
+ }
+ }, writableStrategy, readableStrategy);
+
+ stream.controller = controllerToCopyOver;
+ stream.events = [];
+
+ return stream;
+};
diff --git a/test/fixtures/web-platform-tests/streams/resources/rs-test-templates.js b/test/fixtures/web-platform-tests/streams/resources/rs-test-templates.js
new file mode 100644
index 00000000000000..ef68e0ade647ba
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/resources/rs-test-templates.js
@@ -0,0 +1,635 @@
+'use strict';
+
+// These tests can be run against any readable stream produced by the web platform that meets the given descriptions.
+// For readable stream tests, the factory should return the stream. For reader tests, the factory should return a
+// { stream, reader } object. (You can use this to vary the time at which you acquire a reader.)
+
+self.templatedRSEmpty = (label, factory) => {
+ test(() => {}, 'Running templatedRSEmpty with ' + label);
+
+ test(() => {
+
+ const rs = factory();
+
+ assert_equals(typeof rs.locked, 'boolean', 'has a boolean locked getter');
+ assert_equals(typeof rs.cancel, 'function', 'has a cancel method');
+ assert_equals(typeof rs.getReader, 'function', 'has a getReader method');
+ assert_equals(typeof rs.pipeThrough, 'function', 'has a pipeThrough method');
+ assert_equals(typeof rs.pipeTo, 'function', 'has a pipeTo method');
+ assert_equals(typeof rs.tee, 'function', 'has a tee method');
+
+ }, label + ': instances have the correct methods and properties');
+
+ test(() => {
+ const rs = factory();
+
+ assert_throws(new RangeError(), () => rs.getReader({ mode: '' }), 'empty string mode should throw');
+ assert_throws(new RangeError(), () => rs.getReader({ mode: null }), 'null mode should throw');
+ assert_throws(new RangeError(), () => rs.getReader({ mode: 'asdf' }), 'asdf mode should throw');
+ assert_throws(new TypeError(), () => rs.getReader(null), 'null should throw');
+
+ }, label + ': calling getReader with invalid arguments should throw appropriate errors');
+};
+
+self.templatedRSClosed = (label, factory) => {
+ test(() => {}, 'Running templatedRSClosed with ' + label);
+
+ promise_test(() => {
+
+ const rs = factory();
+ const cancelPromise1 = rs.cancel();
+ const cancelPromise2 = rs.cancel();
+
+ assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
+
+ return Promise.all([
+ cancelPromise1.then(v => assert_equals(v, undefined, 'first cancel() call should fulfill with undefined')),
+ cancelPromise2.then(v => assert_equals(v, undefined, 'second cancel() call should fulfill with undefined'))
+ ]);
+
+ }, label + ': cancel() should return a distinct fulfilled promise each time');
+
+ test(() => {
+
+ const rs = factory();
+ assert_false(rs.locked, 'locked getter should return false');
+
+ }, label + ': locked should be false');
+
+ test(() => {
+
+ const rs = factory();
+ rs.getReader(); // getReader() should not throw.
+
+ }, label + ': getReader() should be OK');
+
+ test(() => {
+
+ const rs = factory();
+
+ const reader = rs.getReader();
+ reader.releaseLock();
+
+ const reader2 = rs.getReader(); // Getting a second reader should not throw.
+ reader2.releaseLock();
+
+ rs.getReader(); // Getting a third reader should not throw.
+
+ }, label + ': should be able to acquire multiple readers if they are released in succession');
+
+ test(() => {
+
+ const rs = factory();
+
+ rs.getReader();
+
+ assert_throws(new TypeError(), () => rs.getReader(), 'getting a second reader should throw');
+ assert_throws(new TypeError(), () => rs.getReader(), 'getting a third reader should throw');
+
+ }, label + ': should not be able to acquire a second reader if we don\'t release the first one');
+};
+
+self.templatedRSErrored = (label, factory, error) => {
+ test(() => {}, 'Running templatedRSErrored with ' + label);
+
+ promise_test(t => {
+
+ const rs = factory();
+ const reader = rs.getReader();
+
+ return Promise.all([
+ promise_rejects(t, error, reader.closed),
+ promise_rejects(t, error, reader.read())
+ ]);
+
+ }, label + ': getReader() should return a reader that acts errored');
+
+ promise_test(t => {
+
+ const rs = factory();
+ const reader = rs.getReader();
+
+ return Promise.all([
+ promise_rejects(t, error, reader.read()),
+ promise_rejects(t, error, reader.read()),
+ promise_rejects(t, error, reader.closed)
+ ]);
+
+ }, label + ': read() twice should give the error each time');
+
+ test(() => {
+ const rs = factory();
+
+ assert_false(rs.locked, 'locked getter should return false');
+ }, label + ': locked should be false');
+};
+
+self.templatedRSErroredSyncOnly = (label, factory, error) => {
+ test(() => {}, 'Running templatedRSErroredSyncOnly with ' + label);
+
+ promise_test(t => {
+
+ const rs = factory();
+ rs.getReader().releaseLock();
+ const reader = rs.getReader(); // Calling getReader() twice does not throw (the stream is not locked).
+
+ return promise_rejects(t, error, reader.closed);
+
+ }, label + ': should be able to obtain a second reader, with the correct closed promise');
+
+ test(() => {
+
+ const rs = factory();
+ rs.getReader();
+
+ assert_throws(new TypeError(), () => rs.getReader(), 'getting a second reader should throw a TypeError');
+ assert_throws(new TypeError(), () => rs.getReader(), 'getting a third reader should throw a TypeError');
+
+ }, label + ': should not be able to obtain additional readers if we don\'t release the first lock');
+
+ promise_test(t => {
+
+ const rs = factory();
+ const cancelPromise1 = rs.cancel();
+ const cancelPromise2 = rs.cancel();
+
+ assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
+
+ return Promise.all([
+ promise_rejects(t, error, cancelPromise1),
+ promise_rejects(t, error, cancelPromise2)
+ ]);
+
+ }, label + ': cancel() should return a distinct rejected promise each time');
+
+ promise_test(t => {
+
+ const rs = factory();
+ const reader = rs.getReader();
+ const cancelPromise1 = reader.cancel();
+ const cancelPromise2 = reader.cancel();
+
+ assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
+
+ return Promise.all([
+ promise_rejects(t, error, cancelPromise1),
+ promise_rejects(t, error, cancelPromise2)
+ ]);
+
+ }, label + ': reader cancel() should return a distinct rejected promise each time');
+};
+
+self.templatedRSEmptyReader = (label, factory) => {
+ test(() => {}, 'Running templatedRSEmptyReader with ' + label);
+
+ test(() => {
+
+ const reader = factory().reader;
+
+ assert_true('closed' in reader, 'has a closed property');
+ assert_equals(typeof reader.closed.then, 'function', 'closed property is thenable');
+
+ assert_equals(typeof reader.cancel, 'function', 'has a cancel method');
+ assert_equals(typeof reader.read, 'function', 'has a read method');
+ assert_equals(typeof reader.releaseLock, 'function', 'has a releaseLock method');
+
+ }, label + ': instances have the correct methods and properties');
+
+ test(() => {
+
+ const stream = factory().stream;
+
+ assert_true(stream.locked, 'locked getter should return true');
+
+ }, label + ': locked should be true');
+
+ promise_test(t => {
+
+ const reader = factory().reader;
+
+ reader.read().then(
+ t.unreached_func('read() should not fulfill'),
+ t.unreached_func('read() should not reject')
+ );
+
+ return delay(500);
+
+ }, label + ': read() should never settle');
+
+ promise_test(t => {
+
+ const reader = factory().reader;
+
+ reader.read().then(
+ t.unreached_func('read() should not fulfill'),
+ t.unreached_func('read() should not reject')
+ );
+
+ reader.read().then(
+ t.unreached_func('read() should not fulfill'),
+ t.unreached_func('read() should not reject')
+ );
+
+ return delay(500);
+
+ }, label + ': two read()s should both never settle');
+
+ test(() => {
+
+ const reader = factory().reader;
+ assert_not_equals(reader.read(), reader.read(), 'the promises returned should be distinct');
+
+ }, label + ': read() should return distinct promises each time');
+
+ test(() => {
+
+ const stream = factory().stream;
+ assert_throws(new TypeError(), () => stream.getReader(), 'stream.getReader() should throw a TypeError');
+
+ }, label + ': getReader() again on the stream should fail');
+
+ promise_test(t => {
+
+ const streamAndReader = factory();
+ const stream = streamAndReader.stream;
+ const reader = streamAndReader.reader;
+
+ reader.read().then(
+ t.unreached_func('first read() should not fulfill'),
+ t.unreached_func('first read() should not reject')
+ );
+
+ reader.read().then(
+ t.unreached_func('second read() should not fulfill'),
+ t.unreached_func('second read() should not reject')
+ );
+
+ reader.closed.then(
+ t.unreached_func('closed should not fulfill'),
+ t.unreached_func('closed should not reject')
+ );
+
+ assert_throws(new TypeError(), () => reader.releaseLock(), 'releaseLock should throw a TypeError');
+
+ assert_true(stream.locked, 'the stream should still be locked');
+
+ return delay(500);
+
+ }, label + ': releasing the lock with pending read requests should throw but the read requests should stay pending');
+
+ promise_test(t => {
+
+ const reader = factory().reader;
+ reader.releaseLock();
+
+ return Promise.all([
+ promise_rejects(t, new TypeError(), reader.read()),
+ promise_rejects(t, new TypeError(), reader.read())
+ ]);
+
+ }, label + ': releasing the lock should cause further read() calls to reject with a TypeError');
+
+ promise_test(t => {
+
+ const reader = factory().reader;
+
+ const closedBefore = reader.closed;
+ reader.releaseLock();
+ const closedAfter = reader.closed;
+
+ assert_equals(closedBefore, closedAfter, 'the closed promise should not change identity');
+
+ return promise_rejects(t, new TypeError(), closedBefore);
+
+ }, label + ': releasing the lock should cause closed calls to reject with a TypeError');
+
+ test(() => {
+
+ const streamAndReader = factory();
+ const stream = streamAndReader.stream;
+ const reader = streamAndReader.reader;
+
+ reader.releaseLock();
+ assert_false(stream.locked, 'locked getter should return false');
+
+ }, label + ': releasing the lock should cause locked to become false');
+
+ promise_test(() => {
+
+ const reader = factory().reader;
+ reader.cancel();
+
+ return reader.read().then(r => {
+ assert_object_equals(r, { value: undefined, done: true }, 'read()ing from the reader should give a done result');
+ });
+
+ }, label + ': canceling via the reader should cause the reader to act closed');
+
+ promise_test(t => {
+
+ const stream = factory().stream;
+ return promise_rejects(t, new TypeError(), stream.cancel());
+
+ }, label + ': canceling via the stream should fail');
+};
+
+self.templatedRSClosedReader = (label, factory) => {
+ test(() => {}, 'Running templatedRSClosedReader with ' + label);
+
+ promise_test(() => {
+
+ const reader = factory().reader;
+
+ return reader.read().then(v => {
+ assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
+ });
+
+ }, label + ': read() should fulfill with { value: undefined, done: true }');
+
+ promise_test(() => {
+
+ const reader = factory().reader;
+
+ return Promise.all([
+ reader.read().then(v => {
+ assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
+ }),
+ reader.read().then(v => {
+ assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
+ })
+ ]);
+
+ }, label + ': read() multiple times should fulfill with { value: undefined, done: true }');
+
+ promise_test(() => {
+
+ const reader = factory().reader;
+
+ return reader.read().then(() => reader.read()).then(v => {
+ assert_object_equals(v, { value: undefined, done: true }, 'read() should fulfill correctly');
+ });
+
+ }, label + ': read() should work when used within another read() fulfill callback');
+
+ promise_test(() => {
+
+ const reader = factory().reader;
+
+ return reader.closed.then(v => assert_equals(v, undefined, 'reader closed should fulfill with undefined'));
+
+ }, label + ': closed should fulfill with undefined');
+
+ promise_test(t => {
+
+ const reader = factory().reader;
+
+ const closedBefore = reader.closed;
+ reader.releaseLock();
+ const closedAfter = reader.closed;
+
+ assert_not_equals(closedBefore, closedAfter, 'the closed promise should change identity');
+
+ return Promise.all([
+ closedBefore.then(v => assert_equals(v, undefined, 'reader.closed acquired before release should fulfill')),
+ promise_rejects(t, new TypeError(), closedAfter)
+ ]);
+
+ }, label + ': releasing the lock should cause closed to reject and change identity');
+
+ promise_test(() => {
+
+ const reader = factory().reader;
+ const cancelPromise1 = reader.cancel();
+ const cancelPromise2 = reader.cancel();
+ const closedReaderPromise = reader.closed;
+
+ assert_not_equals(cancelPromise1, cancelPromise2, 'cancel() calls should return distinct promises');
+ assert_not_equals(cancelPromise1, closedReaderPromise, 'cancel() promise 1 should be distinct from reader.closed');
+ assert_not_equals(cancelPromise2, closedReaderPromise, 'cancel() promise 2 should be distinct from reader.closed');
+
+ return Promise.all([
+ cancelPromise1.then(v => assert_equals(v, undefined, 'first cancel() should fulfill with undefined')),
+ cancelPromise2.then(v => assert_equals(v, undefined, 'second cancel() should fulfill with undefined'))
+ ]);
+
+ }, label + ': cancel() should return a distinct fulfilled promise each time');
+};
+
+self.templatedRSErroredReader = (label, factory, error) => {
+ test(() => {}, 'Running templatedRSErroredReader with ' + label);
+
+ promise_test(t => {
+
+ const reader = factory().reader;
+ return promise_rejects(t, error, reader.closed);
+
+ }, label + ': closed should reject with the error');
+
+ promise_test(t => {
+
+ const reader = factory().reader;
+ const closedBefore = reader.closed;
+
+ return promise_rejects(t, error, closedBefore).then(() => {
+ reader.releaseLock();
+
+ const closedAfter = reader.closed;
+ assert_not_equals(closedBefore, closedAfter, 'the closed promise should change identity');
+
+ return promise_rejects(t, new TypeError(), closedAfter);
+ });
+
+ }, label + ': releasing the lock should cause closed to reject and change identity');
+
+ promise_test(t => {
+
+ const reader = factory().reader;
+ return promise_rejects(t, error, reader.read());
+
+ }, label + ': read() should reject with the error');
+};
+
+self.templatedRSTwoChunksOpenReader = (label, factory, chunks) => {
+ test(() => {}, 'Running templatedRSTwoChunksOpenReader with ' + label);
+
+ promise_test(() => {
+
+ const reader = factory().reader;
+
+ return Promise.all([
+ reader.read().then(r => {
+ assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
+ }),
+ reader.read().then(r => {
+ assert_object_equals(r, { value: chunks[1], done: false }, 'second result should be correct');
+ })
+ ]);
+
+ }, label + ': calling read() twice without waiting will eventually give both chunks (sequential)');
+
+ promise_test(() => {
+
+ const reader = factory().reader;
+
+ return reader.read().then(r => {
+ assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
+
+ return reader.read().then(r2 => {
+ assert_object_equals(r2, { value: chunks[1], done: false }, 'second result should be correct');
+ });
+ });
+
+ }, label + ': calling read() twice without waiting will eventually give both chunks (nested)');
+
+ test(() => {
+
+ const reader = factory().reader;
+ assert_not_equals(reader.read(), reader.read(), 'the promises returned should be distinct');
+
+ }, label + ': read() should return distinct promises each time');
+
+ promise_test(() => {
+
+ const reader = factory().reader;
+
+ const promise1 = reader.closed.then(v => {
+ assert_equals(v, undefined, 'reader closed should fulfill with undefined');
+ });
+
+ const promise2 = reader.read().then(r => {
+ assert_object_equals(r, { value: chunks[0], done: false },
+ 'promise returned before cancellation should fulfill with a chunk');
+ });
+
+ reader.cancel();
+
+ const promise3 = reader.read().then(r => {
+ assert_object_equals(r, { value: undefined, done: true },
+ 'promise returned after cancellation should fulfill with an end-of-stream signal');
+ });
+
+ return Promise.all([promise1, promise2, promise3]);
+
+ }, label + ': cancel() after a read() should still give that single read result');
+};
+
+self.templatedRSTwoChunksClosedReader = function (label, factory, chunks) {
+ test(() => {}, 'Running templatedRSTwoChunksClosedReader with ' + label);
+
+ promise_test(() => {
+
+ const reader = factory().reader;
+
+ return Promise.all([
+ reader.read().then(r => {
+ assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
+ }),
+ reader.read().then(r => {
+ assert_object_equals(r, { value: chunks[1], done: false }, 'second result should be correct');
+ }),
+ reader.read().then(r => {
+ assert_object_equals(r, { value: undefined, done: true }, 'third result should be correct');
+ })
+ ]);
+
+ }, label + ': third read(), without waiting, should give { value: undefined, done: true } (sequential)');
+
+ promise_test(() => {
+
+ const reader = factory().reader;
+
+ return reader.read().then(r => {
+ assert_object_equals(r, { value: chunks[0], done: false }, 'first result should be correct');
+
+ return reader.read().then(r2 => {
+ assert_object_equals(r2, { value: chunks[1], done: false }, 'second result should be correct');
+
+ return reader.read().then(r3 => {
+ assert_object_equals(r3, { value: undefined, done: true }, 'third result should be correct');
+ });
+ });
+ });
+
+ }, label + ': third read(), without waiting, should give { value: undefined, done: true } (nested)');
+
+ promise_test(() => {
+
+ const streamAndReader = factory();
+ const stream = streamAndReader.stream;
+ const reader = streamAndReader.reader;
+
+ assert_true(stream.locked, 'stream should start locked');
+
+ const promise = reader.closed.then(v => {
+ assert_equals(v, undefined, 'reader closed should fulfill with undefined');
+ assert_true(stream.locked, 'stream should remain locked');
+ });
+
+ reader.read();
+ reader.read();
+
+ return promise;
+
+ }, label +
+ ': draining the stream via read() should cause the reader closed promise to fulfill, but locked stays true');
+
+ promise_test(() => {
+
+ const streamAndReader = factory();
+ const stream = streamAndReader.stream;
+ const reader = streamAndReader.reader;
+
+ const promise = reader.closed.then(() => {
+ assert_true(stream.locked, 'the stream should start locked');
+ reader.releaseLock(); // Releasing the lock after reader closed should not throw.
+ assert_false(stream.locked, 'the stream should end unlocked');
+ });
+
+ reader.read();
+ reader.read();
+
+ return promise;
+
+ }, label + ': releasing the lock after the stream is closed should cause locked to become false');
+
+ promise_test(t => {
+
+ const reader = factory().reader;
+
+ reader.releaseLock();
+
+ return Promise.all([
+ promise_rejects(t, new TypeError(), reader.read()),
+ promise_rejects(t, new TypeError(), reader.read()),
+ promise_rejects(t, new TypeError(), reader.read())
+ ]);
+
+ }, label + ': releasing the lock should cause further read() calls to reject with a TypeError');
+
+ promise_test(() => {
+
+ const streamAndReader = factory();
+ const stream = streamAndReader.stream;
+ const reader = streamAndReader.reader;
+
+ const readerClosed = reader.closed;
+
+ assert_equals(reader.closed, readerClosed, 'accessing reader.closed twice in succession gives the same value');
+
+ const promise = reader.read().then(() => {
+ assert_equals(reader.closed, readerClosed, 'reader.closed is the same after read() fulfills');
+
+ reader.releaseLock();
+
+ assert_equals(reader.closed, readerClosed, 'reader.closed is the same after releasing the lock');
+
+ const newReader = stream.getReader();
+ return newReader.read();
+ });
+
+ assert_equals(reader.closed, readerClosed, 'reader.closed is the same after calling read()');
+
+ return promise;
+
+ }, label + ': reader\'s closed property always returns the same promise');
+};
diff --git a/test/fixtures/web-platform-tests/streams/resources/rs-utils.js b/test/fixtures/web-platform-tests/streams/resources/rs-utils.js
new file mode 100644
index 00000000000000..0f3222e23cab7a
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/resources/rs-utils.js
@@ -0,0 +1,185 @@
+'use strict';
+(function () {
+
+ class RandomPushSource {
+ constructor(toPush) {
+ this.pushed = 0;
+ this.toPush = toPush;
+ this.started = false;
+ this.paused = false;
+ this.closed = false;
+
+ this._intervalHandle = null;
+ }
+
+ readStart() {
+ if (this.closed) {
+ return;
+ }
+
+ if (!this.started) {
+ this._intervalHandle = setInterval(writeChunk, 2);
+ this.started = true;
+ }
+
+ if (this.paused) {
+ this._intervalHandle = setInterval(writeChunk, 2);
+ this.paused = false;
+ }
+
+ const source = this;
+ function writeChunk() {
+ if (source.paused) {
+ return;
+ }
+
+ source.pushed++;
+
+ if (source.toPush > 0 && source.pushed > source.toPush) {
+ if (source._intervalHandle) {
+ clearInterval(source._intervalHandle);
+ source._intervalHandle = undefined;
+ }
+ source.closed = true;
+ source.onend();
+ } else {
+ source.ondata(randomChunk(128));
+ }
+ }
+ }
+
+ readStop() {
+ if (this.paused) {
+ return;
+ }
+
+ if (this.started) {
+ this.paused = true;
+ clearInterval(this._intervalHandle);
+ this._intervalHandle = undefined;
+ } else {
+ throw new Error('Can\'t pause reading an unstarted source.');
+ }
+ }
+ }
+
+ function randomChunk(size) {
+ let chunk = '';
+
+ for (let i = 0; i < size; ++i) {
+ // Add a random character from the basic printable ASCII set.
+ chunk += String.fromCharCode(Math.round(Math.random() * 84) + 32);
+ }
+
+ return chunk;
+ }
+
+ function readableStreamToArray(readable, reader) {
+ if (reader === undefined) {
+ reader = readable.getReader();
+ }
+
+ const chunks = [];
+
+ return pump();
+
+ function pump() {
+ return reader.read().then(result => {
+ if (result.done) {
+ return chunks;
+ }
+
+ chunks.push(result.value);
+ return pump();
+ });
+ }
+ }
+
+ class SequentialPullSource {
+ constructor(limit, options) {
+ const async = options && options.async;
+
+ this.current = 0;
+ this.limit = limit;
+ this.opened = false;
+ this.closed = false;
+
+ this._exec = f => f();
+ if (async) {
+ this._exec = f => setTimeout(f, 0);
+ }
+ }
+
+ open(cb) {
+ this._exec(() => {
+ this.opened = true;
+ cb();
+ });
+ }
+
+ read(cb) {
+ this._exec(() => {
+ if (++this.current <= this.limit) {
+ cb(null, false, this.current);
+ } else {
+ cb(null, true, null);
+ }
+ });
+ }
+
+ close(cb) {
+ this._exec(() => {
+ this.closed = true;
+ cb();
+ });
+ }
+ }
+
+ function sequentialReadableStream(limit, options) {
+ const sequentialSource = new SequentialPullSource(limit, options);
+
+ const stream = new ReadableStream({
+ start() {
+ return new Promise((resolve, reject) => {
+ sequentialSource.open(err => {
+ if (err) {
+ reject(err);
+ }
+ resolve();
+ });
+ });
+ },
+
+ pull(c) {
+ return new Promise((resolve, reject) => {
+ sequentialSource.read((err, done, chunk) => {
+ if (err) {
+ reject(err);
+ } else if (done) {
+ sequentialSource.close(err2 => {
+ if (err2) {
+ reject(err2);
+ }
+ c.close();
+ resolve();
+ });
+ } else {
+ c.enqueue(chunk);
+ resolve();
+ }
+ });
+ });
+ }
+ });
+
+ stream.source = sequentialSource;
+
+ return stream;
+ }
+
+
+ self.RandomPushSource = RandomPushSource;
+ self.readableStreamToArray = readableStreamToArray;
+ self.sequentialReadableStream = sequentialReadableStream;
+
+}());
diff --git a/test/fixtures/web-platform-tests/streams/resources/test-utils.js b/test/fixtures/web-platform-tests/streams/resources/test-utils.js
new file mode 100644
index 00000000000000..871ab49fa81dbf
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/resources/test-utils.js
@@ -0,0 +1,73 @@
+'use strict';
+
+self.getterRejects = (t, obj, getterName, target) => {
+ const getter = Object.getOwnPropertyDescriptor(obj, getterName).get;
+
+ return promise_rejects(t, new TypeError(), getter.call(target), getterName + ' should reject with a TypeError');
+};
+
+self.getterRejectsForAll = (t, obj, getterName, targets) => {
+ return Promise.all(targets.map(target => self.getterRejects(t, obj, getterName, target)));
+};
+
+self.methodRejects = (t, obj, methodName, target, args) => {
+ const method = obj[methodName];
+
+ return promise_rejects(t, new TypeError(), method.apply(target, args),
+ methodName + ' should reject with a TypeError');
+};
+
+self.methodRejectsForAll = (t, obj, methodName, targets, args) => {
+ return Promise.all(targets.map(target => self.methodRejects(t, obj, methodName, target, args)));
+};
+
+self.getterThrows = (obj, getterName, target) => {
+ const getter = Object.getOwnPropertyDescriptor(obj, getterName).get;
+
+ assert_throws(new TypeError(), () => getter.call(target), getterName + ' should throw a TypeError');
+};
+
+self.getterThrowsForAll = (obj, getterName, targets) => {
+ targets.forEach(target => self.getterThrows(obj, getterName, target));
+};
+
+self.methodThrows = (obj, methodName, target, args) => {
+ const method = obj[methodName];
+ assert_equals(typeof method, 'function', methodName + ' should exist');
+
+ assert_throws(new TypeError(), () => method.apply(target, args), methodName + ' should throw a TypeError');
+};
+
+self.methodThrowsForAll = (obj, methodName, targets, args) => {
+ targets.forEach(target => self.methodThrows(obj, methodName, target, args));
+};
+
+self.constructorThrowsForAll = (constructor, firstArgs) => {
+ firstArgs.forEach(firstArg => assert_throws(new TypeError(), () => new constructor(firstArg),
+ 'constructor should throw a TypeError'));
+};
+
+self.garbageCollect = () => {
+ if (self.gc) {
+ // Use --expose_gc for V8 (and Node.js)
+ // Exposed in SpiderMonkey shell as well
+ self.gc();
+ } else if (self.GCController) {
+ // Present in some WebKit development environments
+ GCController.collect();
+ } else {
+ /* eslint-disable no-console */
+ console.warn('Tests are running without the ability to do manual garbage collection. They will still work, but ' +
+ 'coverage will be suboptimal.');
+ /* eslint-enable no-console */
+ }
+};
+
+self.delay = ms => new Promise(resolve => step_timeout(resolve, ms));
+
+// For tests which verify that the implementation doesn't do something it shouldn't, it's better not to use a
+// timeout. Instead, assume that any reasonable implementation is going to finish work after 2 times around the event
+// loop, and use flushAsyncEvents().then(() => assert_array_equals(...));
+// Some tests include promise resolutions which may mean the test code takes a couple of event loop visits itself. So go
+// around an extra 2 times to avoid complicating those tests.
+self.flushAsyncEvents = () => delay(0).then(() => delay(0)).then(() => delay(0)).then(() => delay(0));
diff --git a/test/fixtures/web-platform-tests/streams/transform-streams/backpressure.js b/test/fixtures/web-platform-tests/streams/transform-streams/backpressure.js
new file mode 100644
index 00000000000000..7446b770901622
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/transform-streams/backpressure.js
@@ -0,0 +1,200 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/recording-streams.js');
+ self.importScripts('../resources/test-utils.js');
+}
+
+const error1 = new Error('error1 message');
+error1.name = 'error1';
+
+promise_test(() => {
+ const ts = recordingTransformStream();
+ const writer = ts.writable.getWriter();
+ // This call never resolves.
+ writer.write('a');
+ return flushAsyncEvents().then(() => {
+ assert_array_equals(ts.events, [], 'transform should not be called');
+ });
+}, 'backpressure allows no transforms with a default identity transform and no reader');
+
+promise_test(() => {
+ const ts = recordingTransformStream({}, undefined, { highWaterMark: 1 });
+ const writer = ts.writable.getWriter();
+ // This call to write() resolves asynchronously.
+ writer.write('a');
+ // This call to write() waits for backpressure that is never relieved and never calls transform().
+ writer.write('b');
+ return flushAsyncEvents().then(() => {
+ assert_array_equals(ts.events, ['transform', 'a'], 'transform should be called once');
+ });
+}, 'backpressure only allows one transform() with a identity transform with a readable HWM of 1 and no reader');
+
+promise_test(() => {
+ // Without a transform() implementation, recordingTransformStream() never enqueues anything.
+ const ts = recordingTransformStream({
+ transform() {
+ // Discard all chunks. As a result, the readable side is never full enough to exert backpressure and transform()
+ // keeps being called.
+ }
+ }, undefined, { highWaterMark: 1 });
+ const writer = ts.writable.getWriter();
+ const writePromises = [];
+ for (let i = 0; i < 4; ++i) {
+ writePromises.push(writer.write(i));
+ }
+ return Promise.all(writePromises).then(() => {
+ assert_array_equals(ts.events, ['transform', 0, 'transform', 1, 'transform', 2, 'transform', 3],
+ 'all 4 events should be transformed');
+ });
+}, 'transform() should keep being called as long as there is no backpressure');
+
+promise_test(() => {
+ const ts = new TransformStream({}, undefined, { highWaterMark: 1 });
+ const writer = ts.writable.getWriter();
+ const reader = ts.readable.getReader();
+ const events = [];
+ const writerPromises = [
+ writer.write('a').then(() => events.push('a')),
+ writer.write('b').then(() => events.push('b')),
+ writer.close().then(() => events.push('closed'))];
+ return delay(0).then(() => {
+ assert_array_equals(events, ['a'], 'the first write should have resolved');
+ return reader.read();
+ }).then(({ value, done }) => {
+ assert_false(done, 'done should not be true');
+ assert_equals('a', value, 'value should be "a"');
+ return delay(0);
+ }).then(() => {
+ assert_array_equals(events, ['a', 'b', 'closed'], 'both writes and close() should have resolved');
+ return reader.read();
+ }).then(({ value, done }) => {
+ assert_false(done, 'done should still not be true');
+ assert_equals('b', value, 'value should be "b"');
+ return reader.read();
+ }).then(({ done }) => {
+ assert_true(done, 'done should be true');
+ return writerPromises;
+ });
+}, 'writes should resolve as soon as transform completes');
+
+promise_test(() => {
+ const ts = new TransformStream(undefined, undefined, { highWaterMark: 0 });
+ const writer = ts.writable.getWriter();
+ const reader = ts.readable.getReader();
+ const readPromise = reader.read();
+ writer.write('a');
+ return readPromise.then(({ value, done }) => {
+ assert_false(done, 'not done');
+ assert_equals(value, 'a', 'value should be "a"');
+ });
+}, 'calling pull() before the first write() with backpressure should work');
+
+promise_test(() => {
+ let reader;
+ const ts = recordingTransformStream({
+ transform(chunk, controller) {
+ controller.enqueue(chunk);
+ return reader.read();
+ }
+ }, undefined, { highWaterMark: 1 });
+ const writer = ts.writable.getWriter();
+ reader = ts.readable.getReader();
+ return writer.write('a');
+}, 'transform() should be able to read the chunk it just enqueued');
+
+promise_test(() => {
+ let resolveTransform;
+ const transformPromise = new Promise(resolve => {
+ resolveTransform = resolve;
+ });
+ const ts = recordingTransformStream({
+ transform() {
+ return transformPromise;
+ }
+ }, undefined, new CountQueuingStrategy({ highWaterMark: Infinity }));
+ const writer = ts.writable.getWriter();
+ assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
+ return delay(0).then(() => {
+ writer.write('a');
+ assert_array_equals(ts.events, ['transform', 'a']);
+ assert_equals(writer.desiredSize, 0, 'desiredSize should be 0');
+ return flushAsyncEvents();
+ }).then(() => {
+ assert_equals(writer.desiredSize, 0, 'desiredSize should still be 0');
+ resolveTransform();
+ return delay(0);
+ }).then(() => {
+ assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
+ });
+}, 'blocking transform() should cause backpressure');
+
+promise_test(t => {
+ const ts = new TransformStream();
+ ts.readable.cancel(error1);
+ return promise_rejects(t, error1, ts.writable.getWriter().closed, 'closed should reject');
+}, 'writer.closed should resolve after readable is canceled during start');
+
+promise_test(t => {
+ const ts = new TransformStream({}, undefined, { highWaterMark: 0 });
+ return delay(0).then(() => {
+ ts.readable.cancel(error1);
+ return promise_rejects(t, error1, ts.writable.getWriter().closed, 'closed should reject');
+ });
+}, 'writer.closed should resolve after readable is canceled with backpressure');
+
+promise_test(t => {
+ const ts = new TransformStream({}, undefined, { highWaterMark: 1 });
+ return delay(0).then(() => {
+ ts.readable.cancel(error1);
+ return promise_rejects(t, error1, ts.writable.getWriter().closed, 'closed should reject');
+ });
+}, 'writer.closed should resolve after readable is canceled with no backpressure');
+
+promise_test(() => {
+ const ts = new TransformStream({}, undefined, { highWaterMark: 1 });
+ const writer = ts.writable.getWriter();
+ return delay(0).then(() => {
+ const writePromise = writer.write('a');
+ ts.readable.cancel(error1);
+ return writePromise;
+ });
+}, 'cancelling the readable should cause a pending write to resolve');
+
+promise_test(t => {
+ const rs = new ReadableStream();
+ const ts = new TransformStream();
+ const pipePromise = rs.pipeTo(ts.writable);
+ ts.readable.cancel(error1);
+ return promise_rejects(t, error1, pipePromise, 'promise returned from pipeTo() should be rejected');
+}, 'cancelling the readable side of a TransformStream should abort an empty pipe');
+
+promise_test(t => {
+ const rs = new ReadableStream();
+ const ts = new TransformStream();
+ const pipePromise = rs.pipeTo(ts.writable);
+ return delay(0).then(() => {
+ ts.readable.cancel(error1);
+ return promise_rejects(t, error1, pipePromise, 'promise returned from pipeTo() should be rejected');
+ });
+}, 'cancelling the readable side of a TransformStream should abort an empty pipe after startup');
+
+promise_test(t => {
+ const rs = new ReadableStream({
+ start(controller) {
+ controller.enqueue('a');
+ controller.enqueue('b');
+ controller.enqueue('c');
+ }
+ });
+ const ts = new TransformStream();
+ const pipePromise = rs.pipeTo(ts.writable);
+ // Allow data to flow into the pipe.
+ return delay(0).then(() => {
+ ts.readable.cancel(error1);
+ return promise_rejects(t, error1, pipePromise, 'promise returned from pipeTo() should be rejected');
+ });
+}, 'cancelling the readable side of a TransformStream should abort a full pipe');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/transform-streams/brand-checks.js b/test/fixtures/web-platform-tests/streams/transform-streams/brand-checks.js
new file mode 100644
index 00000000000000..0dd0d91b318223
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/transform-streams/brand-checks.js
@@ -0,0 +1,79 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+}
+
+const TransformStreamDefaultController = getTransformStreamDefaultControllerConstructor();
+
+function getTransformStreamDefaultControllerConstructor() {
+ return realTSDefaultController().constructor;
+}
+
+function fakeTS() {
+ return Object.setPrototypeOf({
+ get readable() { return new ReadableStream(); },
+ get writable() { return new WritableStream(); }
+ }, TransformStream.prototype);
+}
+
+function realTS() {
+ return new TransformStream();
+}
+
+function fakeTSDefaultController() {
+ return Object.setPrototypeOf({
+ get desiredSize() { return 1; },
+ enqueue() { },
+ close() { },
+ error() { }
+ }, TransformStreamDefaultController.prototype);
+}
+
+function realTSDefaultController() {
+ let controller;
+ new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ return controller;
+}
+
+test(() => {
+ getterThrowsForAll(TransformStream.prototype, 'readable',
+ [fakeTS(), realTSDefaultController(), undefined, null]);
+}, 'TransformStream.prototype.readable enforces a brand check');
+
+test(() => {
+ getterThrowsForAll(TransformStream.prototype, 'writable',
+ [fakeTS(), realTSDefaultController(), undefined, null]);
+}, 'TransformStream.prototype.writable enforces a brand check');
+
+test(() => {
+ constructorThrowsForAll(TransformStreamDefaultController,
+ [fakeTS(), realTS(), realTSDefaultController(), undefined, null]);
+}, 'TransformStreamDefaultConstructor enforces a brand check and doesn\'t permit independent construction');
+
+test(() => {
+ getterThrowsForAll(TransformStreamDefaultController.prototype, 'desiredSize',
+ [fakeTSDefaultController(), realTS(), undefined, null]);
+}, 'TransformStreamDefaultController.prototype.desiredSize enforces a brand check');
+
+test(() => {
+ methodThrowsForAll(TransformStreamDefaultController.prototype, 'enqueue',
+ [fakeTSDefaultController(), realTS(), undefined, null]);
+}, 'TransformStreamDefaultController.prototype.enqueue enforces a brand check');
+
+test(() => {
+ methodThrowsForAll(TransformStreamDefaultController.prototype, 'terminate',
+ [fakeTSDefaultController(), realTS(), undefined, null]);
+}, 'TransformStreamDefaultController.prototype.terminate enforces a brand check');
+
+test(() => {
+ methodThrowsForAll(TransformStreamDefaultController.prototype, 'error',
+ [fakeTSDefaultController(), realTS(), undefined, null]);
+}, 'TransformStreamDefaultController.prototype.error enforces a brand check');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/transform-streams/constructor.js b/test/fixtures/web-platform-tests/streams/transform-streams/constructor.js
new file mode 100644
index 00000000000000..62770031184842
--- /dev/null
+++ b/test/fixtures/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();
diff --git a/test/fixtures/web-platform-tests/streams/transform-streams/errors.js b/test/fixtures/web-platform-tests/streams/transform-streams/errors.js
new file mode 100644
index 00000000000000..fdb2154554696a
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/transform-streams/errors.js
@@ -0,0 +1,346 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+}
+
+const thrownError = new Error('bad things are happening!');
+thrownError.name = 'error1';
+
+promise_test(t => {
+ const ts = new TransformStream({
+ transform() {
+ throw thrownError;
+ }
+ });
+
+ const reader = ts.readable.getReader();
+
+ const writer = ts.writable.getWriter();
+
+ return Promise.all([
+ promise_rejects(t, thrownError, writer.write('a'),
+ 'writable\'s write should reject with the thrown error'),
+ promise_rejects(t, thrownError, reader.read(),
+ 'readable\'s read should reject with the thrown error'),
+ promise_rejects(t, thrownError, reader.closed,
+ 'readable\'s closed should be rejected with the thrown error'),
+ promise_rejects(t, thrownError, writer.closed,
+ 'writable\'s closed should be rejected with the thrown error')
+ ]);
+}, 'TransformStream errors thrown in transform put the writable and readable in an errored state');
+
+promise_test(t => {
+ const ts = new TransformStream({
+ transform() {
+ },
+ flush() {
+ throw thrownError;
+ }
+ });
+
+ const reader = ts.readable.getReader();
+
+ const writer = ts.writable.getWriter();
+
+ return Promise.all([
+ writer.write('a'),
+ promise_rejects(t, thrownError, writer.close(),
+ 'writable\'s close should reject with the thrown error'),
+ promise_rejects(t, thrownError, reader.read(),
+ 'readable\'s read should reject with the thrown error'),
+ promise_rejects(t, thrownError, reader.closed,
+ 'readable\'s closed should be rejected with the thrown error'),
+ promise_rejects(t, thrownError, writer.closed,
+ 'writable\'s closed should be rejected with the thrown error')
+ ]);
+}, 'TransformStream errors thrown in flush put the writable and readable in an errored state');
+
+test(() => {
+ new TransformStream({
+ start(c) {
+ c.enqueue('a');
+ c.error(new Error('generic error'));
+ assert_throws(new TypeError(), () => c.enqueue('b'), 'enqueue() should throw');
+ }
+ });
+}, 'errored TransformStream should not enqueue new chunks');
+
+promise_test(t => {
+ const ts = new TransformStream({
+ start() {
+ return flushAsyncEvents().then(() => {
+ throw thrownError;
+ });
+ },
+ transform: t.unreached_func('transform should not be called'),
+ flush: t.unreached_func('flush should not be called')
+ });
+
+ const writer = ts.writable.getWriter();
+ const reader = ts.readable.getReader();
+ return Promise.all([
+ promise_rejects(t, thrownError, writer.write('a'), 'writer should reject with thrownError'),
+ promise_rejects(t, thrownError, writer.close(), 'close() should reject with thrownError'),
+ promise_rejects(t, thrownError, reader.read(), 'reader should reject with thrownError')
+ ]);
+}, 'TransformStream transformer.start() rejected promise should error the stream');
+
+promise_test(t => {
+ const controllerError = new Error('start failure');
+ controllerError.name = 'controllerError';
+ const ts = new TransformStream({
+ start(c) {
+ return flushAsyncEvents()
+ .then(() => {
+ c.error(controllerError);
+ throw new Error('ignored error');
+ });
+ },
+ transform: t.unreached_func('transform should never be called if start() fails'),
+ flush: t.unreached_func('flush should never be called if start() fails')
+ });
+
+ const writer = ts.writable.getWriter();
+ const reader = ts.readable.getReader();
+ return Promise.all([
+ promise_rejects(t, controllerError, writer.write('a'), 'writer should reject with controllerError'),
+ promise_rejects(t, controllerError, writer.close(), 'close should reject with same error'),
+ promise_rejects(t, controllerError, reader.read(), 'reader should reject with same error')
+ ]);
+}, 'when controller.error is followed by a rejection, the error reason should come from controller.error');
+
+test(() => {
+ assert_throws(new URIError(), () => new TransformStream({
+ start() { throw new URIError('start thrown error'); },
+ transform() {}
+ }), 'constructor should throw');
+}, 'TransformStream constructor should throw when start does');
+
+test(() => {
+ const strategy = {
+ size() { throw new URIError('size thrown error'); }
+ };
+
+ assert_throws(new URIError(), () => new TransformStream({
+ start(c) {
+ c.enqueue('a');
+ },
+ transform() {}
+ }, undefined, strategy), 'constructor should throw the same error strategy.size throws');
+}, 'when strategy.size throws inside start(), the constructor should throw the same error');
+
+test(() => {
+ const controllerError = new URIError('controller.error');
+
+ let controller;
+ const strategy = {
+ size() {
+ controller.error(controllerError);
+ throw new Error('redundant error');
+ }
+ };
+
+ assert_throws(new URIError(), () => new TransformStream({
+ start(c) {
+ controller = c;
+ c.enqueue('a');
+ },
+ transform() {}
+ }, undefined, strategy), 'the first error should be thrown');
+}, 'when strategy.size calls controller.error() then throws, the constructor should throw the first error');
+
+promise_test(t => {
+ const ts = new TransformStream();
+ const writer = ts.writable.getWriter();
+ const closedPromise = writer.closed;
+ return Promise.all([
+ ts.readable.cancel(thrownError),
+ promise_rejects(t, thrownError, closedPromise, 'closed should throw a TypeError')
+ ]);
+}, 'cancelling the readable side should error the writable');
+
+promise_test(t => {
+ let controller;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ const writer = ts.writable.getWriter();
+ const reader = ts.readable.getReader();
+ const writePromise = writer.write('a');
+ const closePromise = writer.close();
+ controller.error(thrownError);
+ return Promise.all([
+ promise_rejects(t, thrownError, reader.closed, 'reader.closed should reject'),
+ promise_rejects(t, thrownError, writePromise, 'writePromise should reject'),
+ promise_rejects(t, thrownError, closePromise, 'closePromise should reject')]);
+}, 'it should be possible to error the readable between close requested and complete');
+
+promise_test(t => {
+ const ts = new TransformStream({
+ transform(chunk, controller) {
+ controller.enqueue(chunk);
+ controller.terminate();
+ throw thrownError;
+ }
+ }, undefined, { highWaterMark: 1 });
+ const writePromise = ts.writable.getWriter().write('a');
+ const closedPromise = ts.readable.getReader().closed;
+ return Promise.all([
+ promise_rejects(t, thrownError, writePromise, 'write() should reject'),
+ promise_rejects(t, thrownError, closedPromise, 'reader.closed should reject')
+ ]);
+}, 'an exception from transform() should error the stream if terminate has been requested but not completed');
+
+promise_test(t => {
+ const ts = new TransformStream();
+ const writer = ts.writable.getWriter();
+ // The microtask following transformer.start() hasn't completed yet, so the abort is queued and not notified to the
+ // TransformStream yet.
+ const abortPromise = writer.abort(thrownError);
+ const cancelPromise = ts.readable.cancel(new Error('cancel reason'));
+ return Promise.all([
+ abortPromise,
+ cancelPromise,
+ promise_rejects(t, thrownError, writer.closed, 'writer.closed should reject with thrownError')]);
+}, 'abort should set the close reason for the writable when it happens before cancel during start, but cancel should ' +
+ 'still succeed');
+
+promise_test(t => {
+ let resolveTransform;
+ const transformPromise = new Promise(resolve => {
+ resolveTransform = resolve;
+ });
+ const ts = new TransformStream({
+ transform() {
+ return transformPromise;
+ }
+ }, undefined, { highWaterMark: 2 });
+ const writer = ts.writable.getWriter();
+ return delay(0).then(() => {
+ const writePromise = writer.write();
+ const abortPromise = writer.abort(thrownError);
+ const cancelPromise = ts.readable.cancel(new Error('cancel reason'));
+ resolveTransform();
+ return Promise.all([
+ writePromise,
+ abortPromise,
+ cancelPromise,
+ promise_rejects(t, thrownError, writer.closed, 'writer.closed should reject with thrownError')]);
+ });
+}, 'abort should set the close reason for the writable when it happens before cancel during underlying sink write, ' +
+ 'but cancel should still succeed');
+
+const ignoredError = new Error('ignoredError');
+ignoredError.name = 'ignoredError';
+
+promise_test(t => {
+ const ts = new TransformStream({
+ start(controller) {
+ controller.error(thrownError);
+ controller.error(ignoredError);
+ }
+ });
+ return promise_rejects(t, thrownError, ts.writable.abort(), 'abort() should reject with thrownError');
+}, 'controller.error() should do nothing the second time it is called');
+
+promise_test(t => {
+ let controller;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ const cancelPromise = ts.readable.cancel(thrownError);
+ controller.error(ignoredError);
+ return Promise.all([
+ cancelPromise,
+ promise_rejects(t, thrownError, ts.writable.getWriter().closed, 'closed should reject with thrownError')
+ ]);
+}, 'controller.error() should do nothing after readable.cancel()');
+
+promise_test(t => {
+ let controller;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ return ts.writable.abort(thrownError).then(() => {
+ controller.error(ignoredError);
+ return promise_rejects(t, thrownError, ts.writable.getWriter().closed, 'closed should reject with thrownError');
+ });
+}, 'controller.error() should do nothing after writable.abort() has completed');
+
+promise_test(t => {
+ let controller;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ },
+ transform() {
+ throw thrownError;
+ }
+ }, undefined, { highWaterMark: Infinity });
+ const writer = ts.writable.getWriter();
+ return promise_rejects(t, thrownError, writer.write(), 'write() should reject').then(() => {
+ controller.error();
+ return promise_rejects(t, thrownError, writer.closed, 'closed should reject with thrownError');
+ });
+}, 'controller.error() should do nothing after a transformer method has thrown an exception');
+
+promise_test(t => {
+ let controller;
+ let calls = 0;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ },
+ transform() {
+ ++calls;
+ }
+ }, undefined, { highWaterMark: 1 });
+ return delay(0).then(() => {
+ // Create backpressure.
+ controller.enqueue('a');
+ const writer = ts.writable.getWriter();
+ // transform() will not be called until backpressure is relieved.
+ const writePromise = writer.write('b');
+ assert_equals(calls, 0, 'transform() should not have been called');
+ controller.error(thrownError);
+ // Now backpressure has been relieved and the write can proceed.
+ return promise_rejects(t, thrownError, writePromise, 'write() should reject').then(() => {
+ assert_equals(calls, 0, 'transform() should not be called');
+ });
+ });
+}, 'erroring during write with backpressure should result in the write failing');
+
+promise_test(t => {
+ const ts = new TransformStream({}, undefined, { highWaterMark: 0 });
+ return delay(0).then(() => {
+ const writer = ts.writable.getWriter();
+ // write should start synchronously
+ const writePromise = writer.write(0);
+ // The underlying sink's abort() is not called until the write() completes.
+ const abortPromise = writer.abort(thrownError);
+ // Perform a read to relieve backpressure and permit the write() to complete.
+ const readPromise = ts.readable.getReader().read();
+ return Promise.all([
+ promise_rejects(t, thrownError, readPromise, 'read() should reject'),
+ promise_rejects(t, thrownError, writePromise, 'write() should reject'),
+ abortPromise
+ ]);
+ });
+}, 'a write() that was waiting for backpressure should reject if the writable is aborted');
+
+promise_test(t => {
+ const ts = new TransformStream();
+ ts.writable.abort(thrownError);
+ const reader = ts.readable.getReader();
+ return promise_rejects(t, thrownError, reader.read(), 'read() should reject with thrownError');
+}, 'the readable should be errored with the reason passed to the writable abort() method');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/transform-streams/flush.js b/test/fixtures/web-platform-tests/streams/transform-streams/flush.js
new file mode 100644
index 00000000000000..1e9909a7041779
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/transform-streams/flush.js
@@ -0,0 +1,136 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('/resources/testharness.js');
+}
+
+promise_test(() => {
+ let flushCalled = false;
+ const ts = new TransformStream({
+ transform() { },
+ flush() {
+ flushCalled = true;
+ }
+ });
+
+ return ts.writable.getWriter().close().then(() => {
+ return assert_true(flushCalled, 'closing the writable triggers the transform flush immediately');
+ });
+}, 'TransformStream flush is called immediately when the writable is closed, if no writes are queued');
+
+promise_test(() => {
+ let flushCalled = false;
+ let resolveTransform;
+ const ts = new TransformStream({
+ transform() {
+ return new Promise(resolve => {
+ resolveTransform = resolve;
+ });
+ },
+ flush() {
+ flushCalled = true;
+ return new Promise(() => {}); // never resolves
+ }
+ }, undefined, { highWaterMark: 1 });
+
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+ writer.close();
+ assert_false(flushCalled, 'closing the writable does not immediately call flush if writes are not finished');
+
+ let rsClosed = false;
+ ts.readable.getReader().closed.then(() => {
+ rsClosed = true;
+ });
+
+ return delay(0).then(() => {
+ assert_false(flushCalled, 'closing the writable does not asynchronously call flush if writes are not finished');
+ resolveTransform();
+ return delay(0);
+ }).then(() => {
+ assert_true(flushCalled, 'flush is eventually called');
+ assert_false(rsClosed, 'if flushPromise does not resolve, the readable does not become closed');
+ });
+}, 'TransformStream flush is called after all queued writes finish, once the writable is closed');
+
+promise_test(() => {
+ let c;
+ const ts = new TransformStream({
+ start(controller) {
+ c = controller;
+ },
+ transform() {
+ },
+ flush() {
+ c.enqueue('x');
+ c.enqueue('y');
+ }
+ });
+
+ const reader = ts.readable.getReader();
+
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+ writer.close();
+ return reader.read().then(result1 => {
+ assert_equals(result1.value, 'x', 'the first chunk read is the first one enqueued in flush');
+ assert_equals(result1.done, false, 'the first chunk read is the first one enqueued in flush');
+
+ return reader.read().then(result2 => {
+ assert_equals(result2.value, 'y', 'the second chunk read is the second one enqueued in flush');
+ assert_equals(result2.done, false, 'the second chunk read is the second one enqueued in flush');
+ });
+ });
+}, 'TransformStream flush gets a chance to enqueue more into the readable');
+
+promise_test(() => {
+ let c;
+ const ts = new TransformStream({
+ start(controller) {
+ c = controller;
+ },
+ transform() {
+ },
+ flush() {
+ c.enqueue('x');
+ c.enqueue('y');
+ return delay(0);
+ }
+ });
+
+ const reader = ts.readable.getReader();
+
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+ writer.close();
+
+ return Promise.all([
+ reader.read().then(result1 => {
+ assert_equals(result1.value, 'x', 'the first chunk read is the first one enqueued in flush');
+ assert_equals(result1.done, false, 'the first chunk read is the first one enqueued in flush');
+
+ return reader.read().then(result2 => {
+ assert_equals(result2.value, 'y', 'the second chunk read is the second one enqueued in flush');
+ assert_equals(result2.done, false, 'the second chunk read is the second one enqueued in flush');
+ });
+ }),
+ reader.closed.then(() => {
+ assert_true(true, 'readable reader becomes closed');
+ })
+ ]);
+}, 'TransformStream flush gets a chance to enqueue more into the readable, and can then async close');
+
+const error1 = new Error('error1');
+error1.name = 'error1';
+
+promise_test(t => {
+ const ts = new TransformStream({
+ flush(controller) {
+ controller.error(error1);
+ }
+ });
+ return promise_rejects(t, error1, ts.writable.getWriter().close(), 'close() should reject');
+}, 'error() during flush should cause writer.close() to reject');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/transform-streams/general.js b/test/fixtures/web-platform-tests/streams/transform-streams/general.js
new file mode 100644
index 00000000000000..02614c495c2c23
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/transform-streams/general.js
@@ -0,0 +1,444 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/rs-utils.js');
+}
+
+test(() => {
+ new TransformStream({ transform() { } });
+}, 'TransformStream can be constructed with a transform function');
+
+test(() => {
+ new TransformStream();
+ new TransformStream({});
+}, 'TransformStream can be constructed with no transform function');
+
+test(() => {
+ const ts = new TransformStream({ transform() { } });
+ const proto = Object.getPrototypeOf(ts);
+
+ const writableStream = Object.getOwnPropertyDescriptor(proto, 'writable');
+ assert_true(writableStream !== undefined, 'it has a writable property');
+ assert_false(writableStream.enumerable, 'writable should be non-enumerable');
+ assert_equals(typeof writableStream.get, 'function', 'writable should have a getter');
+ assert_equals(writableStream.set, undefined, 'writable should not have a setter');
+ assert_true(writableStream.configurable, 'writable should be configurable');
+ assert_true(ts.writable instanceof WritableStream, 'writable is an instance of WritableStream');
+ assert_not_equals(WritableStream.prototype.getWriter.call(ts.writable), undefined,
+ 'writable should pass WritableStream brand check');
+
+ const readableStream = Object.getOwnPropertyDescriptor(proto, 'readable');
+ assert_true(readableStream !== undefined, 'it has a readable property');
+ assert_false(readableStream.enumerable, 'readable should be non-enumerable');
+ assert_equals(typeof readableStream.get, 'function', 'readable should have a getter');
+ assert_equals(readableStream.set, undefined, 'readable should not have a setter');
+ assert_true(readableStream.configurable, 'readable should be configurable');
+ assert_true(ts.readable instanceof ReadableStream, 'readable is an instance of ReadableStream');
+ assert_not_equals(ReadableStream.prototype.getReader.call(ts.readable), undefined,
+ 'readable should pass ReadableStream brand check');
+}, 'TransformStream instances must have writable and readable properties of the correct types');
+
+test(() => {
+ const ts = new TransformStream({ transform() { } });
+
+ const writer = ts.writable.getWriter();
+ assert_equals(writer.desiredSize, 1, 'writer.desiredSize should be 1');
+}, 'TransformStream writable starts in the writable state');
+
+
+promise_test(() => {
+ const ts = new TransformStream();
+
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+ assert_equals(writer.desiredSize, 0, 'writer.desiredSize should be 0 after write()');
+
+ return ts.readable.getReader().read().then(result => {
+ assert_equals(result.value, 'a',
+ 'result from reading the readable is the same as was written to writable');
+ assert_false(result.done, 'stream should not be done');
+
+ return delay(0).then(() => assert_equals(writer.desiredSize, 1, 'desiredSize should be 1 again'));
+ });
+}, 'Identity TransformStream: can read from readable what is put into writable');
+
+promise_test(() => {
+ let c;
+ const ts = new TransformStream({
+ start(controller) {
+ c = controller;
+ },
+ transform(chunk) {
+ c.enqueue(chunk.toUpperCase());
+ }
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+
+ return ts.readable.getReader().read().then(result => {
+ assert_equals(result.value, 'A',
+ 'result from reading the readable is the transformation of what was written to writable');
+ assert_false(result.done, 'stream should not be done');
+ });
+}, 'Uppercaser sync TransformStream: can read from readable transformed version of what is put into writable');
+
+promise_test(() => {
+ let c;
+ const ts = new TransformStream({
+ start(controller) {
+ c = controller;
+ },
+ transform(chunk) {
+ c.enqueue(chunk.toUpperCase());
+ c.enqueue(chunk.toUpperCase());
+ }
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+
+ const reader = ts.readable.getReader();
+
+ return reader.read().then(result1 => {
+ assert_equals(result1.value, 'A',
+ 'the first chunk read is the transformation of the single chunk written');
+ assert_false(result1.done, 'stream should not be done');
+
+ return reader.read().then(result2 => {
+ assert_equals(result2.value, 'A',
+ 'the second chunk read is also the transformation of the single chunk written');
+ assert_false(result2.done, 'stream should not be done');
+ });
+ });
+}, 'Uppercaser-doubler sync TransformStream: can read both chunks put into the readable');
+
+promise_test(() => {
+ let c;
+ const ts = new TransformStream({
+ start(controller) {
+ c = controller;
+ },
+ transform(chunk) {
+ return delay(0).then(() => c.enqueue(chunk.toUpperCase()));
+ }
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+
+ return ts.readable.getReader().read().then(result => {
+ assert_equals(result.value, 'A',
+ 'result from reading the readable is the transformation of what was written to writable');
+ assert_false(result.done, 'stream should not be done');
+ });
+}, 'Uppercaser async TransformStream: can read from readable transformed version of what is put into writable');
+
+promise_test(() => {
+ let doSecondEnqueue;
+ let returnFromTransform;
+ const ts = new TransformStream({
+ transform(chunk, controller) {
+ delay(0).then(() => controller.enqueue(chunk.toUpperCase()));
+ doSecondEnqueue = () => controller.enqueue(chunk.toUpperCase());
+ return new Promise(resolve => {
+ returnFromTransform = resolve;
+ });
+ }
+ });
+
+ const reader = ts.readable.getReader();
+
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+
+ return reader.read().then(result1 => {
+ assert_equals(result1.value, 'A',
+ 'the first chunk read is the transformation of the single chunk written');
+ assert_false(result1.done, 'stream should not be done');
+ doSecondEnqueue();
+
+ return reader.read().then(result2 => {
+ assert_equals(result2.value, 'A',
+ 'the second chunk read is also the transformation of the single chunk written');
+ assert_false(result2.done, 'stream should not be done');
+ returnFromTransform();
+ });
+ });
+}, 'Uppercaser-doubler async TransformStream: can read both chunks put into the readable');
+
+promise_test(() => {
+ const ts = new TransformStream({ transform() { } });
+
+ const writer = ts.writable.getWriter();
+ writer.close();
+
+ return Promise.all([writer.closed, ts.readable.getReader().closed]);
+}, 'TransformStream: by default, closing the writable closes the readable (when there are no queued writes)');
+
+promise_test(() => {
+ let transformResolve;
+ const transformPromise = new Promise(resolve => {
+ transformResolve = resolve;
+ });
+ const ts = new TransformStream({
+ transform() {
+ return transformPromise;
+ }
+ }, undefined, { highWaterMark: 1 });
+
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+ writer.close();
+
+ let rsClosed = false;
+ ts.readable.getReader().closed.then(() => {
+ rsClosed = true;
+ });
+
+ return delay(0).then(() => {
+ assert_equals(rsClosed, false, 'readable is not closed after a tick');
+ transformResolve();
+
+ return writer.closed.then(() => {
+ // TODO: Is this expectation correct?
+ assert_equals(rsClosed, true, 'readable is closed at that point');
+ });
+ });
+}, 'TransformStream: by default, closing the writable waits for transforms to finish before closing both');
+
+promise_test(() => {
+ let c;
+ const ts = new TransformStream({
+ start(controller) {
+ c = controller;
+ },
+ transform() {
+ c.enqueue('x');
+ c.enqueue('y');
+ return delay(0);
+ }
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+ writer.close();
+
+ const readableChunks = readableStreamToArray(ts.readable);
+
+ return writer.closed.then(() => {
+ return readableChunks.then(chunks => {
+ assert_array_equals(chunks, ['x', 'y'], 'both enqueued chunks can be read from the readable');
+ });
+ });
+}, 'TransformStream: by default, closing the writable closes the readable after sync enqueues and async done');
+
+promise_test(() => {
+ let c;
+ const ts = new TransformStream({
+ start(controller) {
+ c = controller;
+ },
+ transform() {
+ return delay(0)
+ .then(() => c.enqueue('x'))
+ .then(() => c.enqueue('y'))
+ .then(() => delay(0));
+ }
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+ writer.close();
+
+ const readableChunks = readableStreamToArray(ts.readable);
+
+ return writer.closed.then(() => {
+ return readableChunks.then(chunks => {
+ assert_array_equals(chunks, ['x', 'y'], 'both enqueued chunks can be read from the readable');
+ });
+ });
+}, 'TransformStream: by default, closing the writable closes the readable after async enqueues and async done');
+
+promise_test(() => {
+ let c;
+ const ts = new TransformStream({
+ suffix: '-suffix',
+
+ start(controller) {
+ c = controller;
+ c.enqueue('start' + this.suffix);
+ },
+
+ transform(chunk) {
+ c.enqueue(chunk + this.suffix);
+ },
+
+ flush() {
+ c.enqueue('flushed' + this.suffix);
+ }
+ });
+
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+ writer.close();
+
+ const readableChunks = readableStreamToArray(ts.readable);
+
+ return writer.closed.then(() => {
+ return readableChunks.then(chunks => {
+ assert_array_equals(chunks, ['start-suffix', 'a-suffix', 'flushed-suffix'], 'all enqueued chunks have suffixes');
+ });
+ });
+}, 'Transform stream should call transformer methods as methods');
+
+promise_test(() => {
+ function functionWithOverloads() {}
+ functionWithOverloads.apply = () => assert_unreached('apply() should not be called');
+ functionWithOverloads.call = () => assert_unreached('call() should not be called');
+ const ts = new TransformStream({
+ start: functionWithOverloads,
+ transform: functionWithOverloads,
+ flush: functionWithOverloads
+ });
+ const writer = ts.writable.getWriter();
+ writer.write('a');
+ writer.close();
+
+ return readableStreamToArray(ts.readable);
+}, 'methods should not not have .apply() or .call() called');
+
+promise_test(t => {
+ let startCalled = false;
+ let startDone = false;
+ let transformDone = false;
+ let flushDone = false;
+ const ts = new TransformStream({
+ start() {
+ startCalled = true;
+ return flushAsyncEvents().then(() => {
+ startDone = true;
+ });
+ },
+ transform() {
+ return t.step(() => {
+ assert_true(startDone, 'transform() should not be called until the promise returned from start() has resolved');
+ return flushAsyncEvents().then(() => {
+ transformDone = true;
+ });
+ });
+ },
+ flush() {
+ return t.step(() => {
+ assert_true(transformDone,
+ 'flush() should not be called until the promise returned from transform() has resolved');
+ return flushAsyncEvents().then(() => {
+ flushDone = true;
+ });
+ });
+ }
+ }, undefined, { highWaterMark: 1 });
+
+ assert_true(startCalled, 'start() should be called synchronously');
+
+ const writer = ts.writable.getWriter();
+ const writePromise = writer.write('a');
+ return writer.close().then(() => {
+ assert_true(flushDone, 'promise returned from flush() should have resolved');
+ return writePromise;
+ });
+}, 'TransformStream start, transform, and flush should be strictly ordered');
+
+promise_test(() => {
+ let transformCalled = false;
+ const ts = new TransformStream({
+ transform() {
+ transformCalled = true;
+ }
+ }, undefined, { highWaterMark: Infinity });
+ // transform() is only called synchronously when there is no backpressure and all microtasks have run.
+ return delay(0).then(() => {
+ const writePromise = ts.writable.getWriter().write();
+ assert_true(transformCalled, 'transform() should have been called');
+ return writePromise;
+ });
+}, 'it should be possible to call transform() synchronously');
+
+promise_test(() => {
+ const ts = new TransformStream({}, undefined, { highWaterMark: 0 });
+
+ const writer = ts.writable.getWriter();
+ writer.close();
+
+ return Promise.all([writer.closed, ts.readable.getReader().closed]);
+}, 'closing the writable should close the readable when there are no queued chunks, even with backpressure');
+
+test(() => {
+ new TransformStream({
+ start(controller) {
+ controller.terminate();
+ assert_throws(new TypeError(), () => controller.enqueue(), 'enqueue should throw');
+ }
+ });
+}, 'enqueue() should throw after controller.terminate()');
+
+promise_test(() => {
+ let controller;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ const cancelPromise = ts.readable.cancel();
+ assert_throws(new TypeError(), () => controller.enqueue(), 'enqueue should throw');
+ return cancelPromise;
+}, 'enqueue() should throw after readable.cancel()');
+
+test(() => {
+ new TransformStream({
+ start(controller) {
+ controller.terminate();
+ controller.terminate();
+ }
+ });
+}, 'controller.terminate() should do nothing the second time it is called');
+
+promise_test(t => {
+ let controller;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ const cancelReason = { name: 'cancelReason' };
+ const cancelPromise = ts.readable.cancel(cancelReason);
+ controller.terminate();
+ return Promise.all([
+ cancelPromise,
+ promise_rejects(t, cancelReason, ts.writable.getWriter().closed, 'closed should reject with cancelReason')
+ ]);
+}, 'terminate() should do nothing after readable.cancel()');
+
+promise_test(() => {
+ let calls = 0;
+ new TransformStream({
+ start() {
+ ++calls;
+ }
+ });
+ return flushAsyncEvents().then(() => {
+ assert_equals(calls, 1, 'start() should have been called exactly once');
+ });
+}, 'start() should not be called twice');
+
+test(() => {
+ assert_throws(new RangeError(), () => new TransformStream({ readableType: 'bytes' }), 'constructor should throw');
+}, 'specifying a defined readableType should throw');
+
+test(() => {
+ assert_throws(new RangeError(), () => new TransformStream({ writableType: 'bytes' }), 'constructor should throw');
+}, 'specifying a defined writableType should throw');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/transform-streams/lipfuzz.js b/test/fixtures/web-platform-tests/streams/transform-streams/lipfuzz.js
new file mode 100644
index 00000000000000..5c33e1549c43a7
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/transform-streams/lipfuzz.js
@@ -0,0 +1,168 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+class LipFuzzTransformer {
+ constructor(substitutions) {
+ this.substitutions = substitutions;
+ this.partialChunk = '';
+ this.lastIndex = undefined;
+ }
+
+ transform(chunk, controller) {
+ chunk = this.partialChunk + chunk;
+ this.partialChunk = '';
+ // lastIndex is the index of the first character after the last substitution.
+ this.lastIndex = 0;
+ chunk = chunk.replace(/\{\{([a-zA-Z0-9_-]+)\}\}/g, this.replaceTag.bind(this));
+ // Regular expression for an incomplete template at the end of a string.
+ const partialAtEndRegexp = /\{(\{([a-zA-Z0-9_-]+(\})?)?)?$/g;
+ // Avoid looking at any characters that have already been substituted.
+ partialAtEndRegexp.lastIndex = this.lastIndex;
+ this.lastIndex = undefined;
+ const match = partialAtEndRegexp.exec(chunk);
+ if (match) {
+ this.partialChunk = chunk.substring(match.index);
+ chunk = chunk.substring(0, match.index);
+ }
+ controller.enqueue(chunk);
+ }
+
+ flush(controller) {
+ if (this.partialChunk.length > 0) {
+ controller.enqueue(this.partialChunk);
+ }
+ }
+
+ replaceTag(match, p1, offset) {
+ let replacement = this.substitutions[p1];
+ if (replacement === undefined) {
+ replacement = '';
+ }
+ this.lastIndex = offset + replacement.length;
+ return replacement;
+ }
+}
+
+const substitutions = {
+ in1: 'out1',
+ in2: 'out2',
+ quine: '{{quine}}',
+ bogusPartial: '{{incompleteResult}'
+};
+
+const cases = [
+ {
+ input: [''],
+ output: ['']
+ },
+ {
+ input: [],
+ output: []
+ },
+ {
+ input: ['{{in1}}'],
+ output: ['out1']
+ },
+ {
+ input: ['z{{in1}}'],
+ output: ['zout1']
+ },
+ {
+ input: ['{{in1}}q'],
+ output: ['out1q']
+ },
+ {
+ input: ['{{in1}}{{in1}'],
+ output: ['out1', '{{in1}']
+ },
+ {
+ input: ['{{in1}}{{in1}', '}'],
+ output: ['out1', 'out1']
+ },
+ {
+ input: ['{{in1', '}}'],
+ output: ['', 'out1']
+ },
+ {
+ input: ['{{', 'in1}}'],
+ output: ['', 'out1']
+ },
+ {
+ input: ['{', '{in1}}'],
+ output: ['', 'out1']
+ },
+ {
+ input: ['{{', 'in1}'],
+ output: ['', '', '{{in1}']
+ },
+ {
+ input: ['{'],
+ output: ['', '{']
+ },
+ {
+ input: ['{', ''],
+ output: ['', '', '{']
+ },
+ {
+ input: ['{', '{', 'i', 'n', '1', '}', '}'],
+ output: ['', '', '', '', '', '', 'out1']
+ },
+ {
+ input: ['{{in1}}{{in2}}{{in1}}'],
+ output: ['out1out2out1']
+ },
+ {
+ input: ['{{wrong}}'],
+ output: ['']
+ },
+ {
+ input: ['{{wron', 'g}}'],
+ output: ['', '']
+ },
+ {
+ input: ['{{quine}}'],
+ output: ['{{quine}}']
+ },
+ {
+ input: ['{{bogusPartial}}'],
+ output: ['{{incompleteResult}']
+ },
+ {
+ input: ['{{bogusPartial}}}'],
+ output: ['{{incompleteResult}}']
+ }
+];
+
+for (const testCase of cases) {
+ const inputChunks = testCase.input;
+ const outputChunks = testCase.output;
+ promise_test(() => {
+ const lft = new TransformStream(new LipFuzzTransformer(substitutions));
+ const writer = lft.writable.getWriter();
+ const promises = [];
+ for (const inputChunk of inputChunks) {
+ promises.push(writer.write(inputChunk));
+ }
+ promises.push(writer.close());
+ const reader = lft.readable.getReader();
+ let readerChain = Promise.resolve();
+ for (const outputChunk of outputChunks) {
+ readerChain = readerChain.then(() => {
+ return reader.read().then(({ value, done }) => {
+ assert_false(done, `done should be false when reading ${outputChunk}`);
+ assert_equals(value, outputChunk, `value should match outputChunk`);
+ });
+ });
+ }
+ readerChain = readerChain.then(() => {
+ return reader.read().then(({ done }) => assert_true(done, `done should be true`));
+ });
+ promises.push(readerChain);
+ return Promise.all(promises);
+ }, `testing "${inputChunks}" (length ${inputChunks.length})`);
+}
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/transform-streams/patched-global.js b/test/fixtures/web-platform-tests/streams/transform-streams/patched-global.js
new file mode 100644
index 00000000000000..d27b9cdd119eda
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/transform-streams/patched-global.js
@@ -0,0 +1,53 @@
+'use strict';
+
+// Tests which patch the global environment are kept separate to avoid interfering with other tests.
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+// eslint-disable-next-line no-extend-native, accessor-pairs
+Object.defineProperty(Object.prototype, 'highWaterMark', {
+ set() { throw new Error('highWaterMark setter called'); }
+});
+
+// eslint-disable-next-line no-extend-native, accessor-pairs
+Object.defineProperty(Object.prototype, 'size', {
+ set() { throw new Error('size setter called'); }
+});
+
+test(() => {
+ assert_not_equals(new TransformStream(), null, 'constructor should work');
+}, 'TransformStream constructor should not call setters for highWaterMark or size');
+
+test(t => {
+ /* eslint-disable no-native-reassign */
+
+ const oldReadableStream = ReadableStream;
+ const oldWritableStream = WritableStream;
+ const getReader = ReadableStream.prototype.getReader;
+ const getWriter = WritableStream.prototype.getWriter;
+
+ // Replace ReadableStream and WritableStream with broken versions.
+ ReadableStream = function () {
+ throw new Error('Called the global ReadableStream constructor');
+ };
+ WritableStream = function () {
+ throw new Error('Called the global WritableStream constructor');
+ };
+ t.add_cleanup(() => {
+ ReadableStream = oldReadableStream;
+ WritableStream = oldWritableStream;
+ });
+
+ const ts = new TransformStream();
+
+ // Just to be sure, ensure the readable and writable pass brand checks.
+ assert_not_equals(getReader.call(ts.readable), undefined,
+ 'getReader should work when called on ts.readable');
+ assert_not_equals(getWriter.call(ts.writable), undefined,
+ 'getWriter should work when called on ts.writable');
+ /* eslint-enable no-native-reassign */
+}, 'TransformStream should use the original value of ReadableStream and WritableStream');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/transform-streams/properties.js b/test/fixtures/web-platform-tests/streams/transform-streams/properties.js
new file mode 100644
index 00000000000000..f8d8face2d44f2
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/transform-streams/properties.js
@@ -0,0 +1,194 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+// The purpose of this file is to test for objects, attributes and arguments that should not exist.
+// The test cases are generated from data tables to reduce duplication.
+
+// Courtesy of André Bargull. Source is https://esdiscuss.org/topic/isconstructor#content-11.
+function IsConstructor(o) {
+ try {
+ new new Proxy(o, { construct: () => ({}) })();
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+test(() => {
+ assert_equals(self['TransformStreamDefaultController'], undefined,
+ `TransformStreamDefaultController should not be defined`);
+}, `TransformStreamDefaultController should not be exported on the global object`);
+
+// Now get hold of the symbol so we can test its properties.
+self.TransformStreamDefaultController = (() => {
+ let controller;
+ new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ return controller.constructor;
+})();
+
+const expected = {
+ TransformStream: {
+ constructor: {
+ type: 'constructor',
+ length: 0
+ },
+ readable: {
+ type: 'getter'
+ },
+ writable: {
+ type: 'getter'
+ }
+ },
+ TransformStreamDefaultController: {
+ constructor: {
+ type: 'constructor',
+ length: 0
+ },
+ desiredSize: {
+ type: 'getter'
+ },
+ enqueue: {
+ type: 'method',
+ length: 1
+ },
+ error: {
+ type: 'method',
+ length: 1
+ },
+ terminate: {
+ type: 'method',
+ length: 0
+ }
+ }
+};
+
+for (const c in expected) {
+ const properties = expected[c];
+ const prototype = self[c].prototype;
+ for (const name in properties) {
+ const fullName = `${c}.prototype.${name}`;
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
+ test(() => {
+ const { configurable, enumerable } = descriptor;
+ assert_true(configurable, `${name} should be configurable`);
+ assert_false(enumerable, `${name} should not be enumerable`);
+ }, `${fullName} should have standard properties`);
+ const type = properties[name].type;
+ switch (type) {
+ case 'getter':
+ test(() => {
+ const { writable, get, set } = descriptor;
+ assert_equals(writable, undefined, `${name} should not be a data descriptor`);
+ assert_equals(typeof get, 'function', `${name} should have a getter`);
+ assert_equals(set, undefined, `${name} should not have a setter`);
+ }, `${fullName} should be a getter`);
+ break;
+
+ case 'constructor':
+ case 'method':
+ test(() => {
+ assert_true(descriptor.writable, `${name} should be writable`);
+ assert_equals(typeof prototype[name], 'function', `${name} should be a function`);
+ assert_equals(prototype[name].length, properties[name].length,
+ `${name} should take ${properties[name].length} arguments`);
+ if (type === 'constructor') {
+ assert_true(IsConstructor(prototype[name]), `${name} should be a constructor`);
+ assert_equals(prototype[name].name, c, `${name}.name should be '${c}'`);
+ } else {
+ assert_false(IsConstructor(prototype[name]), `${name} should not be a constructor`);
+ assert_equals(prototype[name].name, name, `${name}.name should be '${name}`);
+ }
+ }, `${fullName} should be a ${type}`);
+ break;
+ }
+ }
+ test(() => {
+ const expectedPropertyNames = Object.keys(properties).sort();
+ const actualPropertyNames = Object.getOwnPropertyNames(prototype).sort();
+ assert_array_equals(actualPropertyNames, expectedPropertyNames,
+ `${c} properties should match expected properties`);
+ }, `${c}.prototype should have exactly the expected properties`);
+}
+
+const transformerMethods = {
+ start: {
+ length: 1,
+ trigger: () => Promise.resolve()
+ },
+ transform: {
+ length: 2,
+ trigger: ts => ts.writable.getWriter().write()
+ },
+ flush: {
+ length: 1,
+ trigger: ts => ts.writable.getWriter().close()
+ }
+};
+
+for (const method in transformerMethods) {
+ const { length, trigger } = transformerMethods[method];
+
+ // Some semantic tests of how transformer methods are called can be found in general.js, as well as in the test files
+ // specific to each method.
+ promise_test(() => {
+ let argCount;
+ const ts = new TransformStream({
+ [method](...args) {
+ argCount = args.length;
+ }
+ }, undefined, { highWaterMark: Infinity });
+ return Promise.resolve(trigger(ts)).then(() => {
+ assert_equals(argCount, length, `${method} should be called with ${length} arguments`);
+ });
+ }, `transformer method ${method} should be called with the right number of arguments`);
+
+ promise_test(() => {
+ let methodWasCalled = false;
+ function Transformer() {}
+ Transformer.prototype = {
+ [method]() {
+ methodWasCalled = true;
+ }
+ };
+ const ts = new TransformStream(new Transformer(), undefined, { highWaterMark: Infinity });
+ return Promise.resolve(trigger(ts)).then(() => {
+ assert_true(methodWasCalled, `${method} should be called`);
+ });
+ }, `transformer method ${method} should be called even when it's located on the prototype chain`);
+
+ promise_test(t => {
+ const unreachedTraps = ['getPrototypeOf', 'setPrototypeOf', 'isExtensible', 'preventExtensions',
+ 'getOwnPropertyDescriptor', 'defineProperty', 'has', 'set', 'deleteProperty', 'ownKeys',
+ 'apply', 'construct'];
+ const touchedProperties = [];
+ const handler = {
+ get: t.step_func((target, property) => {
+ touchedProperties.push(property);
+ if (property === 'readableType' || property === 'writableType') {
+ return undefined;
+ }
+ 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, ['writableType', 'readableType', 'transform', 'flush', 'start'],
+ 'expected properties should be got');
+ return trigger(ts).then(() => {
+ 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();
diff --git a/test/fixtures/web-platform-tests/streams/transform-streams/reentrant-strategies.js b/test/fixtures/web-platform-tests/streams/transform-streams/reentrant-strategies.js
new file mode 100644
index 00000000000000..50285bbbcd97a1
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/transform-streams/reentrant-strategies.js
@@ -0,0 +1,324 @@
+'use strict';
+
+// The size() function of readableStrategy can re-entrantly call back into the TransformStream implementation. This
+// makes it risky to cache state across the call to ReadableStreamDefaultControllerEnqueue. These tests attempt to catch
+// such errors. They are separated from the other strategy tests because no real user code should ever do anything like
+// this.
+//
+// There is no such issue with writableStrategy size() because it is never called from within TransformStream
+// algorithms.
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/recording-streams.js');
+ self.importScripts('../resources/rs-utils.js');
+ self.importScripts('../resources/test-utils.js');
+}
+
+const error1 = new Error('error1');
+error1.name = 'error1';
+
+promise_test(() => {
+ let controller;
+ let calls = 0;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ }, undefined, {
+ size() {
+ ++calls;
+ if (calls < 2) {
+ controller.enqueue('b');
+ }
+ return 1;
+ },
+ highWaterMark: Infinity
+ });
+ const writer = ts.writable.getWriter();
+ return Promise.all([writer.write('a'), writer.close()])
+ .then(() => readableStreamToArray(ts.readable))
+ .then(array => assert_array_equals(array, ['b', 'a'], 'array should contain two chunks'));
+}, 'enqueue() inside size() should work');
+
+promise_test(() => {
+ let controller;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ }, undefined, {
+ size() {
+ // The readable queue is empty.
+ controller.terminate();
+ // The readable state has gone from "readable" to "closed".
+ return 1;
+ // This chunk will be enqueued, but will be impossible to read because the state is already "closed".
+ },
+ highWaterMark: Infinity
+ });
+ const writer = ts.writable.getWriter();
+ return writer.write('a')
+ .then(() => readableStreamToArray(ts.readable))
+ .then(array => assert_array_equals(array, [], 'array should contain no chunks'));
+ // The chunk 'a' is still in readable's queue. readable is closed so 'a' cannot be read. writable's queue is empty and
+ // it is still writable.
+}, 'terminate() inside size() should work');
+
+promise_test(t => {
+ let controller;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ }, undefined, {
+ size() {
+ controller.error(error1);
+ return 1;
+ },
+ highWaterMark: Infinity
+ });
+ const writer = ts.writable.getWriter();
+ return writer.write('a')
+ .then(() => promise_rejects(t, error1, ts.readable.getReader().read(), 'read() should reject'));
+}, 'error() inside size() should work');
+
+promise_test(() => {
+ let controller;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ }, undefined, {
+ size() {
+ assert_equals(controller.desiredSize, 1, 'desiredSize should be 1');
+ return 1;
+ },
+ highWaterMark: 1
+ });
+ const writer = ts.writable.getWriter();
+ return Promise.all([writer.write('a'), writer.close()])
+ .then(() => readableStreamToArray(ts.readable))
+ .then(array => assert_array_equals(array, ['a'], 'array should contain one chunk'));
+}, 'desiredSize inside size() should work');
+
+promise_test(t => {
+ let cancelPromise;
+ const ts = new TransformStream({}, undefined, {
+ size() {
+ cancelPromise = ts.readable.cancel(error1);
+ return 1;
+ },
+ highWaterMark: Infinity
+ });
+ const writer = ts.writable.getWriter();
+ return writer.write('a')
+ .then(() => {
+ promise_rejects(t, error1, writer.closed, 'writer.closed should reject');
+ return cancelPromise;
+ });
+}, 'readable cancel() inside size() should work');
+
+promise_test(() => {
+ let controller;
+ let pipeToPromise;
+ const ws = recordingWritableStream();
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ }, undefined, {
+ size() {
+ if (!pipeToPromise) {
+ pipeToPromise = ts.readable.pipeTo(ws);
+ }
+ return 1;
+ },
+ highWaterMark: 1
+ });
+ // Allow promise returned by start() to resolve so that enqueue() will happen synchronously.
+ return delay(0).then(() => {
+ controller.enqueue('a');
+ assert_not_equals(pipeToPromise, undefined);
+
+ // Some pipeTo() implementations need an additional chunk enqueued in order for the first one to be processed. See
+ // https://github.com/whatwg/streams/issues/794 for background.
+ controller.enqueue('a');
+
+ // Give pipeTo() a chance to process the queued chunks.
+ return delay(0);
+ }).then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'a'], 'ws should contain two chunks');
+ controller.terminate();
+ return pipeToPromise;
+ }).then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'write', 'a', 'close'], 'target should have been closed');
+ });
+}, 'pipeTo() inside size() should work');
+
+promise_test(() => {
+ let controller;
+ let readPromise;
+ let calls = 0;
+ let reader;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ }, undefined, {
+ size() {
+ // This is triggered by controller.enqueue(). The queue is empty and there are no pending reads. pull() is called
+ // synchronously, allowing transform() to proceed asynchronously. This results in a second call to enqueue(),
+ // which resolves this pending read() without calling size() again.
+ readPromise = reader.read();
+ ++calls;
+ return 1;
+ },
+ highWaterMark: 0
+ });
+ reader = ts.readable.getReader();
+ const writer = ts.writable.getWriter();
+ let writeResolved = false;
+ const writePromise = writer.write('b').then(() => {
+ writeResolved = true;
+ });
+ return flushAsyncEvents().then(() => {
+ assert_false(writeResolved);
+ controller.enqueue('a');
+ assert_equals(calls, 1, 'size() should have been called once');
+ return delay(0);
+ }).then(() => {
+ assert_true(writeResolved);
+ assert_equals(calls, 1, 'size() should only be called once');
+ return readPromise;
+ }).then(({ value, done }) => {
+ assert_false(done, 'done should be false');
+ // See https://github.com/whatwg/streams/issues/794 for why this chunk is not 'a'.
+ assert_equals(value, 'b', 'chunk should have been read');
+ assert_equals(calls, 1, 'calls should still be 1');
+ return writePromise;
+ });
+}, 'read() inside of size() should work');
+
+promise_test(() => {
+ let writer;
+ let writePromise1;
+ let calls = 0;
+ const ts = new TransformStream({}, undefined, {
+ size() {
+ ++calls;
+ if (calls < 2) {
+ writePromise1 = writer.write('a');
+ }
+ return 1;
+ },
+ highWaterMark: Infinity
+ });
+ writer = ts.writable.getWriter();
+ // Give pull() a chance to be called.
+ return delay(0).then(() => {
+ // This write results in a synchronous call to transform(), enqueue(), and size().
+ const writePromise2 = writer.write('b');
+ assert_equals(calls, 1, 'size() should have been called once');
+ return Promise.all([writePromise1, writePromise2, writer.close()]);
+ }).then(() => {
+ assert_equals(calls, 2, 'size() should have been called twice');
+ return readableStreamToArray(ts.readable);
+ }).then(array => {
+ assert_array_equals(array, ['b', 'a'], 'both chunks should have been enqueued');
+ assert_equals(calls, 2, 'calls should still be 2');
+ });
+}, 'writer.write() inside size() should work');
+
+promise_test(() => {
+ let controller;
+ let writer;
+ let writePromise;
+ let calls = 0;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ }, undefined, {
+ size() {
+ ++calls;
+ if (calls < 2) {
+ writePromise = writer.write('a');
+ }
+ return 1;
+ },
+ highWaterMark: Infinity
+ });
+ writer = ts.writable.getWriter();
+ // Give pull() a chance to be called.
+ return delay(0).then(() => {
+ // This enqueue results in synchronous calls to size(), write(), transform() and enqueue().
+ controller.enqueue('b');
+ assert_equals(calls, 2, 'size() should have been called twice');
+ return Promise.all([writePromise, writer.close()]);
+ }).then(() => {
+ return readableStreamToArray(ts.readable);
+ }).then(array => {
+ // Because one call to enqueue() is nested inside the other, they finish in the opposite order that they were
+ // called, so the chunks end up reverse order.
+ assert_array_equals(array, ['a', 'b'], 'both chunks should have been enqueued');
+ assert_equals(calls, 2, 'calls should still be 2');
+ });
+}, 'synchronous writer.write() inside size() should work');
+
+promise_test(() => {
+ let writer;
+ let closePromise;
+ let controller;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ }, undefined, {
+ size() {
+ closePromise = writer.close();
+ return 1;
+ },
+ highWaterMark: 1
+ });
+ writer = ts.writable.getWriter();
+ const reader = ts.readable.getReader();
+ // Wait for the promise returned by start() to be resolved so that the call to close() will result in a synchronous
+ // call to TransformStreamDefaultSink.
+ return delay(0).then(() => {
+ controller.enqueue('a');
+ return reader.read();
+ }).then(({ value, done }) => {
+ assert_false(done, 'done should be false');
+ assert_equals(value, 'a', 'value should be correct');
+ return reader.read();
+ }).then(({ done }) => {
+ assert_true(done, 'done should be true');
+ return closePromise;
+ });
+}, 'writer.close() inside size() should work');
+
+promise_test(t => {
+ let abortPromise;
+ let controller;
+ const ts = new TransformStream({
+ start(c) {
+ controller = c;
+ }
+ }, undefined, {
+ size() {
+ abortPromise = ts.writable.abort(error1);
+ return 1;
+ },
+ highWaterMark: 1
+ });
+ const reader = ts.readable.getReader();
+ // Wait for the promise returned by start() to be resolved so that the call to abort() will result in a synchronous
+ // call to TransformStreamDefaultSink.
+ return delay(0).then(() => {
+ controller.enqueue('a');
+ return Promise.all([promise_rejects(t, error1, reader.read(), 'read() should reject'), abortPromise]);
+ });
+}, 'writer.abort() inside size() should work');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/transform-streams/strategies.js b/test/fixtures/web-platform-tests/streams/transform-streams/strategies.js
new file mode 100644
index 00000000000000..1775b7fa170dc3
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/transform-streams/strategies.js
@@ -0,0 +1,155 @@
+'use strict';
+
+// Here we just test that the strategies are correctly passed to the readable and writable sides. We assume that
+// ReadableStream and WritableStream will correctly apply the strategies when they are being used by a TransformStream
+// and so it isn't necessary to repeat their tests here.
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/recording-streams.js');
+ self.importScripts('../resources/test-utils.js');
+}
+
+test(() => {
+ const ts = new TransformStream({}, { highWaterMark: 17 });
+ assert_equals(ts.writable.getWriter().desiredSize, 17, 'desiredSize should be 17');
+}, 'writableStrategy highWaterMark should work');
+
+promise_test(() => {
+ const ts = recordingTransformStream({}, undefined, { highWaterMark: 9 });
+ const writer = ts.writable.getWriter();
+ for (let i = 0; i < 10; ++i) {
+ writer.write(i);
+ }
+ return delay(0).then(() => {
+ assert_array_equals(ts.events, [
+ 'transform', 0, 'transform', 1, 'transform', 2, 'transform', 3, 'transform', 4,
+ 'transform', 5, 'transform', 6, 'transform', 7, 'transform', 8],
+ 'transform() should have been called 9 times');
+ });
+}, 'readableStrategy highWaterMark should work');
+
+promise_test(t => {
+ let writableSizeCalled = false;
+ let readableSizeCalled = false;
+ let transformCalled = false;
+ const ts = new TransformStream(
+ {
+ transform(chunk, controller) {
+ t.step(() => {
+ transformCalled = true;
+ assert_true(writableSizeCalled, 'writableStrategy.size() should have been called');
+ assert_false(readableSizeCalled, 'readableStrategy.size() should not have been called');
+ controller.enqueue(chunk);
+ assert_true(readableSizeCalled, 'readableStrategy.size() should have been called');
+ });
+ }
+ },
+ {
+ size() {
+ writableSizeCalled = true;
+ return 1;
+ }
+ },
+ {
+ size() {
+ readableSizeCalled = true;
+ return 1;
+ },
+ highWaterMark: Infinity
+ });
+ return ts.writable.getWriter().write().then(() => {
+ assert_true(transformCalled, 'transform() should be called');
+ });
+}, 'writable should have the correct size() function');
+
+test(() => {
+ const ts = new TransformStream();
+ const writer = ts.writable.getWriter();
+ assert_equals(writer.desiredSize, 1, 'default writable HWM is 1');
+ writer.write(undefined);
+ assert_equals(writer.desiredSize, 0, 'default chunk size is 1');
+}, 'default writable strategy should be equivalent to { highWaterMark: 1 }');
+
+promise_test(t => {
+ const ts = new TransformStream({
+ transform(chunk, controller) {
+ return t.step(() => {
+ assert_equals(controller.desiredSize, 0, 'desiredSize should be 0');
+ controller.enqueue(undefined);
+ // The first chunk enqueued is consumed by the pending read().
+ assert_equals(controller.desiredSize, 0, 'desiredSize should still be 0');
+ controller.enqueue(undefined);
+ assert_equals(controller.desiredSize, -1, 'desiredSize should be -1');
+ });
+ }
+ });
+ const writePromise = ts.writable.getWriter().write();
+ return ts.readable.getReader().read().then(() => writePromise);
+}, 'default readable strategy should be equivalent to { highWaterMark: 0 }');
+
+test(() => {
+ assert_throws(new RangeError(), () => new TransformStream(undefined, { highWaterMark: -1 }),
+ 'should throw RangeError for negative writableHighWaterMark');
+ assert_throws(new RangeError(), () => new TransformStream(undefined, undefined, { highWaterMark: -1 }),
+ 'should throw RangeError for negative readableHighWaterMark');
+ assert_throws(new RangeError(), () => new TransformStream(undefined, { highWaterMark: NaN }),
+ 'should throw RangeError for NaN writableHighWaterMark');
+ assert_throws(new RangeError(), () => new TransformStream(undefined, undefined, { highWaterMark: NaN }),
+ 'should throw RangeError for NaN readableHighWaterMark');
+}, 'a RangeError should be thrown for an invalid highWaterMark');
+
+const objectThatConvertsTo42 = {
+ toString() {
+ return '42';
+ }
+};
+
+test(() => {
+ const ts = new TransformStream(undefined, { highWaterMark: objectThatConvertsTo42 });
+ const writer = ts.writable.getWriter();
+ assert_equals(writer.desiredSize, 42, 'writable HWM is 42');
+}, 'writableStrategy highWaterMark should be converted to a number');
+
+test(() => {
+ const ts = new TransformStream({
+ start(controller) {
+ assert_equals(controller.desiredSize, 42, 'desiredSize should be 42');
+ }
+ }, undefined, { highWaterMark: objectThatConvertsTo42 });
+}, 'readableStrategy highWaterMark should be converted to a number');
+
+promise_test(t => {
+ const ts = new TransformStream(undefined, undefined, {
+ size() { return NaN; },
+ highWaterMark: 1
+ });
+ const writer = ts.writable.getWriter();
+ return promise_rejects(t, new RangeError(), writer.write(), 'write should reject');
+}, 'a bad readableStrategy size function should cause writer.write() to reject on an identity transform');
+
+promise_test(t => {
+ const ts = new TransformStream({
+ transform(chunk, controller) {
+ // This assert has the important side-effect of catching the error, so transform() does not throw.
+ assert_throws(new RangeError(), () => controller.enqueue(chunk), 'enqueue should throw');
+ }
+ }, undefined, {
+ size() {
+ return -1;
+ },
+ highWaterMark: 1
+ });
+
+ const writer = ts.writable.getWriter();
+ return writer.write().then(() => {
+ return Promise.all([
+ promise_rejects(t, new RangeError(), writer.ready, 'ready should reject'),
+ promise_rejects(t, new RangeError(), writer.closed, 'closed should reject'),
+ promise_rejects(t, new RangeError(), ts.readable.getReader().closed, 'readable closed should reject')
+ ]);
+ });
+}, 'a bad readableStrategy size function should error the stream on enqueue even when transformer.transform() ' +
+ 'catches the exception');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/transform-streams/terminate.js b/test/fixtures/web-platform-tests/streams/transform-streams/terminate.js
new file mode 100644
index 00000000000000..36c6fbd379b1e2
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/transform-streams/terminate.js
@@ -0,0 +1,105 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/recording-streams.js');
+ self.importScripts('../resources/test-utils.js');
+}
+
+promise_test(t => {
+ const ts = recordingTransformStream({}, undefined, { highWaterMark: 0 });
+ const rs = new ReadableStream({
+ start(controller) {
+ controller.enqueue(0);
+ }
+ });
+ let pipeToRejected = false;
+ const pipeToPromise = promise_rejects(t, new TypeError(), rs.pipeTo(ts.writable), 'pipeTo should reject').then(() => {
+ pipeToRejected = true;
+ });
+ return delay(0).then(() => {
+ assert_array_equals(ts.events, [], 'transform() should have seen no chunks');
+ assert_false(pipeToRejected, 'pipeTo() should not have rejected yet');
+ ts.controller.terminate();
+ return pipeToPromise;
+ }).then(() => {
+ assert_array_equals(ts.events, [], 'transform() should still have seen no chunks');
+ assert_true(pipeToRejected, 'pipeToRejected must be true');
+ });
+}, 'controller.terminate() should error pipeTo()');
+
+promise_test(t => {
+ const ts = recordingTransformStream({}, undefined, { highWaterMark: 1 });
+ const rs = new ReadableStream({
+ start(controller) {
+ controller.enqueue(0);
+ controller.enqueue(1);
+ }
+ });
+ const pipeToPromise = rs.pipeTo(ts.writable);
+ return delay(0).then(() => {
+ assert_array_equals(ts.events, ['transform', 0], 'transform() should have seen one chunk');
+ ts.controller.terminate();
+ return promise_rejects(t, new TypeError(), pipeToPromise, 'pipeTo() should reject');
+ }).then(() => {
+ assert_array_equals(ts.events, ['transform', 0], 'transform() should still have seen only one chunk');
+ });
+}, 'controller.terminate() should prevent remaining chunks from being processed');
+
+test(() => {
+ new TransformStream({
+ start(controller) {
+ controller.enqueue(0);
+ controller.terminate();
+ assert_throws(new TypeError(), () => controller.enqueue(1), 'enqueue should throw');
+ }
+ });
+}, 'controller.enqueue() should throw after controller.terminate()');
+
+const error1 = new Error('error1');
+error1.name = 'error1';
+
+promise_test(t => {
+ const ts = new TransformStream({
+ start(controller) {
+ controller.enqueue(0);
+ controller.terminate();
+ controller.error(error1);
+ }
+ });
+ return Promise.all([
+ promise_rejects(t, new TypeError(), ts.writable.abort(), 'abort() should reject with a TypeError'),
+ promise_rejects(t, error1, ts.readable.cancel(), 'cancel() should reject with error1'),
+ promise_rejects(t, error1, ts.readable.getReader().closed, 'closed should reject with error1')
+ ]);
+}, 'controller.error() after controller.terminate() with queued chunk should error the readable');
+
+promise_test(t => {
+ const ts = new TransformStream({
+ start(controller) {
+ controller.terminate();
+ controller.error(error1);
+ }
+ });
+ return Promise.all([
+ promise_rejects(t, new TypeError(), ts.writable.abort(), 'abort() should reject with a TypeError'),
+ ts.readable.cancel(),
+ ts.readable.getReader().closed
+ ]);
+}, 'controller.error() after controller.terminate() without queued chunk should do nothing');
+
+promise_test(() => {
+ const ts = new TransformStream({
+ flush(controller) {
+ controller.terminate();
+ }
+ });
+ const writer = ts.writable.getWriter();
+ return Promise.all([
+ writer.close(),
+ writer.closed,
+ ts.readable.getReader().closed
+ ]);
+}, 'controller.terminate() inside flush() should not prevent writer.close() from succeeding');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/aborting.js b/test/fixtures/web-platform-tests/streams/writable-streams/aborting.js
new file mode 100644
index 00000000000000..9fb175f2b53bd0
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/aborting.js
@@ -0,0 +1,1375 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+const error1 = new Error('error1');
+error1.name = 'error1';
+
+const error2 = new Error('error2');
+error2.name = 'error2';
+
+promise_test(t => {
+ const ws = new WritableStream({
+ write: t.unreached_func('write() should not be called')
+ });
+
+ const writer = ws.getWriter();
+ const writePromise = writer.write('a');
+
+ const readyPromise = writer.ready;
+
+ writer.abort(error1);
+
+ assert_equals(writer.ready, readyPromise, 'the ready promise property should not change');
+
+ return Promise.all([
+ promise_rejects(t, error1, readyPromise, 'the ready promise should reject with error1'),
+ promise_rejects(t, error1, writePromise, 'the write() promise should reject with error1')
+ ]);
+}, 'Aborting a WritableStream before it starts should cause the writer\'s unsettled ready promise to reject');
+
+promise_test(t => {
+ const ws = new WritableStream();
+
+ const writer = ws.getWriter();
+ writer.write('a');
+
+ const readyPromise = writer.ready;
+
+ return readyPromise.then(() => {
+ writer.abort(error1);
+
+ assert_not_equals(writer.ready, readyPromise, 'the ready promise property should change');
+ return promise_rejects(t, error1, writer.ready, 'the ready promise should reject with error1');
+ });
+}, 'Aborting a WritableStream should cause the writer\'s fulfilled ready promise to reset to a rejected one');
+
+promise_test(t => {
+ const ws = new WritableStream();
+ const writer = ws.getWriter();
+
+ writer.releaseLock();
+
+ return promise_rejects(t, new TypeError(), writer.abort(), 'abort() should reject with a TypeError');
+}, 'abort() on a released writer rejects');
+
+promise_test(t => {
+ const ws = recordingWritableStream();
+
+ return delay(0)
+ .then(() => {
+ const writer = ws.getWriter();
+
+ const abortPromise = writer.abort(error1);
+
+ return Promise.all([
+ promise_rejects(t, error1, writer.write(1), 'write(1) must reject with error1'),
+ promise_rejects(t, error1, writer.write(2), 'write(2) must reject with error1'),
+ abortPromise
+ ]);
+ })
+ .then(() => {
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+}, 'Aborting a WritableStream immediately prevents future writes');
+
+promise_test(t => {
+ const ws = recordingWritableStream();
+ const results = [];
+
+ return delay(0)
+ .then(() => {
+ const writer = ws.getWriter();
+
+ results.push(
+ writer.write(1),
+ promise_rejects(t, error1, writer.write(2), 'write(2) must reject with error1'),
+ promise_rejects(t, error1, writer.write(3), 'write(3) must reject with error1')
+ );
+
+ const abortPromise = writer.abort(error1);
+
+ results.push(
+ promise_rejects(t, error1, writer.write(4), 'write(4) must reject with error1'),
+ promise_rejects(t, error1, writer.write(5), 'write(5) must reject with error1')
+ );
+
+ return abortPromise;
+ }).then(() => {
+ assert_array_equals(ws.events, ['write', 1, 'abort', error1]);
+
+ return Promise.all(results);
+ });
+}, 'Aborting a WritableStream prevents further writes after any that are in progress');
+
+promise_test(() => {
+ const ws = new WritableStream({
+ abort() {
+ return 'Hello';
+ }
+ });
+ const writer = ws.getWriter();
+
+ return writer.abort('a').then(value => {
+ assert_equals(value, undefined, 'fulfillment value must be undefined');
+ });
+}, 'Fulfillment value of ws.abort() call must be undefined even if the underlying sink returns a non-undefined value');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ abort() {
+ throw error1;
+ }
+ });
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.abort(undefined),
+ 'rejection reason of abortPromise must be the error thrown by abort');
+}, 'WritableStream if sink\'s abort throws, the promise returned by writer.abort() rejects');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ abort() {
+ throw error1;
+ }
+ });
+ const writer = ws.getWriter();
+
+ const abortPromise1 = writer.abort(undefined);
+ const abortPromise2 = writer.abort(undefined);
+
+ assert_equals(abortPromise1, abortPromise2, 'the promises must be the same');
+
+ return promise_rejects(t, error1, abortPromise1, 'promise must have matching rejection');
+}, 'WritableStream if sink\'s abort throws, the promise returned by multiple writer.abort()s is the same and rejects');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ abort() {
+ throw error1;
+ }
+ });
+
+ return promise_rejects(t, error1, ws.abort(undefined),
+ 'rejection reason of abortPromise must be the error thrown by abort');
+}, 'WritableStream if sink\'s abort throws, the promise returned by ws.abort() rejects');
+
+promise_test(t => {
+ let resolveWritePromise;
+ const ws = new WritableStream({
+ write() {
+ return new Promise(resolve => {
+ resolveWritePromise = resolve;
+ });
+ },
+ abort() {
+ throw error1;
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ writer.write().catch(() => {});
+ return flushAsyncEvents().then(() => {
+ const abortPromise = writer.abort(undefined);
+
+ resolveWritePromise();
+ return promise_rejects(t, error1, abortPromise,
+ 'rejection reason of abortPromise must be the error thrown by abort');
+ });
+}, 'WritableStream if sink\'s abort throws, for an abort performed during a write, the promise returned by ' +
+ 'ws.abort() rejects');
+
+promise_test(() => {
+ const ws = recordingWritableStream();
+ const writer = ws.getWriter();
+
+ return writer.abort(error1).then(() => {
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+}, 'Aborting a WritableStream passes through the given reason');
+
+promise_test(t => {
+ const ws = new WritableStream();
+ const writer = ws.getWriter();
+
+ const abortPromise = writer.abort(error1);
+
+ const events = [];
+ writer.ready.catch(() => {
+ events.push('ready');
+ });
+ writer.closed.catch(() => {
+ events.push('closed');
+ });
+
+ return Promise.all([
+ abortPromise,
+ promise_rejects(t, error1, writer.write(), 'writing should reject with error1'),
+ promise_rejects(t, error1, writer.close(), 'closing should reject with error1'),
+ promise_rejects(t, error1, writer.ready, 'ready should reject with error1'),
+ promise_rejects(t, error1, writer.closed, 'closed should reject with error1')
+ ]).then(() => {
+ assert_array_equals(['ready', 'closed'], events, 'ready should reject before closed');
+ });
+}, 'Aborting a WritableStream puts it in an errored state with the error passed to abort()');
+
+promise_test(t => {
+ const ws = new WritableStream();
+ const writer = ws.getWriter();
+
+ const writePromise = promise_rejects(t, error1, writer.write('a'),
+ 'writing should reject with error1');
+
+ writer.abort(error1);
+
+ return writePromise;
+}, 'Aborting a WritableStream causes any outstanding write() promises to be rejected with the reason supplied');
+
+promise_test(t => {
+ const ws = recordingWritableStream();
+ const writer = ws.getWriter();
+
+ const closePromise = writer.close();
+ const abortPromise = writer.abort(error1);
+
+ return Promise.all([
+ promise_rejects(t, error1, writer.closed, 'closed should reject with error1'),
+ promise_rejects(t, error1, closePromise, 'close() should reject with error1'),
+ abortPromise
+ ]).then(() => {
+ assert_array_equals(ws.events, ['abort', error1]);
+ });
+}, 'Closing but then immediately aborting a WritableStream causes the stream to error');
+
+promise_test(() => {
+ let resolveClose;
+ const ws = new WritableStream({
+ close() {
+ return new Promise(resolve => {
+ resolveClose = resolve;
+ });
+ }
+ });
+ const writer = ws.getWriter();
+
+ const closePromise = writer.close();
+
+ return delay(0).then(() => {
+ const abortPromise = writer.abort(error1);
+ resolveClose();
+ return Promise.all([
+ writer.closed,
+ abortPromise,
+ closePromise
+ ]);
+ });
+}, 'Closing a WritableStream and aborting it while it closes causes the stream to ignore the abort attempt');
+
+promise_test(() => {
+ const ws = new WritableStream();
+ const writer = ws.getWriter();
+
+ writer.close();
+
+ return delay(0).then(() => writer.abort());
+}, 'Aborting a WritableStream after it is closed is a no-op');
+
+promise_test(t => {
+ // Testing that per https://github.com/whatwg/streams/issues/620#issuecomment-263483953 the fallback to close was
+ // removed.
+
+ // Cannot use recordingWritableStream since it always has an abort
+ let closeCalled = false;
+ const ws = new WritableStream({
+ close() {
+ closeCalled = true;
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ writer.abort(error1);
+
+ return promise_rejects(t, error1, writer.closed, 'closed should reject with error1').then(() => {
+ assert_false(closeCalled, 'close must not have been called');
+ });
+}, 'WritableStream should NOT call underlying sink\'s close if no abort is supplied (historical)');
+
+promise_test(() => {
+ let thenCalled = false;
+ const ws = new WritableStream({
+ abort() {
+ return {
+ then(onFulfilled) {
+ thenCalled = true;
+ onFulfilled();
+ }
+ };
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.abort().then(() => assert_true(thenCalled, 'then() should be called'));
+}, 'returning a thenable from abort() should work');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ write() {
+ return flushAsyncEvents();
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const writePromise = writer.write('a');
+ writer.abort(error1);
+ let closedRejected = false;
+ return Promise.all([
+ writePromise.then(() => assert_false(closedRejected, '.closed should not resolve before write()')),
+ promise_rejects(t, error1, writer.closed, '.closed should reject').then(() => {
+ closedRejected = true;
+ })
+ ]);
+ });
+}, '.closed should not resolve before fulfilled write()');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ write() {
+ return Promise.reject(error1);
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const writePromise = writer.write('a');
+ const abortPromise = writer.abort(error2);
+ let closedRejected = false;
+ return Promise.all([
+ promise_rejects(t, error1, writePromise, 'write() should reject')
+ .then(() => assert_false(closedRejected, '.closed should not resolve before write()')),
+ promise_rejects(t, error2, writer.closed, '.closed should reject')
+ .then(() => {
+ closedRejected = true;
+ }),
+ abortPromise
+ ]);
+ });
+}, '.closed should not resolve before rejected write(); write() error should not overwrite abort() error');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ write() {
+ return flushAsyncEvents();
+ }
+ }, new CountQueuingStrategy(4));
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const settlementOrder = [];
+ return Promise.all([
+ writer.write('1').then(() => settlementOrder.push(1)),
+ promise_rejects(t, error1, writer.write('2'), 'first queued write should be rejected')
+ .then(() => settlementOrder.push(2)),
+ promise_rejects(t, error1, writer.write('3'), 'second queued write should be rejected')
+ .then(() => settlementOrder.push(3)),
+ writer.abort(error1)
+ ]).then(() => assert_array_equals([1, 2, 3], settlementOrder, 'writes should be satisfied in order'));
+ });
+}, 'writes should be satisfied in order when aborting');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ write() {
+ return Promise.reject(error1);
+ }
+ }, new CountQueuingStrategy(4));
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const settlementOrder = [];
+ return Promise.all([
+ promise_rejects(t, error1, writer.write('1'), 'in-flight write should be rejected')
+ .then(() => settlementOrder.push(1)),
+ promise_rejects(t, error2, writer.write('2'), 'first queued write should be rejected')
+ .then(() => settlementOrder.push(2)),
+ promise_rejects(t, error2, writer.write('3'), 'second queued write should be rejected')
+ .then(() => settlementOrder.push(3)),
+ writer.abort(error2)
+ ]).then(() => assert_array_equals([1, 2, 3], settlementOrder, 'writes should be satisfied in order'));
+ });
+}, 'writes should be satisfied in order after rejected write when aborting');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ write() {
+ return Promise.reject(error1);
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ return Promise.all([
+ promise_rejects(t, error1, writer.write('a'), 'writer.write() should reject with error from underlying write()'),
+ promise_rejects(t, error2, writer.close(),
+ 'writer.close() should reject with error from underlying write()'),
+ writer.abort(error2)
+ ]);
+ });
+}, 'close() should reject with abort reason why abort() is first error');
+
+promise_test(() => {
+ let resolveWrite;
+ const ws = recordingWritableStream({
+ write() {
+ return new Promise(resolve => {
+ resolveWrite = resolve;
+ });
+ }
+ });
+
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ writer.write('a');
+ const abortPromise = writer.abort('b');
+ return flushAsyncEvents().then(() => {
+ assert_array_equals(ws.events, ['write', 'a'], 'abort should not be called while write is in-flight');
+ resolveWrite();
+ return abortPromise.then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'abort', 'b'], 'abort should be called after the write finishes');
+ });
+ });
+ });
+}, 'underlying abort() should not be called until underlying write() completes');
+
+promise_test(() => {
+ let resolveClose;
+ const ws = recordingWritableStream({
+ close() {
+ return new Promise(resolve => {
+ resolveClose = resolve;
+ });
+ }
+ });
+
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ writer.close();
+ const abortPromise = writer.abort();
+ return flushAsyncEvents().then(() => {
+ assert_array_equals(ws.events, ['close'], 'abort should not be called while close is in-flight');
+ resolveClose();
+ return abortPromise.then(() => {
+ assert_array_equals(ws.events, ['close'], 'abort should not be called');
+ });
+ });
+ });
+}, 'underlying abort() should not be called if underlying close() has started');
+
+promise_test(t => {
+ let rejectClose;
+ let abortCalled = false;
+ const ws = new WritableStream({
+ close() {
+ return new Promise((resolve, reject) => {
+ rejectClose = reject;
+ });
+ },
+ abort() {
+ abortCalled = true;
+ }
+ });
+
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const closePromise = writer.close();
+ const abortPromise = writer.abort();
+ return flushAsyncEvents().then(() => {
+ assert_false(abortCalled, 'underlying abort should not be called while close is in-flight');
+ rejectClose(error1);
+ return promise_rejects(t, error1, abortPromise, 'abort should reject with the same reason').then(() => {
+ return promise_rejects(t, error1, closePromise, 'close should reject with the same reason');
+ }).then(() => {
+ assert_false(abortCalled, 'underlying abort should not be called after close completes');
+ });
+ });
+ });
+}, 'if underlying close() has started and then rejects, the abort() and close() promises should reject with the ' +
+ 'underlying close rejection reason');
+
+promise_test(t => {
+ let resolveWrite;
+ const ws = recordingWritableStream({
+ write() {
+ return new Promise(resolve => {
+ resolveWrite = resolve;
+ });
+ }
+ });
+
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ writer.write('a');
+ const closePromise = writer.close();
+ const abortPromise = writer.abort(error1);
+
+ return flushAsyncEvents().then(() => {
+ assert_array_equals(ws.events, ['write', 'a'], 'abort should not be called while write is in-flight');
+ resolveWrite();
+ return abortPromise.then(() => {
+ assert_array_equals(ws.events, ['write', 'a', 'abort', error1], 'abort should be called after write completes');
+ return promise_rejects(t, error1, closePromise, 'promise returned by close() should be rejected');
+ });
+ });
+ });
+}, 'an abort() that happens during a write() should trigger the underlying abort() even with a close() queued');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ write() {
+ return new Promise(() => {});
+ }
+ });
+
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ writer.write('a');
+ writer.abort(error1);
+ writer.releaseLock();
+ const writer2 = ws.getWriter();
+ return promise_rejects(t, error1, writer2.ready,
+ 'ready of the second writer should be rejected with error1');
+ });
+}, 'if a writer is created for a stream with a pending abort, its ready should be rejected with the abort error');
+
+promise_test(() => {
+ const ws = new WritableStream();
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const closePromise = writer.close();
+ const abortPromise = writer.abort();
+ const events = [];
+ return Promise.all([
+ closePromise.then(() => { events.push('close'); }),
+ abortPromise.then(() => { events.push('abort'); })
+ ]).then(() => {
+ assert_array_equals(events, ['close', 'abort']);
+ });
+ });
+}, 'writer close() promise should resolve before abort() promise');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ write(chunk, controller) {
+ controller.error(error1);
+ return new Promise(() => {});
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ writer.write('a');
+ return promise_rejects(t, error1, writer.ready, 'writer.ready should reject');
+ });
+}, 'writer.ready should reject on controller error without waiting for underlying write');
+
+promise_test(t => {
+ let rejectWrite;
+ const ws = new WritableStream({
+ write() {
+ return new Promise((resolve, reject) => {
+ rejectWrite = reject;
+ });
+ }
+ });
+
+ let writePromise;
+ let abortPromise;
+
+ const events = [];
+
+ const writer = ws.getWriter();
+
+ writer.closed.catch(() => {
+ events.push('closed');
+ });
+
+ // Wait for ws to start
+ return flushAsyncEvents().then(() => {
+ writePromise = writer.write('a');
+ writePromise.catch(() => {
+ events.push('writePromise');
+ });
+
+ abortPromise = writer.abort(error1);
+ abortPromise.then(() => {
+ events.push('abortPromise');
+ });
+
+ const writePromise2 = writer.write('a');
+
+ return Promise.all([
+ promise_rejects(t, error1, writePromise2, 'writePromise2 must reject with the error from abort'),
+ promise_rejects(t, error1, writer.ready, 'writer.ready must reject with the error from abort'),
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(events, [], 'writePromise, abortPromise and writer.closed must not be rejected yet');
+
+ rejectWrite(error2);
+
+ return Promise.all([
+ promise_rejects(t, error2, writePromise,
+ 'writePromise must reject with the error returned from the sink\'s write method'),
+ abortPromise,
+ promise_rejects(t, error1, writer.closed,
+ 'writer.closed must reject with the error from abort'),
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(events, ['writePromise', 'abortPromise', 'closed'],
+ 'writePromise, abortPromise and writer.closed must settle');
+
+ const writePromise3 = writer.write('a');
+
+ return Promise.all([
+ promise_rejects(t, error1, writePromise3,
+ 'writePromise3 must reject with the error from abort'),
+ promise_rejects(t, error1, writer.ready,
+ 'writer.ready must be still rejected with the error indicating abort')
+ ]);
+ }).then(() => {
+ writer.releaseLock();
+
+ return Promise.all([
+ promise_rejects(t, new TypeError(), writer.ready,
+ 'writer.ready must be rejected with an error indicating release'),
+ promise_rejects(t, new TypeError(), writer.closed,
+ 'writer.closed must be rejected with an error indicating release')
+ ]);
+ });
+}, 'writer.abort() while there is an in-flight write, and then finish the write with rejection');
+
+promise_test(t => {
+ let resolveWrite;
+ let controller;
+ const ws = new WritableStream({
+ write(chunk, c) {
+ controller = c;
+ return new Promise(resolve => {
+ resolveWrite = resolve;
+ });
+ }
+ });
+
+ let writePromise;
+ let abortPromise;
+
+ const events = [];
+
+ const writer = ws.getWriter();
+
+ writer.closed.catch(() => {
+ events.push('closed');
+ });
+
+ // Wait for ws to start
+ return flushAsyncEvents().then(() => {
+ writePromise = writer.write('a');
+ writePromise.then(() => {
+ events.push('writePromise');
+ });
+
+ abortPromise = writer.abort(error1);
+ abortPromise.then(() => {
+ events.push('abortPromise');
+ });
+
+ const writePromise2 = writer.write('a');
+
+ return Promise.all([
+ promise_rejects(t, error1, writePromise2, 'writePromise2 must reject with the error from abort'),
+ promise_rejects(t, error1, writer.ready, 'writer.ready must reject with the error from abort'),
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(events, [], 'writePromise, abortPromise and writer.closed must not be fulfilled/rejected yet');
+
+ // This error is too late to change anything. abort() has already changed the stream state to 'erroring'.
+ controller.error(error2);
+
+ const writePromise3 = writer.write('a');
+
+ return Promise.all([
+ promise_rejects(t, error1, writePromise3,
+ 'writePromise3 must reject with the error from abort'),
+ promise_rejects(t, error1, writer.ready,
+ 'writer.ready must be still rejected with the error indicating abort'),
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(
+ events, [],
+ 'writePromise, abortPromise and writer.closed must not be fulfilled/rejected yet even after ' +
+ 'controller.error() call');
+
+ resolveWrite();
+
+ return Promise.all([
+ writePromise,
+ abortPromise,
+ promise_rejects(t, error1, writer.closed,
+ 'writer.closed must reject with the error from abort'),
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(events, ['writePromise', 'abortPromise', 'closed'],
+ 'writePromise, abortPromise and writer.closed must settle');
+
+ const writePromise4 = writer.write('a');
+
+ return Promise.all([
+ writePromise,
+ promise_rejects(t, error1, writePromise4,
+ 'writePromise4 must reject with the error from abort'),
+ promise_rejects(t, error1, writer.ready,
+ 'writer.ready must be still rejected with the error indicating abort')
+ ]);
+ }).then(() => {
+ writer.releaseLock();
+
+ return Promise.all([
+ promise_rejects(t, new TypeError(), writer.ready,
+ 'writer.ready must be rejected with an error indicating release'),
+ promise_rejects(t, new TypeError(), writer.closed,
+ 'writer.closed must be rejected with an error indicating release')
+ ]);
+ });
+}, 'writer.abort(), controller.error() while there is an in-flight write, and then finish the write');
+
+promise_test(t => {
+ let resolveClose;
+ let controller;
+ const ws = new WritableStream({
+ start(c) {
+ controller = c;
+ },
+ close() {
+ return new Promise(resolve => {
+ resolveClose = resolve;
+ });
+ }
+ });
+
+ let closePromise;
+ let abortPromise;
+
+ const events = [];
+
+ const writer = ws.getWriter();
+
+ writer.closed.then(() => {
+ events.push('closed');
+ });
+
+ // Wait for ws to start
+ return flushAsyncEvents().then(() => {
+ closePromise = writer.close();
+ closePromise.then(() => {
+ events.push('closePromise');
+ });
+
+ abortPromise = writer.abort(error1);
+ abortPromise.then(() => {
+ events.push('abortPromise');
+ });
+
+ return Promise.all([
+ promise_rejects(t, new TypeError(), writer.close(),
+ 'writer.close() must reject with an error indicating already closing'),
+ promise_rejects(t, error1, writer.ready, 'writer.ready must reject with the error from abort'),
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(events, [], 'closePromise, abortPromise and writer.closed must not be fulfilled/rejected yet');
+
+ controller.error(error2);
+
+ return Promise.all([
+ promise_rejects(t, new TypeError(), writer.close(),
+ 'writer.close() must reject with an error indicating already closing'),
+ promise_rejects(t, error1, writer.ready,
+ 'writer.ready must be still rejected with the error indicating abort'),
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(
+ events, [],
+ 'closePromise, abortPromise and writer.closed must not be fulfilled/rejected yet even after ' +
+ 'controller.error() call');
+
+ resolveClose();
+
+ return Promise.all([
+ closePromise,
+ abortPromise,
+ writer.closed,
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
+ 'closedPromise, abortPromise and writer.closed must fulfill');
+
+ return Promise.all([
+ promise_rejects(t, new TypeError(), writer.close(),
+ 'writer.close() must reject with an error indicating already closing'),
+ promise_rejects(t, error1, writer.ready,
+ 'writer.ready must be still rejected with the error indicating abort')
+ ]);
+ }).then(() => {
+ writer.releaseLock();
+
+ return Promise.all([
+ promise_rejects(t, new TypeError(), writer.close(),
+ 'writer.close() must reject with an error indicating release'),
+ promise_rejects(t, new TypeError(), writer.ready,
+ 'writer.ready must be rejected with an error indicating release'),
+ promise_rejects(t, new TypeError(), writer.closed,
+ 'writer.closed must be rejected with an error indicating release')
+ ]);
+ });
+}, 'writer.abort(), controller.error() while there is an in-flight close, and then finish the close');
+
+promise_test(t => {
+ let resolveWrite;
+ let controller;
+ const ws = recordingWritableStream({
+ write(chunk, c) {
+ controller = c;
+ return new Promise(resolve => {
+ resolveWrite = resolve;
+ });
+ }
+ });
+
+ let writePromise;
+ let abortPromise;
+
+ const events = [];
+
+ const writer = ws.getWriter();
+
+ writer.closed.catch(() => {
+ events.push('closed');
+ });
+
+ // Wait for ws to start
+ return flushAsyncEvents().then(() => {
+ writePromise = writer.write('a');
+ writePromise.then(() => {
+ events.push('writePromise');
+ });
+
+ controller.error(error2);
+
+ const writePromise2 = writer.write('a');
+
+ return Promise.all([
+ promise_rejects(t, error2, writePromise2,
+ 'writePromise2 must reject with the error passed to the controller\'s error method'),
+ promise_rejects(t, error2, writer.ready,
+ 'writer.ready must reject with the error passed to the controller\'s error method'),
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(events, [], 'writePromise and writer.closed must not be fulfilled/rejected yet');
+
+ abortPromise = writer.abort(error1);
+ abortPromise.catch(() => {
+ events.push('abortPromise');
+ });
+
+ const writePromise3 = writer.write('a');
+
+ return Promise.all([
+ promise_rejects(t, error2, writePromise3,
+ 'writePromise3 must reject with the error passed to the controller\'s error method'),
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(
+ events, [],
+ 'writePromise and writer.closed must not be fulfilled/rejected yet even after writer.abort()');
+
+ resolveWrite();
+
+ return Promise.all([
+ promise_rejects(t, error2, abortPromise,
+ 'abort() must reject with the error passed to the controller\'s error method'),
+ promise_rejects(t, error2, writer.closed,
+ 'writer.closed must reject with the error passed to the controller\'s error method'),
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(events, ['writePromise', 'abortPromise', 'closed'],
+ 'writePromise, abortPromise and writer.closed must fulfill/reject');
+ assert_array_equals(ws.events, ['write', 'a'], 'sink abort() should not be called');
+
+ const writePromise4 = writer.write('a');
+
+ return Promise.all([
+ writePromise,
+ promise_rejects(t, error2, writePromise4,
+ 'writePromise4 must reject with the error passed to the controller\'s error method'),
+ promise_rejects(t, error2, writer.ready,
+ 'writer.ready must be still rejected with the error passed to the controller\'s error method')
+ ]);
+ }).then(() => {
+ writer.releaseLock();
+
+ return Promise.all([
+ promise_rejects(t, new TypeError(), writer.ready,
+ 'writer.ready must be rejected with an error indicating release'),
+ promise_rejects(t, new TypeError(), writer.closed,
+ 'writer.closed must be rejected with an error indicating release')
+ ]);
+ });
+}, 'controller.error(), writer.abort() while there is an in-flight write, and then finish the write');
+
+promise_test(t => {
+ let resolveClose;
+ let controller;
+ const ws = new WritableStream({
+ start(c) {
+ controller = c;
+ },
+ close() {
+ return new Promise(resolve => {
+ resolveClose = resolve;
+ });
+ }
+ });
+
+ let closePromise;
+ let abortPromise;
+
+ const events = [];
+
+ const writer = ws.getWriter();
+
+ writer.closed.then(() => {
+ events.push('closed');
+ });
+
+ // Wait for ws to start
+ return flushAsyncEvents().then(() => {
+ closePromise = writer.close();
+ closePromise.then(() => {
+ events.push('closePromise');
+ });
+
+ controller.error(error2);
+
+ return flushAsyncEvents();
+ }).then(() => {
+ assert_array_equals(events, [], 'closePromise must not be fulfilled/rejected yet');
+
+ abortPromise = writer.abort(error1);
+ abortPromise.then(() => {
+ events.push('abortPromise');
+ });
+
+ return Promise.all([
+ promise_rejects(t, error2, writer.ready,
+ 'writer.ready must reject with the error passed to the controller\'s error method'),
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(
+ events, [],
+ 'closePromise and writer.closed must not be fulfilled/rejected yet even after writer.abort()');
+
+ resolveClose();
+
+ return Promise.all([
+ closePromise,
+ promise_rejects(t, error2, writer.ready,
+ 'writer.ready must be still rejected with the error passed to the controller\'s error method'),
+ writer.closed,
+ flushAsyncEvents()
+ ]);
+ }).then(() => {
+ assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
+ 'abortPromise, closePromise and writer.closed must fulfill/reject');
+ }).then(() => {
+ writer.releaseLock();
+
+ return Promise.all([
+ promise_rejects(t, new TypeError(), writer.ready,
+ 'writer.ready must be rejected with an error indicating release'),
+ promise_rejects(t, new TypeError(), writer.closed,
+ 'writer.closed must be rejected with an error indicating release')
+ ]);
+ });
+}, 'controller.error(), writer.abort() while there is an in-flight close, and then finish the close');
+
+promise_test(t => {
+ let resolveWrite;
+ const ws = new WritableStream({
+ write() {
+ return new Promise(resolve => {
+ resolveWrite = resolve;
+ });
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const writePromise = writer.write('a');
+ const closed = writer.closed;
+ const abortPromise = writer.abort();
+ writer.releaseLock();
+ resolveWrite();
+ return Promise.all([
+ writePromise,
+ abortPromise,
+ promise_rejects(t, new TypeError(), closed, 'closed should reject')]);
+ });
+}, 'releaseLock() while aborting should reject the original closed promise');
+
+// TODO(ricea): Consider removing this test if it is no longer useful.
+promise_test(t => {
+ let resolveWrite;
+ let resolveAbort;
+ let resolveAbortStarted;
+ const abortStarted = new Promise(resolve => {
+ resolveAbortStarted = resolve;
+ });
+ const ws = new WritableStream({
+ write() {
+ return new Promise(resolve => {
+ resolveWrite = resolve;
+ });
+ },
+ abort() {
+ resolveAbortStarted();
+ return new Promise(resolve => {
+ resolveAbort = resolve;
+ });
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const writePromise = writer.write('a');
+ const closed = writer.closed;
+ const abortPromise = writer.abort();
+ resolveWrite();
+ return abortStarted.then(() => {
+ writer.releaseLock();
+ assert_equals(writer.closed, closed, 'closed promise should not have changed');
+ resolveAbort();
+ return Promise.all([
+ writePromise,
+ abortPromise,
+ promise_rejects(t, new TypeError(), closed, 'closed should reject')]);
+ });
+ });
+}, 'releaseLock() during delayed async abort() should reject the writer.closed promise');
+
+promise_test(() => {
+ let resolveStart;
+ const ws = recordingWritableStream({
+ start() {
+ return new Promise(resolve => {
+ resolveStart = resolve;
+ });
+ }
+ });
+ const abortPromise = ws.abort('done');
+ return flushAsyncEvents().then(() => {
+ assert_array_equals(ws.events, [], 'abort() should not be called during start()');
+ resolveStart();
+ return abortPromise.then(() => {
+ assert_array_equals(ws.events, ['abort', 'done'], 'abort() should be called after start() is done');
+ });
+ });
+}, 'sink abort() should not be called until sink start() is done');
+
+promise_test(() => {
+ let resolveStart;
+ let controller;
+ const ws = recordingWritableStream({
+ start(c) {
+ controller = c;
+ return new Promise(resolve => {
+ resolveStart = resolve;
+ });
+ }
+ });
+ const abortPromise = ws.abort('done');
+ controller.error(error1);
+ resolveStart();
+ return abortPromise.then(() =>
+ assert_array_equals(ws.events, ['abort', 'done'],
+ 'abort() should still be called if start() errors the controller'));
+}, 'if start attempts to error the controller after abort() has been called, then it should lose');
+
+promise_test(() => {
+ const ws = recordingWritableStream({
+ start() {
+ return Promise.reject(error1);
+ }
+ });
+ return ws.abort('done').then(() =>
+ assert_array_equals(ws.events, ['abort', 'done'], 'abort() should still be called if start() rejects'));
+}, 'stream abort() promise should still resolve if sink start() rejects');
+
+promise_test(t => {
+ const ws = new WritableStream();
+ const writer = ws.getWriter();
+ const writerReady1 = writer.ready;
+ writer.abort(error1);
+ const writerReady2 = writer.ready;
+ assert_not_equals(writerReady1, writerReady2, 'abort() should replace the ready promise with a rejected one');
+ return Promise.all([writerReady1,
+ promise_rejects(t, error1, writerReady2, 'writerReady2 should reject')]);
+}, 'writer abort() during sink start() should replace the writer.ready promise synchronously');
+
+promise_test(t => {
+ const events = [];
+ const ws = recordingWritableStream();
+ const writer = ws.getWriter();
+ const writePromise1 = writer.write(1);
+ const abortPromise = writer.abort(error1);
+ const writePromise2 = writer.write(2);
+ const closePromise = writer.close();
+ writePromise1.catch(() => events.push('write1'));
+ abortPromise.then(() => events.push('abort'));
+ writePromise2.catch(() => events.push('write2'));
+ closePromise.catch(() => events.push('close'));
+ return Promise.all([
+ promise_rejects(t, error1, writePromise1, 'first write() should reject'),
+ abortPromise,
+ promise_rejects(t, error1, writePromise2, 'second write() should reject'),
+ promise_rejects(t, error1, closePromise, 'close() should reject')
+ ])
+ .then(() => {
+ assert_array_equals(events, ['write2', 'write1', 'abort', 'close'],
+ 'promises should resolve in the standard order');
+ assert_array_equals(ws.events, ['abort', error1], 'underlying sink write() should not be called');
+ });
+}, 'promises returned from other writer methods should be rejected when writer abort() happens during sink start()');
+
+promise_test(t => {
+ let writeReject;
+ let controller;
+ const ws = new WritableStream({
+ write(chunk, c) {
+ controller = c;
+ return new Promise((resolve, reject) => {
+ writeReject = reject;
+ });
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const writePromise = writer.write('a');
+ const abortPromise = writer.abort();
+ controller.error(error1);
+ writeReject(error2);
+ return Promise.all([
+ promise_rejects(t, error2, writePromise, 'write() should reject with error2'),
+ abortPromise
+ ]);
+ });
+}, 'abort() should succeed despite rejection from write');
+
+promise_test(t => {
+ let closeReject;
+ let controller;
+ const ws = new WritableStream({
+ start(c) {
+ controller = c;
+ },
+ close() {
+ return new Promise((resolve, reject) => {
+ closeReject = reject;
+ });
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const closePromise = writer.close();
+ const abortPromise = writer.abort();
+ controller.error(error1);
+ closeReject(error2);
+ return Promise.all([
+ promise_rejects(t, error2, closePromise, 'close() should reject with error2'),
+ promise_rejects(t, error2, abortPromise, 'abort() should reject with error2')
+ ]);
+ });
+}, 'abort() should be rejected with the rejection returned from close()');
+
+promise_test(t => {
+ let rejectWrite;
+ const ws = recordingWritableStream({
+ write() {
+ return new Promise((resolve, reject) => {
+ rejectWrite = reject;
+ });
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const writePromise = writer.write('1');
+ const abortPromise = writer.abort(error2);
+ rejectWrite(error1);
+ return Promise.all([
+ promise_rejects(t, error1, writePromise, 'write should reject'),
+ abortPromise,
+ promise_rejects(t, error2, writer.closed, 'closed should reject with error2')
+ ]);
+ }).then(() => {
+ assert_array_equals(ws.events, ['write', '1', 'abort', error2], 'abort sink method should be called');
+ });
+}, 'a rejecting sink.write() should not prevent sink.abort() from being called');
+
+promise_test(() => {
+ const ws = recordingWritableStream({
+ start() {
+ return Promise.reject(error1);
+ }
+ });
+ return ws.abort(error2)
+ .then(() => {
+ assert_array_equals(ws.events, ['abort', error2]);
+ });
+}, 'when start errors after stream abort(), underlying sink abort() should be called anyway');
+
+promise_test(() => {
+ const ws = new WritableStream();
+ const abortPromise1 = ws.abort();
+ const abortPromise2 = ws.abort();
+ assert_equals(abortPromise1, abortPromise2, 'the promises must be the same');
+
+ return abortPromise1.then(
+ v => assert_equals(v, undefined, 'abort() should fulfill with undefined'));
+}, 'when calling abort() twice on the same stream, both should give the same promise that fulfills with undefined');
+
+promise_test(() => {
+ const ws = new WritableStream();
+ const abortPromise1 = ws.abort();
+
+ return abortPromise1.then(v1 => {
+ assert_equals(v1, undefined, 'first abort() should fulfill with undefined');
+
+ const abortPromise2 = ws.abort();
+ assert_not_equals(abortPromise2, abortPromise1, 'because we waited, the second promise should be a new promise');
+
+ return abortPromise2.then(v2 => {
+ assert_equals(v2, undefined, 'second abort() should fulfill with undefined');
+ });
+ });
+}, 'when calling abort() twice on the same stream, but sequentially so so there\'s no pending abort the second time, ' +
+ 'both should fulfill with undefined');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ start(c) {
+ c.error(error1);
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.closed, 'writer.closed should reject').then(() => {
+ return writer.abort().then(
+ v => assert_equals(v, undefined, 'abort() should fulfill with undefined'));
+ });
+}, 'calling abort() on an errored stream should fulfill with undefined');
+
+promise_test(t => {
+ let controller;
+ let resolveWrite;
+ const ws = recordingWritableStream({
+ start(c) {
+ controller = c;
+ },
+ write() {
+ return new Promise(resolve => {
+ resolveWrite = resolve;
+ });
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const writePromise = writer.write('chunk');
+ controller.error(error1);
+ const abortPromise = writer.abort(error2);
+ resolveWrite();
+ return Promise.all([
+ writePromise,
+ promise_rejects(t, error1, abortPromise, 'abort() should reject')
+ ]).then(() => {
+ assert_array_equals(ws.events, ['write', 'chunk'], 'sink abort() should not be called');
+ });
+ });
+}, 'sink abort() should not be called if stream was erroring due to controller.error() before abort() was called');
+
+promise_test(t => {
+ let resolveWrite;
+ let size = 1;
+ const ws = recordingWritableStream({
+ write() {
+ return new Promise(resolve => {
+ resolveWrite = resolve;
+ });
+ }
+ }, {
+ size() {
+ return size;
+ },
+ highWaterMark: 1
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const writePromise1 = writer.write('chunk1');
+ size = NaN;
+ const writePromise2 = writer.write('chunk2');
+ const abortPromise = writer.abort(error2);
+ resolveWrite();
+ return Promise.all([
+ writePromise1,
+ promise_rejects(t, new RangeError(), writePromise2, 'second write() should reject'),
+ promise_rejects(t, new RangeError(), abortPromise, 'abort() should reject')
+ ]).then(() => {
+ assert_array_equals(ws.events, ['write', 'chunk1'], 'sink abort() should not be called');
+ });
+ });
+}, 'sink abort() should not be called if stream was erroring due to bad strategy before abort() was called');
+
+promise_test(t => {
+ const ws = new WritableStream();
+ return ws.abort().then(() => {
+ const writer = ws.getWriter();
+ return writer.closed.then(t.unreached_func('closed promise should not fulfill'),
+ e => assert_equals(e, undefined, 'e should be undefined'));
+ });
+}, 'abort with no arguments should set the stored error to undefined');
+
+promise_test(t => {
+ const ws = new WritableStream();
+ return ws.abort(undefined).then(() => {
+ const writer = ws.getWriter();
+ return writer.closed.then(t.unreached_func('closed promise should not fulfill'),
+ e => assert_equals(e, undefined, 'e should be undefined'));
+ });
+}, 'abort with an undefined argument should set the stored error to undefined');
+
+promise_test(t => {
+ const ws = new WritableStream();
+ return ws.abort('string argument').then(() => {
+ const writer = ws.getWriter();
+ return writer.closed.then(t.unreached_func('closed promise should not fulfill'),
+ e => assert_equals(e, 'string argument', 'e should be \'string argument\''));
+ });
+}, 'abort with a string argument should set the stored error to that argument');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/bad-strategies.js b/test/fixtures/web-platform-tests/streams/writable-streams/bad-strategies.js
new file mode 100644
index 00000000000000..1dba393811bdf3
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/bad-strategies.js
@@ -0,0 +1,100 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+const error1 = new Error('a unique string');
+error1.name = 'error1';
+
+test(() => {
+ assert_throws(error1, () => {
+ new WritableStream({}, {
+ get size() {
+ throw error1;
+ },
+ highWaterMark: 5
+ });
+ }, 'construction should re-throw the error');
+}, 'Writable stream: throwing strategy.size getter');
+
+test(() => {
+ assert_throws(new TypeError(), () => {
+ new WritableStream({}, { size: 'a string' });
+ });
+}, 'reject any non-function value for strategy.size');
+
+test(() => {
+ assert_throws(error1, () => {
+ new WritableStream({}, {
+ size() {
+ return 1;
+ },
+ get highWaterMark() {
+ throw error1;
+ }
+ });
+ }, 'construction should re-throw the error');
+}, 'Writable stream: throwing strategy.highWaterMark getter');
+
+test(() => {
+
+ for (const highWaterMark of [-1, -Infinity, NaN, 'foo', {}]) {
+ assert_throws(new RangeError(), () => {
+ new WritableStream({}, {
+ size() {
+ return 1;
+ },
+ highWaterMark
+ });
+ }, `construction should throw a RangeError for ${highWaterMark}`);
+ }
+}, 'Writable stream: invalid strategy.highWaterMark');
+
+promise_test(t => {
+ const ws = new WritableStream({}, {
+ size() {
+ throw error1;
+ },
+ highWaterMark: 5
+ });
+
+ const writer = ws.getWriter();
+
+ const p1 = promise_rejects(t, error1, writer.write('a'), 'write should reject with the thrown error');
+
+ const p2 = promise_rejects(t, error1, writer.closed, 'closed should reject with the thrown error');
+
+ return Promise.all([p1, p2]);
+}, 'Writable stream: throwing strategy.size method');
+
+promise_test(() => {
+ const sizes = [NaN, -Infinity, Infinity, -1];
+ return Promise.all(sizes.map(size => {
+ const ws = new WritableStream({}, {
+ size() {
+ return size;
+ },
+ highWaterMark: 5
+ });
+
+ const writer = ws.getWriter();
+
+ return writer.write('a').then(() => assert_unreached('write must reject'), writeE => {
+ assert_equals(writeE.name, 'RangeError', `write must reject with a RangeError for ${size}`);
+
+ return writer.closed.then(() => assert_unreached('write must reject'), closedE => {
+ assert_equals(closedE, writeE, `closed should reject with the same error as write`);
+ });
+ });
+ }));
+}, 'Writable stream: invalid strategy.size return value');
+
+test(() => {
+ assert_throws(new TypeError(), () => new WritableStream(undefined, {
+ size: 'not a function',
+ highWaterMark: NaN
+ }), 'WritableStream constructor should throw a TypeError');
+}, 'Writable stream: invalid size beats invalid highWaterMark');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/bad-underlying-sinks.js b/test/fixtures/web-platform-tests/streams/writable-streams/bad-underlying-sinks.js
new file mode 100644
index 00000000000000..2c7c44831d1769
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/bad-underlying-sinks.js
@@ -0,0 +1,195 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+const error1 = new Error('error1');
+error1.name = 'error1';
+
+test(() => {
+ assert_throws(error1, () => {
+ new WritableStream({
+ get start() {
+ throw error1;
+ }
+ });
+ }, 'constructor should throw same error as throwing start getter');
+
+ assert_throws(error1, () => {
+ new WritableStream({
+ start() {
+ throw error1;
+ }
+ });
+ }, 'constructor should throw same error as throwing start method');
+
+ assert_throws(new TypeError(), () => {
+ new WritableStream({
+ start: 'not a function or undefined'
+ });
+ }, 'constructor should throw TypeError when passed a non-function start property');
+
+ assert_throws(new TypeError(), () => {
+ new WritableStream({
+ start: { apply() {} }
+ });
+ }, 'constructor should throw TypeError when passed a non-function start property with an .apply method');
+}, 'start: errors in start cause WritableStream constructor to throw');
+
+promise_test(t => {
+
+ const ws = recordingWritableStream({
+ close() {
+ throw error1;
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.close(), 'close() promise must reject with the thrown error')
+ .then(() => promise_rejects(t, error1, writer.ready, 'ready promise must reject with the thrown error'))
+ .then(() => promise_rejects(t, error1, writer.closed, 'closed promise must reject with the thrown error'))
+ .then(() => {
+ assert_array_equals(ws.events, ['close']);
+ });
+
+}, 'close: throwing method should cause writer close() and ready to reject');
+
+promise_test(t => {
+
+ const ws = recordingWritableStream({
+ close() {
+ return Promise.reject(error1);
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.close(), 'close() promise must reject with the same error')
+ .then(() => promise_rejects(t, error1, writer.ready, 'ready promise must reject with the same error'))
+ .then(() => assert_array_equals(ws.events, ['close']));
+
+}, 'close: returning a rejected promise should cause writer close() and ready to reject');
+
+test(() => {
+ assert_throws(error1, () => new WritableStream({
+ get close() {
+ throw error1;
+ }
+ }), 'constructor should throw');
+}, 'close: throwing getter should cause constructor to throw');
+
+test(() => {
+ assert_throws(error1, () => new WritableStream({
+ get write() {
+ throw error1;
+ }
+ }), 'constructor should throw');
+}, 'write: throwing getter should cause write() and closed to reject');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ write() {
+ throw error1;
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.write('a'), 'write should reject with the thrown error')
+ .then(() => promise_rejects(t, error1, writer.closed, 'closed should reject with the thrown error'));
+}, 'write: throwing method should cause write() and closed to reject');
+
+promise_test(t => {
+
+ const startPromise = Promise.resolve();
+ let rejectSinkWritePromise;
+ const ws = recordingWritableStream({
+ start() {
+ return startPromise;
+ },
+ write() {
+ return new Promise((r, reject) => {
+ rejectSinkWritePromise = reject;
+ });
+ }
+ });
+
+ return startPromise.then(() => {
+ const writer = ws.getWriter();
+ const writePromise = writer.write('a');
+ rejectSinkWritePromise(error1);
+
+ return Promise.all([
+ promise_rejects(t, error1, writePromise, 'writer write must reject with the same error'),
+ promise_rejects(t, error1, writer.ready, 'ready promise must reject with the same error')
+ ]);
+ })
+ .then(() => {
+ assert_array_equals(ws.events, ['write', 'a']);
+ });
+
+}, 'write: returning a promise that becomes rejected after the writer write() should cause writer write() and ready ' +
+ 'to reject');
+
+promise_test(t => {
+
+ const ws = recordingWritableStream({
+ write() {
+ if (ws.events.length === 2) {
+ return delay(0);
+ }
+
+ return Promise.reject(error1);
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ // Do not wait for this; we want to test the ready promise when the stream is "full" (desiredSize = 0), but if we wait
+ // then the stream will transition back to "empty" (desiredSize = 1)
+ writer.write('a');
+ const readyPromise = writer.ready;
+
+ return promise_rejects(t, error1, writer.write('b'), 'second write must reject with the same error').then(() => {
+ assert_equals(writer.ready, readyPromise,
+ 'the ready promise must not change, since the queue was full after the first write, so the pending one simply ' +
+ 'transitioned');
+ return promise_rejects(t, error1, writer.ready, 'ready promise must reject with the same error');
+ })
+ .then(() => assert_array_equals(ws.events, ['write', 'a', 'write', 'b']));
+
+}, 'write: returning a rejected promise (second write) should cause writer write() and ready to reject');
+
+test(() => {
+ assert_throws(new TypeError(), () => new WritableStream({
+ abort: { apply() {} }
+ }), 'constructor should throw');
+}, 'abort: non-function abort method with .apply');
+
+test(() => {
+ assert_throws(error1, () => new WritableStream({
+ get abort() {
+ throw error1;
+ }
+ }), 'constructor should throw');
+}, 'abort: throwing getter should cause abort() and closed to reject');
+
+promise_test(t => {
+ const abortReason = new Error('different string');
+ const ws = new WritableStream({
+ abort() {
+ throw error1;
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.abort(abortReason), 'abort should reject with the thrown error')
+ .then(() => promise_rejects(t, abortReason, writer.closed, 'closed should reject with abortReason'));
+}, 'abort: throwing method should cause abort() and closed to reject');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/brand-checks.js b/test/fixtures/web-platform-tests/streams/writable-streams/brand-checks.js
new file mode 100644
index 00000000000000..256fbb21ff53c3
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/brand-checks.js
@@ -0,0 +1,114 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+}
+
+const WritableStreamDefaultWriter = new WritableStream().getWriter().constructor;
+const WriterProto = WritableStreamDefaultWriter.prototype;
+const WritableStreamDefaultController = getWritableStreamDefaultControllerConstructor();
+
+function getWritableStreamDefaultControllerConstructor() {
+ return realWSDefaultController().constructor;
+}
+
+function fakeWS() {
+ return Object.setPrototypeOf({
+ get locked() { return false; },
+ abort() { return Promise.resolve(); },
+ getWriter() { return fakeWSDefaultWriter(); }
+ }, WritableStream.prototype);
+}
+
+function realWS() {
+ return new WritableStream();
+}
+
+function fakeWSDefaultWriter() {
+ return Object.setPrototypeOf({
+ get closed() { return Promise.resolve(); },
+ get desiredSize() { return 1; },
+ get ready() { return Promise.resolve(); },
+ abort() { return Promise.resolve(); },
+ close() { return Promise.resolve(); },
+ write() { return Promise.resolve(); }
+ }, WritableStreamDefaultWriter.prototype);
+}
+
+function realWSDefaultWriter() {
+ const ws = new WritableStream();
+ return ws.getWriter();
+}
+
+function fakeWSDefaultController() {
+ return Object.setPrototypeOf({
+ error() { return Promise.resolve(); }
+ }, WritableStreamDefaultController.prototype);
+}
+
+function realWSDefaultController() {
+ let controller;
+ new WritableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ return controller;
+}
+
+test(() => {
+ getterThrowsForAll(WritableStream.prototype, 'locked',
+ [fakeWS(), realWSDefaultWriter(), realWSDefaultController(), undefined, null]);
+}, 'WritableStream.prototype.locked enforces a brand check');
+
+promise_test(t => {
+ return methodRejectsForAll(t, WritableStream.prototype, 'abort',
+ [fakeWS(), realWSDefaultWriter(), realWSDefaultController(), undefined, null]);
+}, 'WritableStream.prototype.abort enforces a brand check');
+
+test(() => {
+ methodThrowsForAll(WritableStream.prototype, 'getWriter',
+ [fakeWS(), realWSDefaultWriter(), realWSDefaultController(), undefined, null]);
+}, 'WritableStream.prototype.getWriter enforces a brand check');
+
+test(() => {
+ assert_throws(new TypeError(), () => new WritableStreamDefaultWriter(fakeWS()), 'constructor should throw');
+}, 'WritableStreamDefaultWriter constructor enforces a brand check');
+
+test(() => {
+ getterThrowsForAll(WriterProto, 'desiredSize',
+ [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
+}, 'WritableStreamDefaultWriter.prototype.desiredSize enforces a brand check');
+
+promise_test(t => {
+ return getterRejectsForAll(t, WriterProto, 'closed',
+ [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
+}, 'WritableStreamDefaultWriter.prototype.closed enforces a brand check');
+
+promise_test(t => {
+ return getterRejectsForAll(t, WriterProto, 'ready',
+ [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
+}, 'WritableStreamDefaultWriter.prototype.ready enforces a brand check');
+
+promise_test(t => {
+ return methodRejectsForAll(t, WriterProto, 'abort',
+ [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
+}, 'WritableStreamDefaultWriter.prototype.abort enforces a brand check');
+
+promise_test(t => {
+ return methodRejectsForAll(t, WriterProto, 'write',
+ [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
+}, 'WritableStreamDefaultWriter.prototype.write enforces a brand check');
+
+promise_test(t => {
+ return methodRejectsForAll(t, WriterProto, 'close',
+ [fakeWSDefaultWriter(), realWS(), realWSDefaultController(), undefined, null]);
+}, 'WritableStreamDefaultWriter.prototype.close enforces a brand check');
+
+test(() => {
+ methodThrowsForAll(WritableStreamDefaultController.prototype, 'error',
+ [fakeWSDefaultController(), realWS(), realWSDefaultWriter(), undefined, null]);
+}, 'WritableStreamDefaultController.prototype.error enforces a brand check');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/byte-length-queuing-strategy.js b/test/fixtures/web-platform-tests/streams/writable-streams/byte-length-queuing-strategy.js
new file mode 100644
index 00000000000000..611689f8817139
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/byte-length-queuing-strategy.js
@@ -0,0 +1,33 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+promise_test(() => {
+ let isDone = false;
+ const ws = new WritableStream(
+ {
+ write() {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ isDone = true;
+ resolve();
+ }, 200);
+ });
+ },
+
+ close() {
+ assert_true(isDone, 'close is only called once the promise has been resolved');
+ }
+ },
+ new ByteLengthQueuingStrategy({ highWaterMark: 1024 * 16 })
+ );
+
+ const writer = ws.getWriter();
+ writer.write({ byteLength: 1024 });
+
+ return writer.close();
+}, 'Closing a writable stream with in-flight writes below the high water mark delays the close call properly');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/close.js b/test/fixtures/web-platform-tests/streams/writable-streams/close.js
new file mode 100644
index 00000000000000..5cbe5701ba5cda
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/close.js
@@ -0,0 +1,406 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+const error1 = new Error('error1');
+error1.name = 'error1';
+
+const error2 = new Error('error2');
+error2.name = 'error2';
+
+promise_test(() => {
+ const ws = new WritableStream({
+ close() {
+ return 'Hello';
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ const closePromise = writer.close();
+ return closePromise.then(value => assert_equals(value, undefined, 'fulfillment value must be undefined'));
+}, 'fulfillment value of ws.close() call must be undefined even if the underlying sink returns a non-undefined ' +
+ 'value');
+
+promise_test(() => {
+ let controller;
+ let resolveClose;
+ const ws = new WritableStream({
+ start(c) {
+ controller = c;
+ },
+ close() {
+ return new Promise(resolve => {
+ resolveClose = resolve;
+ });
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ const closePromise = writer.close();
+ return flushAsyncEvents().then(() => {
+ controller.error(error1);
+ return flushAsyncEvents();
+ }).then(() => {
+ resolveClose();
+ return Promise.all([
+ closePromise,
+ writer.closed,
+ flushAsyncEvents().then(() => writer.closed)]);
+ });
+}, 'when sink calls error asynchronously while sink close is in-flight, the stream should not become errored');
+
+promise_test(() => {
+ let controller;
+ const passedError = new Error('error me');
+ const ws = new WritableStream({
+ start(c) {
+ controller = c;
+ },
+ close() {
+ controller.error(passedError);
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return writer.close().then(() => writer.closed);
+}, 'when sink calls error synchronously while closing, the stream should not become errored');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ close() {
+ throw error1;
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return Promise.all([
+ writer.write('y'),
+ promise_rejects(t, error1, writer.close(), 'close() must reject with the error'),
+ promise_rejects(t, error1, writer.closed, 'closed must reject with the error')
+ ]);
+}, 'when the sink throws during close, and the close is requested while a write is still in-flight, the stream should ' +
+ 'become errored during the close');
+
+promise_test(() => {
+ const ws = new WritableStream({
+ write(chunk, controller) {
+ controller.error(error1);
+ return new Promise(() => {});
+ }
+ });
+
+ const writer = ws.getWriter();
+ writer.write('a');
+
+ return delay(0).then(() => {
+ writer.releaseLock();
+ });
+}, 'releaseLock on a stream with a pending write in which the stream has been errored');
+
+promise_test(() => {
+ let controller;
+ const ws = new WritableStream({
+ start(c) {
+ controller = c;
+ },
+ close() {
+ controller.error(error1);
+ return new Promise(() => {});
+ }
+ });
+
+ const writer = ws.getWriter();
+ writer.close();
+
+ return delay(0).then(() => {
+ writer.releaseLock();
+ });
+}, 'releaseLock on a stream with a pending close in which controller.error() was called');
+
+promise_test(() => {
+ const ws = recordingWritableStream();
+
+ const writer = ws.getWriter();
+
+ return writer.ready.then(() => {
+ assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
+
+ writer.close();
+ assert_equals(writer.desiredSize, 1, 'desiredSize should be still 1');
+
+ return writer.ready.then(v => {
+ assert_equals(v, undefined, 'ready promise should be fulfilled with undefined');
+ assert_array_equals(ws.events, ['close'], 'write and abort should not be called');
+ });
+ });
+}, 'when close is called on a WritableStream in writable state, ready should return a fulfilled promise');
+
+promise_test(() => {
+ const ws = recordingWritableStream({
+ write() {
+ return new Promise(() => {});
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return writer.ready.then(() => {
+ writer.write('a');
+
+ assert_equals(writer.desiredSize, 0, 'desiredSize should be 0');
+
+ let calledClose = false;
+ return Promise.all([
+ writer.ready.then(v => {
+ assert_equals(v, undefined, 'ready promise should be fulfilled with undefined');
+ assert_true(calledClose, 'ready should not be fulfilled before writer.close() is called');
+ assert_array_equals(ws.events, ['write', 'a'], 'sink abort() should not be called');
+ }),
+ flushAsyncEvents().then(() => {
+ writer.close();
+ calledClose = true;
+ })
+ ]);
+ });
+}, 'when close is called on a WritableStream in waiting state, ready promise should be fulfilled');
+
+promise_test(() => {
+ let asyncCloseFinished = false;
+ const ws = recordingWritableStream({
+ close() {
+ return flushAsyncEvents().then(() => {
+ asyncCloseFinished = true;
+ });
+ }
+ });
+
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ writer.write('a');
+
+ writer.close();
+
+ return writer.ready.then(v => {
+ assert_false(asyncCloseFinished, 'ready promise should be fulfilled before async close completes');
+ assert_equals(v, undefined, 'ready promise should be fulfilled with undefined');
+ assert_array_equals(ws.events, ['write', 'a', 'close'], 'sink abort() should not be called');
+ });
+ });
+}, 'when close is called on a WritableStream in waiting state, ready should be fulfilled immediately even if close ' +
+ 'takes a long time');
+
+promise_test(t => {
+ const rejection = { name: 'letter' };
+ const ws = new WritableStream({
+ close() {
+ return {
+ then(onFulfilled, onRejected) { onRejected(rejection); }
+ };
+ }
+ });
+ return promise_rejects(t, rejection, ws.getWriter().close(), 'close() should return a rejection');
+}, 'returning a thenable from close() should work');
+
+promise_test(t => {
+ const ws = new WritableStream();
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const closePromise = writer.close();
+ const closedPromise = writer.closed;
+ writer.releaseLock();
+ return Promise.all([
+ closePromise,
+ promise_rejects(t, new TypeError(), closedPromise, '.closed promise should be rejected')
+ ]);
+ });
+}, 'releaseLock() should not change the result of sync close()');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ close() {
+ return flushAsyncEvents();
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const closePromise = writer.close();
+ const closedPromise = writer.closed;
+ writer.releaseLock();
+ return Promise.all([
+ closePromise,
+ promise_rejects(t, new TypeError(), closedPromise, '.closed promise should be rejected')
+ ]);
+ });
+}, 'releaseLock() should not change the result of async close()');
+
+promise_test(() => {
+ let resolveClose;
+ const ws = new WritableStream({
+ close() {
+ const promise = new Promise(resolve => {
+ resolveClose = resolve;
+ });
+ return promise;
+ }
+ });
+ const writer = ws.getWriter();
+ const closePromise = writer.close();
+ writer.releaseLock();
+ return delay(0).then(() => {
+ resolveClose();
+ return closePromise.then(() => {
+ assert_equals(ws.getWriter().desiredSize, 0, 'desiredSize should be 0');
+ });
+ });
+}, 'close() should set state to CLOSED even if writer has detached');
+
+promise_test(() => {
+ let resolveClose;
+ const ws = new WritableStream({
+ close() {
+ const promise = new Promise(resolve => {
+ resolveClose = resolve;
+ });
+ return promise;
+ }
+ });
+ const writer = ws.getWriter();
+ writer.close();
+ writer.releaseLock();
+ return delay(0).then(() => {
+ const abortingWriter = ws.getWriter();
+ const abortPromise = abortingWriter.abort();
+ abortingWriter.releaseLock();
+ resolveClose();
+ return abortPromise;
+ });
+}, 'the promise returned by async abort during close should resolve');
+
+// Though the order in which the promises are fulfilled or rejected is arbitrary, we're checking it for
+// interoperability. We can change the order as long as we file bugs on all implementers to update to the latest tests
+// to keep them interoperable.
+
+promise_test(() => {
+ const ws = new WritableStream({});
+
+ const writer = ws.getWriter();
+
+ const closePromise = writer.close();
+
+ const events = [];
+ return Promise.all([
+ closePromise.then(() => {
+ events.push('closePromise');
+ }),
+ writer.closed.then(() => {
+ events.push('closed');
+ })
+ ]).then(() => {
+ assert_array_equals(events, ['closePromise', 'closed'],
+ 'promises must fulfill/reject in the expected order');
+ });
+}, 'promises must fulfill/reject in the expected order on closure');
+
+promise_test(() => {
+ const ws = new WritableStream({});
+
+ // Wait until the WritableStream starts so that the close() call gets processed. Otherwise, abort() will be
+ // processed without waiting for completion of the close().
+ return delay(0).then(() => {
+ const writer = ws.getWriter();
+
+ const closePromise = writer.close();
+ const abortPromise = writer.abort(error1);
+
+ const events = [];
+ return Promise.all([
+ closePromise.then(() => {
+ events.push('closePromise');
+ }),
+ abortPromise.then(() => {
+ events.push('abortPromise');
+ }),
+ writer.closed.then(() => {
+ events.push('closed');
+ })
+ ]).then(() => {
+ assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
+ 'promises must fulfill/reject in the expected order');
+ });
+ });
+}, 'promises must fulfill/reject in the expected order on aborted closure');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ close() {
+ return Promise.reject(error1);
+ }
+ });
+
+ // Wait until the WritableStream starts so that the close() call gets processed.
+ return delay(0).then(() => {
+ const writer = ws.getWriter();
+
+ const closePromise = writer.close();
+ const abortPromise = writer.abort(error2);
+
+ const events = [];
+ closePromise.catch(() => events.push('closePromise'));
+ abortPromise.catch(() => events.push('abortPromise'));
+ writer.closed.catch(() => events.push('closed'));
+ return Promise.all([
+ promise_rejects(t, error1, closePromise,
+ 'closePromise must reject with the error returned from the sink\'s close method'),
+ promise_rejects(t, error1, abortPromise,
+ 'abortPromise must reject with the error returned from the sink\'s close method'),
+ promise_rejects(t, error2, writer.closed,
+ 'writer.closed must reject with error2')
+ ]).then(() => {
+ assert_array_equals(events, ['closePromise', 'abortPromise', 'closed'],
+ 'promises must fulfill/reject in the expected order');
+ });
+ });
+}, 'promises must fulfill/reject in the expected order on aborted and errored closure');
+
+promise_test(t => {
+ let resolveWrite;
+ let controller;
+ const ws = new WritableStream({
+ write(chunk, c) {
+ controller = c;
+ return new Promise(resolve => {
+ resolveWrite = resolve;
+ });
+ }
+ });
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ const writePromise = writer.write('c');
+ controller.error(error1);
+ const closePromise = writer.close();
+ let closeRejected = false;
+ closePromise.catch(() => {
+ closeRejected = true;
+ });
+ return flushAsyncEvents().then(() => {
+ assert_false(closeRejected);
+ resolveWrite();
+ return Promise.all([
+ writePromise,
+ promise_rejects(t, error1, closePromise, 'close() should reject')
+ ]).then(() => {
+ assert_true(closeRejected);
+ });
+ });
+ });
+}, 'close() should not reject until no sink methods are in flight');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/constructor.js b/test/fixtures/web-platform-tests/streams/writable-streams/constructor.js
new file mode 100644
index 00000000000000..5f28c8be1dffb9
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/constructor.js
@@ -0,0 +1,190 @@
+'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({
+ start(c) {
+ controller = c;
+ }
+ });
+
+ // Now error the stream after its construction.
+ controller.error(error1);
+
+ const writer = ws.getWriter();
+
+ assert_equals(writer.desiredSize, null, 'desiredSize should be null');
+ return writer.closed.catch(r => {
+ assert_equals(r, error1, 'ws should be errored by the passed error');
+ });
+}, 'controller argument should be passed to start method');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ write(chunk, controller) {
+ controller.error(error1);
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return Promise.all([
+ writer.write('a'),
+ promise_rejects(t, error1, writer.closed, 'controller.error() in write() should error the stream')
+ ]);
+}, 'controller argument should be passed to write method');
+
+// Older versions of the standard had the controller argument passed to close(). It wasn't useful, and so has been
+// removed. This test remains to identify implementations that haven't been updated.
+promise_test(t => {
+ const ws = new WritableStream({
+ close(...args) {
+ t.step(() => {
+ assert_array_equals(args, [], 'no arguments should be passed to close');
+ });
+ }
+ });
+
+ return ws.getWriter().close();
+}, 'controller argument should not be passed to close method');
+
+promise_test(() => {
+ const ws = new WritableStream({}, {
+ highWaterMark: 1000,
+ size() { return 1; }
+ });
+
+ const writer = ws.getWriter();
+
+ assert_equals(writer.desiredSize, 1000, 'desiredSize should be 1000');
+ return writer.ready.then(v => {
+ assert_equals(v, undefined, 'ready promise should fulfill with undefined');
+ });
+}, 'highWaterMark should be reflected to desiredSize');
+
+promise_test(() => {
+ const ws = new WritableStream({}, {
+ highWaterMark: Infinity,
+ size() { return 0; }
+ });
+
+ const writer = ws.getWriter();
+
+ assert_equals(writer.desiredSize, Infinity, 'desiredSize should be Infinity');
+
+ return writer.ready;
+}, 'WritableStream should be writable and ready should fulfill immediately if the strategy does not apply ' +
+ 'backpressure');
+
+test(() => {
+ new WritableStream();
+}, 'WritableStream should be constructible with no arguments');
+
+test(() => {
+ const ws = new WritableStream({});
+
+ const writer = ws.getWriter();
+
+ assert_equals(typeof writer.write, 'function', 'writer should have a write method');
+ assert_equals(typeof writer.abort, 'function', 'writer should have an abort method');
+ assert_equals(typeof writer.close, 'function', 'writer should have a close method');
+
+ assert_equals(writer.desiredSize, 1, 'desiredSize should start at 1');
+
+ assert_not_equals(typeof writer.ready, 'undefined', 'writer should have a ready property');
+ assert_equals(typeof writer.ready.then, 'function', 'ready property should be thenable');
+ assert_not_equals(typeof writer.closed, 'undefined', 'writer should have a closed property');
+ assert_equals(typeof writer.closed.then, 'function', 'closed property should be thenable');
+}, 'WritableStream instances should have standard methods and properties');
+
+test(() => {
+ ['WritableStreamDefaultWriter', 'WritableStreamDefaultController'].forEach(c =>
+ assert_equals(typeof self[c], 'undefined', `${c} should not be exported`));
+}, 'private constructors should not be exported');
+
+test(() => {
+ let WritableStreamDefaultController;
+ new WritableStream({
+ start(c) {
+ WritableStreamDefaultController = c.constructor;
+ }
+ });
+
+ assert_throws(new TypeError(), () => new WritableStreamDefaultController({}),
+ 'constructor should throw a TypeError exception');
+}, 'WritableStreamDefaultController constructor should throw');
+
+test(() => {
+ let WritableStreamDefaultController;
+ const stream = new WritableStream({
+ start(c) {
+ WritableStreamDefaultController = c.constructor;
+ }
+ });
+
+ assert_throws(new TypeError(), () => new WritableStreamDefaultController(stream),
+ 'constructor should throw a TypeError exception');
+}, 'WritableStreamDefaultController constructor should throw when passed an initialised WritableStream');
+
+test(() => {
+ const stream = new WritableStream();
+ const writer = stream.getWriter();
+ const WritableStreamDefaultWriter = writer.constructor;
+ writer.releaseLock();
+ assert_throws(new TypeError(), () => new WritableStreamDefaultWriter({}),
+ 'constructor should throw a TypeError exception');
+}, 'WritableStreamDefaultWriter should throw unless passed a WritableStream');
+
+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();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/count-queuing-strategy.js b/test/fixtures/web-platform-tests/streams/writable-streams/count-queuing-strategy.js
new file mode 100644
index 00000000000000..56d6090f3db469
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/count-queuing-strategy.js
@@ -0,0 +1,129 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+test(() => {
+ new WritableStream({}, new CountQueuingStrategy({ highWaterMark: 4 }));
+}, 'Can construct a writable stream with a valid CountQueuingStrategy');
+
+promise_test(() => {
+ const dones = Object.create(null);
+
+ const ws = new WritableStream(
+ {
+ write(chunk) {
+ return new Promise(resolve => {
+ dones[chunk] = resolve;
+ });
+ }
+ },
+ new CountQueuingStrategy({ highWaterMark: 0 })
+ );
+
+ const writer = ws.getWriter();
+ let writePromiseB;
+ let writePromiseC;
+
+ return Promise.resolve().then(() => {
+ assert_equals(writer.desiredSize, 0, 'desiredSize should be initially 0');
+
+ const writePromiseA = writer.write('a');
+ assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after 1st write()');
+
+ writePromiseB = writer.write('b');
+ assert_equals(writer.desiredSize, -2, 'desiredSize should be -2 after 2nd write()');
+
+ dones.a();
+ return writePromiseA;
+ }).then(() => {
+ assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after completing 1st write()');
+
+ dones.b();
+ return writePromiseB;
+ }).then(() => {
+ assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after completing 2nd write()');
+
+ writePromiseC = writer.write('c');
+ assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after 3rd write()');
+
+ dones.c();
+ return writePromiseC;
+ }).then(() => {
+ assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after completing 3rd write()');
+ });
+}, 'Correctly governs the value of a WritableStream\'s state property (HWM = 0)');
+
+promise_test(() => {
+ const dones = Object.create(null);
+
+ const ws = new WritableStream(
+ {
+ write(chunk) {
+ return new Promise(resolve => {
+ dones[chunk] = resolve;
+ });
+ }
+ },
+ new CountQueuingStrategy({ highWaterMark: 4 })
+ );
+
+ const writer = ws.getWriter();
+ let writePromiseB;
+ let writePromiseC;
+ let writePromiseD;
+
+ return Promise.resolve().then(() => {
+ assert_equals(writer.desiredSize, 4, 'desiredSize should be initially 4');
+
+ const writePromiseA = writer.write('a');
+ assert_equals(writer.desiredSize, 3, 'desiredSize should be 3 after 1st write()');
+
+ writePromiseB = writer.write('b');
+ assert_equals(writer.desiredSize, 2, 'desiredSize should be 2 after 2nd write()');
+
+ writePromiseC = writer.write('c');
+ assert_equals(writer.desiredSize, 1, 'desiredSize should be 1 after 3rd write()');
+
+ writePromiseD = writer.write('d');
+ assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after 4th write()');
+
+ writer.write('e');
+ assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after 5th write()');
+
+ writer.write('f');
+ assert_equals(writer.desiredSize, -2, 'desiredSize should be -2 after 6th write()');
+
+ writer.write('g');
+ assert_equals(writer.desiredSize, -3, 'desiredSize should be -3 after 7th write()');
+
+ dones.a();
+ return writePromiseA;
+ }).then(() => {
+ assert_equals(writer.desiredSize, -2, 'desiredSize should be -2 after completing 1st write()');
+
+ dones.b();
+ return writePromiseB;
+ }).then(() => {
+ assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after completing 2nd write()');
+
+ dones.c();
+ return writePromiseC;
+ }).then(() => {
+ assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after completing 3rd write()');
+
+ writer.write('h');
+ assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after 8th write()');
+
+ dones.d();
+ return writePromiseD;
+ }).then(() => {
+ assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after completing 4th write()');
+
+ writer.write('i');
+ assert_equals(writer.desiredSize, -1, 'desiredSize should be -1 after 9th write()');
+ });
+}, 'Correctly governs the value of a WritableStream\'s state property (HWM = 4)');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/error.js b/test/fixtures/web-platform-tests/streams/writable-streams/error.js
new file mode 100644
index 00000000000000..511f5f7572b14f
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/error.js
@@ -0,0 +1,69 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+const error1 = new Error('error1');
+error1.name = 'error1';
+
+const error2 = new Error('error2');
+error2.name = 'error2';
+
+promise_test(t => {
+ const ws = new WritableStream({
+ start(controller) {
+ controller.error(error1);
+ }
+ });
+ return promise_rejects(t, error1, ws.getWriter().closed, 'stream should be errored');
+}, 'controller.error() should error the stream');
+
+test(() => {
+ let controller;
+ const ws = new WritableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ ws.abort();
+ controller.error(error1);
+}, 'controller.error() on erroring stream should not throw');
+
+promise_test(t => {
+ let controller;
+ const ws = new WritableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ controller.error(error1);
+ controller.error(error2);
+ return promise_rejects(t, error1, ws.getWriter().closed, 'first controller.error() should win');
+}, 'surplus calls to controller.error() should be a no-op');
+
+promise_test(() => {
+ let controller;
+ const ws = new WritableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ return ws.abort().then(() => {
+ controller.error(error1);
+ });
+}, 'controller.error() on errored stream should not throw');
+
+promise_test(() => {
+ let controller;
+ const ws = new WritableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ return ws.getWriter().close().then(() => {
+ controller.error(error1);
+ });
+}, 'controller.error() on closed stream should not throw');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/floating-point-total-queue-size.js b/test/fixtures/web-platform-tests/streams/writable-streams/floating-point-total-queue-size.js
new file mode 100644
index 00000000000000..932ac2715e70da
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/floating-point-total-queue-size.js
@@ -0,0 +1,92 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+// Due to the limitations of floating-point precision, the calculation of desiredSize sometimes gives different answers
+// than adding up the items in the queue would. It is important that implementations give the same result in these edge
+// cases so that developers do not come to depend on non-standard behaviour. See
+// https://github.com/whatwg/streams/issues/582 and linked issues for further discussion.
+
+promise_test(() => {
+ const writer = setupTestStream();
+
+ const writePromises = [
+ writer.write(2),
+ writer.write(Number.MAX_SAFE_INTEGER)
+ ];
+
+ assert_equals(writer.desiredSize, 0 - 2 - Number.MAX_SAFE_INTEGER,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (after writing two chunks)');
+
+ return Promise.all(writePromises).then(() => {
+ assert_equals(writer.desiredSize, 0, '[[queueTotalSize]] must clamp to 0 if it becomes negative');
+ });
+}, 'Floating point arithmetic must manifest near NUMBER.MAX_SAFE_INTEGER (total ends up positive)');
+
+promise_test(() => {
+ const writer = setupTestStream();
+
+ const writePromises = [
+ writer.write(1e-16),
+ writer.write(1)
+ ];
+
+ assert_equals(writer.desiredSize, 0 - 1e-16 - 1,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (after writing two chunks)');
+
+ return Promise.all(writePromises).then(() => {
+ assert_equals(writer.desiredSize, 0, '[[queueTotalSize]] must clamp to 0 if it becomes negative');
+ });
+}, 'Floating point arithmetic must manifest near 0 (total ends up positive, but clamped)');
+
+promise_test(() => {
+ const writer = setupTestStream();
+
+ const writePromises = [
+ writer.write(1e-16),
+ writer.write(1),
+ writer.write(2e-16)
+ ];
+
+ assert_equals(writer.desiredSize, 0 - 1e-16 - 1 - 2e-16,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (after writing three chunks)');
+
+ return Promise.all(writePromises).then(() => {
+ assert_equals(writer.desiredSize, 0 - 1e-16 - 1 - 2e-16 + 1e-16 + 1 + 2e-16,
+ 'desiredSize must be calculated using floating-point arithmetic (after the three chunks have finished writing)');
+ });
+}, 'Floating point arithmetic must manifest near 0 (total ends up positive, and not clamped)');
+
+promise_test(() => {
+ const writer = setupTestStream();
+
+ const writePromises = [
+ writer.write(2e-16),
+ writer.write(1)
+ ];
+
+ assert_equals(writer.desiredSize, 0 - 2e-16 - 1,
+ 'desiredSize must be calculated using double-precision floating-point arithmetic (after writing two chunks)');
+
+ return Promise.all(writePromises).then(() => {
+ assert_equals(writer.desiredSize, 0 - 2e-16 - 1 + 2e-16 + 1,
+ 'desiredSize must be calculated using floating-point arithmetic (after the two chunks have finished writing)');
+ });
+}, 'Floating point arithmetic must manifest near 0 (total ends up zero)');
+
+function setupTestStream() {
+ const strategy = {
+ size(x) {
+ return x;
+ },
+ highWaterMark: 0
+ };
+
+ const ws = new WritableStream({}, strategy);
+
+ return ws.getWriter();
+}
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/general.js b/test/fixtures/web-platform-tests/streams/writable-streams/general.js
new file mode 100644
index 00000000000000..1fd041b54677f2
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/general.js
@@ -0,0 +1,251 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+test(() => {
+ const ws = new WritableStream({});
+ const writer = ws.getWriter();
+ writer.releaseLock();
+
+ assert_throws(new TypeError(), () => writer.desiredSize, 'desiredSize should throw a TypeError');
+}, 'desiredSize on a released writer');
+
+test(() => {
+ const ws = new WritableStream({});
+
+ const writer = ws.getWriter();
+
+ assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
+}, 'desiredSize initial value');
+
+promise_test(() => {
+ const ws = new WritableStream({});
+
+ const writer = ws.getWriter();
+
+ writer.close();
+
+ return writer.closed.then(() => {
+ assert_equals(writer.desiredSize, 0, 'desiredSize should be 0');
+ });
+}, 'desiredSize on a writer for a closed stream');
+
+test(() => {
+ const ws = new WritableStream({
+ start(c) {
+ c.error();
+ }
+ });
+
+ const writer = ws.getWriter();
+ assert_equals(writer.desiredSize, null, 'desiredSize should be null');
+}, 'desiredSize on a writer for an errored stream');
+
+test(() => {
+ const ws = new WritableStream({});
+
+ const writer = ws.getWriter();
+ writer.close();
+ writer.releaseLock();
+
+ ws.getWriter();
+}, 'ws.getWriter() on a closing WritableStream');
+
+promise_test(() => {
+ const ws = new WritableStream({});
+
+ const writer = ws.getWriter();
+ return writer.close().then(() => {
+ writer.releaseLock();
+
+ ws.getWriter();
+ });
+}, 'ws.getWriter() on a closed WritableStream');
+
+test(() => {
+ const ws = new WritableStream({});
+
+ const writer = ws.getWriter();
+ writer.abort();
+ writer.releaseLock();
+
+ ws.getWriter();
+}, 'ws.getWriter() on an aborted WritableStream');
+
+promise_test(() => {
+ const ws = new WritableStream({
+ start(c) {
+ c.error();
+ }
+ });
+
+ const writer = ws.getWriter();
+ return writer.closed.then(
+ v => assert_unreached('writer.closed fulfilled unexpectedly with: ' + v),
+ () => {
+ writer.releaseLock();
+
+ ws.getWriter();
+ }
+ );
+}, 'ws.getWriter() on an errored WritableStream');
+
+promise_test(() => {
+ const ws = new WritableStream({});
+
+ const writer = ws.getWriter();
+ writer.releaseLock();
+
+ return writer.closed.then(
+ v => assert_unreached('writer.closed fulfilled unexpectedly with: ' + v),
+ closedRejection => {
+ assert_equals(closedRejection.name, 'TypeError', 'closed promise should reject with a TypeError');
+ return writer.ready.then(
+ v => assert_unreached('writer.ready fulfilled unexpectedly with: ' + v),
+ readyRejection => assert_equals(readyRejection, closedRejection,
+ 'ready promise should reject with the same error')
+ );
+ }
+ );
+}, 'closed and ready on a released writer');
+
+promise_test(t => {
+ let thisObject = null;
+ // Calls to Sink methods after the first are implicitly ignored. Only the first value that is passed to the resolver
+ // is used.
+ class Sink {
+ start() {
+ // Called twice
+ t.step(() => {
+ assert_equals(this, thisObject, 'start should be called as a method');
+ });
+ }
+
+ write() {
+ t.step(() => {
+ assert_equals(this, thisObject, 'write should be called as a method');
+ });
+ }
+
+ close() {
+ t.step(() => {
+ assert_equals(this, thisObject, 'close should be called as a method');
+ });
+ }
+
+ abort() {
+ t.step(() => {
+ assert_equals(this, thisObject, 'abort should be called as a method');
+ });
+ }
+ }
+
+ const theSink = new Sink();
+ thisObject = theSink;
+ const ws = new WritableStream(theSink);
+
+ const writer = ws.getWriter();
+
+ writer.write('a');
+ const closePromise = writer.close();
+
+ const ws2 = new WritableStream(theSink);
+ const writer2 = ws2.getWriter();
+ const abortPromise = writer2.abort();
+
+ return Promise.all([
+ closePromise,
+ abortPromise
+ ]);
+}, 'WritableStream should call underlying sink methods as methods');
+
+promise_test(t => {
+ function functionWithOverloads() {}
+ functionWithOverloads.apply = t.unreached_func('apply() should not be called');
+ functionWithOverloads.call = t.unreached_func('call() should not be called');
+ const underlyingSink = {
+ start: functionWithOverloads,
+ write: functionWithOverloads,
+ close: functionWithOverloads,
+ abort: functionWithOverloads
+ };
+ // Test start(), write(), close().
+ const ws1 = new WritableStream(underlyingSink);
+ const writer1 = ws1.getWriter();
+ writer1.write('a');
+ writer1.close();
+
+ // Test abort().
+ const abortError = new Error();
+ abortError.name = 'abort error';
+
+ const ws2 = new WritableStream(underlyingSink);
+ const writer2 = ws2.getWriter();
+ writer2.abort(abortError);
+
+ // Test abort() with a close underlying sink method present. (Historical; see
+ // https://github.com/whatwg/streams/issues/620#issuecomment-263483953 for what used to be
+ // tested here. But more coverage can't hurt.)
+ const ws3 = new WritableStream({
+ start: functionWithOverloads,
+ write: functionWithOverloads,
+ close: functionWithOverloads
+ });
+ const writer3 = ws3.getWriter();
+ writer3.abort(abortError);
+
+ return writer1.closed
+ .then(() => promise_rejects(t, abortError, writer2.closed, 'writer2.closed should be rejected'))
+ .then(() => promise_rejects(t, abortError, writer3.closed, 'writer3.closed should be rejected'));
+}, 'methods should not not have .apply() or .call() called');
+
+promise_test(() => {
+ const strategy = {
+ size() {
+ if (this !== undefined) {
+ throw new Error('size called as a method');
+ }
+ return 1;
+ }
+ };
+
+ const ws = new WritableStream({}, strategy);
+ const writer = ws.getWriter();
+ return writer.write('a');
+}, 'WritableStream\'s strategy.size should not be called as a method');
+
+promise_test(() => {
+ const ws = new WritableStream();
+ const writer1 = ws.getWriter();
+ assert_equals(undefined, writer1.releaseLock(), 'releaseLock() should return undefined');
+ const writer2 = ws.getWriter();
+ assert_equals(undefined, writer1.releaseLock(), 'no-op releaseLock() should return undefined');
+ // Calling releaseLock() on writer1 should not interfere with writer2. If it did, then the ready promise would be
+ // rejected.
+ return writer2.ready;
+}, 'redundant releaseLock() is no-op');
+
+promise_test(() => {
+ const events = [];
+ const ws = new WritableStream();
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ // Force the ready promise back to a pending state.
+ const writerPromise = writer.write('dummy');
+ const readyPromise = writer.ready.catch(() => events.push('ready'));
+ const closedPromise = writer.closed.catch(() => events.push('closed'));
+ writer.releaseLock();
+ return Promise.all([readyPromise, closedPromise]).then(() => {
+ assert_array_equals(events, ['ready', 'closed'], 'ready promise should fire before closed promise');
+ // Stop the writer promise hanging around after the test has finished.
+ return Promise.all([
+ writerPromise,
+ ws.abort()
+ ]);
+ });
+ });
+}, 'ready promise should fire before closed on releaseLock');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/properties.js b/test/fixtures/web-platform-tests/streams/writable-streams/properties.js
new file mode 100644
index 00000000000000..7f420a79b6f1f4
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/properties.js
@@ -0,0 +1,225 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+}
+
+// The purpose of this file is to test for objects, attributes and arguments that should not exist.
+// The test cases are generated from data tables to reduce duplication.
+
+// Courtesy of André Bargull. Source is https://esdiscuss.org/topic/isconstructor#content-11.
+function IsConstructor(o) {
+ try {
+ new new Proxy(o, { construct: () => ({}) })();
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+for (const func of ['WritableStreamDefaultController', 'WritableStreamDefaultWriter']) {
+ test(() => {
+ assert_equals(self[func], undefined, `${func} should not be defined`);
+ }, `${func} should not be exported on the global object`);
+}
+
+// Now get hold of the symbols so we can test their properties.
+self.WritableStreamDefaultController = (() => {
+ let controller;
+ new WritableStream({
+ start(c) {
+ controller = c;
+ }
+ });
+ return controller.constructor;
+})();
+self.WritableStreamDefaultWriter = new WritableStream().getWriter().constructor;
+
+const expected = {
+ WritableStream: {
+ constructor: {
+ type: 'constructor',
+ length: 0
+ },
+ locked: {
+ type: 'getter'
+ },
+ abort: {
+ type: 'method',
+ length: 1
+ },
+ getWriter: {
+ type: 'method',
+ length: 0
+ }
+ },
+ WritableStreamDefaultController: {
+ constructor: {
+ type: 'constructor',
+ length: 0
+ },
+ error: {
+ type: 'method',
+ length: 1
+ }
+ },
+ WritableStreamDefaultWriter: {
+ constructor: {
+ type: 'constructor',
+ length: 1
+ },
+ closed: {
+ type: 'getter'
+ },
+ desiredSize: {
+ type: 'getter'
+ },
+ ready: {
+ type: 'getter'
+ },
+ abort: {
+ type: 'method',
+ length: 1
+ },
+ close: {
+ type: 'method',
+ length: 0
+ },
+ releaseLock: {
+ type: 'method',
+ length: 0
+ },
+ write: {
+ type: 'method',
+ length: 1
+ }
+ }
+};
+
+for (const c in expected) {
+ const properties = expected[c];
+ const prototype = self[c].prototype;
+ for (const name in properties) {
+ const fullName = `${c}.prototype.${name}`;
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
+ test(() => {
+ const { configurable, enumerable } = descriptor;
+ assert_true(configurable, `${name} should be configurable`);
+ assert_false(enumerable, `${name} should not be enumerable`);
+ }, `${fullName} should have standard properties`);
+ const type = properties[name].type;
+ switch (type) {
+ case 'getter':
+ test(() => {
+ const { writable, get, set } = descriptor;
+ assert_equals(writable, undefined, `${name} should not be a data descriptor`);
+ assert_equals(typeof get, 'function', `${name} should have a getter`);
+ assert_equals(set, undefined, `${name} should not have a setter`);
+ }, `${fullName} should be a getter`);
+ break;
+
+ case 'constructor':
+ case 'method':
+ test(() => {
+ assert_true(descriptor.writable, `${name} should be writable`);
+ assert_equals(typeof prototype[name], 'function', `${name} should be a function`);
+ assert_equals(prototype[name].length, properties[name].length,
+ `${name} should take ${properties[name].length} arguments`);
+ if (type === 'constructor') {
+ assert_true(IsConstructor(prototype[name]), `${name} should be a constructor`);
+ assert_equals(prototype[name].name, c, `${name}.name should be '${c}'`);
+ } else {
+ assert_false(IsConstructor(prototype[name]), `${name} should not be a constructor`);
+ assert_equals(prototype[name].name, name, `${name}.name should be '${name}`);
+ }
+ }, `${fullName} should be a ${type}`);
+ break;
+ }
+ }
+ test(() => {
+ const expectedPropertyNames = Object.keys(properties).sort();
+ const actualPropertyNames = Object.getOwnPropertyNames(prototype).sort();
+ assert_array_equals(actualPropertyNames, expectedPropertyNames,
+ `${c} properties should match expected properties`);
+ }, `${c}.prototype should have exactly the expected properties`);
+}
+
+const sinkMethods = {
+ start: {
+ length: 1,
+ trigger: () => Promise.resolve()
+ },
+ write: {
+ length: 2,
+ trigger: writer => writer.write()
+ },
+ close: {
+ length: 0,
+ trigger: writer => writer.close()
+ },
+ abort: {
+ length: 1,
+ trigger: writer => writer.abort()
+ }
+};
+
+for (const method in sinkMethods) {
+ const { length, trigger } = sinkMethods[method];
+
+ // Some semantic tests of how sink methods are called can be found in general.js, as well as in the test files
+ // specific to each method.
+ promise_test(() => {
+ let argCount;
+ const ws = new WritableStream({
+ [method](...args) {
+ argCount = args.length;
+ }
+ });
+ return Promise.resolve(trigger(ws.getWriter())).then(() => {
+ assert_equals(argCount, length, `${method} should be called with ${length} arguments`);
+ });
+ }, `sink method ${method} should be called with the right number of arguments`);
+
+ promise_test(() => {
+ let methodWasCalled = false;
+ function Sink() {}
+ Sink.prototype = {
+ [method]() {
+ methodWasCalled = true;
+ }
+ };
+ const ws = new WritableStream(new Sink());
+ return Promise.resolve(trigger(ws.getWriter())).then(() => {
+ assert_true(methodWasCalled, `${method} should be called`);
+ });
+ }, `sink method ${method} should be called even when it's located on the prototype chain`);
+
+ promise_test(t => {
+ const unreachedTraps = ['getPrototypeOf', 'setPrototypeOf', 'isExtensible', 'preventExtensions',
+ 'getOwnPropertyDescriptor', 'defineProperty', 'has', 'set', 'deleteProperty', 'ownKeys',
+ 'apply', 'construct'];
+ const touchedProperties = [];
+ const handler = {
+ get: t.step_func((target, property) => {
+ touchedProperties.push(property);
+ if (property === 'type') {
+ return undefined;
+ }
+ return () => Promise.resolve();
+ })
+ };
+ for (const trap of unreachedTraps) {
+ handler[trap] = t.unreached_func(`${trap} should not be trapped`);
+ }
+ const sink = new Proxy({}, handler);
+ const ws = new WritableStream(sink);
+ assert_array_equals(touchedProperties, ['type', 'write', 'close', 'abort', 'start'],
+ 'expected properties should be got');
+ return trigger(ws.getWriter()).then(() => {
+ assert_array_equals(touchedProperties, ['type', 'write', 'close', 'abort', 'start'],
+ 'no properties should be accessed on method call');
+ });
+ }, `unexpected properties should not be accessed when calling sink method ${method}`);
+}
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/reentrant-strategy.js b/test/fixtures/web-platform-tests/streams/writable-streams/reentrant-strategy.js
new file mode 100644
index 00000000000000..6e1b52c986c2b0
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/reentrant-strategy.js
@@ -0,0 +1,179 @@
+'use strict';
+
+// These tests exercise the pathological case of calling WritableStream* methods from within the strategy.size()
+// callback. This is not something any real code should ever do. Failures here indicate subtle deviations from the
+// standard that may affect real, non-pathological code.
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+const error1 = { name: 'error1' };
+
+promise_test(() => {
+ let writer;
+ const strategy = {
+ size(chunk) {
+ if (chunk > 0) {
+ writer.write(chunk - 1);
+ }
+ return chunk;
+ }
+ };
+
+ const ws = recordingWritableStream({}, strategy);
+ writer = ws.getWriter();
+ return writer.write(2)
+ .then(() => {
+ assert_array_equals(ws.events, ['write', 0, 'write', 1, 'write', 2], 'writes should appear in order');
+ });
+}, 'writes should be written in the standard order');
+
+promise_test(() => {
+ let writer;
+ const events = [];
+ const strategy = {
+ size(chunk) {
+ events.push('size', chunk);
+ if (chunk > 0) {
+ writer.write(chunk - 1)
+ .then(() => events.push('writer.write done', chunk - 1));
+ }
+ return chunk;
+ }
+ };
+ const ws = new WritableStream({
+ write(chunk) {
+ events.push('sink.write', chunk);
+ }
+ }, strategy);
+ writer = ws.getWriter();
+ return writer.write(2)
+ .then(() => events.push('writer.write done', 2))
+ .then(() => flushAsyncEvents())
+ .then(() => {
+ assert_array_equals(events, ['size', 2, 'size', 1, 'size', 0,
+ 'sink.write', 0, 'sink.write', 1, 'writer.write done', 0,
+ 'sink.write', 2, 'writer.write done', 1,
+ 'writer.write done', 2],
+ 'events should happen in standard order');
+ });
+}, 'writer.write() promises should resolve in the standard order');
+
+promise_test(t => {
+ let controller;
+ const strategy = {
+ size() {
+ controller.error(error1);
+ return 1;
+ }
+ };
+ const ws = recordingWritableStream({
+ start(c) {
+ controller = c;
+ }
+ }, strategy);
+ const resolved = [];
+ const writer = ws.getWriter();
+ const readyPromise1 = writer.ready.then(() => resolved.push('ready1'));
+ const writePromise = promise_rejects(t, error1, writer.write(),
+ 'write() should reject with the error')
+ .then(() => resolved.push('write'));
+ const readyPromise2 = promise_rejects(t, error1, writer.ready, 'ready should reject with error1')
+ .then(() => resolved.push('ready2'));
+ const closedPromise = promise_rejects(t, error1, writer.closed, 'closed should reject with error1')
+ .then(() => resolved.push('closed'));
+ return Promise.all([readyPromise1, writePromise, readyPromise2, closedPromise])
+ .then(() => {
+ assert_array_equals(resolved, ['ready1', 'write', 'ready2', 'closed'],
+ 'promises should resolve in standard order');
+ assert_array_equals(ws.events, [], 'underlying sink write should not be called');
+ });
+}, 'controller.error() should work when called from within strategy.size()');
+
+promise_test(t => {
+ let writer;
+ const strategy = {
+ size() {
+ writer.close();
+ return 1;
+ }
+ };
+
+ const ws = recordingWritableStream({}, strategy);
+ writer = ws.getWriter();
+ return promise_rejects(t, new TypeError(), writer.write('a'), 'write() promise should reject')
+ .then(() => {
+ assert_array_equals(ws.events, ['close'], 'sink.write() should not be called');
+ });
+}, 'close() should work when called from within strategy.size()');
+
+promise_test(t => {
+ let writer;
+ const strategy = {
+ size() {
+ writer.abort(error1);
+ return 1;
+ }
+ };
+
+ const ws = recordingWritableStream({}, strategy);
+ writer = ws.getWriter();
+ return promise_rejects(t, error1, writer.write('a'), 'write() promise should reject')
+ .then(() => {
+ assert_array_equals(ws.events, ['abort', error1], 'sink.write() should not be called');
+ });
+}, 'abort() should work when called from within strategy.size()');
+
+promise_test(t => {
+ let writer;
+ const strategy = {
+ size() {
+ writer.releaseLock();
+ return 1;
+ }
+ };
+
+ const ws = recordingWritableStream({}, strategy);
+ writer = ws.getWriter();
+ const writePromise = promise_rejects(t, new TypeError(), writer.write('a'), 'write() promise should reject');
+ const readyPromise = promise_rejects(t, new TypeError(), writer.ready, 'ready promise should reject');
+ const closedPromise = promise_rejects(t, new TypeError(), writer.closed, 'closed promise should reject');
+ return Promise.all([writePromise, readyPromise, closedPromise])
+ .then(() => {
+ assert_array_equals(ws.events, [], 'sink.write() should not be called');
+ });
+}, 'releaseLock() should abort the write() when called within strategy.size()');
+
+promise_test(t => {
+ let writer1;
+ let ws;
+ let writePromise2;
+ let closePromise;
+ let closedPromise2;
+ const strategy = {
+ size(chunk) {
+ if (chunk > 0) {
+ writer1.releaseLock();
+ const writer2 = ws.getWriter();
+ writePromise2 = writer2.write(0);
+ closePromise = writer2.close();
+ closedPromise2 = writer2.closed;
+ }
+ return 1;
+ }
+ };
+ ws = recordingWritableStream({}, strategy);
+ writer1 = ws.getWriter();
+ const writePromise1 = promise_rejects(t, new TypeError(), writer1.write(1), 'write() promise should reject');
+ const readyPromise = promise_rejects(t, new TypeError(), writer1.ready, 'ready promise should reject');
+ const closedPromise1 = promise_rejects(t, new TypeError(), writer1.closed, 'closed promise should reject');
+ return Promise.all([writePromise1, readyPromise, closedPromise1, writePromise2, closePromise, closedPromise2])
+ .then(() => {
+ assert_array_equals(ws.events, ['write', 0, 'close'], 'sink.write() should only be called once');
+ });
+}, 'original reader should error when new reader is created within strategy.size()');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/start.js b/test/fixtures/web-platform-tests/streams/writable-streams/start.js
new file mode 100644
index 00000000000000..52bcb28317189c
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/start.js
@@ -0,0 +1,168 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+const error1 = { name: 'error1' };
+
+promise_test(() => {
+ let resolveStartPromise;
+ const ws = recordingWritableStream({
+ start() {
+ return new Promise(resolve => {
+ resolveStartPromise = resolve;
+ });
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
+ writer.write('a');
+ assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after writer.write()');
+
+ // Wait and verify that write isn't called.
+ return flushAsyncEvents()
+ .then(() => {
+ assert_array_equals(ws.events, [], 'write should not be called until start promise resolves');
+ resolveStartPromise();
+ return writer.ready;
+ })
+ .then(() => assert_array_equals(ws.events, ['write', 'a'],
+ 'write should not be called until start promise resolves'));
+}, 'underlying sink\'s write should not be called until start finishes');
+
+promise_test(() => {
+ let resolveStartPromise;
+ const ws = recordingWritableStream({
+ start() {
+ return new Promise(resolve => {
+ resolveStartPromise = resolve;
+ });
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ writer.close();
+ assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
+
+ // Wait and verify that write isn't called.
+ return flushAsyncEvents().then(() => {
+ assert_array_equals(ws.events, [], 'close should not be called until start promise resolves');
+ resolveStartPromise();
+ return writer.closed;
+ });
+}, 'underlying sink\'s close should not be called until start finishes');
+
+test(() => {
+ const passedError = new Error('horrible things');
+
+ let writeCalled = false;
+ let closeCalled = false;
+ assert_throws(passedError, () => {
+ // recordingWritableStream cannot be used here because the exception in the
+ // constructor prevents assigning the object to a variable.
+ new WritableStream({
+ start() {
+ throw passedError;
+ },
+ write() {
+ writeCalled = true;
+ },
+ close() {
+ closeCalled = true;
+ }
+ });
+ }, 'constructor should throw passedError');
+ assert_false(writeCalled, 'write should not be called');
+ assert_false(closeCalled, 'close should not be called');
+}, 'underlying sink\'s write or close should not be called if start throws');
+
+promise_test(() => {
+ const ws = recordingWritableStream({
+ start() {
+ return Promise.reject();
+ }
+ });
+
+ // Wait and verify that write or close aren't called.
+ return flushAsyncEvents()
+ .then(() => assert_array_equals(ws.events, [], 'write and close should not be called'));
+}, 'underlying sink\'s write or close should not be invoked if the promise returned by start is rejected');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ start() {
+ return {
+ then(onFulfilled, onRejected) { onRejected(error1); }
+ };
+ }
+ });
+ return promise_rejects(t, error1, ws.getWriter().closed, 'closed promise should be rejected');
+}, 'returning a thenable from start() should work');
+
+promise_test(t => {
+ const ws = recordingWritableStream({
+ start(controller) {
+ controller.error(error1);
+ }
+ });
+ return promise_rejects(t, error1, ws.getWriter().write('a'), 'write() should reject with the error')
+ .then(() => {
+ assert_array_equals(ws.events, [], 'sink write() should not have been called');
+ });
+}, 'controller.error() during start should cause writes to fail');
+
+promise_test(t => {
+ let controller;
+ let resolveStart;
+ const ws = recordingWritableStream({
+ start(c) {
+ controller = c;
+ return new Promise(resolve => {
+ resolveStart = resolve;
+ });
+ }
+ });
+ const writer = ws.getWriter();
+ const writePromise = writer.write('a');
+ const closePromise = writer.close();
+ controller.error(error1);
+ resolveStart();
+ return Promise.all([
+ promise_rejects(t, error1, writePromise, 'write() should fail'),
+ promise_rejects(t, error1, closePromise, 'close() should fail')
+ ]).then(() => {
+ assert_array_equals(ws.events, [], 'sink write() and close() should not have been called');
+ });
+}, 'controller.error() during async start should cause existing writes to fail');
+
+promise_test(t => {
+ const events = [];
+ const promises = [];
+ function catchAndRecord(promise, name) {
+ promises.push(promise.then(t.unreached_func(`promise ${name} should not resolve`),
+ () => {
+ events.push(name);
+ }));
+ }
+ const ws = new WritableStream({
+ start() {
+ return Promise.reject();
+ }
+ }, { highWaterMark: 0 });
+ const writer = ws.getWriter();
+ catchAndRecord(writer.ready, 'ready');
+ catchAndRecord(writer.closed, 'closed');
+ catchAndRecord(writer.write(), 'write');
+ return Promise.all(promises)
+ .then(() => {
+ assert_array_equals(events, ['ready', 'write', 'closed'], 'promises should reject in standard order');
+ });
+}, 'when start() rejects, writer promises should reject in standard order');
+
+done();
diff --git a/test/fixtures/web-platform-tests/streams/writable-streams/write.js b/test/fixtures/web-platform-tests/streams/writable-streams/write.js
new file mode 100644
index 00000000000000..7d040f8ef74ced
--- /dev/null
+++ b/test/fixtures/web-platform-tests/streams/writable-streams/write.js
@@ -0,0 +1,258 @@
+'use strict';
+
+if (self.importScripts) {
+ self.importScripts('/resources/testharness.js');
+ self.importScripts('../resources/test-utils.js');
+ self.importScripts('../resources/recording-streams.js');
+}
+
+const error1 = new Error('error1');
+error1.name = 'error1';
+
+const error2 = new Error('error2');
+error2.name = 'error2';
+
+function writeArrayToStream(array, writableStreamWriter) {
+ array.forEach(chunk => writableStreamWriter.write(chunk));
+ return writableStreamWriter.close();
+}
+
+promise_test(() => {
+ let storage;
+ const ws = new WritableStream({
+ start() {
+ storage = [];
+ },
+
+ write(chunk) {
+ return delay(0).then(() => storage.push(chunk));
+ },
+
+ close() {
+ return delay(0);
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ const input = [1, 2, 3, 4, 5];
+ return writeArrayToStream(input, writer)
+ .then(() => assert_array_equals(storage, input, 'correct data should be relayed to underlying sink'));
+}, 'WritableStream should complete asynchronous writes before close resolves');
+
+promise_test(() => {
+ const ws = recordingWritableStream();
+
+ const writer = ws.getWriter();
+
+ const input = [1, 2, 3, 4, 5];
+ return writeArrayToStream(input, writer)
+ .then(() => assert_array_equals(ws.events, ['write', 1, 'write', 2, 'write', 3, 'write', 4, 'write', 5, 'close'],
+ 'correct data should be relayed to underlying sink'));
+}, 'WritableStream should complete synchronous writes before close resolves');
+
+promise_test(() => {
+ const ws = new WritableStream({
+ write() {
+ return 'Hello';
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ const writePromise = writer.write('a');
+ return writePromise
+ .then(value => assert_equals(value, undefined, 'fulfillment value must be undefined'));
+}, 'fulfillment value of ws.write() call should be undefined even if the underlying sink returns a non-undefined ' +
+ 'value');
+
+promise_test(() => {
+ let resolveSinkWritePromise;
+ const ws = new WritableStream({
+ write() {
+ return new Promise(resolve => {
+ resolveSinkWritePromise = resolve;
+ });
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
+
+ return writer.ready.then(() => {
+ const writePromise = writer.write('a');
+ let writePromiseResolved = false;
+ assert_not_equals(resolveSinkWritePromise, undefined, 'resolveSinkWritePromise should not be undefined');
+
+ assert_equals(writer.desiredSize, 0, 'desiredSize should be 0 after writer.write()');
+
+ return Promise.all([
+ writePromise.then(value => {
+ writePromiseResolved = true;
+ assert_equals(resolveSinkWritePromise, undefined, 'sinkWritePromise should be fulfilled before writePromise');
+
+ assert_equals(value, undefined, 'writePromise should be fulfilled with undefined');
+ }),
+ writer.ready.then(value => {
+ assert_equals(resolveSinkWritePromise, undefined, 'sinkWritePromise should be fulfilled before writer.ready');
+ assert_true(writePromiseResolved, 'writePromise should be fulfilled before writer.ready');
+
+ assert_equals(writer.desiredSize, 1, 'desiredSize should be 1 again');
+
+ assert_equals(value, undefined, 'writePromise should be fulfilled with undefined');
+ }),
+ flushAsyncEvents().then(() => {
+ resolveSinkWritePromise();
+ resolveSinkWritePromise = undefined;
+ })
+ ]);
+ });
+}, 'WritableStream should transition to waiting until write is acknowledged');
+
+promise_test(t => {
+ let sinkWritePromiseRejectors = [];
+ const ws = new WritableStream({
+ write() {
+ const sinkWritePromise = new Promise((r, reject) => sinkWritePromiseRejectors.push(reject));
+ return sinkWritePromise;
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
+
+ return writer.ready.then(() => {
+ const writePromise = writer.write('a');
+ assert_equals(sinkWritePromiseRejectors.length, 1, 'there should be 1 rejector');
+ assert_equals(writer.desiredSize, 0, 'desiredSize should be 0');
+
+ const writePromise2 = writer.write('b');
+ assert_equals(sinkWritePromiseRejectors.length, 1, 'there should be still 1 rejector');
+ assert_equals(writer.desiredSize, -1, 'desiredSize should be -1');
+
+ const closedPromise = writer.close();
+
+ assert_equals(writer.desiredSize, -1, 'desiredSize should still be -1');
+
+ return Promise.all([
+ promise_rejects(t, error1, closedPromise,
+ 'closedPromise should reject with the error returned from the sink\'s write method')
+ .then(() => assert_equals(sinkWritePromiseRejectors.length, 0,
+ 'sinkWritePromise should reject before closedPromise')),
+ promise_rejects(t, error1, writePromise,
+ 'writePromise should reject with the error returned from the sink\'s write method')
+ .then(() => assert_equals(sinkWritePromiseRejectors.length, 0,
+ 'sinkWritePromise should reject before writePromise')),
+ promise_rejects(t, error1, writePromise2,
+ 'writePromise2 should reject with the error returned from the sink\'s write method')
+ .then(() => assert_equals(sinkWritePromiseRejectors.length, 0,
+ 'sinkWritePromise should reject before writePromise2')),
+ flushAsyncEvents().then(() => {
+ sinkWritePromiseRejectors[0](error1);
+ sinkWritePromiseRejectors = [];
+ })
+ ]);
+ });
+}, 'when write returns a rejected promise, queued writes and close should be cleared');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ write() {
+ throw error1;
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error1, writer.write('a'),
+ 'write() should reject with the error returned from the sink\'s write method')
+ .then(() => promise_rejects(t, new TypeError(), writer.close(), 'close() should be rejected'));
+}, 'when sink\'s write throws an error, the stream should become errored and the promise should reject');
+
+promise_test(t => {
+ const ws = new WritableStream({
+ write(chunk, controller) {
+ controller.error(error1);
+ throw error2;
+ }
+ });
+
+ const writer = ws.getWriter();
+
+ return promise_rejects(t, error2, writer.write('a'),
+ 'write() should reject with the error returned from the sink\'s write method ')
+ .then(() => {
+ return Promise.all([
+ promise_rejects(t, error1, writer.ready,
+ 'writer.ready must reject with the error passed to the controller'),
+ promise_rejects(t, error1, writer.closed,
+ 'writer.closed must reject with the error passed to the controller')
+ ]);
+ });
+}, 'writer.write(), ready and closed reject with the error passed to controller.error() made before sink.write' +
+ ' rejection');
+
+promise_test(() => {
+ const numberOfWrites = 1000;
+
+ let resolveFirstWritePromise;
+ let writeCount = 0;
+ const ws = new WritableStream({
+ write() {
+ ++writeCount;
+ if (!resolveFirstWritePromise) {
+ return new Promise(resolve => {
+ resolveFirstWritePromise = resolve;
+ });
+ }
+ return Promise.resolve();
+ }
+ });
+
+ const writer = ws.getWriter();
+ return writer.ready.then(() => {
+ for (let i = 1; i < numberOfWrites; ++i) {
+ writer.write('a');
+ }
+ const writePromise = writer.write('a');
+
+ assert_equals(writeCount, 1, 'should have called sink\'s write once');
+
+ resolveFirstWritePromise();
+
+ return writePromise
+ .then(() =>
+ assert_equals(writeCount, numberOfWrites, `should have called sink's write ${numberOfWrites} times`));
+ });
+}, 'a large queue of writes should be processed completely');
+
+promise_test(() => {
+ const stream = recordingWritableStream();
+ const w = stream.getWriter();
+ const WritableStreamDefaultWriter = w.constructor;
+ w.releaseLock();
+ const writer = new WritableStreamDefaultWriter(stream);
+ return writer.ready.then(() => {
+ writer.write('a');
+ assert_array_equals(stream.events, ['write', 'a'], 'write() should be passed to sink');
+ });
+}, 'WritableStreamDefaultWriter should work when manually constructed');
+
+promise_test(() => {
+ let thenCalled = false;
+ const ws = new WritableStream({
+ write() {
+ return {
+ then(onFulfilled) {
+ thenCalled = true;
+ onFulfilled();
+ }
+ };
+ }
+ });
+ return ws.getWriter().write('a').then(() => assert_true(thenCalled, 'thenCalled should be true'));
+}, 'returning a thenable from write() should work');
+
+done();
diff --git a/test/parallel/test-stream-acquire-standard.js b/test/parallel/test-stream-acquire-standard.js
new file mode 100644
index 00000000000000..d82605d74cca7c
--- /dev/null
+++ b/test/parallel/test-stream-acquire-standard.js
@@ -0,0 +1,103 @@
+'use strict';
+
+require('../common');
+const assert = require('assert');
+
+const { Readable, Writable, Transform } = require('stream');
+
+{
+ class RT extends Readable {
+ constructor() {
+ super({ objectMode: true });
+
+ this.i = 0;
+ }
+
+ _read() {
+ if (this.i === 30) {
+ this.push(null);
+ } else {
+ this.push(this.i);
+ }
+ this.i++;
+ }
+ }
+
+ const stream = new RT().acquireStandardStream();
+ const reader = stream.getReader();
+ const q = [];
+ reader.read().then(function read({ value, done }) {
+ if (done) {
+ return;
+ }
+ q.push(value);
+ return reader.read().then(read);
+ }).then(() => {
+ assert.deepStrictEqual(q, Array.from({ length: 30 }, (_, i) => i));
+ });
+}
+
+{
+ const q = [];
+
+ class WT extends Writable {
+ constructor() {
+ super({ objectMode: true });
+ }
+
+ _write(chunk) {
+ q.push(chunk);
+ }
+
+ _final(callback) {
+ assert.deepStrictEqual(q, Array.from({ length: 30 }, (_, i) => i));
+ callback();
+ }
+ }
+
+ const stream = new WT().acquireStandardStream();
+ const writer = stream.getWriter();
+ let i = 0;
+ writer.write(i).then(function cb() {
+ if (i === 30) {
+ return writer.close();
+ }
+ i++;
+ return writer.write(i).then(cb);
+ });
+}
+
+{
+ class TT extends Transform {
+ constructor() {
+ super({ objectMode: true });
+ }
+
+ _transform(chunk, encoding, callback) {
+ this.push(chunk * 2);
+ }
+ }
+
+ const stream = new TT().acquireStandardStream();
+ const writer = stream.writable.getWriter();
+ let i = 0;
+ writer.write(i).then(function cb() {
+ if (i === 30) {
+ return writer.close();
+ }
+ i++;
+ return writer.write(i).then(cb);
+ });
+
+ const reader = stream.readable.getReader();
+ const q = [];
+ reader.read().then(function read({ value, done }) {
+ if (done) {
+ return;
+ }
+ q.push(value);
+ return reader.read().then(read);
+ }).then(() => {
+ assert.deepStrictEqual(q, Array.from({ length: 30 }, (_, i) => i));
+ });
+}
diff --git a/test/wpt.js b/test/wpt.js
new file mode 100644
index 00000000000000..904d2b7eb4feb6
--- /dev/null
+++ b/test/wpt.js
@@ -0,0 +1,210 @@
+/* eslint-disable node-core/required-modules */
+
+'use strict';
+
+const {
+ statSync,
+ readFileSync,
+ readdirSync,
+} = require('fs');
+const { fork } = require('child_process');
+const { runInThisContext } = require('vm');
+const path = require('path');
+const { performance } = require('perf_hooks');
+
+const BASE = `${__dirname}/fixtures/web-platform-tests`;
+
+const target = process.argv[2];
+const runTap = target === '--tap';
+
+const tests = [
+ 'streams/',
+ 'streams/readable-streams/',
+ 'streams/writable-streams/',
+ 'streams/transform-streams/',
+ 'streams/piping/',
+ '!streams/generate-test-wrappers.js',
+];
+
+let passed = 0;
+let failed = 0;
+let total = 0;
+
+const start = Date.now();
+
+function updateUI(name = '') {
+ if (runTap) {
+ return;
+ }
+
+ const dt = Math.floor(Date.now() - start) / 1000;
+ const m = Math.floor(dt / 60).toString().padStart(2, '0');
+ const s = Math.round(dt % 60).toString().padStart(2, '0');
+
+ const line =
+ `WPT [${m}:${s} | PASS ${passed} | FAIL ${failed}] ${name.trim()}`.trim();
+
+ process.stdout.clearLine();
+ process.stdout.cursorTo(0);
+ process.stdout.write(line.length > 80 ? line.slice(0, 76) + '...' : line);
+}
+
+function runTest(test) {
+ return new Promise((resolve, reject) => {
+ const child = fork(__filename, [test], {
+ execArgv: [
+ '--no-warnings',
+ '--expose-gc',
+ ],
+ });
+ child.on('message', ({ pass, fail }) => {
+ if (pass) {
+ passed++;
+ if (runTap) {
+ console.log(`ok ${total} ${pass.test}`);
+ }
+ } else if (fail) {
+ failed++;
+ const { stack, reason, test } = fail;
+ if (runTap) {
+ console.log(`not ok ${total} ${test}`);
+ console.log(` ---
+ severity: fail
+ stack |-
+ ${stack.split('\n').join('\n ')}
+ ...`);
+ } else {
+ process.exitCode = 1;
+ console.error(`\n\u00D7 ${test} (${reason})`);
+ console.error(stack);
+ }
+ }
+ total++;
+ updateUI((pass || fail).test);
+ });
+ child.on('exit', () => {
+ resolve();
+ });
+ });
+}
+
+if (!target || runTap) {
+ if (runTap) {
+ console.log('TAP version 13');
+ } else {
+ process.stdout.write('Finding tests...');
+ }
+
+ const queue = new Set();
+
+ tests.forEach((s) => {
+ if (s.startsWith('!')) {
+ const key = `${BASE}/${s.slice(1)}`;
+ queue.delete(key);
+ return;
+ }
+
+ const p = `${BASE}/${s}`;
+ const stat = statSync(p);
+ if (stat.isDirectory()) {
+ readdirSync(p)
+ .filter((n) => n.endsWith('.js'))
+ .forEach((n) => queue.add(`${p}${n}`));
+ } else {
+ queue.add(p);
+ }
+ });
+
+ const a = [...queue];
+
+ if (!runTap) {
+ console.log(' Done.');
+ }
+
+ updateUI();
+
+ runTest(a.shift()).then(function t() {
+ const test = a.shift();
+ if (!test) {
+ return;
+ }
+ return runTest(test).then(t);
+ }).then(() => {
+ updateUI();
+ process.stdout.write('\n');
+ });
+} else {
+ global.importScripts = (s) => {
+ const p = path.isAbsolute(s) ?
+ `${BASE}${s}` :
+ path.resolve(path.dirname(target), s);
+ if (p === `${BASE}/resources/testharness.js`) {
+ return;
+ }
+ const source = readFileSync(p, 'utf8');
+ runInThisContext(source, { filename: p });
+ };
+
+ global.performance = performance;
+
+ process.on('unhandledRejection', () => 0);
+
+ function pass(test) {
+ if (process.send) {
+ process.send({ pass: { test: test.name } });
+ } else {
+ console.log(`✓ ${test.name}`);
+ }
+ }
+
+ function fail({ test, reason }) {
+ if (process.send) {
+ process.send({ fail: {
+ test: test.name,
+ reason,
+ stack: test.stack,
+ } });
+ } else {
+ console.error(`\u00D7 ${test.name} (${reason})`.trim());
+ const err = new Error(test.message);
+ err.stack = test.stack;
+ console.error(err);
+ }
+ }
+
+ {
+ const harness = `${BASE}/resources/testharness.js`;
+ const source = readFileSync(harness, 'utf8');
+ runInThisContext(source, { filename: harness });
+ }
+
+ global.add_result_callback((test) => {
+ if (test.status === 1) {
+ fail({ test, reason: 'failure' });
+ } else if (test.status === 2) {
+ fail({ test, reason: 'timeout' });
+ } else if (test.status === 3) {
+ fail({ test, reason: 'incomplete' });
+ } else {
+ pass(test);
+ }
+ });
+
+ global.add_completion_callback((tests, harnessStatus) => {
+ if (harnessStatus.status === 2) {
+ fail({
+ test: {
+ message: 'test harness should not timeout',
+ },
+ reason: 'timeout',
+ });
+ }
+ });
+
+ // assign global.self late to trick wpt into
+ // thinking this is a shell environment
+ global.self = global;
+
+ const source = readFileSync(target, 'utf8');
+ runInThisContext(source, { filename: target });
+}
diff --git a/tools/doc/type-parser.js b/tools/doc/type-parser.js
index 2a63f97c798075..42761a1c71b6c5 100644
--- a/tools/doc/type-parser.js
+++ b/tools/doc/type-parser.js
@@ -101,6 +101,10 @@ const customTypesMap = {
'readline.Interface': 'readline.html#readline_class_interface',
+ 'ReadableStream': 'https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream',
+ 'WritableStream': 'https://developer.mozilla.org/en-US/docs/Web/API/WritableStream',
+ 'TransformStream': 'https://streams.spec.whatwg.org/#ts-class',
+
'Stream': 'stream.html#stream_stream',
'stream.Duplex': 'stream.html#stream_class_stream_duplex',
'stream.Readable': 'stream.html#stream_class_stream_readable',