From 6000d4413b1299a27f6e619eff19b04cb2f27fe8 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Mon, 20 Aug 2018 09:51:50 -0500 Subject: [PATCH 1/3] stream-whatwg: add whatwg streams --- .eslintrc.js | 5 + common.gypi | 11 + lib/v8_extras/.eslintrc.js | 13 + lib/v8_extras/ByteLengthQueuingStrategy.js | 55 + lib/v8_extras/CommonOperations.js | 334 ++++++ lib/v8_extras/CommonStrings.js | 47 + lib/v8_extras/CountQueuingStragety.js | 71 ++ lib/v8_extras/ReadableStream.js | 1262 ++++++++++++++++++++ lib/v8_extras/SimpleQueue.js | 176 +++ lib/v8_extras/TransformStream.js | 509 ++++++++ lib/v8_extras/WritableStream.js | 1097 +++++++++++++++++ src/node.cc | 4 + 12 files changed, 3584 insertions(+) create mode 100644 lib/v8_extras/.eslintrc.js create mode 100644 lib/v8_extras/ByteLengthQueuingStrategy.js create mode 100644 lib/v8_extras/CommonOperations.js create mode 100644 lib/v8_extras/CommonStrings.js create mode 100644 lib/v8_extras/CountQueuingStragety.js create mode 100644 lib/v8_extras/ReadableStream.js create mode 100644 lib/v8_extras/SimpleQueue.js create mode 100644 lib/v8_extras/TransformStream.js create mode 100644 lib/v8_extras/WritableStream.js diff --git a/.eslintrc.js b/.eslintrc.js index 9c3a78d81cd27a..15446b963cf6a7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -255,6 +255,11 @@ module.exports = { BigInt: false, BigInt64Array: false, BigUint64Array: false, + ReadableStream: false, + WritableStream: false, + TransformStream: false, + CountQueuingStrategy: false, + ByteLengthQueuingStrategy: false, COUNTER_HTTP_CLIENT_REQUEST: false, COUNTER_HTTP_CLIENT_RESPONSE: false, COUNTER_HTTP_SERVER_REQUEST: false, diff --git a/common.gypi b/common.gypi index ec877db9900e0e..91471c6f273dc8 100644 --- a/common.gypi +++ b/common.gypi @@ -24,6 +24,17 @@ 'openssl_fips%': '', + 'v8_extra_library_files': [ + './lib/v8_extras/ByteLengthQueuingStrategy.js', + './lib/v8_extras/CommonOperations.js', + './lib/v8_extras/CommonStrings.js', + './lib/v8_extras/CountQueuingStragety.js', + './lib/v8_extras/ReadableStream.js', + './lib/v8_extras/SimpleQueue.js', + './lib/v8_extras/TransformStream.js', + './lib/v8_extras/WritableStream.js', + ], + # Default to -O0 for debug builds. 'v8_optimized_debug%': 0, diff --git a/lib/v8_extras/.eslintrc.js b/lib/v8_extras/.eslintrc.js new file mode 100644 index 00000000000000..8bc4650844e412 --- /dev/null +++ b/lib/v8_extras/.eslintrc.js @@ -0,0 +1,13 @@ +/* eslint-disable no-restricted-globals */ + +const globals = new Set(Object.getOwnPropertyNames(global)); +globals.delete('undefined'); + +module.exports = { + extends: ['../.eslintrc.yaml'], + rules: { + 'strict': ['error', 'function'], + 'no-restricted-syntax': 'off', + 'no-restricted-globals': [2, ...globals], + }, +}; diff --git a/lib/v8_extras/ByteLengthQueuingStrategy.js b/lib/v8_extras/ByteLengthQueuingStrategy.js new file mode 100644 index 00000000000000..22285bf76c777f --- /dev/null +++ b/lib/v8_extras/ByteLengthQueuingStrategy.js @@ -0,0 +1,55 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// 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 Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software 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. + +// eslint-disable-next-line no-unused-vars +(function(global, binding, v8) { + 'use strict'; + + const defineProperty = global.Object.defineProperty; + + class ByteLengthQueuingStrategy { + constructor(options) { + defineProperty(this, 'highWaterMark', { + value: options.highWaterMark, + enumerable: true, + configurable: true, + writable: true + }); + } + size(chunk) { + return chunk.byteLength; + } + } + + defineProperty(global, 'ByteLengthQueuingStrategy', { + value: ByteLengthQueuingStrategy, + enumerable: false, + configurable: true, + writable: true + }); +}); diff --git a/lib/v8_extras/CommonOperations.js b/lib/v8_extras/CommonOperations.js new file mode 100644 index 00000000000000..5c325f8147d96d --- /dev/null +++ b/lib/v8_extras/CommonOperations.js @@ -0,0 +1,334 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// 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 Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software 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. + +// Implementation of functions that are shared between ReadableStream and +// WritableStream. + +(function(global, binding, v8) { + 'use strict'; + + // Common private symbols. These correspond directly to internal slots in the + // standard. "[[X]]" in the standard is spelt _X here. + const _queue = v8.createPrivateSymbol('[[queue]]'); + const _queueTotalSize = v8.createPrivateSymbol('[[queueTotalSize]]'); + + // Javascript functions. It is important to use these copies for security and + // robustness. See "V8 Extras Design Doc", section "Security Considerations". + // https://docs.google.com/document/d/1AT5-T0aHGp7Lt29vPWFr2-qG8r3l9CByyvKwEuA8Ec0/edit#heading=h.9yixony1a18r + const Boolean = global.Boolean; + const Number = global.Number; + const Number_isFinite = Number.isFinite; + const Number_isNaN = Number.isNaN; + + const RangeError = global.RangeError; + const TypeError = global.TypeError; + + const hasOwnProperty = v8.uncurryThis(global.Object.hasOwnProperty); + + function hasOwnPropertyNoThrow(x, property) { + // The cast of |x| to Boolean will eliminate undefined and null, which would + // cause hasOwnProperty to throw a TypeError, as well as some other values + // that can't be objects and so will fail the check anyway. + return Boolean(x) && hasOwnProperty(x, property); + } + + // + // Assert is not normally enabled, to avoid the space and time overhead. To + // enable, uncomment this definition and then in the file you wish to enable + // asserts for, uncomment the assert statements and add this definition: + // const assert = pred => binding.SimpleAssert(pred); + // + // binding.SimpleAssert = pred => { + // if (pred) { + // return; + // } + // v8.log('\n\n\n *** ASSERTION FAILURE ***\n\n'); + // v8.logStackTrace(); + // v8.log('**************************************************\n\n'); + // class StreamsAssertionError extends Error {} + // throw new StreamsAssertionError('Streams Assertion Failure'); + // }; + + // + // Promise-manipulation functions + // + + // Not exported. + function streamInternalError() { + throw new RangeError('Stream API Internal Error'); + } + + function rejectPromise(p, reason) { + if (!v8.isPromise(p)) { + streamInternalError(); + } + v8.rejectPromise(p, reason); + } + + function resolvePromise(p, value) { + if (!v8.isPromise(p)) { + streamInternalError(); + } + v8.resolvePromise(p, value); + } + + function markPromiseAsHandled(p) { + if (!v8.isPromise(p)) { + streamInternalError(); + } + v8.markPromiseAsHandled(p); + } + + function promiseState(p) { + if (!v8.isPromise(p)) { + streamInternalError(); + } + return v8.promiseState(p); + } + + // + // Queue-with-Sizes Operations + // + function DequeueValue(container) { + // assert( + // hasOwnProperty(container, _queue) && + // hasOwnProperty(container, _queueTotalSize), + // '_container_ has [[queue]] and [[queueTotalSize]] internal slots.'); + // assert(container[_queue].length !== 0, + // '_container_.[[queue]] is not empty.'); + const pair = container[_queue].shift(); + container[_queueTotalSize] -= pair.size; + if (container[_queueTotalSize] < 0) { + container[_queueTotalSize] = 0; + } + return pair.value; + } + + function EnqueueValueWithSize(container, value, size) { + // assert( + // hasOwnProperty(container, _queue) && + // hasOwnProperty(container, _queueTotalSize), + // '_container_ has [[queue]] and [[queueTotalSize]] internal 'slots.'); + size = Number(size); + if (!IsFiniteNonNegativeNumber(size)) { + throw new RangeError(binding.streamErrors.invalidSize); + } + + container[_queue].push({ value, size }); + container[_queueTotalSize] += size; + } + + function PeekQueueValue(container) { + // assert( + // hasOwnProperty(container, _queue) && + // hasOwnProperty(container, _queueTotalSize), + // '_container_ has [[queue]] and [[queueTotalSize]] internal slots.'); + // assert(container[_queue].length !== 0, + // '_container_.[[queue]] is not empty.'); + const pair = container[_queue].peek(); + return pair.value; + } + + function ResetQueue(container) { + // assert( + // hasOwnProperty(container, _queue) && + // hasOwnProperty(container, _queueTotalSize), + // '_container_ has [[queue]] and [[queueTotalSize]] internal slots.'); + container[_queue] = new binding.SimpleQueue(); + container[_queueTotalSize] = 0; + } + + // Not exported. + function IsFiniteNonNegativeNumber(v) { + return Number_isFinite(v) && v >= 0; + } + + function ValidateAndNormalizeHighWaterMark(highWaterMark) { + highWaterMark = Number(highWaterMark); + if (Number_isNaN(highWaterMark)) { + throw new RangeError(binding.streamErrors.invalidHWM); + } + if (highWaterMark < 0) { + throw new RangeError(binding.streamErrors.invalidHWM); + } + return highWaterMark; + } + + // Unlike the version in the standard, this implementation returns the + // original function as-is if it is set. This means users of the return value + // need to be careful to explicitly set |this| when calling it. + function MakeSizeAlgorithmFromSizeFunction(size) { + if (size === undefined) { + return () => 1; + } + + if (typeof size !== 'function') { + throw new TypeError(binding.streamErrors.sizeNotAFunction); + } + + return size; + } + + // + // Invoking functions. + // These differ from the Invoke versions in the spec in that they take a fixed + // number of arguments rather than a list, and also take a name to be used for + // the function on error. + // + + // Internal utility functions. Not exported. + const callFunction = v8.uncurryThis(global.Function.prototype.call); + const errTmplMustBeFunctionOrUndefined = (name) => + `${name} must be a function or undefined`; + const Promise_resolve = global.Promise.resolve.bind(global.Promise); + const Promise_reject = global.Promise.reject.bind(global.Promise); + const Function_bind = v8.uncurryThis(global.Function.prototype.bind); + + function resolveMethod(O, P, nameForError) { + const method = O[P]; + + if (typeof method !== 'function' && typeof method !== 'undefined') { + throw new TypeError(errTmplMustBeFunctionOrUndefined(nameForError)); + } + + return method; + } + + function CreateAlgorithmFromUnderlyingMethod( + underlyingObject, methodName, algoArgCount, methodNameForError) { + // assert(underlyingObject !== undefined, + // 'underlyingObject is not undefined.'); + // assert(IsPropertyKey(methodName), + // '! IsPropertyKey(methodName) is true.'); + // assert(algoArgCount === 0 || algoArgCount === 1, + // 'algoArgCount is 0 or 1.'); + // assert( + // typeof methodNameForError === 'string', + // 'methodNameForError is a string'); + const method = + resolveMethod(underlyingObject, methodName, methodNameForError); + // The implementation uses bound functions rather than lambdas where + // possible to give the compiler the maximum opportunity to optimise. + if (method === undefined) { + return () => Promise_resolve(); + } + + if (algoArgCount === 0) { + return Function_bind(PromiseCall0, undefined, method, underlyingObject); + } + + return Function_bind(PromiseCall1, undefined, method, underlyingObject); + } + + function CreateAlgorithmFromUnderlyingMethodPassingController( + underlyingObject, methodName, algoArgCount, controller, + methodNameForError) { + // assert(underlyingObject !== undefined, + // 'underlyingObject is not undefined.'); + // assert(IsPropertyKey(methodName), + // '! IsPropertyKey(methodName) is true.'); + // assert(algoArgCount === 0 || algoArgCount === 1, + // 'algoArgCount is 0 or 1.'); + // assert(typeof controller === 'object', 'controller is an object'); + // assert( + // typeof methodNameForError === 'string', + // 'methodNameForError is a string'); + const method = + resolveMethod(underlyingObject, methodName, methodNameForError); + if (method === undefined) { + return () => Promise_resolve(); + } + + if (algoArgCount === 0) { + return Function_bind( + PromiseCall1, undefined, method, underlyingObject, controller); + } + + return (arg) => PromiseCall2(method, underlyingObject, arg, controller); + } + + // Modified from InvokeOrNoop in spec. Takes 1 argument. + function CallOrNoop1(O, P, arg0, nameForError) { + const method = resolveMethod(O, P, nameForError); + if (method === undefined) { + return undefined; + } + + return callFunction(method, O, arg0); + } + + function PromiseCall0(F, V) { + // assert(typeof F === 'function', 'IsCallable(F) is true.'); + // assert(V !== undefined, 'V is not undefined.'); + try { + return Promise_resolve(callFunction(F, V)); + } catch (e) { + return Promise_reject(e); + } + } + + function PromiseCall1(F, V, arg0) { + // assert(typeof F === 'function', 'IsCallable(F) is true.'); + // assert(V !== undefined, 'V is not undefined.'); + try { + return Promise_resolve(callFunction(F, V, arg0)); + } catch (e) { + return Promise_reject(e); + } + } + + function PromiseCall2(F, V, arg0, arg1) { + // assert(typeof F === 'function', 'IsCallable(F) is true.'); + // assert(V !== undefined, 'V is not undefined.'); + try { + return Promise_resolve(callFunction(F, V, arg0, arg1)); + } catch (e) { + return Promise_reject(e); + } + } + + binding.streamOperations = { + _queue, + _queueTotalSize, + hasOwnPropertyNoThrow, + rejectPromise, + resolvePromise, + markPromiseAsHandled, + promiseState, + CreateAlgorithmFromUnderlyingMethod, + CreateAlgorithmFromUnderlyingMethodPassingController, + DequeueValue, + EnqueueValueWithSize, + PeekQueueValue, + ResetQueue, + ValidateAndNormalizeHighWaterMark, + MakeSizeAlgorithmFromSizeFunction, + CallOrNoop1, + PromiseCall2 + }; +}); diff --git a/lib/v8_extras/CommonStrings.js b/lib/v8_extras/CommonStrings.js new file mode 100644 index 00000000000000..c2203fb01f2f2d --- /dev/null +++ b/lib/v8_extras/CommonStrings.js @@ -0,0 +1,47 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// 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 Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software 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. + + +// User-visible strings shared between ReadableStream and WritableStream. + +// eslint-disable-next-line no-unused-vars +(function(global, binding, v8) { + 'use strict'; + + binding.streamErrors = { + illegalInvocation: 'Illegal invocation', + illegalConstructor: 'Illegal constructor', + invalidType: 'Invalid type is specified', + invalidSize: 'The return value of a queuing strategy\'s size function ' + + 'must be a finite, non-NaN, non-negative number', + sizeNotAFunction: 'A queuing strategy\'s size property must be a function', + invalidHWM: + 'A queueing strategy\'s highWaterMark property must be a nonnegative, ' + + 'non-NaN number', + }; +}); diff --git a/lib/v8_extras/CountQueuingStragety.js b/lib/v8_extras/CountQueuingStragety.js new file mode 100644 index 00000000000000..154c1c8d64b386 --- /dev/null +++ b/lib/v8_extras/CountQueuingStragety.js @@ -0,0 +1,71 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// 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 Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software 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. + +// eslint-disable-next-line no-unused-vars +(function(global, binding, v8) { + 'use strict'; + + const defineProperty = global.Object.defineProperty; + + class CountQueuingStrategy { + constructor(options) { + defineProperty(this, 'highWaterMark', { + value: options.highWaterMark, + enumerable: true, + configurable: true, + writable: true + }); + } + + size() { + return 1; + } + } + + defineProperty(global, 'CountQueuingStrategy', { + value: CountQueuingStrategy, + enumerable: false, + configurable: true, + writable: true + }); + + // Export a separate copy that doesn't need options objects and can't be + // interfered with. + class BuiltInCountQueuingStrategy { + constructor(highWaterMark) { + defineProperty(this, 'highWaterMark', { value: highWaterMark }); + } + + size() { + return 1; + } + } + + binding.createBuiltInCountQueuingStrategy = (highWaterMark) => + new BuiltInCountQueuingStrategy(highWaterMark); +}); diff --git a/lib/v8_extras/ReadableStream.js b/lib/v8_extras/ReadableStream.js new file mode 100644 index 00000000000000..f3803e192f09a5 --- /dev/null +++ b/lib/v8_extras/ReadableStream.js @@ -0,0 +1,1262 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// 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 Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software 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. + +(function(global, binding, v8) { + 'use strict'; + + const _reader = v8.createPrivateSymbol('[[reader]]'); + const _storedError = v8.createPrivateSymbol('[[storedError]]'); + const _controller = v8.createPrivateSymbol('[[controller]]'); + + const _closedPromise = v8.createPrivateSymbol('[[closedPromise]]'); + const _ownerReadableStream = + v8.createPrivateSymbol('[[ownerReadableStream]]'); + + const _readRequests = v8.createPrivateSymbol('[[readRequests]]'); + + const createWithExternalControllerSentinel = + v8.createPrivateSymbol('flag for UA-created ReadableStream to pass'); + + const _readableStreamBits = + v8.createPrivateSymbol('bit field for [[state]] and [[disturbed]]'); + const DISTURBED = 0b1; + // The 2nd and 3rd bit are for [[state]]. + const STATE_MASK = 0b110; + const STATE_BITS_OFFSET = 1; + const STATE_READABLE = 0; + const STATE_CLOSED = 1; + const STATE_ERRORED = 2; + + const _controlledReadableStream = + v8.createPrivateSymbol('[[controlledReadableStream]]'); + const _strategyHWM = v8.createPrivateSymbol('[[strategyHWM]]'); + + const _readableStreamDefaultControllerBits = v8.createPrivateSymbol( + 'bit field for [[started]], [[closeRequested]], [[pulling]], ' + + '[[pullAgain]]'); + // Remove this once C++ code has been updated to use CreateReadableStream. + const _lockNotifyTarget = v8.createPrivateSymbol('[[lockNotifyTarget]]'); + const _strategySizeAlgorithm = v8.createPrivateSymbol( + '[[strategySizeAlgorithm]]'); + const _pullAlgorithm = v8.createPrivateSymbol('[[pullAlgorithm]]'); + const _cancelAlgorithm = v8.createPrivateSymbol('[[cancelAlgorithm]]'); + const STARTED = 0b1; + const CLOSE_REQUESTED = 0b10; + const PULLING = 0b100; + const PULL_AGAIN = 0b1000; + // TODO(ricea): Remove this once blink::UnderlyingSourceBase no longer needs + // it. + const BLINK_LOCK_NOTIFICATIONS = 0b10000; + + const defineProperty = global.Object.defineProperty; + const ObjectCreate = global.Object.create; + const ObjectAssign = global.Object.assign; + + const callFunction = v8.uncurryThis(global.Function.prototype.call); + const applyFunction = v8.uncurryThis(global.Function.prototype.apply); + + const TypeError = global.TypeError; + const RangeError = global.RangeError; + + const Boolean = global.Boolean; + const String = global.String; + + const Promise = global.Promise; + const thenPromise = v8.uncurryThis(Promise.prototype.then); + const Promise_resolve = Promise.resolve.bind(Promise); + const Promise_reject = Promise.reject.bind(Promise); + + // From CommonOperations.js + const { + _queue, + _queueTotalSize, + hasOwnPropertyNoThrow, + rejectPromise, + resolvePromise, + markPromiseAsHandled, + CallOrNoop1, + CreateAlgorithmFromUnderlyingMethod, + CreateAlgorithmFromUnderlyingMethodPassingController, + DequeueValue, + EnqueueValueWithSize, + MakeSizeAlgorithmFromSizeFunction, + ValidateAndNormalizeHighWaterMark, + } = binding.streamOperations; + + const streamErrors = binding.streamErrors; + const errCancelLockedStream = + 'Cannot cancel a readable stream that is locked to a reader'; + const errEnqueueCloseRequestedStream = + 'Cannot enqueue a chunk into a readable stream that is closed or ' + + 'has been requested to be closed'; + const errCancelReleasedReader = + 'This readable stream reader has been released and cannot be used ' + + 'to cancel its previous owner stream'; + const errReadReleasedReader = + 'This readable stream reader has been released and cannot be used ' + + 'to read from its previous owner stream'; + const errCloseCloseRequestedStream = + 'Cannot close a readable stream that has already been requested to ' + + 'be closed'; + const errEnqueueClosedStream = + 'Cannot enqueue a chunk into a closed readable stream'; + const errEnqueueErroredStream = + 'Cannot enqueue a chunk into an errored readable stream'; + const errCloseClosedStream = 'Cannot close a closed readable stream'; + const errCloseErroredStream = 'Cannot close an errored readable stream'; + const errGetReaderNotByteStream = + 'This readable stream does not support BYOB readers'; + const errGetReaderBadMode = + 'Invalid reader mode given: expected undefined or "byob"'; + const errReaderConstructorBadArgument = + 'ReadableStreamReader constructor argument is not a readable stream'; + const errReaderConstructorStreamAlreadyLocked = + 'ReadableStreamReader constructor can only accept readable streams ' + + 'that are not yet locked to a reader'; + const errReleaseReaderWithPendingRead = + 'Cannot release a readable stream reader when it still has ' + + 'outstanding read() calls that have not yet settled'; + const errReleasedReaderClosedPromise = + 'This readable stream reader has been released and cannot be used ' + + 'to monitor the stream\'s state'; + + const errCannotPipeLockedStream = 'Cannot pipe a locked stream'; + const errCannotPipeToALockedStream = 'Cannot pipe to a locked stream'; + const errDestinationStreamClosed = 'Destination stream closed'; + const errPipeThroughUndefinedWritable = + 'Failed to execute \'pipeThrough\' on \'ReadableStream\': parameter ' + + '1\'s \'writable\' property is undefined.'; + const errPipeThroughUndefinedReadable = + 'Failed to execute \'pipeThrough\' on \'ReadableStream\': parameter ' + + '1\'s \'readable\' property is undefined.'; + + class ReadableStream { + // TODO(ricea): Remove |internalArgument| once + // blink::ReadableStreamOperations has been updated to use + // CreateReadableStream. + constructor(underlyingSource = {}, strategy = {}, + internalArgument = undefined) { + const enableBlinkLockNotifications = + internalArgument === createWithExternalControllerSentinel; + + InitializeReadableStream(this); + const size = strategy.size; + let highWaterMark = strategy.highWaterMark; + const type = underlyingSource.type; + const typeString = String(type); + + if (typeString === 'bytes') { + throw new RangeError('bytes type is not yet implemented'); + } + + if (type !== undefined) { + throw new RangeError(streamErrors.invalidType); + } + + const sizeAlgorithm = MakeSizeAlgorithmFromSizeFunction(size); + + if (highWaterMark === undefined) { + highWaterMark = 1; + } + + highWaterMark = ValidateAndNormalizeHighWaterMark(highWaterMark); + SetUpReadableStreamDefaultControllerFromUnderlyingSource( + this, underlyingSource, highWaterMark, sizeAlgorithm, + enableBlinkLockNotifications); + } + + get locked() { + if (IsReadableStream(this) === false) { + throw new TypeError(streamErrors.illegalInvocation); + } + + return IsReadableStreamLocked(this); + } + + cancel(reason) { + if (IsReadableStream(this) === false) { + return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + } + + if (IsReadableStreamLocked(this) === true) { + return Promise_reject(new TypeError(errCancelLockedStream)); + } + + return ReadableStreamCancel(this, reason); + } + + getReader({ mode } = {}) { + if (IsReadableStream(this) === false) { + throw new TypeError(streamErrors.illegalInvocation); + } + + if (mode === undefined) { + return AcquireReadableStreamDefaultReader(this); + } + + mode = String(mode); + + if (mode === 'byob') { + // TODO(ricea): When BYOB readers are supported: + // + // Return ? AcquireReadableStreamBYOBReader(this). + throw new TypeError(errGetReaderNotByteStream); + } + + throw new RangeError(errGetReaderBadMode); + } + + pipeThrough({ writable, readable }, options) { + if (writable === undefined) { + throw new TypeError(errPipeThroughUndefinedWritable); + } + if (readable === undefined) { + throw new TypeError(errPipeThroughUndefinedReadable); + } + const promise = this.pipeTo(writable, options); + if (v8.isPromise(promise)) { + markPromiseAsHandled(promise); + } + return readable; + } + + pipeTo(dest, { preventClose, preventAbort, preventCancel } = {}) { + if (!IsReadableStream(this)) { + return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + } + + if (!binding.IsWritableStream(dest)) { + // TODO(ricea): Think about having a better error message. + return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + } + + preventClose = Boolean(preventClose); + preventAbort = Boolean(preventAbort); + preventCancel = Boolean(preventCancel); + + if (IsReadableStreamLocked(this)) { + return Promise_reject(new TypeError(errCannotPipeLockedStream)); + } + + if (binding.IsWritableStreamLocked(dest)) { + return Promise_reject(new TypeError(errCannotPipeToALockedStream)); + } + + return ReadableStreamPipeTo( + this, dest, preventClose, preventAbort, preventCancel); + } + + tee() { + if (IsReadableStream(this) === false) { + throw new TypeError(streamErrors.illegalInvocation); + } + + return ReadableStreamTee(this); + } + } + + const ReadableStream_prototype = ReadableStream.prototype; + + function ReadableStreamPipeTo( + readable, dest, preventClose, preventAbort, preventCancel) { + // Callers of this function must ensure that the following invariants + // are enforced: + // assert(IsReadableStream(readable)); + // assert(binding.IsWritableStream(dest)); + // assert(!IsReadableStreamLocked(readable)); + // assert(!binding.IsWritableStreamLocked(dest)); + + const reader = AcquireReadableStreamDefaultReader(readable); + const writer = binding.AcquireWritableStreamDefaultWriter(dest); + let shuttingDown = false; + const promise = v8.createPromise(); + let reading = false; + let lastWrite; + + if (checkInitialState()) { + // Need to detect closing and error when we are not reading. + thenPromise(reader[_closedPromise], onReaderClosed, readableError); + // Need to detect error when we are not writing. + thenPromise( + binding.getWritableStreamDefaultWriterClosedPromise(writer), + undefined, writableError); + pump(); + } + + // Checks the state of the streams and executes the shutdown handlers if + // necessary. Returns true if piping can continue. + function checkInitialState() { + const state = ReadableStreamGetState(readable); + + // Both streams can be errored or closed. To perform the right action the + // order of the checks must match the standard. + if (state === STATE_ERRORED) { + readableError(readable[_storedError]); + return false; + } + + if (binding.isWritableStreamErrored(dest)) { + writableError(binding.getWritableStreamStoredError(dest)); + return false; + } + + if (state === STATE_CLOSED) { + readableClosed(); + return false; + } + + if (binding.isWritableStreamClosingOrClosed(dest)) { + writableStartedClosed(); + return false; + } + + return true; + } + + function pump() { + if (shuttingDown) { + return; + } + const desiredSize = + binding.WritableStreamDefaultWriterGetDesiredSize(writer); + if (desiredSize === null) { + // This can happen if abort() is queued but not yet started when + // pipeTo() is called. In that case [[storedError]] is not set yet, and + // we need to wait until it is before we can cancel the pipe. Once + // [[storedError]] has been set, the rejection handler set on the writer + // closed promise above will detect it, so all we need to do here is + // nothing. + return; + } + if (desiredSize <= 0) { + thenPromise( + binding.getWritableStreamDefaultWriterReadyPromise(writer), pump, + writableError); + return; + } + reading = true; + thenPromise( + ReadableStreamDefaultReaderRead(reader), readFulfilled, readRejected); + } + + function readFulfilled({ value, done }) { + reading = false; + if (done) { + readableClosed(); + return; + } + const write = binding.WritableStreamDefaultWriterWrite(writer, value); + lastWrite = write; + thenPromise(write, undefined, writableError); + pump(); + } + + function readRejected() { + reading = false; + readableError(readable[_storedError]); + } + + // If read() is in progress, then wait for it to tell us that the stream is + // closed so that we write all the data before shutdown. + function onReaderClosed() { + if (!reading) { + readableClosed(); + } + } + + // These steps are from "Errors must be propagated forward" in the + // standard. + function readableError(error) { + if (!preventAbort) { + shutdownWithAction( + binding.WritableStreamAbort, [dest, error], error, true); + } else { + shutdown(error, true); + } + } + + // These steps are from "Errors must be propagated backward". + function writableError(error) { + if (!preventCancel) { + shutdownWithAction( + ReadableStreamCancel, [readable, error], error, true); + } else { + shutdown(error, true); + } + } + + // These steps are from "Closing must be propagated forward". + function readableClosed() { + if (!preventClose) { + shutdownWithAction( + binding.WritableStreamDefaultWriterCloseWithErrorPropagation, + [writer]); + } else { + shutdown(); + } + } + + // These steps are from "Closing must be propagated backward". + function writableStartedClosed() { + const destClosed = new TypeError(errDestinationStreamClosed); + if (!preventCancel) { + shutdownWithAction( + ReadableStreamCancel, [readable, destClosed], destClosed, true); + } else { + shutdown(destClosed, true); + } + } + + function shutdownWithAction( + action, args, originalError = undefined, errorGiven = false) { + if (shuttingDown) { + return; + } + shuttingDown = true; + let p; + if (shouldWriteQueuedChunks()) { + p = thenPromise(writeQueuedChunks(), + () => applyFunction(action, undefined, args)); + } else { + p = applyFunction(action, undefined, args); + } + thenPromise( + p, () => finalize(originalError, errorGiven), + (newError) => finalize(newError, true)); + } + + function shutdown(error = undefined, errorGiven = false) { + if (shuttingDown) { + return; + } + shuttingDown = true; + if (shouldWriteQueuedChunks()) { + thenPromise(writeQueuedChunks(), () => finalize(error, errorGiven)); + } else { + finalize(error, errorGiven); + } + } + + function finalize(error, errorGiven) { + binding.WritableStreamDefaultWriterRelease(writer); + ReadableStreamReaderGenericRelease(reader); + if (errorGiven) { + rejectPromise(promise, error); + } else { + resolvePromise(promise, undefined); + } + } + + function shouldWriteQueuedChunks() { + return binding.isWritableStreamWritable(dest) && + !binding.WritableStreamCloseQueuedOrInFlight(dest); + } + + function writeQueuedChunks() { + if (lastWrite) { + // "Wait until every chunk that has been read has been written (i.e. + // the corresponding promises have settled)" + // This implies that we behave the same whether the promise fulfills or + // rejects. + return thenPromise(lastWrite, () => undefined, () => undefined); + } + return Promise_resolve(undefined); + } + + return promise; + } + + // + // Readable stream abstract operations + // + + function AcquireReadableStreamDefaultReader(stream) { + // eslint-disable-next-line no-use-before-define + return new ReadableStreamDefaultReader(stream); + } + + // The non-standard boolean |enableBlinkLockNotifications| argument indicates + // whether the stream is being created from C++. + function CreateReadableStream(startAlgorithm, pullAlgorithm, cancelAlgorithm, + highWaterMark, sizeAlgorithm, + enableBlinkLockNotifications) { + if (highWaterMark === undefined) { + highWaterMark = 1; + } + if (sizeAlgorithm === undefined) { + sizeAlgorithm = () => 1; + } + // assert(IsNonNegativeNumber(highWaterMark), + // '! IsNonNegativeNumber(highWaterMark) is true.'); + const stream = ObjectCreate(ReadableStream_prototype); + InitializeReadableStream(stream); + const controller = ObjectCreate(ReadableStreamDefaultController_prototype); + SetUpReadableStreamDefaultController( + stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, + highWaterMark, sizeAlgorithm, enableBlinkLockNotifications); + return stream; + } + + function InitializeReadableStream(stream) { + stream[_readableStreamBits] = 0b0; + ReadableStreamSetState(stream, STATE_READABLE); + stream[_reader] = undefined; + stream[_storedError] = undefined; + } + + function IsReadableStream(x) { + return hasOwnPropertyNoThrow(x, _controller); + } + + function IsReadableStreamDisturbed(stream) { + return stream[_readableStreamBits] & DISTURBED; + } + + function IsReadableStreamLocked(stream) { + return stream[_reader] !== undefined; + } + + // TODO(domenic): cloneForBranch2 argument from spec not supported yet + function ReadableStreamTee(stream) { + const reader = AcquireReadableStreamDefaultReader(stream); + + let closedOrErrored = false; + let canceled1 = false; + let canceled2 = false; + let reason1; + let reason2; + const cancelPromise = v8.createPromise(); + + function pullAlgorithm() { + return thenPromise( + ReadableStreamDefaultReaderRead(reader), ({ value, done }) => { + if (done && !closedOrErrored) { + if (!canceled1) { + ReadableStreamDefaultControllerClose(branch1controller); + } + if (!canceled2) { + ReadableStreamDefaultControllerClose(branch2controller); + } + closedOrErrored = true; + } + + if (closedOrErrored) { + return; + } + + // TODO(ricea): Implement these steps for cloning. + // + // vii. Let _value1_ and _value2_ be _value_. + // viii. If _canceled2_ is false and _cloneForBranch2_ is true, set + // value2 to ? StructuredDeserialize(? StructuredSerialize(value2), + // the current Realm Record). + + if (!canceled1) { + ReadableStreamDefaultControllerEnqueue(branch1controller, value); + } + + if (!canceled2) { + ReadableStreamDefaultControllerEnqueue(branch2controller, value); + } + }); + } + + function cancel1Algorithm(reason) { + canceled1 = true; + reason1 = reason; + if (canceled2) { + const cancelResult = ReadableStreamCancel(stream, [reason1, reason2]); + resolvePromise(cancelPromise, cancelResult); + } + return cancelPromise; + } + + function cancel2Algorithm(reason) { + canceled2 = true; + reason2 = reason; + if (canceled1) { + const cancelResult = ReadableStreamCancel(stream, [reason1, reason2]); + resolvePromise(cancelPromise, cancelResult); + } + return cancelPromise; + } + + const startAlgorithm = () => undefined; + + const branch1Stream = CreateReadableStream( + startAlgorithm, pullAlgorithm, cancel1Algorithm, undefined, undefined, + false); + const branch2Stream = CreateReadableStream( + startAlgorithm, pullAlgorithm, cancel2Algorithm, undefined, undefined, + false); + const branch1controller = branch1Stream[_controller]; + const branch2controller = branch2Stream[_controller]; + + thenPromise(reader[_closedPromise], undefined, (r) => { + if (closedOrErrored === true) { + return; + } + + ReadableStreamDefaultControllerError(branch1controller, r); + ReadableStreamDefaultControllerError(branch2controller, r); + closedOrErrored = true; + }); + + return [branch1Stream, branch2Stream]; + } + + // + // Abstract Operations Used By Controllers + // + + function ReadableStreamAddReadRequest(stream, forAuthorCode) { + const promise = v8.createPromise(); + stream[_reader][_readRequests].push({ promise, forAuthorCode }); + return promise; + } + + function ReadableStreamCancel(stream, reason) { + stream[_readableStreamBits] |= DISTURBED; + + const state = ReadableStreamGetState(stream); + if (state === STATE_CLOSED) { + return Promise_resolve(undefined); + } + if (state === STATE_ERRORED) { + return Promise_reject(stream[_storedError]); + } + + ReadableStreamClose(stream); + + const sourceCancelPromise = + ReadableStreamDefaultControllerCancel(stream[_controller], reason); + return thenPromise(sourceCancelPromise, () => undefined); + } + + function ReadableStreamClose(stream) { + ReadableStreamSetState(stream, STATE_CLOSED); + + const reader = stream[_reader]; + if (reader === undefined) { + return; + } + + if (IsReadableStreamDefaultReader(reader) === true) { + reader[_readRequests].forEach( + (request) => + resolvePromise( + request.promise, + ReadableStreamCreateReadResult(undefined, true, + request.forAuthorCode))); + reader[_readRequests] = new binding.SimpleQueue(); + } + + resolvePromise(reader[_closedPromise], undefined); + } + + function ReadableStreamCreateReadResult(value, done, forAuthorCode) { + // assert(typeof done === 'boolean', 'Type(_done_) is Boolean.'); + if (forAuthorCode) { + return { value, done }; + } + const obj = ObjectCreate(null); + obj.value = value; + obj.done = done; + return obj; + } + + function ReadableStreamError(stream, e) { + ReadableStreamSetState(stream, STATE_ERRORED); + stream[_storedError] = e; + + const reader = stream[_reader]; + if (reader === undefined) { + return; + } + + if (IsReadableStreamDefaultReader(reader) === true) { + reader[_readRequests].forEach((request) => + rejectPromise(request.promise, e)); + reader[_readRequests] = new binding.SimpleQueue(); + } + + rejectPromise(reader[_closedPromise], e); + markPromiseAsHandled(reader[_closedPromise]); + } + + function ReadableStreamFulfillReadRequest(stream, chunk, done) { + const readRequest = stream[_reader][_readRequests].shift(); + resolvePromise(readRequest.promise, + ReadableStreamCreateReadResult(chunk, done, + readRequest.forAuthorCode)); + } + + function ReadableStreamGetNumReadRequests(stream) { + const reader = stream[_reader]; + const readRequests = reader[_readRequests]; + return readRequests.length; + } + + // + // Class ReadableStreamDefaultReader + // + + class ReadableStreamDefaultReader { + constructor(stream) { + if (IsReadableStream(stream) === false) { + throw new TypeError(errReaderConstructorBadArgument); + } + if (IsReadableStreamLocked(stream) === true) { + throw new TypeError(errReaderConstructorStreamAlreadyLocked); + } + + ReadableStreamReaderGenericInitialize(this, stream); + + this[_readRequests] = new binding.SimpleQueue(); + } + + get closed() { + if (IsReadableStreamDefaultReader(this) === false) { + return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + } + + return this[_closedPromise]; + } + + cancel(reason) { + if (IsReadableStreamDefaultReader(this) === false) { + return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + } + + if (this[_ownerReadableStream] === undefined) { + return Promise_reject(new TypeError(errCancelReleasedReader)); + } + + return ReadableStreamReaderGenericCancel(this, reason); + } + + read() { + if (IsReadableStreamDefaultReader(this) === false) { + return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + } + + if (this[_ownerReadableStream] === undefined) { + return Promise_reject(new TypeError(errReadReleasedReader)); + } + + return ReadableStreamDefaultReaderRead(this, true); + } + + releaseLock() { + if (IsReadableStreamDefaultReader(this) === false) { + throw new TypeError(streamErrors.illegalInvocation); + } + + if (this[_ownerReadableStream] === undefined) { + return; + } + + if (this[_readRequests].length > 0) { + throw new TypeError(errReleaseReaderWithPendingRead); + } + + ReadableStreamReaderGenericRelease(this); + } + } + + // + // Readable Stream Reader Abstract Operations + // + + function IsReadableStreamDefaultReader(x) { + return hasOwnPropertyNoThrow(x, _readRequests); + } + + function ReadableStreamReaderGenericCancel(reader, reason) { + return ReadableStreamCancel(reader[_ownerReadableStream], reason); + } + + function ReadableStreamReaderGenericInitialize(reader, stream) { + // TODO(yhirano): Remove this when we don't need hasPendingActivity in + // blink::UnderlyingSourceBase. + const controller = stream[_controller]; + if (controller[_readableStreamDefaultControllerBits] & + BLINK_LOCK_NOTIFICATIONS) { + // The stream is created with an external controller (i.e. made in + // Blink). + const lockNotifyTarget = controller[_lockNotifyTarget]; + callFunction(lockNotifyTarget.notifyLockAcquired, lockNotifyTarget); + } + + reader[_ownerReadableStream] = stream; + stream[_reader] = reader; + + switch (ReadableStreamGetState(stream)) { + case STATE_READABLE: + reader[_closedPromise] = v8.createPromise(); + break; + case STATE_CLOSED: + reader[_closedPromise] = Promise_resolve(undefined); + break; + case STATE_ERRORED: + reader[_closedPromise] = Promise_reject(stream[_storedError]); + markPromiseAsHandled(reader[_closedPromise]); + break; + } + } + + function ReadableStreamReaderGenericRelease(reader) { + // TODO(yhirano): Remove this when we don't need hasPendingActivity in + // blink::UnderlyingSourceBase. + const controller = reader[_ownerReadableStream][_controller]; + if (controller[_readableStreamDefaultControllerBits] & + BLINK_LOCK_NOTIFICATIONS) { + // The stream is created with an external controller (i.e. made in + // Blink). + const lockNotifyTarget = controller[_lockNotifyTarget]; + callFunction(lockNotifyTarget.notifyLockReleased, lockNotifyTarget); + } + + if (ReadableStreamGetState(reader[_ownerReadableStream]) === + STATE_READABLE) { + rejectPromise( + reader[_closedPromise], + new TypeError(errReleasedReaderClosedPromise)); + } else { + reader[_closedPromise] = + Promise_reject(new TypeError(errReleasedReaderClosedPromise)); + } + markPromiseAsHandled(reader[_closedPromise]); + + reader[_ownerReadableStream][_reader] = undefined; + reader[_ownerReadableStream] = undefined; + } + + function ReadableStreamDefaultReaderRead(reader, forAuthorCode = false) { + const stream = reader[_ownerReadableStream]; + stream[_readableStreamBits] |= DISTURBED; + + switch (ReadableStreamGetState(stream)) { + case STATE_CLOSED: + return Promise_resolve(ReadableStreamCreateReadResult(undefined, true, + forAuthorCode)); + + case STATE_ERRORED: + return Promise_reject(stream[_storedError]); + + default: + return ReadableStreamDefaultControllerPull(stream[_controller], + forAuthorCode); + } + } + + // + // Class ReadableStreamDefaultController + // + + class ReadableStreamDefaultController { + constructor() { + throw new TypeError(streamErrors.illegalConstructor); + } + + get desiredSize() { + if (IsReadableStreamDefaultController(this) === false) { + throw new TypeError(streamErrors.illegalInvocation); + } + + return ReadableStreamDefaultControllerGetDesiredSize(this); + } + + close() { + if (IsReadableStreamDefaultController(this) === false) { + throw new TypeError(streamErrors.illegalInvocation); + } + + if (ReadableStreamDefaultControllerCanCloseOrEnqueue(this) === false) { + let errorDescription; + if (this[_readableStreamDefaultControllerBits] & CLOSE_REQUESTED) { + errorDescription = errCloseCloseRequestedStream; + } else { + const stream = this[_controlledReadableStream]; + switch (ReadableStreamGetState(stream)) { + case STATE_ERRORED: + errorDescription = errCloseErroredStream; + break; + + case STATE_CLOSED: + errorDescription = errCloseClosedStream; + break; + } + } + throw new TypeError(errorDescription); + } + + return ReadableStreamDefaultControllerClose(this); + } + + enqueue(chunk) { + if (IsReadableStreamDefaultController(this) === false) { + throw new TypeError(streamErrors.illegalInvocation); + } + + if (!ReadableStreamDefaultControllerCanCloseOrEnqueue(this)) { + const stream = this[_controlledReadableStream]; + throw getReadableStreamEnqueueError(stream, this); + } + + return ReadableStreamDefaultControllerEnqueue(this, chunk); + } + + error(e) { + if (IsReadableStreamDefaultController(this) === false) { + throw new TypeError(streamErrors.illegalInvocation); + } + + return ReadableStreamDefaultControllerError(this, e); + } + } + + const ReadableStreamDefaultController_prototype = + ReadableStreamDefaultController.prototype; + + // [[CancelSteps]] in the standard. + function ReadableStreamDefaultControllerCancel(controller, reason) { + controller[_queue] = new binding.SimpleQueue(); + return controller[_cancelAlgorithm](reason); + } + + // [[PullSteps]] in the standard. + function ReadableStreamDefaultControllerPull(controller, forAuthorCode) { + const stream = controller[_controlledReadableStream]; + + if (controller[_queue].length > 0) { + const chunk = DequeueValue(controller); + + if ((controller[_readableStreamDefaultControllerBits] & + CLOSE_REQUESTED) && + controller[_queue].length === 0) { + ReadableStreamClose(stream); + } else { + ReadableStreamDefaultControllerCallPullIfNeeded(controller); + } + + return Promise_resolve(ReadableStreamCreateReadResult(chunk, false, + forAuthorCode)); + } + + const pendingPromise = ReadableStreamAddReadRequest(stream, forAuthorCode); + ReadableStreamDefaultControllerCallPullIfNeeded(controller); + return pendingPromise; + } + + // + // Readable Stream Default Controller Abstract Operations + // + + function IsReadableStreamDefaultController(x) { + return hasOwnPropertyNoThrow(x, _controlledReadableStream); + } + + function ReadableStreamDefaultControllerCallPullIfNeeded(controller) { + const shouldPull = + ReadableStreamDefaultControllerShouldCallPull(controller); + if (shouldPull === false) { + return; + } + + if (controller[_readableStreamDefaultControllerBits] & PULLING) { + controller[_readableStreamDefaultControllerBits] |= PULL_AGAIN; + return; + } + + controller[_readableStreamDefaultControllerBits] |= PULLING; + + thenPromise( + controller[_pullAlgorithm](), + () => { + controller[_readableStreamDefaultControllerBits] &= ~PULLING; + + if (controller[_readableStreamDefaultControllerBits] & PULL_AGAIN) { + controller[_readableStreamDefaultControllerBits] &= ~PULL_AGAIN; + ReadableStreamDefaultControllerCallPullIfNeeded(controller); + } + }, + (e) => { + ReadableStreamDefaultControllerError(controller, e); + }); + } + + function ReadableStreamDefaultControllerShouldCallPull(controller) { + if (!ReadableStreamDefaultControllerCanCloseOrEnqueue(controller)) { + return false; + } + if (!(controller[_readableStreamDefaultControllerBits] & STARTED)) { + return false; + } + + const stream = controller[_controlledReadableStream]; + if (IsReadableStreamLocked(stream) === true && + ReadableStreamGetNumReadRequests(stream) > 0) { + return true; + } + + const desiredSize = + ReadableStreamDefaultControllerGetDesiredSize(controller); + // assert(desiredSize !== null, '_desiredSize_ is not *null*.'); + return desiredSize > 0; + } + + function ReadableStreamDefaultControllerClose(controller) { + controller[_readableStreamDefaultControllerBits] |= CLOSE_REQUESTED; + + if (controller[_queue].length === 0) { + ReadableStreamClose(controller[_controlledReadableStream]); + } + } + + function ReadableStreamDefaultControllerEnqueue(controller, chunk) { + const stream = controller[_controlledReadableStream]; + + if (IsReadableStreamLocked(stream) === true && + ReadableStreamGetNumReadRequests(stream) > 0) { + ReadableStreamFulfillReadRequest(stream, chunk, false); + } else { + let chunkSize; + + // TODO(ricea): Would it be more efficient if we avoided the + // try ... catch when we're using the default strategy size algorithm? + try { + // Unlike other algorithms, strategySizeAlgorithm isn't indirected, so + // we need to be careful with the |this| value. + chunkSize = callFunction(controller[_strategySizeAlgorithm], undefined, + chunk); + } catch (chunkSizeE) { + ReadableStreamDefaultControllerError(controller, chunkSizeE); + throw chunkSizeE; + } + + try { + EnqueueValueWithSize(controller, chunk, chunkSize); + } catch (enqueueE) { + ReadableStreamDefaultControllerError(controller, enqueueE); + throw enqueueE; + } + } + + ReadableStreamDefaultControllerCallPullIfNeeded(controller); + } + + function ReadableStreamDefaultControllerError(controller, e) { + const stream = controller[_controlledReadableStream]; + if (ReadableStreamGetState(stream) !== STATE_READABLE) { + return; + } + controller[_queue] = new binding.SimpleQueue(); + ReadableStreamError(stream, e); + } + + function ReadableStreamDefaultControllerGetDesiredSize(controller) { + switch (ReadableStreamGetState(controller[_controlledReadableStream])) { + case STATE_ERRORED: + return null; + + case STATE_CLOSED: + return 0; + + default: + return controller[_strategyHWM] - controller[_queueTotalSize]; + } + } + + function ReadableStreamDefaultControllerHasBackpressure(controller) { + return !ReadableStreamDefaultControllerShouldCallPull(controller); + } + + function ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) { + if (controller[_readableStreamDefaultControllerBits] & CLOSE_REQUESTED) { + return false; + } + const state = ReadableStreamGetState(controller[_controlledReadableStream]); + return state === STATE_READABLE; + } + + function SetUpReadableStreamDefaultController( + stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, + highWaterMark, sizeAlgorithm, enableBlinkLockNotifications) { + controller[_controlledReadableStream] = stream; + controller[_queue] = new binding.SimpleQueue(); + controller[_queueTotalSize] = 0; + controller[_readableStreamDefaultControllerBits] = + enableBlinkLockNotifications ? BLINK_LOCK_NOTIFICATIONS : 0b0; + controller[_strategySizeAlgorithm] = sizeAlgorithm; + controller[_strategyHWM] = highWaterMark; + controller[_pullAlgorithm] = pullAlgorithm; + controller[_cancelAlgorithm] = cancelAlgorithm; + stream[_controller] = controller; + + thenPromise(Promise_resolve(startAlgorithm()), () => { + controller[_readableStreamDefaultControllerBits] |= STARTED; + ReadableStreamDefaultControllerCallPullIfNeeded(controller); + }, (r) => ReadableStreamDefaultControllerError(controller, r)); + } + + function SetUpReadableStreamDefaultControllerFromUnderlyingSource( + stream, underlyingSource, highWaterMark, sizeAlgorithm, + enableBlinkLockNotifications) { + const controller = ObjectCreate(ReadableStreamDefaultController_prototype); + const startAlgorithm = + () => CallOrNoop1(underlyingSource, 'start', controller, + 'underlyingSource.start'); + const pullAlgorithm = CreateAlgorithmFromUnderlyingMethodPassingController( + underlyingSource, 'pull', 0, controller, 'underlyingSource.pull'); + const cancelAlgorithm = CreateAlgorithmFromUnderlyingMethod( + underlyingSource, 'cancel', 1, 'underlyingSource.cancel'); + // TODO(ricea): Remove this once C++ API has been updated. + if (enableBlinkLockNotifications) { + controller[_lockNotifyTarget] = underlyingSource; + } + SetUpReadableStreamDefaultController( + stream, controller, startAlgorithm, pullAlgorithm, cancelAlgorithm, + highWaterMark, sizeAlgorithm, enableBlinkLockNotifications); + } + + // + // Internal functions. Not part of the standard. + // + + function ReadableStreamGetState(stream) { + return (stream[_readableStreamBits] & STATE_MASK) >> STATE_BITS_OFFSET; + } + + function ReadableStreamSetState(stream, state) { + stream[_readableStreamBits] = (stream[_readableStreamBits] & ~STATE_MASK) | + (state << STATE_BITS_OFFSET); + } + + // + // Functions exported for use by TransformStream. Not part of the standard. + // + + function IsReadableStreamReadable(stream) { + return ReadableStreamGetState(stream) === STATE_READABLE; + } + + function IsReadableStreamClosed(stream) { + return ReadableStreamGetState(stream) === STATE_CLOSED; + } + + function IsReadableStreamErrored(stream) { + return ReadableStreamGetState(stream) === STATE_ERRORED; + } + + // Used internally by enqueue() and also by TransformStream. + function getReadableStreamEnqueueError(stream, controller) { + if (controller[_readableStreamDefaultControllerBits] & CLOSE_REQUESTED) { + return new TypeError(errEnqueueCloseRequestedStream); + } + + const state = ReadableStreamGetState(stream); + if (state === STATE_ERRORED) { + return new TypeError(errEnqueueErroredStream); + } + // assert(state === STATE_CLOSED, 'state is "closed"'); + return new TypeError(errEnqueueClosedStream); + } + + // + // Accessors used by TransformStream + // + + function getReadableStreamController(stream) { + // assert( + // IsReadableStream(stream), '! IsReadableStream(stream) is true.'); + return stream[_controller]; + } + + function getReadableStreamStoredError(stream) { + // assert( + // IsReadableStream(stream), '! IsReadableStream(stream) is true.'); + return stream[_storedError]; + } + + // TODO(ricea): Remove this once the C++ code switches to calling + // CreateReadableStream(). + function createReadableStreamWithExternalController(underlyingSource, + strategy) { + return new ReadableStream( + underlyingSource, strategy, createWithExternalControllerSentinel); + } + + // + // Additions to the global + // + + defineProperty(global, 'ReadableStream', { + value: ReadableStream, + enumerable: false, + configurable: true, + writable: true + }); + + + ObjectAssign(binding, { + // + // ReadableStream exports to Blink C++ + // + AcquireReadableStreamDefaultReader, + createReadableStreamWithExternalController, + IsReadableStream, + IsReadableStreamDisturbed, + IsReadableStreamLocked, + IsReadableStreamReadable, + IsReadableStreamClosed, + IsReadableStreamErrored, + IsReadableStreamDefaultReader, + ReadableStreamDefaultReaderRead, + ReadableStreamTee, + + // + // Controller exports to Blink C++ + // + ReadableStreamDefaultControllerClose, + ReadableStreamDefaultControllerGetDesiredSize, + ReadableStreamDefaultControllerEnqueue, + ReadableStreamDefaultControllerError, + + // + // Exports to TransformStream + // + CreateReadableStream, + ReadableStreamDefaultControllerCanCloseOrEnqueue, + ReadableStreamDefaultControllerHasBackpressure, + + getReadableStreamEnqueueError, + getReadableStreamController, + getReadableStreamStoredError, + }); +}); diff --git a/lib/v8_extras/SimpleQueue.js b/lib/v8_extras/SimpleQueue.js new file mode 100644 index 00000000000000..927851c6f97762 --- /dev/null +++ b/lib/v8_extras/SimpleQueue.js @@ -0,0 +1,176 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// 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 Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software 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. + +// Queue implementation used by ReadableStream and WritableStream. + +(function(global, binding, v8) { + 'use strict'; + + const _front = v8.createPrivateSymbol('front'); + const _back = v8.createPrivateSymbol('back'); + const _cursor = v8.createPrivateSymbol('cursor'); + const _size = v8.createPrivateSymbol('size'); + const _elements = v8.createPrivateSymbol('elements'); + const _next = v8.createPrivateSymbol('next'); + + // Take copies of global objects to protect against them being replaced. + const RangeError = global.RangeError; + + // shift() and peek() can only be called on a non-empty queue. This function + // throws an exception with the message mentioning |functionName| if |queue| + // is empty. + function requireNonEmptyQueue(queue, functionName) { + if (queue[_size] === 0) { + throw new RangeError( + `${functionName}() must not be called on an empty queue`); + } + } + + // Simple queue structure. Avoids scalability issues with using + // InternalPackedArray directly by using multiple arrays in a linked list and + // keeping the array size bounded. + const QUEUE_MAX_ARRAY_SIZE = 16384; + class SimpleQueue { + constructor() { + // [_front] and [_back] are always defined. + this[_front] = { + [_elements]: new v8.InternalPackedArray(), + [_next]: undefined, + }; + this[_back] = this[_front]; + // The cursor is used to avoid calling InternalPackedArray.shift(). It + // contains the index of the front element of the array inside the + // frontmost node. It is always in the range [0, QUEUE_MAX_ARRAY_SIZE). + this[_cursor] = 0; + // When there is only one node, size === elements.length - cursor. + this[_size] = 0; + } + + get length() { + return this[_size]; + } + + // For exception safety, this method is structured in order: + // 1. Read state + // 2. Calculate required state mutations + // 3. Perform state mutations + push(element) { + const oldBack = this[_back]; + let newBack = oldBack; + // assert(oldBack[_next] === undefined); + if (oldBack[_elements].length === QUEUE_MAX_ARRAY_SIZE - 1) { + newBack = { + [_elements]: new v8.InternalPackedArray(), + [_next]: undefined, + }; + } + + // push() is the mutation most likely to throw an exception, so it + // goes first. + oldBack[_elements].push(element); + if (newBack !== oldBack) { + this[_back] = newBack; + oldBack[_next] = newBack; + } + ++this[_size]; + } + + // Like push(), shift() follows the read -> calculate -> mutate pattern for + // exception safety. + shift() { + requireNonEmptyQueue(this, 'shift'); + + const oldFront = this[_front]; + let newFront = oldFront; + const oldCursor = this[_cursor]; + let newCursor = oldCursor + 1; + + const elements = oldFront[_elements]; + const element = elements[oldCursor]; + + if (newCursor === QUEUE_MAX_ARRAY_SIZE) { + // assert(elements.length === QUEUE_MAX_ARRAY_SIZE); + // assert(oldFront[_next] !== undefined); + newFront = oldFront[_next]; + newCursor = 0; + } + + // No mutations before this point. + --this[_size]; + this[_cursor] = newCursor; + if (oldFront !== newFront) { + this[_front] = newFront; + } + + // Permit shifted element to be garbage collected. + elements[oldCursor] = undefined; + + return element; + } + + // The tricky thing about forEach() is that it can be called + // re-entrantly. The queue may be mutated inside the callback. It is easy to + // see that push() within the callback has no negative effects since the end + // of the queue is checked for on every iteration. If shift() is called + // repeatedly within the callback then the next iteration may return an + // element that has been removed. In this case the callback will be called + // with undefined values until we either "catch up" with elements that still + // exist or reach the back of the queue. + forEach(callback) { + let i = this[_cursor]; + let node = this[_front]; + let elements = node[_elements]; + while (i !== elements.length || node[_next] !== undefined) { + if (i === elements.length) { + // assert(node[_next] !== undefined); + // assert(i === QUEUE_MAX_ARRAY_SIZE); + node = node[_next]; + elements = node[_elements]; + i = 0; + if (elements.length === 0) { + break; + } + } + callback(elements[i]); + ++i; + } + } + + // Return the element that would be returned if shift() was called now, + // without modifying the queue. + peek() { + requireNonEmptyQueue(this, 'peek'); + + const front = this[_front]; + const cursor = this[_cursor]; + return front[_elements][cursor]; + } + } + + binding.SimpleQueue = SimpleQueue; +}); diff --git a/lib/v8_extras/TransformStream.js b/lib/v8_extras/TransformStream.js new file mode 100644 index 00000000000000..582b53c4aff01f --- /dev/null +++ b/lib/v8_extras/TransformStream.js @@ -0,0 +1,509 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// 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 Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software 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. + +// Implementation of TransformStream for Blink. See +// https://streams.spec.whatwg.org/#ts. The implementation closely follows the +// standard, except where required for performance or integration with Blink. +// In particular, classes, methods and abstract operations are implemented in +// the same order as in the standard, to simplify side-by-side reading. + +(function(global, binding, v8) { + 'use strict'; + + // Private symbols. These correspond to the internal slots in the standard. + // "[[X]]" in the standard is spelt _X in this implementation. + + const _backpressure = v8.createPrivateSymbol('[[backpressure]]'); + const _backpressureChangePromise = + v8.createPrivateSymbol('[[backpressureChangePromise]]'); + const _readable = v8.createPrivateSymbol('[[readable]]'); + const _transformStreamController = + v8.createPrivateSymbol('[[transformStreamController]]'); + const _writable = v8.createPrivateSymbol('[[writable]]'); + const _controlledTransformStream = + v8.createPrivateSymbol('[[controlledTransformStream]]'); + + // Unlike the version in the standard, the controller is passed to this. + const _flushAlgorithm = v8.createPrivateSymbol('[[flushAlgorithm]]'); + + // Unlike the version in the standard, the controller is passed in as the + // second argument. + const _transformAlgorithm = v8.createPrivateSymbol('[[transformAlgorithm]]'); + + // Javascript functions. It is important to use these copies, as the ones on + // the global object may have been overwritten. See "V8 Extras Design Doc", + // section "Security Considerations". + // https://docs.google.com/document/d/1AT5-T0aHGp7Lt29vPWFr2-qG8r3l9CByyvKwEuA8Ec0/edit#heading=h.9yixony1a18r + const defineProperty = global.Object.defineProperty; + const ObjectCreate = global.Object.create; + const ObjectAssign = global.Object.assign; + + const TypeError = global.TypeError; + const RangeError = global.RangeError; + + const Promise = global.Promise; + const thenPromise = v8.uncurryThis(Promise.prototype.then); + const Promise_resolve = Promise.resolve.bind(Promise); + const Promise_reject = Promise.reject.bind(Promise); + + // From CommonOperations.js + const { + hasOwnPropertyNoThrow, + resolvePromise, + CreateAlgorithmFromUnderlyingMethod, + CallOrNoop1, + MakeSizeAlgorithmFromSizeFunction, + PromiseCall2, + ValidateAndNormalizeHighWaterMark + } = binding.streamOperations; + + // User-visible strings. + const streamErrors = binding.streamErrors; + const errStreamTerminated = 'The transform stream has been terminated'; + + class TransformStream { + constructor(transformer = {}, + writableStrategy = {}, readableStrategy = {}) { + const writableSizeFunction = writableStrategy.size; + let writableHighWaterMark = writableStrategy.highWaterMark; + + const readableSizeFunction = readableStrategy.size; + let readableHighWaterMark = readableStrategy.highWaterMark; + + // readable and writableType are extension points for future byte streams. + const writableType = transformer.writableType; + if (writableType !== undefined) { + throw new RangeError(streamErrors.invalidType); + } + + const writableSizeAlgorithm = + MakeSizeAlgorithmFromSizeFunction(writableSizeFunction); + if (writableHighWaterMark === undefined) { + writableHighWaterMark = 1; + } + writableHighWaterMark = + ValidateAndNormalizeHighWaterMark(writableHighWaterMark); + + const readableType = transformer.readableType; + if (readableType !== undefined) { + throw new RangeError(streamErrors.invalidType); + } + + const readableSizeAlgorithm = + MakeSizeAlgorithmFromSizeFunction(readableSizeFunction); + if (readableHighWaterMark === undefined) { + readableHighWaterMark = 0; + } + readableHighWaterMark = + ValidateAndNormalizeHighWaterMark(readableHighWaterMark); + + const startPromise = v8.createPromise(); + InitializeTransformStream( + this, startPromise, writableHighWaterMark, writableSizeAlgorithm, + readableHighWaterMark, readableSizeAlgorithm); + SetUpTransformStreamDefaultControllerFromTransformer(this, transformer); + const startResult = CallOrNoop1( + transformer, 'start', this[_transformStreamController], + 'transformer.start'); + resolvePromise(startPromise, startResult); + } + + get readable() { + if (!IsTransformStream(this)) { + throw new TypeError(streamErrors.illegalInvocation); + } + + return this[_readable]; + } + + get writable() { + if (!IsTransformStream(this)) { + throw new TypeError(streamErrors.illegalInvocation); + } + + return this[_writable]; + } + } + + const TransformStream_prototype = TransformStream.prototype; + + // The controller is passed to |transformAlgorithm| and |flushAlgorithm|, + // unlike in the standard. + function CreateTransformStream( + startAlgorithm, transformAlgorithm, flushAlgorithm, writableHighWaterMark, + writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm) { + if (writableHighWaterMark === undefined) { + writableHighWaterMark = 1; + } + if (writableSizeAlgorithm === undefined) { + writableSizeAlgorithm = () => 1; + } + if (readableHighWaterMark === undefined) { + readableHighWaterMark = 0; + } + if (readableSizeAlgorithm === undefined) { + readableSizeAlgorithm = () => 1; + } + // assert( + // typeof writableHighWaterMark === 'number' && + // writableHighWaterMark >= 0, + // '! IsNonNegativeNumber(_writableHighWaterMark_) is *true*'); + // assert( + // typeof readableHighWaterMark === 'number' && + // readableHighWaterMark >= 0, + // '! IsNonNegativeNumber(_readableHighWaterMark_) is true'); + const stream = ObjectCreate(TransformStream_prototype); + const startPromise = v8.createPromise(); + InitializeTransformStream( + stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, + readableHighWaterMark, readableSizeAlgorithm); + const controller = ObjectCreate(TransformStreamDefaultController_prototype); + SetUpTransformStreamDefaultController( + stream, controller, transformAlgorithm, flushAlgorithm); + const startResult = startAlgorithm(); + resolvePromise(startPromise, startResult); + return stream; + } + + function InitializeTransformStream( + stream, startPromise, writableHighWaterMark, writableSizeAlgorithm, + readableHighWaterMark, readableSizeAlgorithm) { + const startAlgorithm = () => startPromise; + const writeAlgorithm = (chunk) => + TransformStreamDefaultSinkWriteAlgorithm(stream, chunk); + const abortAlgorithm = (reason) => + TransformStreamDefaultSinkAbortAlgorithm(stream, reason); + const closeAlgorithm = () => + TransformStreamDefaultSinkCloseAlgorithm(stream); + stream[_writable] = binding.CreateWritableStream( + startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, + writableHighWaterMark, writableSizeAlgorithm); + const pullAlgorithm = () => + TransformStreamDefaultSourcePullAlgorithm(stream); + const cancelAlgorithm = (reason) => { + TransformStreamErrorWritableAndUnblockWrite(stream, reason); + return Promise_resolve(undefined); + }; + stream[_readable] = binding.CreateReadableStream( + startAlgorithm, pullAlgorithm, cancelAlgorithm, readableHighWaterMark, + readableSizeAlgorithm, false); + stream[_backpressure] = undefined; + stream[_backpressureChangePromise] = undefined; + TransformStreamSetBackpressure(stream, true); + stream[_transformStreamController] = undefined; + } + + function IsTransformStream(x) { + return hasOwnPropertyNoThrow(x, _transformStreamController); + } + + function TransformStreamError(stream, e) { + const readable = stream[_readable]; + // TODO(ricea): Remove this conditional once ReadableStream is updated. + if (binding.IsReadableStreamReadable(readable)) { + binding.ReadableStreamDefaultControllerError( + binding.getReadableStreamController(readable), e); + } + + TransformStreamErrorWritableAndUnblockWrite(stream, e); + } + + function TransformStreamErrorWritableAndUnblockWrite(stream, e) { + TransformStreamDefaultControllerClearAlgorithms( + stream[_transformStreamController]); + binding.WritableStreamDefaultControllerErrorIfNeeded( + binding.getWritableStreamController(stream[_writable]), e); + + if (stream[_backpressure]) { + TransformStreamSetBackpressure(stream, false); + } + } + + function TransformStreamSetBackpressure(stream, backpressure) { + // assert( + // stream[_backpressure] !== backpressure, + // 'stream.[[backpressure]] is not backpressure'); + + if (stream[_backpressureChangePromise] !== undefined) { + resolvePromise(stream[_backpressureChangePromise], undefined); + } + + stream[_backpressureChangePromise] = v8.createPromise(); + stream[_backpressure] = backpressure; + } + + class TransformStreamDefaultController { + constructor() { + throw new TypeError(streamErrors.illegalConstructor); + } + + get desiredSize() { + if (!IsTransformStreamDefaultController(this)) { + throw new TypeError(streamErrors.illegalInvocation); + } + + const readableController = binding.getReadableStreamController( + this[_controlledTransformStream][_readable]); + return binding.ReadableStreamDefaultControllerGetDesiredSize( + readableController); + } + + enqueue(chunk) { + if (!IsTransformStreamDefaultController(this)) { + throw new TypeError(streamErrors.illegalInvocation); + } + + TransformStreamDefaultControllerEnqueue(this, chunk); + } + + error(reason) { + if (!IsTransformStreamDefaultController(this)) { + throw new TypeError(streamErrors.illegalInvocation); + } + + TransformStreamDefaultControllerError(this, reason); + } + + terminate() { + if (!IsTransformStreamDefaultController(this)) { + throw new TypeError(streamErrors.illegalInvocation); + } + + TransformStreamDefaultControllerTerminate(this); + } + } + + const TransformStreamDefaultController_prototype = + TransformStreamDefaultController.prototype; + + function IsTransformStreamDefaultController(x) { + return hasOwnPropertyNoThrow(x, _controlledTransformStream); + } + + function SetUpTransformStreamDefaultController( + stream, controller, transformAlgorithm, flushAlgorithm) { + // assert( + // IsTransformStream(stream) === true, + // '! IsTransformStream(_stream_) is *true*'); + // assert( + // stream[_transformStreamController] === undefined, + // '_stream_.[[transformStreamController]] is *undefined*'); + controller[_controlledTransformStream] = stream; + stream[_transformStreamController] = controller; + controller[_transformAlgorithm] = transformAlgorithm; + controller[_flushAlgorithm] = flushAlgorithm; + } + + function SetUpTransformStreamDefaultControllerFromTransformer( + stream, transformer) { + // assert(transformer !== undefined, '_transformer_ is not *undefined*'); + const controller = ObjectCreate(TransformStreamDefaultController_prototype); + let transformAlgorithm; + const transformMethod = transformer.transform; + if (transformMethod !== undefined) { + if (typeof transformMethod !== 'function') { + throw new TypeError('transformer.transform is not a function'); + } + transformAlgorithm = (chunk) => { + const transformPromise = + PromiseCall2(transformMethod, transformer, chunk, controller); + return thenPromise(transformPromise, undefined, (e) => { + TransformStreamError(stream, e); + throw e; + }); + }; + } else { + transformAlgorithm = (chunk) => { + try { + TransformStreamDefaultControllerEnqueue(controller, chunk); + return Promise_resolve(); + } catch (resultValue) { + return Promise_reject(resultValue); + } + }; + } + const flushAlgorithm = CreateAlgorithmFromUnderlyingMethod( + transformer, 'flush', 1, 'transformer.flush'); + SetUpTransformStreamDefaultController( + stream, controller, transformAlgorithm, flushAlgorithm); + } + + function TransformStreamDefaultControllerClearAlgorithms(controller) { + controller[_transformAlgorithm] = undefined; + controller[_flushAlgorithm] = undefined; + } + + function TransformStreamDefaultControllerEnqueue(controller, chunk) { + const stream = controller[_controlledTransformStream]; + const readableController = + binding.getReadableStreamController(stream[_readable]); + + if (!binding.ReadableStreamDefaultControllerCanCloseOrEnqueue( + readableController)) { + throw binding.getReadableStreamEnqueueError(stream[_readable]); + } + + try { + binding.ReadableStreamDefaultControllerEnqueue(readableController, chunk); + } catch (e) { + TransformStreamErrorWritableAndUnblockWrite(stream, e); + throw binding.getReadableStreamStoredError(stream[_readable]); + } + + const backpressure = binding.ReadableStreamDefaultControllerHasBackpressure( + readableController); + if (backpressure !== stream[_backpressure]) { + // assert(backpressure, 'backpressure is true'); + TransformStreamSetBackpressure(stream, true); + } + } + + function TransformStreamDefaultControllerError(controller, e) { + TransformStreamError(controller[_controlledTransformStream], e); + } + + function TransformStreamDefaultControllerTerminate(controller) { + const stream = controller[_controlledTransformStream]; + const readableController = + binding.getReadableStreamController(stream[_readable]); + + if (binding.ReadableStreamDefaultControllerCanCloseOrEnqueue( + readableController)) { + binding.ReadableStreamDefaultControllerClose(readableController); + } + + const error = new TypeError(errStreamTerminated); + TransformStreamErrorWritableAndUnblockWrite(stream, error); + } + + function TransformStreamDefaultSinkWriteAlgorithm(stream, chunk) { + // assert( + // binding.isWritableStreamWritable(stream[_writable]), + // `stream.[[writable]][[state]] is "writable"`); + + const controller = stream[_transformStreamController]; + + if (stream[_backpressure]) { + const backpressureChangePromise = stream[_backpressureChangePromise]; + // assert( + // backpressureChangePromise !== undefined, + // `backpressureChangePromise is not undefined`); + + return thenPromise(backpressureChangePromise, () => { + const writable = stream[_writable]; + if (binding.isWritableStreamErroring(writable)) { + throw binding.getWritableStreamStoredError(writable); + } + // assert(binding.isWritableStreamWritable(writable), + // `state is "writable"`); + + return controller[_transformAlgorithm](chunk, controller); + }); + } + + return controller[_transformAlgorithm](chunk, controller); + } + + function TransformStreamDefaultSinkAbortAlgorithm(stream, reason) { + TransformStreamError(stream, reason); + return Promise_resolve(); + } + + function TransformStreamDefaultSinkCloseAlgorithm(stream) { + const readable = stream[_readable]; + const controller = stream[_transformStreamController]; + const flushPromise = controller[_flushAlgorithm](controller); + TransformStreamDefaultControllerClearAlgorithms(controller); + + return thenPromise( + flushPromise, + () => { + if (binding.IsReadableStreamErrored(readable)) { + throw binding.getReadableStreamStoredError(readable); + } + + const readableController = + binding.getReadableStreamController(readable); + if (binding.ReadableStreamDefaultControllerCanCloseOrEnqueue( + readableController)) { + binding.ReadableStreamDefaultControllerClose(readableController); + } + }, + (r) => { + TransformStreamError(stream, r); + throw binding.getReadableStreamStoredError(readable); + }); + } + + function TransformStreamDefaultSourcePullAlgorithm(stream) { + // assert(stream[_backpressure], 'stream.[[backpressure]] is true'); + // assert( + // stream[_backpressureChangePromise] !== undefined, + // 'stream.[[backpressureChangePromise]] is not undefined'); + + TransformStreamSetBackpressure(stream, false); + return stream[_backpressureChangePromise]; + } + + // A wrapper for CreateTransformStream() with only the arguments that + // blink::TransformStream needs. |transformAlgorithm| and |flushAlgorithm| are + // passed the controller, unlike in the standard. + function createTransformStreamSimple(transformAlgorithm, flushAlgorithm) { + return CreateTransformStream(() => Promise_resolve(), + transformAlgorithm, flushAlgorithm); + } + + function getTransformStreamReadable(stream) { + return stream[_readable]; + } + + function getTransformStreamWritable(stream) { + return stream[_writable]; + } + + // + // Additions to the global object + // + + defineProperty(global, 'TransformStream', { + value: TransformStream, + enumerable: false, + configurable: true, + writable: true + }); + + // + // Exports to Blink + // + ObjectAssign(binding, { + createTransformStreamSimple, + TransformStreamDefaultControllerEnqueue, + getTransformStreamReadable, + getTransformStreamWritable + }); +}); diff --git a/lib/v8_extras/WritableStream.js b/lib/v8_extras/WritableStream.js new file mode 100644 index 00000000000000..00f726b146e97e --- /dev/null +++ b/lib/v8_extras/WritableStream.js @@ -0,0 +1,1097 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// 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 Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software 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. + +// Implementation of WritableStream for Blink. See +// https://streams.spec.whatwg.org/#ws. The implementation closely follows the +// standard, except where required for performance or integration with Blink. +// In particular, classes, methods and abstract operations are implemented in +// the same order as in the standard, to simplify side-by-side reading. + +(function(global, binding, v8) { + 'use strict'; + + // Private symbols. These correspond to the internal slots in the standard. + // "[[X]]" in the standard is spelt _X in this implementation. + + // TODO(ricea): Optimise [[closeRequest]] and [[inFlightCloseRequest]] into a + // single slot + a flag to say which one is set at the moment. + const _abortAlgorithm = v8.createPrivateSymbol('[[abortAlgorithm]]'); + const _closeAlgorithm = v8.createPrivateSymbol('[[closeAlgorithm]]'); + const _closeRequest = v8.createPrivateSymbol('[[closeRequest]]'); + const _inFlightWriteRequest = + v8.createPrivateSymbol('[[inFlightWriteRequest]]'); + const _inFlightCloseRequest = + v8.createPrivateSymbol('[[inFlightCloseRequest]]'); + const _pendingAbortRequest = + v8.createPrivateSymbol('[[pendingAbortRequest]]'); + // Flags and state are combined into a single integer field for efficiency. + const _stateAndFlags = v8.createPrivateSymbol('[[state]] and flags'); + const _storedError = v8.createPrivateSymbol('[[storedError]]'); + const _writableStreamController = + v8.createPrivateSymbol('[[writableStreamController]]'); + const _writer = v8.createPrivateSymbol('[[writer]]'); + const _writeRequests = v8.createPrivateSymbol('[[writeRequests]]'); + const _closedPromise = v8.createPrivateSymbol('[[closedPromise]]'); + const _ownerWritableStream = + v8.createPrivateSymbol('[[ownerWritableStream]]'); + const _readyPromise = v8.createPrivateSymbol('[[readyPromise]]'); + const _controlledWritableStream = + v8.createPrivateSymbol('[[controlledWritableStream]]'); + const _started = v8.createPrivateSymbol('[[started]]'); + const _strategyHWM = v8.createPrivateSymbol('[[strategyHWM]]'); + const _strategySizeAlgorithm = + v8.createPrivateSymbol('[[strategySizeAlgorithm]]'); + const _writeAlgorithm = v8.createPrivateSymbol('[[writeAlgorithm]]'); + + // Numeric encodings of stream states. Stored in the _stateAndFlags slot. + const WRITABLE = 0; + const CLOSED = 1; + const ERRORING = 2; + const ERRORED = 3; + + // Mask to extract or assign states to _stateAndFlags. + const STATE_MASK = 0xF; + + // Also stored in _stateAndFlags. + const BACKPRESSURE_FLAG = 0x10; + + // Javascript functions. It is important to use these copies, as the ones on + // the global object may have been overwritten. See "V8 Extras Design Doc", + // section "Security Considerations". + // https://docs.google.com/document/d/1AT5-T0aHGp7Lt29vPWFr2-qG8r3l9CByyvKwEuA8Ec0/edit#heading=h.9yixony1a18r + const defineProperty = global.Object.defineProperty; + const ObjectCreate = global.Object.create; + const ObjectAssign = global.Object.assign; + + const Function_call = v8.uncurryThis(global.Function.prototype.call); + + const TypeError = global.TypeError; + const RangeError = global.RangeError; + + const Boolean = global.Boolean; + + const Promise = global.Promise; + const thenPromise = v8.uncurryThis(Promise.prototype.then); + const Promise_resolve = Promise.resolve.bind(Promise); + const Promise_reject = Promise.reject.bind(Promise); + + // From CommonOperations.js + const { + _queue, + _queueTotalSize, + hasOwnPropertyNoThrow, + rejectPromise, + resolvePromise, + markPromiseAsHandled, + promiseState, + CreateAlgorithmFromUnderlyingMethod, + CreateAlgorithmFromUnderlyingMethodPassingController, + DequeueValue, + EnqueueValueWithSize, + MakeSizeAlgorithmFromSizeFunction, + PeekQueueValue, + ResetQueue, + ValidateAndNormalizeHighWaterMark, + CallOrNoop1, + } = binding.streamOperations; + + // User-visible strings. + const streamErrors = binding.streamErrors; + const errAbortLockedStream = + 'Cannot abort a writable stream that is locked to a writer'; + const errWriterLockReleasedPrefix = + 'This writable stream writer has been released and cannot be '; + const errCloseCloseRequestedStream = 'Cannot close a writable stream that ' + + 'has already been requested to be closed'; + const templateErrorCannotActionOnStateStream = (action, state) => + `Cannot ${action} a ${state} writable stream`; + const errReleasedWriterClosedPromise = 'This writable stream writer has ' + + 'been released and cannot be used to monitor the stream\'s state'; + + // These verbs are used after errWriterLockReleasedPrefix + const verbUsedToGetTheDesiredSize = 'used to get the desiredSize'; + const verbAborted = 'aborted'; + const verbClosed = 'closed'; + const verbWrittenTo = 'written to'; + + // Utility functions (not from the standard). + function createWriterLockReleasedError(verb) { + return new TypeError(errWriterLockReleasedPrefix + verb); + } + + const stateNames = { + [CLOSED]: 'closed', + [ERRORED]: 'errored' + }; + function createCannotActionOnStateStreamError(action, state) { + // assert(stateNames[state] !== undefined, + // `name for state ${state} exists in stateNames`); + return new TypeError( + templateErrorCannotActionOnStateStream(action, stateNames[state])); + } + + function rejectPromises(queue, e) { + queue.forEach((promise) => rejectPromise(promise, e)); + } + + class WritableStream { + constructor(underlyingSink = {}, strategy = {}) { + InitializeWritableStream(this); + const size = strategy.size; + let highWaterMark = strategy.highWaterMark; + const type = underlyingSink.type; + if (type !== undefined) { + throw new RangeError(streamErrors.invalidType); + } + const sizeAlgorithm = MakeSizeAlgorithmFromSizeFunction(size); + if (highWaterMark === undefined) { + highWaterMark = 1; + } + highWaterMark = ValidateAndNormalizeHighWaterMark(highWaterMark); + SetUpWritableStreamDefaultControllerFromUnderlyingSink( + this, underlyingSink, highWaterMark, sizeAlgorithm); + } + + get locked() { + if (!IsWritableStream(this)) { + throw new TypeError(streamErrors.illegalInvocation); + } + return IsWritableStreamLocked(this); + } + + abort(reason) { + if (!IsWritableStream(this)) { + return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + } + if (IsWritableStreamLocked(this)) { + return Promise_reject(new TypeError(errAbortLockedStream)); + } + return WritableStreamAbort(this, reason); + } + + getWriter() { + if (!IsWritableStream(this)) { + throw new TypeError(streamErrors.illegalInvocation); + } + return AcquireWritableStreamDefaultWriter(this); + } + } + + const WritableStream_prototype = WritableStream.prototype; + + // General Writable Stream Abstract Operations + + function AcquireWritableStreamDefaultWriter(stream) { + // eslint-disable-next-line no-use-before-define + return new WritableStreamDefaultWriter(stream); + } + + function CreateWritableStream( + startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, + highWaterMark, sizeAlgorithm) { + if (highWaterMark === undefined) { + highWaterMark = 1; + } + if (sizeAlgorithm === undefined) { + sizeAlgorithm = () => 1; + } + // assert(IsNonNegativeNumber(highWaterMark), + // '! IsNonNegativeNumber(_highWaterMark_) is *true*.') + const stream = ObjectCreate(WritableStream_prototype); + InitializeWritableStream(stream); + const controller = ObjectCreate(WritableStreamDefaultController_prototype); + SetUpWritableStreamDefaultController( + stream, controller, startAlgorithm, writeAlgorithm, closeAlgorithm, + abortAlgorithm, highWaterMark, sizeAlgorithm); + return stream; + } + + function InitializeWritableStream(stream) { + stream[_stateAndFlags] = WRITABLE; + stream[_storedError] = undefined; + stream[_writer] = undefined; + stream[_writableStreamController] = undefined; + stream[_inFlightWriteRequest] = undefined; + stream[_closeRequest] = undefined; + stream[_inFlightCloseRequest] = undefined; + stream[_pendingAbortRequest] = undefined; + stream[_writeRequests] = new binding.SimpleQueue(); + } + + function IsWritableStream(x) { + return hasOwnPropertyNoThrow(x, _writableStreamController); + } + + function IsWritableStreamLocked(stream) { + // assert(IsWritableStream(stream), + // '! IsWritableStream(stream) is true.'); + return stream[_writer] !== undefined; + } + + function WritableStreamAbort(stream, reason) { + const state = stream[_stateAndFlags] & STATE_MASK; + if (state === CLOSED || state === ERRORED) { + return Promise_resolve(undefined); + } + if (stream[_pendingAbortRequest] !== undefined) { + return stream[_pendingAbortRequest].promise; + } + + // assert(state === WRITABLE || state === ERRORING, + // '_state_ is `"writable"` or `"erroring"`'); + + const wasAlreadyErroring = state === ERRORING; + if (wasAlreadyErroring) { + reason = undefined; + } + + const promise = v8.createPromise(); + stream[_pendingAbortRequest] = { promise, reason, wasAlreadyErroring }; + + if (!wasAlreadyErroring) { + WritableStreamStartErroring(stream, reason); + } + return promise; + } + + // Writable Stream Abstract Operations Used by Controllers + + function WritableStreamAddWriteRequest(stream) { + // assert(IsWritableStreamLocked(stream), + // '! IsWritableStreamLocked(writer) is true.'); + // assert((stream[_stateAndFlags] & STATE_MASK) === WRITABLE, + // 'stream.[[state]] is "writable".'); + const promise = v8.createPromise(); + stream[_writeRequests].push(promise); + return promise; + } + + function WritableStreamDealWithRejection(stream, error) { + const state = stream[_stateAndFlags] & STATE_MASK; + if (state === WRITABLE) { + WritableStreamStartErroring(stream, error); + return; + } + + // assert(state === ERRORING, '_state_ is `"erroring"`'); + WritableStreamFinishErroring(stream); + } + + function WritableStreamStartErroring(stream, reason) { + // assert(stream[_storedError] === undefined, + // '_stream_.[[storedError]] is *undefined*'); + // assert((stream[_stateAndFlags] & STATE_MASK) === WRITABLE, + // '_stream_.[[state]] is `"writable"`'); + + const controller = stream[_writableStreamController]; + // assert(controller !== undefined, '_controller_ is not *undefined*'); + + stream[_stateAndFlags] = (stream[_stateAndFlags] & ~STATE_MASK) | ERRORING; + stream[_storedError] = reason; + + const writer = stream[_writer]; + if (writer !== undefined) { + WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason); + } + + if (!WritableStreamHasOperationMarkedInFlight(stream) && + controller[_started]) { + WritableStreamFinishErroring(stream); + } + } + + function WritableStreamFinishErroring(stream) { + // assert((stream[_stateAndFlags] & STATE_MASK) === ERRORING, + // '_stream_.[[state]] is `"erroring"`'); + // assert(!WritableStreamHasOperationMarkedInFlight(stream), + // '! WritableStreamHasOperationMarkedInFlight(_stream_) is ' + + // '*false*'); + + stream[_stateAndFlags] = (stream[_stateAndFlags] & ~STATE_MASK) | ERRORED; + + WritableStreamDefaultControllerErrorSteps( + stream[_writableStreamController]); + + const storedError = stream[_storedError]; + rejectPromises(stream[_writeRequests], storedError); + stream[_writeRequests] = new binding.SimpleQueue(); + + if (stream[_pendingAbortRequest] === undefined) { + WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + return; + } + + const abortRequest = stream[_pendingAbortRequest]; + stream[_pendingAbortRequest] = undefined; + + if (abortRequest.wasAlreadyErroring === true) { + rejectPromise(abortRequest.promise, storedError); + WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + return; + } + + const promise = WritableStreamDefaultControllerAbortSteps( + stream[_writableStreamController], abortRequest.reason); + + thenPromise( + promise, + () => { + resolvePromise(abortRequest.promise, undefined); + WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + }, + (reason) => { + rejectPromise(abortRequest.promise, reason); + WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream); + }); + } + + function WritableStreamFinishInFlightWrite(stream) { + // assert(stream[_inFlightWriteRequest] !== undefined, + // '_stream_.[[inFlightWriteRequest]] is not *undefined*.'); + resolvePromise(stream[_inFlightWriteRequest], undefined); + stream[_inFlightWriteRequest] = undefined; + } + + function WritableStreamFinishInFlightWriteWithError(stream, error) { + // assert(stream[_inFlightWriteRequest] !== undefined, + // '_stream_.[[inFlightWriteRequest]] is not *undefined*.'); + rejectPromise(stream[_inFlightWriteRequest], error); + stream[_inFlightWriteRequest] = undefined; + + // const state = stream[_stateAndFlags] & STATE_MASK; + // assert(state === WRITABLE || state === ERRORING, + // '_stream_.[[state]] is `"writable"` or `"erroring"`'); + + WritableStreamDealWithRejection(stream, error); + } + + function WritableStreamFinishInFlightClose(stream) { + // assert(stream[_inFlightCloseRequest] !== undefined, + // '_stream_.[[inFlightCloseRequest]] is not *undefined*.'); + resolvePromise(stream[_inFlightCloseRequest], undefined); + stream[_inFlightCloseRequest] = undefined; + + const state = stream[_stateAndFlags] & STATE_MASK; + // assert(state === WRITABLE || state === ERRORING, + // '_stream_.[[state]] is `"writable"` or `"erroring"`'); + + if (state === ERRORING) { + stream[_storedError] = undefined; + if (stream[_pendingAbortRequest] !== undefined) { + resolvePromise(stream[_pendingAbortRequest].promise, undefined); + stream[_pendingAbortRequest] = undefined; + } + } + + stream[_stateAndFlags] = (stream[_stateAndFlags] & ~STATE_MASK) | CLOSED; + const writer = stream[_writer]; + if (writer !== undefined) { + resolvePromise(writer[_closedPromise], undefined); + } + + // assert(stream[_pendingAbortRequest] === undefined, + // '_stream_.[[pendingAbortRequest]] is *undefined*'); + // assert(stream[_storedError] === undefined, + // '_stream_.[[storedError]] is *undefined*'); + } + + function WritableStreamFinishInFlightCloseWithError(stream, error) { + // assert(stream[_inFlightCloseRequest] !== undefined, + // '_stream_.[[inFlightCloseRequest]] is not *undefined*.'); + rejectPromise(stream[_inFlightCloseRequest], error); + stream[_inFlightCloseRequest] = undefined; + + // const state = stream[_stateAndFlags] & STATE_MASK; + // assert(state === WRITABLE || state === ERRORING, + // '_stream_.[[state]] is `"writable"` or `"erroring"`'); + + if (stream[_pendingAbortRequest] !== undefined) { + rejectPromise(stream[_pendingAbortRequest].promise, error); + stream[_pendingAbortRequest] = undefined; + } + + WritableStreamDealWithRejection(stream, error); + } + + function WritableStreamCloseQueuedOrInFlight(stream) { + return stream[_closeRequest] !== undefined || + stream[_inFlightCloseRequest] !== undefined; + } + + function WritableStreamHasOperationMarkedInFlight(stream) { + return stream[_inFlightWriteRequest] !== undefined || + stream[_inFlightCloseRequest] !== undefined; + } + + function WritableStreamMarkCloseRequestInFlight(stream) { + // assert(stream[_inFlightCloseRequest] === undefined, + // '_stream_.[[inFlightCloseRequest]] is *undefined*.'); + // assert(stream[_closeRequest] !== undefined, + // '_stream_.[[closeRequest]] is not *undefined*.'); + stream[_inFlightCloseRequest] = stream[_closeRequest]; + stream[_closeRequest] = undefined; + } + + function WritableStreamMarkFirstWriteRequestInFlight(stream) { + // assert(stream[_inFlightWriteRequest] === undefined, + // '_stream_.[[inFlightWriteRequest]] is *undefined*.'); + // assert(stream[_writeRequests].length !== 0, + // '_stream_.[[writeRequests]] is not empty.'); + const writeRequest = stream[_writeRequests].shift(); + stream[_inFlightWriteRequest] = writeRequest; + } + + function WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream) { + // assert((stream[_stateAndFlags] & STATE_MASK) === ERRORED, + // '_stream_.[[state]] is `"errored"`'); + + if (stream[_closeRequest] !== undefined) { + // assert(stream[_inFlightCloseRequest] === undefined, + // '_stream_.[[inFlightCloseRequest]] is *undefined*'); + rejectPromise(stream[_closeRequest], stream[_storedError]); + stream[_closeRequest] = undefined; + } + + const writer = stream[_writer]; + if (writer !== undefined) { + rejectPromise(writer[_closedPromise], stream[_storedError]); + markPromiseAsHandled(writer[_closedPromise]); + } + } + + function WritableStreamUpdateBackpressure(stream, backpressure) { + // assert((stream[_stateAndFlags] & STATE_MASK) === WRITABLE, + // 'stream.[[state]] is "writable".'); + // assert(!WritableStreamCloseQueuedOrInFlight(stream), + // 'WritableStreamCloseQueuedOrInFlight(_stream_) is *false*.'); + const writer = stream[_writer]; + if (writer !== undefined && + backpressure !== Boolean(stream[_stateAndFlags] & BACKPRESSURE_FLAG)) { + if (backpressure) { + writer[_readyPromise] = v8.createPromise(); + } else { + // assert(!backpressure, '_backpressure_ is *false*.'); + resolvePromise(writer[_readyPromise], undefined); + } + } + if (backpressure) { + stream[_stateAndFlags] |= BACKPRESSURE_FLAG; + } else { + stream[_stateAndFlags] &= ~BACKPRESSURE_FLAG; + } + } + + // Functions to expose internals for ReadableStream.pipeTo. These are not + // part of the standard. + function isWritableStreamErrored(stream) { + // assert( + // IsWritableStream(stream), '! IsWritableStream(stream) is true.'); + return (stream[_stateAndFlags] & STATE_MASK) === ERRORED; + } + + function isWritableStreamClosingOrClosed(stream) { + // assert( + // IsWritableStream(stream), '! IsWritableStream(stream) is true.'); + return WritableStreamCloseQueuedOrInFlight(stream) || + (stream[_stateAndFlags] & STATE_MASK) === CLOSED; + } + + function getWritableStreamStoredError(stream) { + // assert( + // IsWritableStream(stream), '! IsWritableStream(stream) is true.'); + return stream[_storedError]; + } + + // Expose internals for TransformStream + function isWritableStreamWritable(stream) { + // assert( + // IsWritableStream(stream), '! IsWritableStream(stream) is true.'); + return (stream[_stateAndFlags] & STATE_MASK) === WRITABLE; + } + + function isWritableStreamErroring(stream) { + // assert( + // IsWritableStream(stream), '! IsWritableStream(stream) is true.'); + return (stream[_stateAndFlags] & STATE_MASK) === ERRORING; + } + + function getWritableStreamController(stream) { + // assert( + // IsWritableStream(stream), '! IsWritableStream(stream) is true.'); + return stream[_writableStreamController]; + } + + class WritableStreamDefaultWriter { + constructor(stream) { + if (!IsWritableStream(stream)) { + throw new TypeError(streamErrors.illegalConstructor); + } + if (IsWritableStreamLocked(stream)) { + throw new TypeError(streamErrors.illegalConstructor); + } + this[_ownerWritableStream] = stream; + stream[_writer] = this; + const state = stream[_stateAndFlags] & STATE_MASK; + switch (state) { + case WRITABLE: { + if (!WritableStreamCloseQueuedOrInFlight(stream) && + stream[_stateAndFlags] & BACKPRESSURE_FLAG) { + this[_readyPromise] = v8.createPromise(); + } else { + this[_readyPromise] = Promise_resolve(undefined); + } + this[_closedPromise] = v8.createPromise(); + break; + } + + case ERRORING: { + this[_readyPromise] = Promise_reject(stream[_storedError]); + markPromiseAsHandled(this[_readyPromise]); + this[_closedPromise] = v8.createPromise(); + break; + } + + case CLOSED: { + this[_readyPromise] = Promise_resolve(undefined); + this[_closedPromise] = Promise_resolve(undefined); + break; + } + + default: { + // assert(state === ERRORED, '_state_ is `"errored"`.'); + const storedError = stream[_storedError]; + this[_readyPromise] = Promise_reject(storedError); + markPromiseAsHandled(this[_readyPromise]); + this[_closedPromise] = Promise_reject(storedError); + markPromiseAsHandled(this[_closedPromise]); + break; + } + } + } + + get closed() { + if (!IsWritableStreamDefaultWriter(this)) { + return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + } + return this[_closedPromise]; + } + + get desiredSize() { + if (!IsWritableStreamDefaultWriter(this)) { + throw new TypeError(streamErrors.illegalInvocation); + } + if (this[_ownerWritableStream] === undefined) { + throw createWriterLockReleasedError(verbUsedToGetTheDesiredSize); + } + return WritableStreamDefaultWriterGetDesiredSize(this); + } + + get ready() { + if (!IsWritableStreamDefaultWriter(this)) { + return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + } + return this[_readyPromise]; + } + + abort(reason) { + if (!IsWritableStreamDefaultWriter(this)) { + return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + } + if (this[_ownerWritableStream] === undefined) { + return Promise_reject(createWriterLockReleasedError(verbAborted)); + } + return WritableStreamDefaultWriterAbort(this, reason); + } + + close() { + if (!IsWritableStreamDefaultWriter(this)) { + return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + } + const stream = this[_ownerWritableStream]; + if (stream === undefined) { + return Promise_reject(createWriterLockReleasedError(verbClosed)); + } + if (WritableStreamCloseQueuedOrInFlight(stream)) { + return Promise_reject(new TypeError(errCloseCloseRequestedStream)); + } + return WritableStreamDefaultWriterClose(this); + } + + releaseLock() { + if (!IsWritableStreamDefaultWriter(this)) { + throw new TypeError(streamErrors.illegalInvocation); + } + const stream = this[_ownerWritableStream]; + if (stream === undefined) { + return; + } + // assert(stream[_writer] !== undefined, + // 'stream.[[writer]] is not undefined.'); + WritableStreamDefaultWriterRelease(this); + } + + write(chunk) { + if (!IsWritableStreamDefaultWriter(this)) { + return Promise_reject(new TypeError(streamErrors.illegalInvocation)); + } + if (this[_ownerWritableStream] === undefined) { + return Promise_reject(createWriterLockReleasedError(verbWrittenTo)); + } + return WritableStreamDefaultWriterWrite(this, chunk); + } + } + + // Writable Stream Writer Abstract Operations + + function IsWritableStreamDefaultWriter(x) { + return hasOwnPropertyNoThrow(x, _ownerWritableStream); + } + + function WritableStreamDefaultWriterAbort(writer, reason) { + const stream = writer[_ownerWritableStream]; + // assert(stream !== undefined, + // 'stream is not undefined.'); + return WritableStreamAbort(stream, reason); + } + + function WritableStreamDefaultWriterClose(writer) { + const stream = writer[_ownerWritableStream]; + // assert(stream !== undefined, 'stream is not undefined.'); + const state = stream[_stateAndFlags] & STATE_MASK; + if (state === CLOSED || state === ERRORED) { + return Promise_reject( + createCannotActionOnStateStreamError('close', state)); + } + + // assert(state === WRITABLE || state === ERRORING, + // '_state_ is `"writable"` or `"erroring"`.'); + // assert(!WritableStreamCloseQueuedOrInFlight(stream), + // '! WritableStreamCloseQueuedOrInFlight(_stream_) is *false*.'); + const promise = v8.createPromise(); + stream[_closeRequest] = promise; + + if ((stream[_stateAndFlags] & BACKPRESSURE_FLAG) && state === WRITABLE) { + resolvePromise(writer[_readyPromise], undefined); + } + WritableStreamDefaultControllerClose(stream[_writableStreamController]); + return promise; + } + + function WritableStreamDefaultWriterCloseWithErrorPropagation(writer) { + const stream = writer[_ownerWritableStream]; + // assert(stream !== undefined, 'stream is not undefined.'); + const state = stream[_stateAndFlags] & STATE_MASK; + if (WritableStreamCloseQueuedOrInFlight(stream) || state === CLOSED) { + return Promise_resolve(undefined); + } + if (state === ERRORED) { + return Promise_reject(stream[_storedError]); + } + + // assert(state === WRITABLE || state === ERRORING, + // '_state_ is `"writable"` or `"erroring"`.'); + + return WritableStreamDefaultWriterClose(writer); + } + + function WritableStreamDefaultWriterEnsureClosedPromiseRejected( + writer, error) { + if (promiseState(writer[_closedPromise]) === v8.kPROMISE_PENDING) { + rejectPromise(writer[_closedPromise], error); + } else { + writer[_closedPromise] = Promise_reject(error); + } + markPromiseAsHandled(writer[_closedPromise]); + } + + + function WritableStreamDefaultWriterEnsureReadyPromiseRejected( + writer, error) { + if (promiseState(writer[_readyPromise]) === v8.kPROMISE_PENDING) { + rejectPromise(writer[_readyPromise], error); + } else { + writer[_readyPromise] = Promise_reject(error); + } + markPromiseAsHandled(writer[_readyPromise]); + } + + function WritableStreamDefaultWriterGetDesiredSize(writer) { + const stream = writer[_ownerWritableStream]; + const state = stream[_stateAndFlags] & STATE_MASK; + if (state === ERRORED || state === ERRORING) { + return null; + } + if (state === CLOSED) { + return 0; + } + return WritableStreamDefaultControllerGetDesiredSize( + stream[_writableStreamController]); + } + + function WritableStreamDefaultWriterRelease(writer) { + const stream = writer[_ownerWritableStream]; + // assert(stream !== undefined, + // 'stream is not undefined.'); + // assert(stream[_writer] === writer, + // 'stream.[[writer]] is writer.'); + const releasedError = new TypeError(errReleasedWriterClosedPromise); + WritableStreamDefaultWriterEnsureReadyPromiseRejected( + writer, releasedError); + WritableStreamDefaultWriterEnsureClosedPromiseRejected( + writer, releasedError); + stream[_writer] = undefined; + writer[_ownerWritableStream] = undefined; + } + + function WritableStreamDefaultWriterWrite(writer, chunk) { + const stream = writer[_ownerWritableStream]; + // assert(stream !== undefined, 'stream is not undefined.'); + const controller = stream[_writableStreamController]; + const chunkSize = + WritableStreamDefaultControllerGetChunkSize(controller, chunk); + if (stream !== writer[_ownerWritableStream]) { + return Promise_reject(createWriterLockReleasedError(verbWrittenTo)); + } + const state = stream[_stateAndFlags] & STATE_MASK; + if (state === ERRORED) { + return Promise_reject(stream[_storedError]); + } + if (WritableStreamCloseQueuedOrInFlight(stream)) { + return Promise_reject(new TypeError( + templateErrorCannotActionOnStateStream('write to', 'closing'))); + } + if (state === CLOSED) { + return Promise_reject( + createCannotActionOnStateStreamError('write to', CLOSED)); + } + if (state === ERRORING) { + return Promise_reject(stream[_storedError]); + } + // assert(state === WRITABLE, '_state_ is `"writable"`'); + const promise = WritableStreamAddWriteRequest(stream); + WritableStreamDefaultControllerWrite(controller, chunk, chunkSize); + return promise; + } + + // Functions to expose internals for ReadableStream.pipeTo. These do not + // appear in the standard. + function getWritableStreamDefaultWriterClosedPromise(writer) { + // assert( + // IsWritableStreamDefaultWriter(writer), + // 'writer is a WritableStreamDefaultWriter.'); + return writer[_closedPromise]; + } + + function getWritableStreamDefaultWriterReadyPromise(writer) { + // assert( + // IsWritableStreamDefaultWriter(writer), + // 'writer is a WritableStreamDefaultWriter.'); + return writer[_readyPromise]; + } + + class WritableStreamDefaultController { + constructor() { + throw new TypeError(streamErrors.illegalConstructor); + } + + error(e) { + if (!IsWritableStreamDefaultController(this)) { + throw new TypeError(streamErrors.illegalInvocation); + } + const state = + this[_controlledWritableStream][_stateAndFlags] & STATE_MASK; + if (state !== WRITABLE) { + return; + } + WritableStreamDefaultControllerError(this, e); + } + } + + const WritableStreamDefaultController_prototype = + WritableStreamDefaultController.prototype; + + // Writable Stream Default Controller Internal Methods + + // TODO(ricea): Virtual dispatch via V8 Private Symbols seems to be difficult + // or impossible, so use static dispatch for now. This will have to be fixed + // when adding a byte controller. + function WritableStreamDefaultControllerAbortSteps(controller, reason) { + const result = controller[_abortAlgorithm](reason); + WritableStreamDefaultControllerClearAlgorithms(controller); + return result; + } + + function WritableStreamDefaultControllerErrorSteps(controller) { + ResetQueue(controller); + } + + // Writable Stream Default Controller Abstract Operations + + function IsWritableStreamDefaultController(x) { + return hasOwnPropertyNoThrow(x, _controlledWritableStream); + } + + function SetUpWritableStreamDefaultController( + stream, controller, startAlgorithm, writeAlgorithm, closeAlgorithm, + abortAlgorithm, highWaterMark, sizeAlgorithm) { + // assert(IsWritableStream(stream), '! IsWritableStream(_stream_) is + // *true*.'); + // assert(stream[_writableStreamController] === undefined, + // '_stream_.[[writableStreamController]] is *undefined*.'); + controller[_controlledWritableStream] = stream; + stream[_writableStreamController] = controller; + // These are just initialised to avoid triggering the assert() in + // ResetQueue. They are overwritten by ResetQueue(). + controller[_queue] = undefined; + controller[_queueTotalSize] = undefined; + ResetQueue(controller); + controller[_started] = false; + controller[_strategySizeAlgorithm] = sizeAlgorithm; + controller[_strategyHWM] = highWaterMark; + controller[_writeAlgorithm] = writeAlgorithm; + controller[_closeAlgorithm] = closeAlgorithm; + controller[_abortAlgorithm] = abortAlgorithm; + const backpressure = + WritableStreamDefaultControllerGetBackpressure(controller); + WritableStreamUpdateBackpressure(stream, backpressure); + const startResult = startAlgorithm(); + const startPromise = Promise_resolve(startResult); + thenPromise( + startPromise, + () => { + // const state = stream[_stateAndFlags] & STATE_MASK; + // assert(state === WRITABLE || state === ERRORING, + // '_stream_.[[state]] is `"writable"` or `"erroring"`'); + controller[_started] = true; + WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + }, + (r) => { + // const state = stream[_stateAndFlags] & STATE_MASK; + // assert(state === WRITABLE || state === ERRORING, + // '_stream_.[[state]] is `"writable"` or `"erroring"`'); + controller[_started] = true; + WritableStreamDealWithRejection(stream, r); + }); + } + + function SetUpWritableStreamDefaultControllerFromUnderlyingSink( + stream, underlyingSink, highWaterMark, sizeAlgorithm) { + // assert(underlyingSink !== undefined, '_underlyingSink_ is not ' + + // '*undefined*.'); + const controller = ObjectCreate(WritableStreamDefaultController_prototype); + const startAlgorithm = + () => CallOrNoop1(underlyingSink, 'start', controller, + 'underlyingSink.start'); + const writeAlgorithm = CreateAlgorithmFromUnderlyingMethodPassingController( + underlyingSink, 'write', 1, controller, 'underlyingSink.write'); + const closeAlgorithm = CreateAlgorithmFromUnderlyingMethod( + underlyingSink, 'close', 0, 'underlyingSink.close'); + const abortAlgorithm = CreateAlgorithmFromUnderlyingMethod( + underlyingSink, 'abort', 1, 'underlyingSink.abort'); + SetUpWritableStreamDefaultController( + stream, controller, startAlgorithm, + writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, + sizeAlgorithm); + } + + function WritableStreamDefaultControllerClearAlgorithms(controller) { + controller[_writeAlgorithm] = undefined; + controller[_closeAlgorithm] = undefined; + controller[_abortAlgorithm] = undefined; + } + + function WritableStreamDefaultControllerClose(controller) { + EnqueueValueWithSize(controller, 'close', 0); + WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + } + + function WritableStreamDefaultControllerGetChunkSize(controller, chunk) { + try { + // Unlike other algorithms, strategySizeAlgorithm isn't indirected, so we + // need to be careful with the |this| value. + return Function_call(controller[_strategySizeAlgorithm], undefined, + chunk); + } catch (e) { + WritableStreamDefaultControllerErrorIfNeeded(controller, e); + return 1; + } + } + + function WritableStreamDefaultControllerGetDesiredSize(controller) { + return controller[_strategyHWM] - controller[_queueTotalSize]; + } + + function WritableStreamDefaultControllerWrite(controller, chunk, chunkSize) { + const writeRecord = { chunk }; + try { + EnqueueValueWithSize(controller, writeRecord, chunkSize); + } catch (e) { + WritableStreamDefaultControllerErrorIfNeeded(controller, e); + return; + } + const stream = controller[_controlledWritableStream]; + if (!WritableStreamCloseQueuedOrInFlight(stream) && + (stream[_stateAndFlags] & STATE_MASK) === WRITABLE) { + const backpressure = + WritableStreamDefaultControllerGetBackpressure(controller); + WritableStreamUpdateBackpressure(stream, backpressure); + } + WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + } + + function WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller) { + const stream = controller[_controlledWritableStream]; + if (!controller[_started]) { + return; + } + if (stream[_inFlightWriteRequest] !== undefined) { + return; + } + const state = stream[_stateAndFlags] & STATE_MASK; + if (state === CLOSED || state === ERRORED) { + return; + } + if (state === ERRORING) { + WritableStreamFinishErroring(stream); + return; + } + if (controller[_queue].length === 0) { + return; + } + const writeRecord = PeekQueueValue(controller); + if (writeRecord === 'close') { + WritableStreamDefaultControllerProcessClose(controller); + } else { + WritableStreamDefaultControllerProcessWrite( + controller, writeRecord.chunk); + } + } + + function WritableStreamDefaultControllerErrorIfNeeded(controller, error) { + const state = + controller[_controlledWritableStream][_stateAndFlags] & STATE_MASK; + if (state === WRITABLE) { + WritableStreamDefaultControllerError(controller, error); + } + } + + function WritableStreamDefaultControllerProcessClose(controller) { + const stream = controller[_controlledWritableStream]; + WritableStreamMarkCloseRequestInFlight(stream); + DequeueValue(controller); + // assert(controller[_queue].length === 0, + // 'controller.[[queue]] is empty.'); + const sinkClosePromise = controller[_closeAlgorithm](); + WritableStreamDefaultControllerClearAlgorithms(controller); + thenPromise( + sinkClosePromise, () => WritableStreamFinishInFlightClose(stream), + (reason) => WritableStreamFinishInFlightCloseWithError(stream, reason)); + } + + function WritableStreamDefaultControllerProcessWrite(controller, chunk) { + const stream = controller[_controlledWritableStream]; + WritableStreamMarkFirstWriteRequestInFlight(stream); + const sinkWritePromise = controller[_writeAlgorithm](chunk); + thenPromise( + sinkWritePromise, + () => { + WritableStreamFinishInFlightWrite(stream); + const state = stream[_stateAndFlags] & STATE_MASK; + // assert(state === WRITABLE || state === ERRORING, + // '_state_ is `"writable"` or `"erroring"`'); + DequeueValue(controller); + if (!WritableStreamCloseQueuedOrInFlight(stream) && + state === WRITABLE) { + const backpressure = + WritableStreamDefaultControllerGetBackpressure(controller); + WritableStreamUpdateBackpressure(stream, backpressure); + } + WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller); + }, + (reason) => { + const state = stream[_stateAndFlags] & STATE_MASK; + if (state === WRITABLE) { + WritableStreamDefaultControllerClearAlgorithms(controller); + } + WritableStreamFinishInFlightWriteWithError(stream, reason); + }); + } + + function WritableStreamDefaultControllerGetBackpressure(controller) { + const desiredSize = + WritableStreamDefaultControllerGetDesiredSize(controller); + return desiredSize <= 0; + } + + function WritableStreamDefaultControllerError(controller, error) { + const stream = controller[_controlledWritableStream]; + // assert((stream[_stateAndFlags] & STATE_MASK) === WRITABLE, + // '_stream_.[[state]] is `"writable"`.'); + WritableStreamDefaultControllerClearAlgorithms(controller); + WritableStreamStartErroring(stream, error); + } + + // + // Additions to the global object + // + + defineProperty(global, 'WritableStream', { + value: WritableStream, + enumerable: false, + configurable: true, + writable: true + }); + + // TODO(ricea): Exports to Blink + + ObjectAssign(binding, { + // Exports for ReadableStream + AcquireWritableStreamDefaultWriter, + IsWritableStream, + isWritableStreamClosingOrClosed, + isWritableStreamErrored, + isWritableStreamWritable, + IsWritableStreamLocked, + WritableStreamAbort, + WritableStreamCloseQueuedOrInFlight, + WritableStreamDefaultWriterCloseWithErrorPropagation, + getWritableStreamDefaultWriterClosedPromise, + WritableStreamDefaultWriterGetDesiredSize, + getWritableStreamDefaultWriterReadyPromise, + WritableStreamDefaultWriterRelease, + WritableStreamDefaultWriterWrite, + getWritableStreamStoredError, + + // Additional exports for TransformStream + CreateWritableStream, + WritableStream, + WritableStreamDefaultControllerErrorIfNeeded, + isWritableStreamErroring, + getWritableStreamController, + }); +}); diff --git a/src/node.cc b/src/node.cc index 33a68b37a46062..ef0c9cec980d28 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1739,6 +1739,10 @@ static void GetInternalBinding(const FunctionCallbackInfo& args) { // internalBinding('natives_hash') exports = Object::New(env->isolate()); DefineJavaScriptHash(env, exports); + } else if (!strcmp(*module_v, "v8_extras")) { + // override `exports` return value, GetExtrasBindingObject is persistent. + args.GetReturnValue().Set(env->context()->GetExtrasBindingObject()); + return; } else { return ThrowIfNoSuchModule(env, *module_v); } From d2e32a13e9ace5d513f8a55414154dc66d2ab673 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Sun, 19 Aug 2018 18:10:25 -0500 Subject: [PATCH 2/3] test: add wpt --- Makefile | 9 +- test/fixtures/web-platform-tests/CODEOWNERS | 2 + .../web-platform-tests/CONTRIBUTING.md | 34 + test/fixtures/web-platform-tests/LICENSE.md | 33 + test/fixtures/web-platform-tests/README.md | 461 ++ .../web-platform-tests/common/META.yml | 5 + .../common/PrefixedLocalStorage.js | 116 + .../common/PrefixedLocalStorage.js.headers | 1 + .../common/PrefixedPostMessage.js | 100 + .../common/PrefixedPostMessage.js.headers | 1 + .../web-platform-tests/common/arrays.js | 16 + .../common/canvas-frame.css | 21 + .../common/canvas-frame.css.headers | 1 + .../common/canvas-index.css | 31 + .../common/canvas-index.css.headers | 1 + .../web-platform-tests/common/canvas-spec.css | 50 + .../common/canvas-spec.css.headers | 1 + .../common/canvas-tests.css | 134 + .../common/canvas-tests.css.headers | 1 + .../web-platform-tests/common/canvas-tests.js | 109 + .../common/canvas-tests.js.headers | 1 + .../common/css-paint-tests.js.headers | 1 + .../web-platform-tests/common/css-red.txt | 1 + .../web-platform-tests/common/dummy.xhtml | 2 + .../web-platform-tests/common/dummy.xml | 1 + .../web-platform-tests/common/entities.json | 2233 +++++++ .../common/form-submission.py | 10 + .../common/get-host-info.sub.js | 40 + .../common/get-host-info.sub.js.headers | 1 + .../web-platform-tests/common/large.py | 45 + .../web-platform-tests/common/media.js | 46 + .../common/media.js.headers | 1 + .../web-platform-tests/common/namespaces.js | 4 + .../common/object-association.js | 66 + .../common/object-association.js.headers | 1 + .../common/performance-timeline-utils.js | 44 + .../performance-timeline-utils.js.headers | 1 + .../common/redirect-opt-in.py | 20 + .../web-platform-tests/common/redirect.py | 19 + .../web-platform-tests/common/reftest-wait.js | 9 + .../common/reftest-wait.js.headers | 1 + .../web-platform-tests/common/stringifiers.js | 52 + .../common/stringifiers.js.headers | 1 + .../common/subset-tests-by-key.js | 76 + .../web-platform-tests/common/subset-tests.js | 53 + .../test-setting-immutable-prototype.js | 57 + ...est-setting-immutable-prototype.js.headers | 1 + .../web-platform-tests/common/text-plain.txt | 4 + .../web-platform-tests/common/utils.js | 80 + .../common/utils.js.headers | 1 + .../common/worklet-reftest.js | 33 + .../web-platform-tests/resources/.gitignore | 3 + .../web-platform-tests/resources/.htaccess | 2 + .../web-platform-tests/resources/LICENSE | 30 + .../web-platform-tests/resources/META.yml | 3 + .../resources/check-layout-th.js | 196 + .../resources/chromium/README.md | 4 + .../resources/chromium/device.mojom.js | 3424 +++++++++++ .../chromium/device.mojom.js.headers | 1 + .../chromium/device_manager.mojom.js | 843 +++ .../chromium/device_manager.mojom.js.headers | 1 + .../chromium/fake_bluetooth.mojom.js | 5323 +++++++++++++++++ .../chromium/fake_bluetooth.mojom.js.headers | 1 + .../chromium/fake_bluetooth_chooser.mojom.js | 822 +++ .../fake_bluetooth_chooser.mojom.js.headers | 1 + .../chromium/generic_sensor_mocks.js | 220 + .../chromium/generic_sensor_mocks.js.headers | 1 + .../resources/chromium/mojo_bindings.js | 5129 ++++++++++++++++ .../chromium/mojo_bindings.js.headers | 1 + .../chromium/mojo_layouttest_test.mojom.js | 264 + .../mojo_layouttest_test.mojom.js.headers | 1 + .../resources/chromium/sensor.mojom.js | 1072 ++++ .../chromium/sensor_provider.mojom.js | 429 ++ .../resources/chromium/string16.mojom.js | 159 + .../chromium/string16.mojom.js.headers | 1 + .../resources/chromium/uuid.mojom.js | 79 + .../resources/chromium/uuid.mojom.js.headers | 1 + .../resources/chromium/web-bluetooth-test.js | 526 ++ .../chromium/web-bluetooth-test.js.headers | 1 + .../chromium/web_usb_service.mojom.js | 777 +++ .../chromium/web_usb_service.mojom.js.headers | 1 + .../resources/chromium/webusb-test.js | 513 ++ .../resources/chromium/webusb-test.js.headers | 1 + .../resources/chromium/webxr-test.js | 374 ++ .../resources/chromium/webxr-test.js.headers | 1 + .../resources/idlharness.js | 3248 ++++++++++ .../resources/idlharness.js.headers | 2 + .../web-platform-tests/resources/readme.md | 26 + .../resources/sriharness.js | 100 + .../resources/test/README.md | 90 + .../resources/test/conftest.py | 223 + .../resources/test/idl-helper.js | 24 + .../test/tests/functional/worker-error.js | 8 + .../functional/worker-uncaught-single.js | 6 + .../resources/test/tests/functional/worker.js | 34 + .../resources/test/tests/unit/META.yml | 2 + .../web-platform-tests/resources/test/tox.ini | 16 + .../resources/test/variants.js | 57 + .../resources/test/wptserver.py | 54 + .../resources/testdriver-vendor.js | 0 .../resources/testdriver-vendor.js.headers | 2 + .../resources/testdriver.js | 210 + .../resources/testdriver.js.headers | 2 + .../resources/testharness.css.headers | 2 + .../resources/testharness.js | 3388 +++++++++++ .../resources/testharness.js.headers | 2 + .../resources/testharnessreport.js | 57 + .../resources/testharnessreport.js.headers | 2 + .../resources/webidl2/.gitignore | 4 + .../resources/webidl2/.travis.yml | 5 + .../resources/webidl2/CHANGELOG.md | 292 + .../resources/webidl2/LICENSE | 21 + .../resources/webidl2/README.md | 629 ++ .../resources/webidl2/index.js | 1 + .../resources/webidl2/lib/webidl2.js | 970 +++ .../resources/webidl2/lib/webidl2.js.headers | 1 + .../resources/webidl2/lib/writer.js | 221 + .../resources/webidl2/package-lock.json | 700 +++ .../resources/webidl2/package.json | 27 + .../resources/webidl2/test/invalid.js | 20 + .../webidl2/test/invalid/idl/array.widl | 6 + .../webidl2/test/invalid/idl/caller.widl | 7 + .../invalid/idl/dict-required-default.widl | 5 + .../webidl2/test/invalid/idl/duplicate.widl | 5 + .../webidl2/test/invalid/idl/enum-empty.widl | 1 + .../test/invalid/idl/enum-wo-comma.widl | 1 + .../webidl2/test/invalid/idl/enum.widl | 1 + .../webidl2/test/invalid/idl/exception.widl | 6 + .../test/invalid/idl/extattr-empty-ids.widl | 2 + .../invalid/idl/id-underscored-number.widl | 1 + .../idl/implements_and_includes_ws.widl | 4 + .../webidl2/test/invalid/idl/iterator.widl | 35 + .../test/invalid/idl/maplike-1type.widl | 3 + .../webidl2/test/invalid/idl/module.widl | 25 + .../test/invalid/idl/namespace-readwrite.widl | 3 + .../invalid/idl/no-semicolon-callback.widl | 7 + .../test/invalid/idl/no-semicolon.widl | 7 + .../test/invalid/idl/nonnullableany.widl | 3 + .../test/invalid/idl/nonnullableobjects.widl | 6 + .../test/invalid/idl/promise-nullable.widl | 4 + .../idl/promise-with-extended-attribute.widl | 3 + .../webidl2/test/invalid/idl/raises.widl | 18 + .../test/invalid/idl/readonly-iterable.widl | 3 + .../record-key-with-extended-attribute.widl | 3 + .../webidl2/test/invalid/idl/record-key.widl | 3 + .../test/invalid/idl/record-single.widl | 3 + .../webidl2/test/invalid/idl/scopedname.widl | 2 + .../test/invalid/idl/sequenceAsAttribute.widl | 3 + .../test/invalid/idl/setlike-2types.widl | 3 + .../test/invalid/idl/setter-creator.widl | 4 + .../invalid/idl/spaced-negative-infinity.widl | 3 + .../test/invalid/idl/spaced-variadic.widl | 3 + .../test/invalid/idl/special-omittable.widl | 8 + .../webidl2/test/invalid/idl/stray-slash.widl | 2 + .../test/invalid/idl/stringconstants.widl | 3 + .../test/invalid/idl/typedef-nested.widl | 22 + .../test/invalid/idl/union-dangling-or.widl | 1 + .../webidl2/test/invalid/idl/union-one.widl | 1 + .../webidl2/test/invalid/idl/union-zero.widl | 1 + .../test/invalid/idl/unknown-generic.widl | 3 + .../webidl2/test/invalid/json/array.json | 4 + .../webidl2/test/invalid/json/caller.json | 4 + .../invalid/json/dict-required-default.json | 4 + .../webidl2/test/invalid/json/duplicate.json | 4 + .../webidl2/test/invalid/json/enum-empty.json | 4 + .../test/invalid/json/enum-wo-comma.json | 4 + .../webidl2/test/invalid/json/enum.json | 4 + .../webidl2/test/invalid/json/exception.json | 4 + .../test/invalid/json/extattr-empty-ids.json | 4 + .../invalid/json/id-underscored-number.json | 4 + .../json/implements_and_includes_ws.json | 4 + .../webidl2/test/invalid/json/iterator.json | 4 + .../test/invalid/json/maplike-1type.json | 4 + .../webidl2/test/invalid/json/module.json | 4 + .../invalid/json/namespace-readwrite.json | 4 + .../invalid/json/no-semicolon-callback.json | 4 + .../test/invalid/json/no-semicolon.json | 4 + .../test/invalid/json/nonnullableany.json | 4 + .../test/invalid/json/nonnullableobjects.json | 4 + .../test/invalid/json/promise-nullable.json | 4 + .../json/promise-with-extended-attribute.json | 4 + .../webidl2/test/invalid/json/raises.json | 4 + .../test/invalid/json/readonly-iterable.json | 4 + .../record-key-with-extended-attribute.json | 4 + .../webidl2/test/invalid/json/record-key.json | 4 + .../test/invalid/json/record-single.json | 4 + .../webidl2/test/invalid/json/scopedname.json | 4 + .../invalid/json/sequenceAsAttribute.json | 4 + .../test/invalid/json/setlike-2types.json | 4 + .../test/invalid/json/setter-creator.json | 4 + .../json/spaced-negative-infinity.json | 4 + .../test/invalid/json/spaced-variadic.json | 4 + .../test/invalid/json/special-omittable.json | 4 + .../test/invalid/json/stray-slash.json | 4 + .../test/invalid/json/stringconstants.json | 4 + .../test/invalid/json/typedef-nested.json | 4 + .../test/invalid/json/union-dangling-or.json | 4 + .../webidl2/test/invalid/json/union-one.json | 4 + .../webidl2/test/invalid/json/union-zero.json | 4 + .../test/invalid/json/unknown-generic.json | 4 + .../resources/webidl2/test/mocha.opts | 1 + .../resources/webidl2/test/syntax.js | 19 + .../webidl2/test/syntax/idl/allowany.widl | 6 + .../webidl2/test/syntax/idl/attributes.widl | 11 + .../webidl2/test/syntax/idl/callback.widl | 7 + .../webidl2/test/syntax/idl/constants.widl | 11 + .../webidl2/test/syntax/idl/constructor.widl | 9 + .../test/syntax/idl/dictionary-inherits.widl | 9 + .../webidl2/test/syntax/idl/dictionary.widl | 15 + .../test/syntax/idl/documentation-dos.widl | 33 + .../test/syntax/idl/documentation.widl | 34 + .../webidl2/test/syntax/idl/enum.widl | 10 + .../test/syntax/idl/equivalent-decl.widl | 18 + .../test/syntax/idl/extended-attributes.widl | 29 + .../webidl2/test/syntax/idl/generic.widl | 17 + .../test/syntax/idl/getter-setter.widl | 7 + .../idl/identifier-qualified-names.widl | 33 + .../webidl2/test/syntax/idl/implements.widl | 14 + .../test/syntax/idl/indexed-properties.widl | 12 + .../test/syntax/idl/inherits-getter.widl | 22 + .../test/syntax/idl/interface-inherits.widl | 12 + .../webidl2/test/syntax/idl/iterable.widl | 11 + .../test/syntax/idl/legacyiterable.widl | 3 + .../webidl2/test/syntax/idl/maplike.widl | 13 + .../webidl2/test/syntax/idl/mixin.widl | 12 + .../test/syntax/idl/namedconstructor.widl | 6 + .../webidl2/test/syntax/idl/namespace.widl | 10 + .../test/syntax/idl/nointerfaceobject.widl | 5 + .../webidl2/test/syntax/idl/nullable.widl | 9 + .../test/syntax/idl/nullableobjects.widl | 13 + .../syntax/idl/operation-optional-arg.widl | 4 + .../webidl2/test/syntax/idl/overloading.widl | 20 + .../test/syntax/idl/overridebuiltins.widl | 6 + .../test/syntax/idl/partial-interface.widl | 7 + .../webidl2/test/syntax/idl/primitives.widl | 19 + .../webidl2/test/syntax/idl/promise-void.widl | 3 + .../test/syntax/idl/prototyperoot.widl | 5 + .../webidl2/test/syntax/idl/putforwards.widl | 5 + .../webidl2/test/syntax/idl/record.widl | 9 + .../test/syntax/idl/reg-operations.widl | 15 + .../webidl2/test/syntax/idl/replaceable.widl | 5 + .../webidl2/test/syntax/idl/sequence.widl | 13 + .../webidl2/test/syntax/idl/setlike.widl | 11 + .../webidl2/test/syntax/idl/static.widl | 11 + .../syntax/idl/stringifier-attribute.widl | 6 + .../test/syntax/idl/stringifier-custom.widl | 9 + .../webidl2/test/syntax/idl/stringifier.widl | 8 + .../webidl2/test/syntax/idl/treatasnull.widl | 7 + .../test/syntax/idl/treatasundefined.widl | 7 + .../test/syntax/idl/typedef-union.widl | 4 + .../webidl2/test/syntax/idl/typedef.widl | 22 + .../webidl2/test/syntax/idl/typesuffixes.widl | 3 + .../webidl2/test/syntax/idl/uniontype.widl | 4 + .../test/syntax/idl/variadic-operations.widl | 7 + .../webidl2/test/syntax/json/allowany.json | 112 + .../webidl2/test/syntax/json/attributes.json | 47 + .../webidl2/test/syntax/json/callback.json | 126 + .../webidl2/test/syntax/json/constants.json | 154 + .../webidl2/test/syntax/json/constructor.json | 113 + .../test/syntax/json/dictionary-inherits.json | 89 + .../webidl2/test/syntax/json/dictionary.json | 146 + .../test/syntax/json/documentation-dos.json | 10 + .../test/syntax/json/documentation.json | 10 + .../webidl2/test/syntax/json/enum.json | 138 + .../test/syntax/json/equivalent-decl.json | 326 + .../syntax/json/exception-inheritance.json | 36 + .../test/syntax/json/extended-attributes.json | 240 + .../webidl2/test/syntax/json/generic.json | 176 + .../test/syntax/json/getter-setter.json | 119 + .../json/identifier-qualified-names.json | 189 + .../webidl2/test/syntax/json/implements.json | 113 + .../test/syntax/json/indexed-properties.json | 283 + .../test/syntax/json/inherits-getter.json | 101 + .../test/syntax/json/interface-inherits.json | 83 + .../webidl2/test/syntax/json/iterable.json | 86 + .../webidl2/test/syntax/json/iterator.json | 276 + .../test/syntax/json/legacyiterable.json | 25 + .../webidl2/test/syntax/json/maplike.json | 112 + .../webidl2/test/syntax/json/mixin.json | 66 + .../test/syntax/json/namedconstructor.json | 46 + .../webidl2/test/syntax/json/namespace.json | 141 + .../test/syntax/json/nointerfaceobject.json | 55 + .../webidl2/test/syntax/json/nullable.json | 56 + .../test/syntax/json/nullableobjects.json | 101 + .../syntax/json/operation-optional-arg.json | 99 + .../webidl2/test/syntax/json/overloading.json | 328 + .../test/syntax/json/overridebuiltins.json | 73 + .../test/syntax/json/partial-interface.json | 55 + .../webidl2/test/syntax/json/primitives.json | 317 + .../test/syntax/json/promise-void.json | 36 + .../test/syntax/json/prototyperoot.json | 36 + .../webidl2/test/syntax/json/putforwards.json | 57 + .../webidl2/test/syntax/json/record.json | 220 + .../test/syntax/json/reg-operations.json | 166 + .../webidl2/test/syntax/json/replaceable.json | 56 + .../webidl2/test/syntax/json/sequence.json | 142 + .../webidl2/test/syntax/json/setlike.json | 81 + .../webidl2/test/syntax/json/static.json | 160 + .../syntax/json/stringifier-attribute.json | 54 + .../test/syntax/json/stringifier-custom.json | 92 + .../webidl2/test/syntax/json/stringifier.json | 49 + .../webidl2/test/syntax/json/treatasnull.json | 94 + .../test/syntax/json/treatasundefined.json | 94 + .../test/syntax/json/typedef-union.json | 48 + .../webidl2/test/syntax/json/typedef.json | 233 + .../test/syntax/json/typesuffixes.json | 55 + .../webidl2/test/syntax/json/uniontype.json | 130 + .../test/syntax/json/variadic-operations.json | 103 + .../resources/webidl2/test/util/acquire.js | 8 + .../resources/webidl2/test/util/collect.js | 59 + .../webidl2/test/web/make-web-tests.js | 52 + .../resources/webidl2/test/web/run-tests.js | 48 + .../resources/webidl2/test/writer.js | 23 + .../web-platform-tests/streams/META.yml | 8 + .../web-platform-tests/streams/README.md | 14 + .../streams/byte-length-queuing-strategy.js | 114 + .../streams/count-queuing-strategy.js | 113 + .../streams/generate-test-wrappers.js | 99 + .../piping/close-propagation-backward.js | 158 + .../piping/close-propagation-forward.js | 594 ++ .../piping/error-propagation-backward.js | 635 ++ .../piping/error-propagation-forward.js | 574 ++ .../streams/piping/flow-control.js | 306 + .../streams/piping/general.js | 195 + .../streams/piping/multiple-propagation.js | 232 + .../streams/piping/pipe-through.js | 261 + .../streams/piping/then-interception.js | 67 + .../streams/piping/transform-streams.js | 27 + .../readable-byte-streams/brand-checks.js | 194 + .../construct-byob-request.js | 82 + .../readable-byte-streams/constructor.js | 53 + .../readable-byte-streams/detached-buffers.js | 156 + .../streams/readable-byte-streams/general.js | 2126 +++++++ .../readable-byte-streams/properties.js | 147 + .../readable-streams/bad-strategies.js | 164 + .../bad-underlying-sources.js | 405 ++ .../streams/readable-streams/brand-checks.js | 164 + .../streams/readable-streams/cancel.js | 241 + .../streams/readable-streams/constructor.js | 42 + .../count-queuing-strategy-integration.js | 213 + .../readable-streams/default-reader.js | 501 ++ .../floating-point-total-queue-size.js | 121 + .../readable-streams/garbage-collection.js | 75 + .../streams/readable-streams/general.js | 901 +++ .../readable-streams/patched-global.js | 67 + .../readable-streams/reentrant-strategies.js | 269 + .../streams/readable-streams/tee.js | 293 + .../streams/readable-streams/templated.js | 148 + .../streams/resources/constructor-ordering.js | 129 + .../streams/resources/recording-streams.js | 130 + .../streams/resources/rs-test-templates.js | 635 ++ .../streams/resources/rs-utils.js | 185 + .../streams/resources/test-utils.js | 73 + .../streams/transform-streams/backpressure.js | 200 + .../streams/transform-streams/brand-checks.js | 79 + .../streams/transform-streams/constructor.js | 51 + .../streams/transform-streams/errors.js | 346 ++ .../streams/transform-streams/flush.js | 136 + .../streams/transform-streams/general.js | 444 ++ .../streams/transform-streams/lipfuzz.js | 168 + .../transform-streams/patched-global.js | 53 + .../streams/transform-streams/properties.js | 194 + .../transform-streams/reentrant-strategies.js | 324 + .../streams/transform-streams/strategies.js | 155 + .../streams/transform-streams/terminate.js | 105 + .../streams/writable-streams/aborting.js | 1375 +++++ .../writable-streams/bad-strategies.js | 100 + .../writable-streams/bad-underlying-sinks.js | 195 + .../streams/writable-streams/brand-checks.js | 114 + .../byte-length-queuing-strategy.js | 33 + .../streams/writable-streams/close.js | 406 ++ .../streams/writable-streams/constructor.js | 190 + .../count-queuing-strategy.js | 129 + .../streams/writable-streams/error.js | 69 + .../floating-point-total-queue-size.js | 92 + .../streams/writable-streams/general.js | 251 + .../streams/writable-streams/properties.js | 225 + .../writable-streams/reentrant-strategy.js | 179 + .../streams/writable-streams/start.js | 168 + .../streams/writable-streams/write.js | 258 + test/parallel/test-stream-acquire-standard.js | 103 + test/wpt.js | 210 + 382 files changed, 59493 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/web-platform-tests/CODEOWNERS create mode 100644 test/fixtures/web-platform-tests/CONTRIBUTING.md create mode 100644 test/fixtures/web-platform-tests/LICENSE.md create mode 100644 test/fixtures/web-platform-tests/README.md create mode 100644 test/fixtures/web-platform-tests/common/META.yml create mode 100644 test/fixtures/web-platform-tests/common/PrefixedLocalStorage.js create mode 100644 test/fixtures/web-platform-tests/common/PrefixedLocalStorage.js.headers create mode 100644 test/fixtures/web-platform-tests/common/PrefixedPostMessage.js create mode 100644 test/fixtures/web-platform-tests/common/PrefixedPostMessage.js.headers create mode 100644 test/fixtures/web-platform-tests/common/arrays.js create mode 100644 test/fixtures/web-platform-tests/common/canvas-frame.css create mode 100644 test/fixtures/web-platform-tests/common/canvas-frame.css.headers create mode 100644 test/fixtures/web-platform-tests/common/canvas-index.css create mode 100644 test/fixtures/web-platform-tests/common/canvas-index.css.headers create mode 100644 test/fixtures/web-platform-tests/common/canvas-spec.css create mode 100644 test/fixtures/web-platform-tests/common/canvas-spec.css.headers create mode 100644 test/fixtures/web-platform-tests/common/canvas-tests.css create mode 100644 test/fixtures/web-platform-tests/common/canvas-tests.css.headers create mode 100644 test/fixtures/web-platform-tests/common/canvas-tests.js create mode 100644 test/fixtures/web-platform-tests/common/canvas-tests.js.headers create mode 100644 test/fixtures/web-platform-tests/common/css-paint-tests.js.headers create mode 100644 test/fixtures/web-platform-tests/common/css-red.txt create mode 100644 test/fixtures/web-platform-tests/common/dummy.xhtml create mode 100644 test/fixtures/web-platform-tests/common/dummy.xml create mode 100644 test/fixtures/web-platform-tests/common/entities.json create mode 100644 test/fixtures/web-platform-tests/common/form-submission.py create mode 100644 test/fixtures/web-platform-tests/common/get-host-info.sub.js create mode 100644 test/fixtures/web-platform-tests/common/get-host-info.sub.js.headers create mode 100644 test/fixtures/web-platform-tests/common/large.py create mode 100644 test/fixtures/web-platform-tests/common/media.js create mode 100644 test/fixtures/web-platform-tests/common/media.js.headers create mode 100644 test/fixtures/web-platform-tests/common/namespaces.js create mode 100644 test/fixtures/web-platform-tests/common/object-association.js create mode 100644 test/fixtures/web-platform-tests/common/object-association.js.headers create mode 100644 test/fixtures/web-platform-tests/common/performance-timeline-utils.js create mode 100644 test/fixtures/web-platform-tests/common/performance-timeline-utils.js.headers create mode 100644 test/fixtures/web-platform-tests/common/redirect-opt-in.py create mode 100644 test/fixtures/web-platform-tests/common/redirect.py create mode 100644 test/fixtures/web-platform-tests/common/reftest-wait.js create mode 100644 test/fixtures/web-platform-tests/common/reftest-wait.js.headers create mode 100644 test/fixtures/web-platform-tests/common/stringifiers.js create mode 100644 test/fixtures/web-platform-tests/common/stringifiers.js.headers create mode 100644 test/fixtures/web-platform-tests/common/subset-tests-by-key.js create mode 100644 test/fixtures/web-platform-tests/common/subset-tests.js create mode 100644 test/fixtures/web-platform-tests/common/test-setting-immutable-prototype.js create mode 100644 test/fixtures/web-platform-tests/common/test-setting-immutable-prototype.js.headers create mode 100644 test/fixtures/web-platform-tests/common/text-plain.txt create mode 100644 test/fixtures/web-platform-tests/common/utils.js create mode 100644 test/fixtures/web-platform-tests/common/utils.js.headers create mode 100644 test/fixtures/web-platform-tests/common/worklet-reftest.js create mode 100644 test/fixtures/web-platform-tests/resources/.gitignore create mode 100644 test/fixtures/web-platform-tests/resources/.htaccess create mode 100644 test/fixtures/web-platform-tests/resources/LICENSE create mode 100644 test/fixtures/web-platform-tests/resources/META.yml create mode 100644 test/fixtures/web-platform-tests/resources/check-layout-th.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/README.md create mode 100644 test/fixtures/web-platform-tests/resources/chromium/device.mojom.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/device.mojom.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/chromium/device_manager.mojom.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/device_manager.mojom.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth.mojom.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth.mojom.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth_chooser.mojom.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/fake_bluetooth_chooser.mojom.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/chromium/generic_sensor_mocks.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/generic_sensor_mocks.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/chromium/mojo_bindings.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/mojo_bindings.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/chromium/mojo_layouttest_test.mojom.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/mojo_layouttest_test.mojom.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/chromium/sensor.mojom.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/sensor_provider.mojom.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/string16.mojom.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/string16.mojom.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/chromium/uuid.mojom.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/uuid.mojom.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/chromium/web-bluetooth-test.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/web-bluetooth-test.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/chromium/web_usb_service.mojom.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/web_usb_service.mojom.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/chromium/webusb-test.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/webusb-test.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/chromium/webxr-test.js create mode 100644 test/fixtures/web-platform-tests/resources/chromium/webxr-test.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/idlharness.js create mode 100644 test/fixtures/web-platform-tests/resources/idlharness.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/readme.md create mode 100644 test/fixtures/web-platform-tests/resources/sriharness.js create mode 100644 test/fixtures/web-platform-tests/resources/test/README.md create mode 100644 test/fixtures/web-platform-tests/resources/test/conftest.py create mode 100644 test/fixtures/web-platform-tests/resources/test/idl-helper.js create mode 100644 test/fixtures/web-platform-tests/resources/test/tests/functional/worker-error.js create mode 100644 test/fixtures/web-platform-tests/resources/test/tests/functional/worker-uncaught-single.js create mode 100644 test/fixtures/web-platform-tests/resources/test/tests/functional/worker.js create mode 100644 test/fixtures/web-platform-tests/resources/test/tests/unit/META.yml create mode 100644 test/fixtures/web-platform-tests/resources/test/tox.ini create mode 100644 test/fixtures/web-platform-tests/resources/test/variants.js create mode 100644 test/fixtures/web-platform-tests/resources/test/wptserver.py create mode 100644 test/fixtures/web-platform-tests/resources/testdriver-vendor.js create mode 100644 test/fixtures/web-platform-tests/resources/testdriver-vendor.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/testdriver.js create mode 100644 test/fixtures/web-platform-tests/resources/testdriver.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/testharness.css.headers create mode 100644 test/fixtures/web-platform-tests/resources/testharness.js create mode 100644 test/fixtures/web-platform-tests/resources/testharness.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/testharnessreport.js create mode 100644 test/fixtures/web-platform-tests/resources/testharnessreport.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/.gitignore create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/.travis.yml create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/CHANGELOG.md create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/LICENSE create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/README.md create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/index.js create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/lib/webidl2.js create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/lib/webidl2.js.headers create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/lib/writer.js create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/package-lock.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/package.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid.js create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/array.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/caller.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/dict-required-default.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/duplicate.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/enum-empty.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/enum-wo-comma.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/enum.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/exception.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/extattr-empty-ids.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/id-underscored-number.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/implements_and_includes_ws.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/iterator.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/maplike-1type.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/module.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/namespace-readwrite.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/no-semicolon-callback.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/no-semicolon.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/nonnullableany.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/nonnullableobjects.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/promise-nullable.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/promise-with-extended-attribute.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/raises.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/readonly-iterable.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/record-key-with-extended-attribute.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/record-key.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/record-single.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/scopedname.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/sequenceAsAttribute.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/setlike-2types.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/setter-creator.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/spaced-negative-infinity.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/spaced-variadic.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/special-omittable.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/stray-slash.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/stringconstants.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/typedef-nested.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/union-dangling-or.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/union-one.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/union-zero.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/idl/unknown-generic.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/array.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/caller.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/dict-required-default.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/duplicate.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/enum-empty.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/enum-wo-comma.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/enum.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/exception.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/extattr-empty-ids.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/id-underscored-number.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/implements_and_includes_ws.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/iterator.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/maplike-1type.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/module.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/namespace-readwrite.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/no-semicolon-callback.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/no-semicolon.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/nonnullableany.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/nonnullableobjects.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/promise-nullable.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/promise-with-extended-attribute.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/raises.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/readonly-iterable.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/record-key-with-extended-attribute.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/record-key.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/record-single.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/scopedname.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/sequenceAsAttribute.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/setlike-2types.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/setter-creator.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/spaced-negative-infinity.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/spaced-variadic.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/special-omittable.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/stray-slash.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/stringconstants.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/typedef-nested.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/union-dangling-or.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/union-one.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/union-zero.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/invalid/json/unknown-generic.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/mocha.opts create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax.js create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/allowany.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/attributes.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/callback.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/constants.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/constructor.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/dictionary-inherits.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/dictionary.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/documentation-dos.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/documentation.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/enum.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/equivalent-decl.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/extended-attributes.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/generic.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/getter-setter.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/identifier-qualified-names.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/implements.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/indexed-properties.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/inherits-getter.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/interface-inherits.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/iterable.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/legacyiterable.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/maplike.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/mixin.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/namedconstructor.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/namespace.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/nointerfaceobject.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/nullable.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/nullableobjects.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/operation-optional-arg.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/overloading.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/overridebuiltins.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/partial-interface.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/primitives.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/promise-void.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/prototyperoot.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/putforwards.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/record.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/reg-operations.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/replaceable.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/sequence.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/setlike.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/static.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/stringifier-attribute.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/stringifier-custom.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/stringifier.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/treatasnull.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/treatasundefined.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/typedef-union.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/typedef.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/typesuffixes.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/uniontype.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/idl/variadic-operations.widl create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/allowany.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/attributes.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/callback.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/constants.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/constructor.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/dictionary-inherits.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/dictionary.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/documentation-dos.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/documentation.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/enum.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/equivalent-decl.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/exception-inheritance.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/extended-attributes.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/generic.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/getter-setter.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/identifier-qualified-names.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/implements.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/indexed-properties.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/inherits-getter.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/interface-inherits.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/iterable.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/iterator.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/legacyiterable.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/maplike.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/mixin.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/namedconstructor.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/namespace.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/nointerfaceobject.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/nullable.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/nullableobjects.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/operation-optional-arg.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/overloading.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/overridebuiltins.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/partial-interface.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/primitives.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/promise-void.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/prototyperoot.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/putforwards.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/record.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/reg-operations.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/replaceable.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/sequence.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/setlike.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/static.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/stringifier-attribute.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/stringifier-custom.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/stringifier.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/treatasnull.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/treatasundefined.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/typedef-union.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/typedef.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/typesuffixes.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/uniontype.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/syntax/json/variadic-operations.json create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/util/acquire.js create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/util/collect.js create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/web/make-web-tests.js create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/web/run-tests.js create mode 100644 test/fixtures/web-platform-tests/resources/webidl2/test/writer.js create mode 100644 test/fixtures/web-platform-tests/streams/META.yml create mode 100644 test/fixtures/web-platform-tests/streams/README.md create mode 100644 test/fixtures/web-platform-tests/streams/byte-length-queuing-strategy.js create mode 100644 test/fixtures/web-platform-tests/streams/count-queuing-strategy.js create mode 100644 test/fixtures/web-platform-tests/streams/generate-test-wrappers.js create mode 100644 test/fixtures/web-platform-tests/streams/piping/close-propagation-backward.js create mode 100644 test/fixtures/web-platform-tests/streams/piping/close-propagation-forward.js create mode 100644 test/fixtures/web-platform-tests/streams/piping/error-propagation-backward.js create mode 100644 test/fixtures/web-platform-tests/streams/piping/error-propagation-forward.js create mode 100644 test/fixtures/web-platform-tests/streams/piping/flow-control.js create mode 100644 test/fixtures/web-platform-tests/streams/piping/general.js create mode 100644 test/fixtures/web-platform-tests/streams/piping/multiple-propagation.js create mode 100644 test/fixtures/web-platform-tests/streams/piping/pipe-through.js create mode 100644 test/fixtures/web-platform-tests/streams/piping/then-interception.js create mode 100644 test/fixtures/web-platform-tests/streams/piping/transform-streams.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-byte-streams/brand-checks.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-byte-streams/construct-byob-request.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-byte-streams/constructor.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-byte-streams/detached-buffers.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-byte-streams/general.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-byte-streams/properties.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/bad-strategies.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/bad-underlying-sources.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/brand-checks.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/cancel.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/constructor.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/count-queuing-strategy-integration.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/default-reader.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/floating-point-total-queue-size.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/garbage-collection.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/general.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/patched-global.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/reentrant-strategies.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/tee.js create mode 100644 test/fixtures/web-platform-tests/streams/readable-streams/templated.js create mode 100644 test/fixtures/web-platform-tests/streams/resources/constructor-ordering.js create mode 100644 test/fixtures/web-platform-tests/streams/resources/recording-streams.js create mode 100644 test/fixtures/web-platform-tests/streams/resources/rs-test-templates.js create mode 100644 test/fixtures/web-platform-tests/streams/resources/rs-utils.js create mode 100644 test/fixtures/web-platform-tests/streams/resources/test-utils.js create mode 100644 test/fixtures/web-platform-tests/streams/transform-streams/backpressure.js create mode 100644 test/fixtures/web-platform-tests/streams/transform-streams/brand-checks.js create mode 100644 test/fixtures/web-platform-tests/streams/transform-streams/constructor.js create mode 100644 test/fixtures/web-platform-tests/streams/transform-streams/errors.js create mode 100644 test/fixtures/web-platform-tests/streams/transform-streams/flush.js create mode 100644 test/fixtures/web-platform-tests/streams/transform-streams/general.js create mode 100644 test/fixtures/web-platform-tests/streams/transform-streams/lipfuzz.js create mode 100644 test/fixtures/web-platform-tests/streams/transform-streams/patched-global.js create mode 100644 test/fixtures/web-platform-tests/streams/transform-streams/properties.js create mode 100644 test/fixtures/web-platform-tests/streams/transform-streams/reentrant-strategies.js create mode 100644 test/fixtures/web-platform-tests/streams/transform-streams/strategies.js create mode 100644 test/fixtures/web-platform-tests/streams/transform-streams/terminate.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/aborting.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/bad-strategies.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/bad-underlying-sinks.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/brand-checks.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/byte-length-queuing-strategy.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/close.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/constructor.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/count-queuing-strategy.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/error.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/floating-point-total-queue-size.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/general.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/properties.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/reentrant-strategy.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/start.js create mode 100644 test/fixtures/web-platform-tests/streams/writable-streams/write.js create mode 100644 test/parallel/test-stream-acquire-standard.js create mode 100644 test/wpt.js diff --git a/Makefile b/Makefile index 1738fb04f17137..fd2cbdd07d728c 100644 --- a/Makefile +++ b/Makefile @@ -256,6 +256,10 @@ endif v8: tools/make-v8.sh $(V8_ARCH).$(BUILDTYPE_LOWER) $(V8_BUILD_OPTIONS) +.PHONY: wpt +wpt: all + $(NODE) ./test/wpt.js + .PHONY: jstest jstest: build-addons build-addons-napi ## Runs addon tests and JS tests $(PYTHON) tools/test.py $(PARALLEL_ARGS) --mode=$(BUILDTYPE_LOWER) \ @@ -273,6 +277,7 @@ test: all ## Runs default tests, linters, and builds docs. $(MAKE) -s build-addons-napi $(MAKE) -s cctest $(MAKE) -s jstest + $(MAKE) -s wpt .PHONY: test-only test-only: all ## For a quick test, does not run linter or build docs. @@ -453,6 +458,7 @@ test-ci: | clear-stalled build-addons build-addons-napi doc-only $(PYTHON) tools/test.py $(PARALLEL_ARGS) -p tap --logfile test.tap \ --mode=$(BUILDTYPE_LOWER) --flaky-tests=$(FLAKY_TESTS) \ $(TEST_CI_ARGS) $(CI_JS_SUITES) $(CI_NATIVE_SUITES) $(CI_DOC) + $(NODE) test/wpt.js --tap @echo "Clean up any leftover processes, error if found." ps awwx | grep Release/node | grep -v grep | cat @PS_OUT=`ps awwx | grep Release/node | grep -v grep | awk '{print $$1}'`; \ @@ -1097,7 +1103,8 @@ endif LINT_MD_TARGETS = src lib benchmark test tools/doc tools/icu LINT_MD_ROOT_DOCS := $(wildcard *.md) LINT_MD_MISC_FILES := $(shell find $(LINT_MD_TARGETS) -type f \ - -not -path '*node_modules*' -name '*.md') $(LINT_MD_ROOT_DOCS) + -not -path '*node_modules*' -not -path '*fixtures*' -name '*.md') \ + $(LINT_MD_ROOT_DOCS) run-lint-misc-md = tools/remark-cli/cli.js -q -f $(LINT_MD_MISC_FILES) # Lint other changed markdown files maintained by us tools/.miscmdlintstamp: $(LINT_MD_MISC_FILES) diff --git a/test/fixtures/web-platform-tests/CODEOWNERS b/test/fixtures/web-platform-tests/CODEOWNERS new file mode 100644 index 00000000000000..e35f9232079afd --- /dev/null +++ b/test/fixtures/web-platform-tests/CODEOWNERS @@ -0,0 +1,2 @@ +# Prevent accidentially touching CSS submodules +/css/tools/ @plinss @kojiishi @jgraham @gsnedders diff --git a/test/fixtures/web-platform-tests/CONTRIBUTING.md b/test/fixtures/web-platform-tests/CONTRIBUTING.md new file mode 100644 index 00000000000000..427ec682793f7e --- /dev/null +++ b/test/fixtures/web-platform-tests/CONTRIBUTING.md @@ -0,0 +1,34 @@ +Grant of License +---------------- + +By contributing to this repository, you and the company you represent, if the +company holds any copyrights in the contribution, grant to the W3C a perpetual, +non-exclusive, royalty-free, world-wide right and license under all of your +copyrights in this contribution to copy, publish, use, and modify the +contribution and to distribute the contribution under a BSD License or one with +more restrictive terms, as well as a right and license of the same scope to any +derivative works prepared by the W3C and based on or incorporating all or part +of the contribution. You further agree that any derivative works of this +contribution prepared by the W3C shall be solely owned by the W3C. + +You state, to the best of your knowledge, that you, or the company you +represent, have all rights necessary to contribute the materials. + +W3C will retain attribution of initial authorship to you. The W3C makes no +a-priori commitment to support or distribute contributions. + +Disclaimer +---------- + +All content from this repository is provided as is, and W3C makes no +representations or warranties, express or implied, including, but not limited +to, warranties of merchantability, fitness for a particular purpose, +non-infringement, or title; nor that the contents of this repository are +suitable for any purpose. We make no representations, express or implied, that +the content of this repository or the use thereof indicates conformance to a +specification. All content is provided as-is to help reach interoperability. + +Documentation +------------- + +See [web-platform-tests.org](https://web-platform-tests.org/). diff --git a/test/fixtures/web-platform-tests/LICENSE.md b/test/fixtures/web-platform-tests/LICENSE.md new file mode 100644 index 00000000000000..6b346a528cd7bd --- /dev/null +++ b/test/fixtures/web-platform-tests/LICENSE.md @@ -0,0 +1,33 @@ +# Dual-License for W3C Test Suites + +All documents in this Repository are licensed by contributors to be distributed under both the [W3C Test Suite License](#w3c-test-suite-license) and the [W3C 3-clause BSD License](#w3c-3-clause-bsd-license), reproduced below. The choice of license is up to the licensee. For more information, see [Licenses for W3C Test Suites](https://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html) + +# W3C Test Suite License + +This document, Test Suites and other documents that link to this statement are provided by the copyright holders under the following license: By using and/or copying this document, or the W3C document from which this statement is linked, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions: + +Permission to copy, and distribute the contents of this document, or the W3C document from which this statement is linked, in any medium for any purpose and without fee or royalty is hereby granted, provided that you include the following on ALL copies of the document, or portions thereof, that you use: + +* A link or URL to the original W3C document. +* The pre-existing copyright notice of the original author, or if it doesn't exist, a notice (hypertext is preferred, but a textual representation is permitted) of the form: "Copyright © [$date-of-document] World Wide Web Consortium, (MIT, ERCIM, Keio, Beihang) and others. All Rights Reserved. http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html" +* If it exists, the STATUS of the W3C document. + +When space permits, inclusion of the full text of this NOTICE should be provided. We request that authorship attribution be provided in any software, documents, or other items or products that you create pursuant to the implementation of the contents of this document, or any portion thereof. + +No right to create modifications or derivatives of W3C documents is granted pursuant to this license. However, if additional requirements (documented in the Copyright FAQ) are satisfied, the right to create modifications or derivatives is sometimes granted by the W3C to individuals complying with those requirements. + +If a Test Suite distinguishes the test harness (or, framework for navigation) and the actual tests, permission is given to remove or alter the harness or navigation if the Test Suite in question allows to do so. The tests themselves shall NOT be changed in any way. + +The name and trademarks of W3C and other copyright holders may NOT be used in advertising or publicity pertaining to this document or other documents that link to this statement without specific, written prior permission. Title to copyright in this document will at all times remain with copyright holders. Permission is given to use the trademarked string "W3C" within claims of performance concerning W3C Specifications or features described therein, and there only, if the test suite so authorizes. + +THIS WORK IS PROVIDED BY W3C, MIT, ERCIM, KEIO, BEIHANG, 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 W3C, MIT, ERCIM, KEIO, BEIHANG, 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. + +# W3C 3-clause BSD License + +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/README.md b/test/fixtures/web-platform-tests/README.md new file mode 100644 index 00000000000000..684f4617f689d0 --- /dev/null +++ b/test/fixtures/web-platform-tests/README.md @@ -0,0 +1,461 @@ +The web-platform-tests Project [![IRC chat](https://goo.gl/6nCIks)](http://irc.w3.org/?channels=testing) +============================== + +The web-platform-tests Project is a W3C-coordinated attempt to build a +cross-browser testsuite for the Web-platform stack. Writing tests in a way +that allows them to be run in all browsers gives browser projects +confidence that they are shipping software that is compatible with other +implementations, and that later implementations will be compatible with +their implementations. This in turn gives Web authors/developers +confidence that they can actually rely on the Web platform to deliver on +the promise of working across browsers and devices without needing extra +layers of abstraction to paper over the gaps left by specification +editors and implementors. + +Setting Up the Repo +=================== + +Clone or otherwise get https://github.com/web-platform-tests/wpt. + +Note: because of the frequent creation and deletion of branches in this +repo, it is recommended to "prune" stale branches when fetching updates, +i.e. use `git pull --prune` (or `git fetch -p && git merge`). + +Running the Tests +================= + +The tests are designed to be run from your local computer. The test +environment requires [Python 2.7+](http://www.python.org/downloads) (but not Python 3.x). + +On Windows, be sure to add the Python directory (`c:\python2x`, by default) to +your `%Path%` [Environment Variable](http://www.computerhope.com/issues/ch000549.htm), +and read the [Windows Notes](#windows-notes) section below. + +To get the tests running, you need to set up the test domains in your +[`hosts` file](http://en.wikipedia.org/wiki/Hosts_%28file%29%23Location_in_the_file_system). + +The necessary content can be generated with `./wpt make-hosts-file`; on +Windows, you will need to precede the prior command with `python` or +the path to the Python binary (`python wpt make-hosts-file`). + +For example, on most UNIX-like systems, you can setup the hosts file with: + +```bash +./wpt make-hosts-file | sudo tee -a /etc/hosts +``` + +And on Windows (this must be run in a PowerShell session with Administrator privileges): + +```bash +python wpt make-hosts-file | Out-File $env:systemroot\System32\drivers\etc\hosts -Encoding ascii -Append +``` + +If you are behind a proxy, you also need to make sure the domains above are +excluded from your proxy lookups. + + +Running Tests Manually +====================== + +The test server can be started using +``` +./wpt serve +``` + +**On Windows**: You will need to precede the prior command with +`python` or the path to the python binary. +```bash +python wpt serve +``` + +This will start HTTP servers on two ports and a websockets server on +one port. By default the web servers start on ports 8000 and 8443 and +the other ports are randomly-chosen free ports. Tests must be loaded +from the *first* HTTP server in the output. To change the ports, +create a `config.json` file in the wpt root directory, and add +port definitions of your choice e.g.: + +``` +{ + "ports": { + "http": [1234, "auto"], + "https":[5678] + } +} +``` + +After your `hosts` file is configured, the servers will be locally accessible at: + +http://web-platform.test:8000/
+https://web-platform.test:8443/ * + +\**See [Trusting Root CA](#trusting-root-ca)* + +Running Tests Automatically +--------------------------- + +Tests can be run automatically in a browser using the `run` command of +the `wpt` script in the root of the checkout. This requires the hosts +file setup documented above, but you must *not* have the +test server already running when calling `wpt run`. The basic command +line syntax is: + +```bash +./wpt run product [tests] +``` + +**On Windows**: You will need to precede the prior command with +`python` or the path to the python binary. +```bash +python wpt run product [tests] +``` + +where `product` is currently `firefox` or `chrome` and `[tests]` is a +list of paths to tests. This will attempt to automatically locate a +browser instance and install required dependencies. The command is +very configurable; for example to specify a particular binary use +`wpt run --binary=path product`. The full range of options can be see +with `wpt run --help` and `wpt run --wptrunner-help`. + +Not all dependencies can be automatically installed; in particular the +`certutil` tool required to run https tests with Firefox must be +installed using a system package manager or similar. + +On Debian/Ubuntu certutil may be installed using: + +``` +sudo apt install libnss3-tools +``` + +And on macOS with homebrew using: + +``` +brew install nss +``` + +On other platforms, download the firefox archive and common.tests.tar.gz +archive for your platform from +[Mozilla CI](https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/). + +Then extract `certutil[.exe]` from the tests.tar.gz package and +`libnss3[.so|.dll|.dynlib]` and put the former on your path and the latter on +your library path. + + +Command Line Tools +================== + +The `wpt` command provides a frontend to a variety of tools for +working with and running web-platform-tests. Some of the most useful +commands are: + +* `wpt serve` - For starting the wpt http server +* `wpt run` - For running tests in a browser +* `wpt lint` - For running the lint against all tests +* `wpt manifest` - For updating or generating a `MANIFEST.json` test manifest +* `wpt install` - For installing the latest release of a browser or + webdriver server on the local machine. + +Submodules +======================================= + +Some optional components of web-platform-tests (test components from +third party software and pieces of the CSS build system) are included +as submodules. To obtain these components run the following in the +root of your checkout: + +``` +git submodule update --init --recursive +``` + +Prior to commit `39d07eb01fab607ab1ffd092051cded1bdd64d78` submodules +were required for basic functionality. If you are working with an +older checkout, the above command is required in all cases. + +When moving between a commit prior to `39d07eb` and one after it git +may complain + +``` +$ git checkout master +error: The following untracked working tree files would be overwritten by checkout: +[…] +``` + +...followed by a long list of files. To avoid this error, remove +the `resources` and `tools` directories before switching branches: + +``` +$ rm -r resources/ tools/ +$ git checkout master +Switched to branch 'master' +Your branch is up-to-date with 'origin/master' +``` + +When moving in the opposite direction, i.e. to a commit that does have +submodules, you will need to `git submodule update`, as above. If git +throws an error like: + +``` +fatal: No url found for submodule path 'resources/webidl2/test/widlproc' in .gitmodules +Failed to recurse into submodule path 'resources/webidl2' +fatal: No url found for submodule path 'tools/html5lib' in .gitmodules +Failed to recurse into submodule path 'resources' +Failed to recurse into submodule path 'tools' +``` + +...then remove the `tools` and `resources` directories, as above. + +Windows Notes +============================================= + +On Windows `wpt` commands must be prefixed with `python` or the path +to the python binary (if `python` is not in your `%PATH%`). + +```bash +python wpt [command] +``` + +Alternatively, you may also use +[Bash on Ubuntu on Windows](https://msdn.microsoft.com/en-us/commandline/wsl/about) +in the Windows 10 Anniversary Update build, then access your windows +partition from there to launch `wpt` commands. + +Please make sure git and your text editor do not automatically convert +line endings, as it will cause lint errors. For git, please set +`git config core.autocrlf false` in your working tree. + +Certificates +============ + +By default pre-generated certificates for the web-platform.test domain +are provided in [`tools/certs`](tools/certs). If you wish to generate new +certificates for any reason it's possible to use OpenSSL when starting +the server, or starting a test run, by providing the +`--ssl-type=openssl` argument to the `wpt serve` or `wpt run` +commands. + +If you installed OpenSSL in such a way that running `openssl` at a +command line doesn't work, you also need to adjust the path to the +OpenSSL binary. This can be done by adding a section to `config.json` +like: + +``` +"ssl": {"openssl": {"binary": "/path/to/openssl"}} +``` + +On Windows using OpenSSL typically requires installing an OpenSSL distribution. +[Shining Light](https://slproweb.com/products/Win32OpenSSL.html) +provide a convenient installer that is known to work, but requires a +little extra setup, i.e.: + +Run the installer for Win32_OpenSSL_v1.1.0b (30MB). During installation, +change the default location for where to Copy OpenSSL Dlls from the +System directory to the /bin directory. + +After installation, ensure that the path to OpenSSL (typically, +this will be `C:\OpenSSL-Win32\bin`) is in your `%Path%` +[Environment Variable](http://www.computerhope.com/issues/ch000549.htm). +If you forget to do this part, you will most likely see a 'File Not Found' +error when you start wptserve. + +Finally, set the path value in the server configuration file to the +default OpenSSL configuration file location. To do this create a file +called `config.json`. Then add the OpenSSL configuration below, +ensuring that the key `ssl/openssl/base_conf_path` has a value that is +the path to the OpenSSL config file (typically this will be +`C:\\OpenSSL-Win32\\bin\\openssl.cfg`): + +``` +{ + "ssl": { + "type": "openssl", + "encrypt_after_connect": false, + "openssl": { + "openssl_binary": "openssl", + "base_path: "_certs", + "force_regenerate": false, + "base_conf_path": "C:\\OpenSSL-Win32\\bin\\openssl.cfg" + }, + }, +} +``` + +### Trusting Root CA + +To prevent browser SSL warnings when running HTTPS tests locally, the +web-platform-tests Root CA file `cacert.pem` in [tools/certs](tools/certs) +must be added as a trusted certificate in your OS/browser. + +**NOTE**: The CA should not be installed in any browser profile used +outside of tests, since it may be used to generate fake +certificates. For browsers that use the OS certificate store, tests +should therefore not be run manually outside a dedicated OS instance +(e.g. a VM). To avoid this problem when running tests in Chrome or +Firefox use `wpt run`, which disables certificate checks and therefore +doesn't require the root CA to be trusted. + +Publication +=========== + +The master branch is automatically synced to http://w3c-test.org/. + +Pull requests are +[automatically mirrored](http://w3c-test.org/submissions/) except those +that modify sensitive resources (such as `.py`). The latter require +someone with merge access to comment with "LGTM" or "w3c-test:mirror" to +indicate the pull request has been checked. + +Finding Things +============== + +Each top-level directory matches the shortname used by a standard, with +some exceptions. (Typically the shortname is from the standard's +corresponding GitHub repository.) + +For some of the specifications, the tree under the top-level directory +represents the sections of the respective documents, using the section +IDs for directory names, with a maximum of three levels deep. + +So if you're looking for tests in HTML for "The History interface", +they will be under `html/browsers/history/the-history-interface/`. + +Various resources that tests depend on are in `common`, `images`, and +`fonts`. + +Branches +======== + +In the vast majority of cases the **only** upstream branch that you +should need to care about is `master`. If you see other branches in +the repository, you can generally safely ignore them. + +Contributing +============ + +Save the Web, Write Some Tests! + +Absolutely everyone is welcome (and even encouraged) to contribute to +test development, so long as you fulfill the contribution requirements +detailed in the [Contributing Guidelines][contributing]. No test is +too small or too simple, especially if it corresponds to something for +which you've noted an interoperability bug in a browser. + +The way to contribute is just as usual: + +* Fork this repository (and make sure you're still relatively in sync + with it if you forked a while ago). +* Create a branch for your changes: + `git checkout -b topic`. +* Make your changes. +* Run the lint script described below. +* Commit locally and push that to your repo. +* Send in a pull request based on the above. + +Issues with web-platform-tests +------------------------------ + +If you spot an issue with a test and are not comfortable providing a +pull request per above to fix it, please +[file a new issue](https://github.com/web-platform-tests/wpt/issues/new). +Thank you! + +Lint tool +--------- + +We have a lint tool for catching common mistakes in test files. You +can run it manually by starting the `lint` executable from the root of +your local web-platform-tests working directory like this: + +``` +./wpt lint +``` + +The lint tool is also run automatically for every submitted pull +request, and reviewers will not merge branches with tests that have +lint errors, so you must fix any errors the lint tool reports. + +In the unusual case of error reports for things essential to a +certain test or that for other exceptional reasons shouldn't prevent +a merge of a test, update and commit the `lint.whitelist` file in the +web-platform-tests root directory to suppress the error reports. + +For more details, see the [lint-tool documentation][lint-tool]. + +[lint-tool]: https://web-platform-tests.org/writing-tests/lint-tool.html + +Adding command-line scripts ("tools" subdirs) +--------------------------------------------- + +Sometimes you may want to add a script to the repository that's meant +to be used from the command line, not from a browser (e.g., a script +for generating test files). If you want to ensure (e.g., for security +reasons) that such scripts won't be handled by the HTTP server, but +will instead only be usable from the command line, then place them in +either: + +* the `tools` subdir at the root of the repository, or + +* the `tools` subdir at the root of any top-level directory in the + repository which contains the tests the script is meant to be used + with + +Any files in those `tools` directories won't be handled by the HTTP +server; instead the server will return a 404 if a user navigates to +the URL for a file within them. + +If you want to add a script for use with a particular set of tests but +there isn't yet any `tools` subdir at the root of a top-level +directory in the repository containing those tests, you can create a +`tools` subdir at the root of that top-level directory and place your +scripts there. + +For example, if you wanted to add a script for use with tests in the +`notifications` directory, create the `notifications/tools` subdir and +put your script there. + +Test Review +=========== + +We can sometimes take a little while to go through pull requests +because we have to go through all the tests and ensure that they match +the specification correctly. But we look at all of them, and take +everything that we can. + +META.yml files are used only to indicate who should be notified of pull +requests. If you are interested in receiving notifications of proposed +changes to tests in a given directory, feel free to add yourself to the +META.yml file. Anyone with expertise in the specification under test can +approve a pull request. In particular, if a test change has already +been adequately reviewed "upstream" in another repository, it can be +pushed here without any further review by supplying a link to the +upstream review. + +Search filters to find things to review: + +* [Open PRs (excluding vendor exports)](https://github.com/web-platform-tests/wpt/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+-label%3A%22mozilla%3Agecko-sync%22+-label%3A%22chromium-export%22+-label%3A%22webkit-export%22+-label%3A%22servo-export%22) +* [Reviewed but still open PRs (excluding vendor exports)](https://github.com/web-platform-tests/wpt/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+-label%3Amozilla%3Agecko-sync+-label%3Achromium-export+-label%3Awebkit-export+-label%3Aservo-export+review%3Aapproved+-label%3A%22do+not+merge+yet%22+-label%3A%22status%3Aneeds-spec-decision%22) (Merge? Something left to fix? Ping other reviewer?) +* [Open PRs without reviewers](https://github.com/web-platform-tests/wpt/pulls?q=is%3Apr+is%3Aopen+label%3Astatus%3Aneeds-reviewers) +* [Open PRs with label `infra` (excluding vendor exports)](https://github.com/web-platform-tests/wpt/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+label%3Ainfra+-label%3A%22mozilla%3Agecko-sync%22+-label%3A%22chromium-export%22+-label%3A%22webkit-export%22+-label%3A%22servo-export%22) +* [Open PRs with label `docs` (excluding vendor exports)](https://github.com/web-platform-tests/wpt/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+label%3Adocs+-label%3A%22mozilla%3Agecko-sync%22+-label%3A%22chromium-export%22+-label%3A%22webkit-export%22+-label%3A%22servo-export%22) + +Getting Involved +================ + +If you wish to contribute actively, you're very welcome to join the +public-test-infra@w3.org mailing list (low traffic) by +[signing up to our mailing list](mailto:public-test-infra-request@w3.org?subject=subscribe). +The mailing list is [archived][mailarchive]. + +Join us on irc #testing ([irc.w3.org][ircw3org], port 6665). The channel +is [archived][ircarchive]. + +[contributing]: https://github.com/web-platform-tests/wpt/blob/master/CONTRIBUTING.md +[ircw3org]: https://www.w3.org/wiki/IRC +[ircarchive]: https://w3.logbot.info/testing +[mailarchive]: https://lists.w3.org/Archives/Public/public-test-infra/ + +Documentation +============= + +* [How to write and review tests](https://web-platform-tests.org/) +* [Documentation for the wptserve server](http://wptserve.readthedocs.org/en/latest/) diff --git a/test/fixtures/web-platform-tests/common/META.yml b/test/fixtures/web-platform-tests/common/META.yml new file mode 100644 index 00000000000000..594c8b170f5043 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/META.yml @@ -0,0 +1,5 @@ +suggested_reviewers: + - zqzhang + - dontcallmedom + - deniak + - gsnedders diff --git a/test/fixtures/web-platform-tests/common/PrefixedLocalStorage.js b/test/fixtures/web-platform-tests/common/PrefixedLocalStorage.js new file mode 100644 index 00000000000000..2f4e7b6a055caa --- /dev/null +++ b/test/fixtures/web-platform-tests/common/PrefixedLocalStorage.js @@ -0,0 +1,116 @@ +/** + * Supports pseudo-"namespacing" localStorage for a given test + * by generating and using a unique prefix for keys. Why trounce on other + * tests' localStorage items when you can keep it "separated"? + * + * PrefixedLocalStorageTest: Instantiate in testharness.js tests to generate + * a new unique-ish prefix + * PrefixedLocalStorageResource: Instantiate in supporting test resource + * files to use/share a prefix generated by a test. + */ +var PrefixedLocalStorage = function () { + this.prefix = ''; // Prefix for localStorage keys + this.param = 'prefixedLocalStorage'; // Param to use in querystrings +}; + +PrefixedLocalStorage.prototype.clear = function () { + if (this.prefix === '') { return; } + Object.keys(localStorage).forEach(sKey => { + if (sKey.indexOf(this.prefix) === 0) { + localStorage.removeItem(sKey); + } + }); +}; + +/** + * Append/replace prefix parameter and value in URI querystring + * Use to generate URLs to resource files that will share the prefix. + */ +PrefixedLocalStorage.prototype.url = function (uri) { + function updateUrlParameter (uri, key, value) { + var i = uri.indexOf('#'); + var hash = (i === -1) ? '' : uri.substr(i); + uri = (i === -1) ? uri : uri.substr(0, i); + var re = new RegExp(`([?&])${key}=.*?(&|$)`, 'i'); + var separator = uri.indexOf('?') !== -1 ? '&' : '?'; + uri = (uri.match(re)) ? uri.replace(re, `$1${key}=${value}$2`) : + `${uri}${separator}${key}=${value}`; + return uri + hash; + } + return updateUrlParameter(uri, this.param, this.prefix); +}; + +PrefixedLocalStorage.prototype.prefixedKey = function (baseKey) { + return `${this.prefix}${baseKey}`; +}; + +PrefixedLocalStorage.prototype.setItem = function (baseKey, value) { + localStorage.setItem(this.prefixedKey(baseKey), value); +}; + +/** + * Listen for `storage` events pertaining to a particular key, + * prefixed with this object's prefix. Ignore when value is being set to null + * (i.e. removeItem). + */ +PrefixedLocalStorage.prototype.onSet = function (baseKey, fn) { + window.addEventListener('storage', e => { + var match = this.prefixedKey(baseKey); + if (e.newValue !== null && e.key.indexOf(match) === 0) { + fn.call(this, e); + } + }); +}; + +/***************************************************************************** + * Use in a testharnessjs test to generate a new key prefix. + * async_test(t => { + * var prefixedStorage = new PrefixedLocalStorageTest(); + * t.add_cleanup(() => prefixedStorage.cleanup()); + * /... + * }); + */ +var PrefixedLocalStorageTest = function () { + PrefixedLocalStorage.call(this); + this.prefix = `${document.location.pathname}-${Math.random()}-${Date.now()}-`; +}; +PrefixedLocalStorageTest.prototype = Object.create(PrefixedLocalStorage.prototype); +PrefixedLocalStorageTest.prototype.constructor = PrefixedLocalStorageTest; + +/** + * Use in a cleanup function to clear out prefixed entries in localStorage + */ +PrefixedLocalStorageTest.prototype.cleanup = function () { + this.setItem('closeAll', 'true'); + this.clear(); +}; + +/***************************************************************************** + * Use in test resource files to share a prefix generated by a + * PrefixedLocalStorageTest. Will look in URL querystring for prefix. + * Setting `close_on_cleanup` opt truthy will make this script's window listen + * for storage `closeAll` event from controlling test and close itself. + * + * var PrefixedLocalStorageResource({ close_on_cleanup: true }); + */ +var PrefixedLocalStorageResource = function (options) { + PrefixedLocalStorage.call(this); + this.options = Object.assign({}, { + close_on_cleanup: false + }, options || {}); + // Check URL querystring for prefix to use + var regex = new RegExp(`[?&]${this.param}(=([^&#]*)|&|#|$)`), + results = regex.exec(document.location.href); + if (results && results[2]) { + this.prefix = results[2]; + } + // Optionally have this window close itself when the PrefixedLocalStorageTest + // sets a `closeAll` item. + if (this.options.close_on_cleanup) { + this.onSet('closeAll', () => { + window.close(); + }); + } +}; +PrefixedLocalStorageResource.prototype = Object.create(PrefixedLocalStorage.prototype); +PrefixedLocalStorageResource.prototype.constructor = PrefixedLocalStorageResource; diff --git a/test/fixtures/web-platform-tests/common/PrefixedLocalStorage.js.headers b/test/fixtures/web-platform-tests/common/PrefixedLocalStorage.js.headers new file mode 100644 index 00000000000000..6805c323df5a97 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/PrefixedLocalStorage.js.headers @@ -0,0 +1 @@ +Content-Type: text/javascript; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/PrefixedPostMessage.js b/test/fixtures/web-platform-tests/common/PrefixedPostMessage.js new file mode 100644 index 00000000000000..674b52877c0d5d --- /dev/null +++ b/test/fixtures/web-platform-tests/common/PrefixedPostMessage.js @@ -0,0 +1,100 @@ +/** + * Supports pseudo-"namespacing" for window-posted messages for a given test + * by generating and using a unique prefix that gets wrapped into message + * objects. This makes it more feasible to have multiple tests that use + * `window.postMessage` in a single test file. Basically, make it possible + * for the each test to listen for only the messages that are pertinent to it. + * + * 'Prefix' not an elegant term to use here but this models itself after + * PrefixedLocalStorage. + * + * PrefixedMessageTest: Instantiate in testharness.js tests to generate + * a new unique-ish prefix that can be used by other test support files + * PrefixedMessageResource: Instantiate in supporting test resource + * files to use/share a prefix generated by a test. + */ +var PrefixedMessage = function () { + this.prefix = ''; + this.param = 'prefixedMessage'; // Param to use in querystrings +}; + +/** + * Generate a URL that adds/replaces param with this object's prefix + * Use to link to test support files that make use of + * PrefixedMessageResource. + */ +PrefixedMessage.prototype.url = function (uri) { + function updateUrlParameter (uri, key, value) { + var i = uri.indexOf('#'); + var hash = (i === -1) ? '' : uri.substr(i); + uri = (i === -1) ? uri : uri.substr(0, i); + var re = new RegExp(`([?&])${key}=.*?(&|$)`, 'i'); + var separator = uri.indexOf('?') !== -1 ? '&' : '?'; + uri = (uri.match(re)) ? uri.replace(re, `$1${key}=${value}$2`) : + `${uri}${separator}${key}=${value}`; + return uri + hash; + } + return updateUrlParameter(uri, this.param, this.prefix); +}; + +/** + * Add an eventListener on `message` but only invoke the given callback + * for messages whose object contains this object's prefix. Remove the + * event listener once the anticipated message has been received. + */ +PrefixedMessage.prototype.onMessage = function (fn) { + window.addEventListener('message', e => { + if (typeof e.data === 'object' && e.data.hasOwnProperty('prefix')) { + if (e.data.prefix === this.prefix) { + // Only invoke callback when `data` is an object containing + // a `prefix` key with this object's prefix value + // Note fn is invoked with "unwrapped" data first, then the event `e` + // (which contains the full, wrapped e.data should it be needed) + fn.call(this, e.data.data, e); + window.removeEventListener('message', fn); + } + } + }); +}; + +/** + * Instantiate in a test file (e.g. during `setup`) to create a unique-ish + * prefix that can be shared by support files + */ +var PrefixedMessageTest = function () { + PrefixedMessage.call(this); + this.prefix = `${document.location.pathname}-${Math.random()}-${Date.now()}-`; +}; +PrefixedMessageTest.prototype = Object.create(PrefixedMessage.prototype); +PrefixedMessageTest.prototype.constructor = PrefixedMessageTest; + +/** + * Instantiate in a test support script to use a "prefix" generated by a + * PrefixedMessageTest in a controlling test file. It will look for + * the prefix in a URL param (see also PrefixedMessage#url) + */ +var PrefixedMessageResource = function () { + PrefixedMessage.call(this); + // Check URL querystring for prefix to use + var regex = new RegExp(`[?&]${this.param}(=([^&#]*)|&|#|$)`), + results = regex.exec(document.location.href); + if (results && results[2]) { + this.prefix = results[2]; + } +}; +PrefixedMessageResource.prototype = Object.create(PrefixedMessage.prototype); +PrefixedMessageResource.prototype.constructor = PrefixedMessageResource; + +/** + * This is how a test resource document can "send info" to its + * opener context. It will whatever message is being sent (`data`) in + * an object that injects the prefix. + */ +PrefixedMessageResource.prototype.postToOpener = function (data) { + if (window.opener) { + window.opener.postMessage({ + prefix: this.prefix, + data: data + }, '*'); + } +}; diff --git a/test/fixtures/web-platform-tests/common/PrefixedPostMessage.js.headers b/test/fixtures/web-platform-tests/common/PrefixedPostMessage.js.headers new file mode 100644 index 00000000000000..6805c323df5a97 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/PrefixedPostMessage.js.headers @@ -0,0 +1 @@ +Content-Type: text/javascript; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/arrays.js b/test/fixtures/web-platform-tests/common/arrays.js new file mode 100644 index 00000000000000..49431dd78adf85 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/arrays.js @@ -0,0 +1,16 @@ +// Returns true if the given arrays are equal. Optionally can pass an equality function. +export function areArraysEqual(a, b, equalityFunction = (c, d) => { return c === d; }) { + try { + if (a.length !== b.length) + return false; + + for (let i = 0; i < a.length; i++) { + if (!equalityFunction(a[i], b[i])) + return false; + } + } catch (ex) { + return false; + } + + return true; +} diff --git a/test/fixtures/web-platform-tests/common/canvas-frame.css b/test/fixtures/web-platform-tests/common/canvas-frame.css new file mode 100644 index 00000000000000..0c97a680d18464 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/canvas-frame.css @@ -0,0 +1,21 @@ +body { + font-size: small; + font-family: sans-serif; +} + +p { + line-height: 0; +} + +p:first-child { + display: inline; +} + +h1 { + display: inline; +} + +iframe, object { + border: 1px black solid; + margin: 2px; +} diff --git a/test/fixtures/web-platform-tests/common/canvas-frame.css.headers b/test/fixtures/web-platform-tests/common/canvas-frame.css.headers new file mode 100644 index 00000000000000..e13897f157263b --- /dev/null +++ b/test/fixtures/web-platform-tests/common/canvas-frame.css.headers @@ -0,0 +1 @@ +Content-Type: text/css; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/canvas-index.css b/test/fixtures/web-platform-tests/common/canvas-index.css new file mode 100644 index 00000000000000..ef35864bc03faf --- /dev/null +++ b/test/fixtures/web-platform-tests/common/canvas-index.css @@ -0,0 +1,31 @@ +body { + font-size: small; + font-family: sans-serif; +} + +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +h3 { + display: inline; + font-size: medium; +} + +h3 + p { + display: inline; + margin-left: 0.5em; +} + +li { + list-style-type: none; +} + +ul { + padding-left: 2em; + margin-left: 0; +} diff --git a/test/fixtures/web-platform-tests/common/canvas-index.css.headers b/test/fixtures/web-platform-tests/common/canvas-index.css.headers new file mode 100644 index 00000000000000..e13897f157263b --- /dev/null +++ b/test/fixtures/web-platform-tests/common/canvas-index.css.headers @@ -0,0 +1 @@ +Content-Type: text/css; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/canvas-spec.css b/test/fixtures/web-platform-tests/common/canvas-spec.css new file mode 100644 index 00000000000000..5882acb68ef3ef --- /dev/null +++ b/test/fixtures/web-platform-tests/common/canvas-spec.css @@ -0,0 +1,50 @@ +.testrefs { + font-size: small; + margin-left: 0.2em; + margin-right: 0.2em; + border-bottom: none !important; + + font-weight: normal; + font-style: normal; + white-space: normal; + font-family: sans-serif; +} + +.kw-must, .kw-required { + background: #fda; +} + +.kw-should { + background: #ffa; +} + +.kw-none { + background: #dfa; +} + + +pre.idl .testrefs :link { + color: #00c; +} + +pre.idl .testrefs :visited { + color: #609; +} + +.testrefs a:hover { + background: transparent; + text-decoration: none; +} + +.testrefs:before { + content: '['; +} + +.testrefs:after { + content: ']'; +} + +.testrefs a:first-child { + font-weight: bold; + text-decoration: none; +} diff --git a/test/fixtures/web-platform-tests/common/canvas-spec.css.headers b/test/fixtures/web-platform-tests/common/canvas-spec.css.headers new file mode 100644 index 00000000000000..e13897f157263b --- /dev/null +++ b/test/fixtures/web-platform-tests/common/canvas-spec.css.headers @@ -0,0 +1 @@ +Content-Type: text/css; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/canvas-tests.css b/test/fixtures/web-platform-tests/common/canvas-tests.css new file mode 100644 index 00000000000000..e006e812de4dfb --- /dev/null +++ b/test/fixtures/web-platform-tests/common/canvas-tests.css @@ -0,0 +1,134 @@ +html.fail { + background: #f66; +} +html.pass { + background: #6f6; +} +html.needs_check { + background: #99f; +} + +body { + font-size: small; + font-family: sans-serif; + color: black; +} + +a:link { + color: #00c; +} +a:visited { + color: #808; +} + +body.framed { + font-size: x-small; +} + +h1 { + font-size: larger; + margin: 0; + padding-left: 0.5em; + text-indent: -0.5em; +} + +p { + margin: 0; +} + +p.notes { + margin-bottom: 0.5em; + font-style: italic; +} + +ul { + margin: 0; + margin-bottom: 0.5em; + padding: 0; + padding-left: 1em; +} + +.refs { + font-style: italic; + margin-bottom: 0.5em; +} + +.refs ul { + display: inline; + margin: 0; + padding: 0; +} + +.refs li { + display: inline; + list-style-type: none; + margin: 0; + padding: 0; +} + +canvas { + display: none; + visibility: hidden; + border: 2px #f0f solid; + background: url(../images/background.png); +} + +img.expected { + display: none; + border: 2px #f0f solid; + background: url(../images/background.png); +} + +iframe { + border: 2px #f0f solid; +} + +.output { + display: none; +} + +.show_output .output, .needs_check .output { + display: block !important; + visibility: visible !important; +} + +.show_output #show_output { + display: none; +} + +.resource { + visibility: hidden; + height: 0; +} + +.fallback { + font-size: 2em; + font-weight: bold; + color: #a00; +} + + +html.minimal body { + color: white; +} +html.fail.minimal { + background: #f00; +} +html.pass.minimal { + background: #080; +} +html.needs_check.minimal { + background: #008; +} +.minimal #d { + display: none !important; +} +.minimal .expectedtext { + visibility: hidden !important; +} +#passtext, #failtext { + display: none; +} +.minimal.pass #passtext, .minimal.fail #failtext { + display: block; +} diff --git a/test/fixtures/web-platform-tests/common/canvas-tests.css.headers b/test/fixtures/web-platform-tests/common/canvas-tests.css.headers new file mode 100644 index 00000000000000..e13897f157263b --- /dev/null +++ b/test/fixtures/web-platform-tests/common/canvas-tests.css.headers @@ -0,0 +1 @@ +Content-Type: text/css; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/canvas-tests.js b/test/fixtures/web-platform-tests/common/canvas-tests.js new file mode 100644 index 00000000000000..732f2a435b8194 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/canvas-tests.js @@ -0,0 +1,109 @@ +function _valToString(val) +{ + if (val === undefined || val === null) + return '[' + typeof(val) + ']'; + return val.toString() + '[' + typeof(val) + ']'; +} + +function _assert(cond, text) +{ + assert_true(!!cond, text); +} + +function _assertSame(a, b, text_a, text_b) +{ + var msg = text_a + ' === ' + text_b + ' (got ' + _valToString(a) + + ', expected ' + _valToString(b) + ')'; + assert_equals(a, b, msg); +} + +function _assertDifferent(a, b, text_a, text_b) +{ + var msg = text_a + ' !== ' + text_b + ' (got ' + _valToString(a) + + ', expected not ' + _valToString(b) + ')'; + assert_not_equals(a, b, msg); +} + + +function _getPixel(canvas, x,y) +{ + var ctx = canvas.getContext('2d'); + var imgdata = ctx.getImageData(x, y, 1, 1); + return [ imgdata.data[0], imgdata.data[1], imgdata.data[2], imgdata.data[3] ]; +} + +function _assertPixel(canvas, x,y, r,g,b,a, pos, colour) +{ + var c = _getPixel(canvas, x,y); + assert_equals(c[0], r, 'Red channel of the pixel at (' + x + ', ' + y + ')'); + assert_equals(c[1], g, 'Green channel of the pixel at (' + x + ', ' + y + ')'); + assert_equals(c[2], b, 'Blue channel of the pixel at (' + x + ', ' + y + ')'); + assert_equals(c[3], a, 'Alpha channel of the pixel at (' + x + ', ' + y + ')'); +} + +function _assertPixelApprox(canvas, x,y, r,g,b,a, pos, colour, tolerance) +{ + var c = _getPixel(canvas, x,y); + assert_approx_equals(c[0], r, tolerance, 'Red channel of the pixel at (' + x + ', ' + y + ')'); + assert_approx_equals(c[1], g, tolerance, 'Green channel of the pixel at (' + x + ', ' + y + ')'); + assert_approx_equals(c[2], b, tolerance, 'Blue channel of the pixel at (' + x + ', ' + y + ')'); + assert_approx_equals(c[3], a, tolerance, 'Alpha channel of the pixel at (' + x + ', ' + y + ')'); +} + +let _deferred = false; + +function deferTest() { + _deferred = true; +} + +function _addTest(testFn) +{ + on_event(window, "load", function() + { + t.step(function() { + var canvas = document.getElementById('c'); + var ctx = canvas.getContext('2d'); + t.step(testFn, window, canvas, ctx); + }); + + if (!_deferred) { + t.done(); + } + }); +} + +function _assertGreen(ctx, canvasWidth, canvasHeight) +{ + var testColor = function(d, idx, expected) { + assert_equals(d[idx], expected, "d[" + idx + "]", String(expected)); + }; + var imagedata = ctx.getImageData(0, 0, canvasWidth, canvasHeight); + var w = imagedata.width, h = imagedata.height, d = imagedata.data; + for (var i = 0; i < h; ++i) { + for (var j = 0; j < w; ++j) { + testColor(d, 4 * (w * i + j) + 0, 0); + testColor(d, 4 * (w * i + j) + 1, 255); + testColor(d, 4 * (w * i + j) + 2, 0); + testColor(d, 4 * (w * i + j) + 3, 255); + } + } +} + +function addCrossOriginYellowImage() +{ + var img = new Image(); + img.id = "yellow.png"; + img.className = "resource"; + img.src = get_host_info().HTTP_REMOTE_ORIGIN + "/images/yellow.png"; + document.body.appendChild(img); +} + +function addCrossOriginRedirectYellowImage() +{ + var img = new Image(); + img.id = "yellow.png"; + img.className = "resource"; + img.src = get_host_info().HTTP_ORIGIN + "/common/redirect.py?location=" + + get_host_info().HTTP_REMOTE_ORIGIN + "/images/yellow.png"; + document.body.appendChild(img); +} diff --git a/test/fixtures/web-platform-tests/common/canvas-tests.js.headers b/test/fixtures/web-platform-tests/common/canvas-tests.js.headers new file mode 100644 index 00000000000000..6805c323df5a97 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/canvas-tests.js.headers @@ -0,0 +1 @@ +Content-Type: text/javascript; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/css-paint-tests.js.headers b/test/fixtures/web-platform-tests/common/css-paint-tests.js.headers new file mode 100644 index 00000000000000..6805c323df5a97 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/css-paint-tests.js.headers @@ -0,0 +1 @@ +Content-Type: text/javascript; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/css-red.txt b/test/fixtures/web-platform-tests/common/css-red.txt new file mode 100644 index 00000000000000..9ef04cbd12daf1 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/css-red.txt @@ -0,0 +1 @@ +html { color: red; } diff --git a/test/fixtures/web-platform-tests/common/dummy.xhtml b/test/fixtures/web-platform-tests/common/dummy.xhtml new file mode 100644 index 00000000000000..5b208d7445050f --- /dev/null +++ b/test/fixtures/web-platform-tests/common/dummy.xhtml @@ -0,0 +1,2 @@ + +Dummy XHTML document diff --git a/test/fixtures/web-platform-tests/common/dummy.xml b/test/fixtures/web-platform-tests/common/dummy.xml new file mode 100644 index 00000000000000..4a60c3035fcc36 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/dummy.xml @@ -0,0 +1 @@ +Dummy XML document diff --git a/test/fixtures/web-platform-tests/common/entities.json b/test/fixtures/web-platform-tests/common/entities.json new file mode 100644 index 00000000000000..8a1f590a6abe48 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/entities.json @@ -0,0 +1,2233 @@ +{ + "Á": { "codepoints": [193], "characters": "\u00C1" }, + "Á": { "codepoints": [193], "characters": "\u00C1" }, + "á": { "codepoints": [225], "characters": "\u00E1" }, + "á": { "codepoints": [225], "characters": "\u00E1" }, + "Ă": { "codepoints": [258], "characters": "\u0102" }, + "ă": { "codepoints": [259], "characters": "\u0103" }, + "∾": { "codepoints": [8766], "characters": "\u223E" }, + "∿": { "codepoints": [8767], "characters": "\u223F" }, + "∾̳": { "codepoints": [8766, 819], "characters": "\u223E\u0333" }, + "Â": { "codepoints": [194], "characters": "\u00C2" }, + "Â": { "codepoints": [194], "characters": "\u00C2" }, + "â": { "codepoints": [226], "characters": "\u00E2" }, + "â": { "codepoints": [226], "characters": "\u00E2" }, + "´": { "codepoints": [180], "characters": "\u00B4" }, + "´": { "codepoints": [180], "characters": "\u00B4" }, + "А": { "codepoints": [1040], "characters": "\u0410" }, + "а": { "codepoints": [1072], "characters": "\u0430" }, + "Æ": { "codepoints": [198], "characters": "\u00C6" }, + "Æ": { "codepoints": [198], "characters": "\u00C6" }, + "æ": { "codepoints": [230], "characters": "\u00E6" }, + "æ": { "codepoints": [230], "characters": "\u00E6" }, + "⁡": { "codepoints": [8289], "characters": "\u2061" }, + "𝔄": { "codepoints": [120068], "characters": "\uD835\uDD04" }, + "𝔞": { "codepoints": [120094], "characters": "\uD835\uDD1E" }, + "À": { "codepoints": [192], "characters": "\u00C0" }, + "À": { "codepoints": [192], "characters": "\u00C0" }, + "à": { "codepoints": [224], "characters": "\u00E0" }, + "à": { "codepoints": [224], "characters": "\u00E0" }, + "ℵ": { "codepoints": [8501], "characters": "\u2135" }, + "ℵ": { "codepoints": [8501], "characters": "\u2135" }, + "Α": { "codepoints": [913], "characters": "\u0391" }, + "α": { "codepoints": [945], "characters": "\u03B1" }, + "Ā": { "codepoints": [256], "characters": "\u0100" }, + "ā": { "codepoints": [257], "characters": "\u0101" }, + "⨿": { "codepoints": [10815], "characters": "\u2A3F" }, + "&": { "codepoints": [38], "characters": "\u0026" }, + "&": { "codepoints": [38], "characters": "\u0026" }, + "&": { "codepoints": [38], "characters": "\u0026" }, + "&": { "codepoints": [38], "characters": "\u0026" }, + "⩕": { "codepoints": [10837], "characters": "\u2A55" }, + "⩓": { "codepoints": [10835], "characters": "\u2A53" }, + "∧": { "codepoints": [8743], "characters": "\u2227" }, + "⩜": { "codepoints": [10844], "characters": "\u2A5C" }, + "⩘": { "codepoints": [10840], "characters": "\u2A58" }, + "⩚": { "codepoints": [10842], "characters": "\u2A5A" }, + "∠": { "codepoints": [8736], "characters": "\u2220" }, + "⦤": { "codepoints": [10660], "characters": "\u29A4" }, + "∠": { "codepoints": [8736], "characters": "\u2220" }, + "⦨": { "codepoints": [10664], "characters": "\u29A8" }, + "⦩": { "codepoints": [10665], "characters": "\u29A9" }, + "⦪": { "codepoints": [10666], "characters": "\u29AA" }, + "⦫": { "codepoints": [10667], "characters": "\u29AB" }, + "⦬": { "codepoints": [10668], "characters": "\u29AC" }, + "⦭": { "codepoints": [10669], "characters": "\u29AD" }, + "⦮": { "codepoints": [10670], "characters": "\u29AE" }, + "⦯": { "codepoints": [10671], "characters": "\u29AF" }, + "∡": { "codepoints": [8737], "characters": "\u2221" }, + "∟": { "codepoints": [8735], "characters": "\u221F" }, + "⊾": { "codepoints": [8894], "characters": "\u22BE" }, + "⦝": { "codepoints": [10653], "characters": "\u299D" }, + "∢": { "codepoints": [8738], "characters": "\u2222" }, + "Å": { "codepoints": [197], "characters": "\u00C5" }, + "⍼": { "codepoints": [9084], "characters": "\u237C" }, + "Ą": { "codepoints": [260], "characters": "\u0104" }, + "ą": { "codepoints": [261], "characters": "\u0105" }, + "𝔸": { "codepoints": [120120], "characters": "\uD835\uDD38" }, + "𝕒": { "codepoints": [120146], "characters": "\uD835\uDD52" }, + "⩯": { "codepoints": [10863], "characters": "\u2A6F" }, + "≈": { "codepoints": [8776], "characters": "\u2248" }, + "⩰": { "codepoints": [10864], "characters": "\u2A70" }, + "≊": { "codepoints": [8778], "characters": "\u224A" }, + "≋": { "codepoints": [8779], "characters": "\u224B" }, + "'": { "codepoints": [39], "characters": "\u0027" }, + "⁡": { "codepoints": [8289], "characters": "\u2061" }, + "≈": { "codepoints": [8776], "characters": "\u2248" }, + "≊": { "codepoints": [8778], "characters": "\u224A" }, + "Å": { "codepoints": [197], "characters": "\u00C5" }, + "Å": { "codepoints": [197], "characters": "\u00C5" }, + "å": { "codepoints": [229], "characters": "\u00E5" }, + "å": { "codepoints": [229], "characters": "\u00E5" }, + "𝒜": { "codepoints": [119964], "characters": "\uD835\uDC9C" }, + "𝒶": { "codepoints": [119990], "characters": "\uD835\uDCB6" }, + "≔": { "codepoints": [8788], "characters": "\u2254" }, + "*": { "codepoints": [42], "characters": "\u002A" }, + "≈": { "codepoints": [8776], "characters": "\u2248" }, + "≍": { "codepoints": [8781], "characters": "\u224D" }, + "Ã": { "codepoints": [195], "characters": "\u00C3" }, + "Ã": { "codepoints": [195], "characters": "\u00C3" }, + "ã": { "codepoints": [227], "characters": "\u00E3" }, + "ã": { "codepoints": [227], "characters": "\u00E3" }, + "Ä": { "codepoints": [196], "characters": "\u00C4" }, + "Ä": { "codepoints": [196], "characters": "\u00C4" }, + "ä": { "codepoints": [228], "characters": "\u00E4" }, + "ä": { "codepoints": [228], "characters": "\u00E4" }, + "∳": { "codepoints": [8755], "characters": "\u2233" }, + "⨑": { "codepoints": [10769], "characters": "\u2A11" }, + "≌": { "codepoints": [8780], "characters": "\u224C" }, + "϶": { "codepoints": [1014], "characters": "\u03F6" }, + "‵": { "codepoints": [8245], "characters": "\u2035" }, + "∽": { "codepoints": [8765], "characters": "\u223D" }, + "⋍": { "codepoints": [8909], "characters": "\u22CD" }, + "∖": { "codepoints": [8726], "characters": "\u2216" }, + "⫧": { "codepoints": [10983], "characters": "\u2AE7" }, + "⊽": { "codepoints": [8893], "characters": "\u22BD" }, + "⌅": { "codepoints": [8965], "characters": "\u2305" }, + "⌆": { "codepoints": [8966], "characters": "\u2306" }, + "⌅": { "codepoints": [8965], "characters": "\u2305" }, + "⎵": { "codepoints": [9141], "characters": "\u23B5" }, + "⎶": { "codepoints": [9142], "characters": "\u23B6" }, + "≌": { "codepoints": [8780], "characters": "\u224C" }, + "Б": { "codepoints": [1041], "characters": "\u0411" }, + "б": { "codepoints": [1073], "characters": "\u0431" }, + "„": { "codepoints": [8222], "characters": "\u201E" }, + "∵": { "codepoints": [8757], "characters": "\u2235" }, + "∵": { "codepoints": [8757], "characters": "\u2235" }, + "∵": { "codepoints": [8757], "characters": "\u2235" }, + "⦰": { "codepoints": [10672], "characters": "\u29B0" }, + "϶": { "codepoints": [1014], "characters": "\u03F6" }, + "ℬ": { "codepoints": [8492], "characters": "\u212C" }, + "ℬ": { "codepoints": [8492], "characters": "\u212C" }, + "Β": { "codepoints": [914], "characters": "\u0392" }, + "β": { "codepoints": [946], "characters": "\u03B2" }, + "ℶ": { "codepoints": [8502], "characters": "\u2136" }, + "≬": { "codepoints": [8812], "characters": "\u226C" }, + "𝔅": { "codepoints": [120069], "characters": "\uD835\uDD05" }, + "𝔟": { "codepoints": [120095], "characters": "\uD835\uDD1F" }, + "⋂": { "codepoints": [8898], "characters": "\u22C2" }, + "◯": { "codepoints": [9711], "characters": "\u25EF" }, + "⋃": { "codepoints": [8899], "characters": "\u22C3" }, + "⨀": { "codepoints": [10752], "characters": "\u2A00" }, + "⨁": { "codepoints": [10753], "characters": "\u2A01" }, + "⨂": { "codepoints": [10754], "characters": "\u2A02" }, + "⨆": { "codepoints": [10758], "characters": "\u2A06" }, + "★": { "codepoints": [9733], "characters": "\u2605" }, + "▽": { "codepoints": [9661], "characters": "\u25BD" }, + "△": { "codepoints": [9651], "characters": "\u25B3" }, + "⨄": { "codepoints": [10756], "characters": "\u2A04" }, + "⋁": { "codepoints": [8897], "characters": "\u22C1" }, + "⋀": { "codepoints": [8896], "characters": "\u22C0" }, + "⤍": { "codepoints": [10509], "characters": "\u290D" }, + "⧫": { "codepoints": [10731], "characters": "\u29EB" }, + "▪": { "codepoints": [9642], "characters": "\u25AA" }, + "▴": { "codepoints": [9652], "characters": "\u25B4" }, + "▾": { "codepoints": [9662], "characters": "\u25BE" }, + "◂": { "codepoints": [9666], "characters": "\u25C2" }, + "▸": { "codepoints": [9656], "characters": "\u25B8" }, + "␣": { "codepoints": [9251], "characters": "\u2423" }, + "▒": { "codepoints": [9618], "characters": "\u2592" }, + "░": { "codepoints": [9617], "characters": "\u2591" }, + "▓": { "codepoints": [9619], "characters": "\u2593" }, + "█": { "codepoints": [9608], "characters": "\u2588" }, + "=⃥": { "codepoints": [61, 8421], "characters": "\u003D\u20E5" }, + "≡⃥": { "codepoints": [8801, 8421], "characters": "\u2261\u20E5" }, + "⫭": { "codepoints": [10989], "characters": "\u2AED" }, + "⌐": { "codepoints": [8976], "characters": "\u2310" }, + "𝔹": { "codepoints": [120121], "characters": "\uD835\uDD39" }, + "𝕓": { "codepoints": [120147], "characters": "\uD835\uDD53" }, + "⊥": { "codepoints": [8869], "characters": "\u22A5" }, + "⊥": { "codepoints": [8869], "characters": "\u22A5" }, + "⋈": { "codepoints": [8904], "characters": "\u22C8" }, + "⧉": { "codepoints": [10697], "characters": "\u29C9" }, + "┐": { "codepoints": [9488], "characters": "\u2510" }, + "╕": { "codepoints": [9557], "characters": "\u2555" }, + "╖": { "codepoints": [9558], "characters": "\u2556" }, + "╗": { "codepoints": [9559], "characters": "\u2557" }, + "┌": { "codepoints": [9484], "characters": "\u250C" }, + "╒": { "codepoints": [9554], "characters": "\u2552" }, + "╓": { "codepoints": [9555], "characters": "\u2553" }, + "╔": { "codepoints": [9556], "characters": "\u2554" }, + "─": { "codepoints": [9472], "characters": "\u2500" }, + "═": { "codepoints": [9552], "characters": "\u2550" }, + "┬": { "codepoints": [9516], "characters": "\u252C" }, + "╤": { "codepoints": [9572], "characters": "\u2564" }, + "╥": { "codepoints": [9573], "characters": "\u2565" }, + "╦": { "codepoints": [9574], "characters": "\u2566" }, + "┴": { "codepoints": [9524], "characters": "\u2534" }, + "╧": { "codepoints": [9575], "characters": "\u2567" }, + "╨": { "codepoints": [9576], "characters": "\u2568" }, + "╩": { "codepoints": [9577], "characters": "\u2569" }, + "⊟": { "codepoints": [8863], "characters": "\u229F" }, + "⊞": { "codepoints": [8862], "characters": "\u229E" }, + "⊠": { "codepoints": [8864], "characters": "\u22A0" }, + "┘": { "codepoints": [9496], "characters": "\u2518" }, + "╛": { "codepoints": [9563], "characters": "\u255B" }, + "╜": { "codepoints": [9564], "characters": "\u255C" }, + "╝": { "codepoints": [9565], "characters": "\u255D" }, + "└": { "codepoints": [9492], "characters": "\u2514" }, + "╘": { "codepoints": [9560], "characters": "\u2558" }, + "╙": { "codepoints": [9561], "characters": "\u2559" }, + "╚": { "codepoints": [9562], "characters": "\u255A" }, + "│": { "codepoints": [9474], "characters": "\u2502" }, + "║": { "codepoints": [9553], "characters": "\u2551" }, + "┼": { "codepoints": [9532], "characters": "\u253C" }, + "╪": { "codepoints": [9578], "characters": "\u256A" }, + "╫": { "codepoints": [9579], "characters": "\u256B" }, + "╬": { "codepoints": [9580], "characters": "\u256C" }, + "┤": { "codepoints": [9508], "characters": "\u2524" }, + "╡": { "codepoints": [9569], "characters": "\u2561" }, + "╢": { "codepoints": [9570], "characters": "\u2562" }, + "╣": { "codepoints": [9571], "characters": "\u2563" }, + "├": { "codepoints": [9500], "characters": "\u251C" }, + "╞": { "codepoints": [9566], "characters": "\u255E" }, + "╟": { "codepoints": [9567], "characters": "\u255F" }, + "╠": { "codepoints": [9568], "characters": "\u2560" }, + "‵": { "codepoints": [8245], "characters": "\u2035" }, + "˘": { "codepoints": [728], "characters": "\u02D8" }, + "˘": { "codepoints": [728], "characters": "\u02D8" }, + "¦": { "codepoints": [166], "characters": "\u00A6" }, + "¦": { "codepoints": [166], "characters": "\u00A6" }, + "𝒷": { "codepoints": [119991], "characters": "\uD835\uDCB7" }, + "ℬ": { "codepoints": [8492], "characters": "\u212C" }, + "⁏": { "codepoints": [8271], "characters": "\u204F" }, + "∽": { "codepoints": [8765], "characters": "\u223D" }, + "⋍": { "codepoints": [8909], "characters": "\u22CD" }, + "⧅": { "codepoints": [10693], "characters": "\u29C5" }, + "\": { "codepoints": [92], "characters": "\u005C" }, + "⟈": { "codepoints": [10184], "characters": "\u27C8" }, + "•": { "codepoints": [8226], "characters": "\u2022" }, + "•": { "codepoints": [8226], "characters": "\u2022" }, + "≎": { "codepoints": [8782], "characters": "\u224E" }, + "⪮": { "codepoints": [10926], "characters": "\u2AAE" }, + "≏": { "codepoints": [8783], "characters": "\u224F" }, + "≎": { "codepoints": [8782], "characters": "\u224E" }, + "≏": { "codepoints": [8783], "characters": "\u224F" }, + "Ć": { "codepoints": [262], "characters": "\u0106" }, + "ć": { "codepoints": [263], "characters": "\u0107" }, + "⩄": { "codepoints": [10820], "characters": "\u2A44" }, + "⩉": { "codepoints": [10825], "characters": "\u2A49" }, + "⩋": { "codepoints": [10827], "characters": "\u2A4B" }, + "∩": { "codepoints": [8745], "characters": "\u2229" }, + "⋒": { "codepoints": [8914], "characters": "\u22D2" }, + "⩇": { "codepoints": [10823], "characters": "\u2A47" }, + "⩀": { "codepoints": [10816], "characters": "\u2A40" }, + "ⅅ": { "codepoints": [8517], "characters": "\u2145" }, + "∩︀": { "codepoints": [8745, 65024], "characters": "\u2229\uFE00" }, + "⁁": { "codepoints": [8257], "characters": "\u2041" }, + "ˇ": { "codepoints": [711], "characters": "\u02C7" }, + "ℭ": { "codepoints": [8493], "characters": "\u212D" }, + "⩍": { "codepoints": [10829], "characters": "\u2A4D" }, + "Č": { "codepoints": [268], "characters": "\u010C" }, + "č": { "codepoints": [269], "characters": "\u010D" }, + "Ç": { "codepoints": [199], "characters": "\u00C7" }, + "Ç": { "codepoints": [199], "characters": "\u00C7" }, + "ç": { "codepoints": [231], "characters": "\u00E7" }, + "ç": { "codepoints": [231], "characters": "\u00E7" }, + "Ĉ": { "codepoints": [264], "characters": "\u0108" }, + "ĉ": { "codepoints": [265], "characters": "\u0109" }, + "∰": { "codepoints": [8752], "characters": "\u2230" }, + "⩌": { "codepoints": [10828], "characters": "\u2A4C" }, + "⩐": { "codepoints": [10832], "characters": "\u2A50" }, + "Ċ": { "codepoints": [266], "characters": "\u010A" }, + "ċ": { "codepoints": [267], "characters": "\u010B" }, + "¸": { "codepoints": [184], "characters": "\u00B8" }, + "¸": { "codepoints": [184], "characters": "\u00B8" }, + "¸": { "codepoints": [184], "characters": "\u00B8" }, + "⦲": { "codepoints": [10674], "characters": "\u29B2" }, + "¢": { "codepoints": [162], "characters": "\u00A2" }, + "¢": { "codepoints": [162], "characters": "\u00A2" }, + "·": { "codepoints": [183], "characters": "\u00B7" }, + "·": { "codepoints": [183], "characters": "\u00B7" }, + "𝔠": { "codepoints": [120096], "characters": "\uD835\uDD20" }, + "ℭ": { "codepoints": [8493], "characters": "\u212D" }, + "Ч": { "codepoints": [1063], "characters": "\u0427" }, + "ч": { "codepoints": [1095], "characters": "\u0447" }, + "✓": { "codepoints": [10003], "characters": "\u2713" }, + "✓": { "codepoints": [10003], "characters": "\u2713" }, + "Χ": { "codepoints": [935], "characters": "\u03A7" }, + "χ": { "codepoints": [967], "characters": "\u03C7" }, + "ˆ": { "codepoints": [710], "characters": "\u02C6" }, + "≗": { "codepoints": [8791], "characters": "\u2257" }, + "↺": { "codepoints": [8634], "characters": "\u21BA" }, + "↻": { "codepoints": [8635], "characters": "\u21BB" }, + "⊛": { "codepoints": [8859], "characters": "\u229B" }, + "⊚": { "codepoints": [8858], "characters": "\u229A" }, + "⊝": { "codepoints": [8861], "characters": "\u229D" }, + "⊙": { "codepoints": [8857], "characters": "\u2299" }, + "®": { "codepoints": [174], "characters": "\u00AE" }, + "Ⓢ": { "codepoints": [9416], "characters": "\u24C8" }, + "⊖": { "codepoints": [8854], "characters": "\u2296" }, + "⊕": { "codepoints": [8853], "characters": "\u2295" }, + "⊗": { "codepoints": [8855], "characters": "\u2297" }, + "○": { "codepoints": [9675], "characters": "\u25CB" }, + "⧃": { "codepoints": [10691], "characters": "\u29C3" }, + "≗": { "codepoints": [8791], "characters": "\u2257" }, + "⨐": { "codepoints": [10768], "characters": "\u2A10" }, + "⫯": { "codepoints": [10991], "characters": "\u2AEF" }, + "⧂": { "codepoints": [10690], "characters": "\u29C2" }, + "∲": { "codepoints": [8754], "characters": "\u2232" }, + "”": { "codepoints": [8221], "characters": "\u201D" }, + "’": { "codepoints": [8217], "characters": "\u2019" }, + "♣": { "codepoints": [9827], "characters": "\u2663" }, + "♣": { "codepoints": [9827], "characters": "\u2663" }, + ":": { "codepoints": [58], "characters": "\u003A" }, + "∷": { "codepoints": [8759], "characters": "\u2237" }, + "⩴": { "codepoints": [10868], "characters": "\u2A74" }, + "≔": { "codepoints": [8788], "characters": "\u2254" }, + "≔": { "codepoints": [8788], "characters": "\u2254" }, + ",": { "codepoints": [44], "characters": "\u002C" }, + "@": { "codepoints": [64], "characters": "\u0040" }, + "∁": { "codepoints": [8705], "characters": "\u2201" }, + "∘": { "codepoints": [8728], "characters": "\u2218" }, + "∁": { "codepoints": [8705], "characters": "\u2201" }, + "ℂ": { "codepoints": [8450], "characters": "\u2102" }, + "≅": { "codepoints": [8773], "characters": "\u2245" }, + "⩭": { "codepoints": [10861], "characters": "\u2A6D" }, + "≡": { "codepoints": [8801], "characters": "\u2261" }, + "∮": { "codepoints": [8750], "characters": "\u222E" }, + "∯": { "codepoints": [8751], "characters": "\u222F" }, + "∮": { "codepoints": [8750], "characters": "\u222E" }, + "𝕔": { "codepoints": [120148], "characters": "\uD835\uDD54" }, + "ℂ": { "codepoints": [8450], "characters": "\u2102" }, + "∐": { "codepoints": [8720], "characters": "\u2210" }, + "∐": { "codepoints": [8720], "characters": "\u2210" }, + "©": { "codepoints": [169], "characters": "\u00A9" }, + "©": { "codepoints": [169], "characters": "\u00A9" }, + "©": { "codepoints": [169], "characters": "\u00A9" }, + "©": { "codepoints": [169], "characters": "\u00A9" }, + "℗": { "codepoints": [8471], "characters": "\u2117" }, + "∳": { "codepoints": [8755], "characters": "\u2233" }, + "↵": { "codepoints": [8629], "characters": "\u21B5" }, + "✗": { "codepoints": [10007], "characters": "\u2717" }, + "⨯": { "codepoints": [10799], "characters": "\u2A2F" }, + "𝒞": { "codepoints": [119966], "characters": "\uD835\uDC9E" }, + "𝒸": { "codepoints": [119992], "characters": "\uD835\uDCB8" }, + "⫏": { "codepoints": [10959], "characters": "\u2ACF" }, + "⫑": { "codepoints": [10961], "characters": "\u2AD1" }, + "⫐": { "codepoints": [10960], "characters": "\u2AD0" }, + "⫒": { "codepoints": [10962], "characters": "\u2AD2" }, + "⋯": { "codepoints": [8943], "characters": "\u22EF" }, + "⤸": { "codepoints": [10552], "characters": "\u2938" }, + "⤵": { "codepoints": [10549], "characters": "\u2935" }, + "⋞": { "codepoints": [8926], "characters": "\u22DE" }, + "⋟": { "codepoints": [8927], "characters": "\u22DF" }, + "↶": { "codepoints": [8630], "characters": "\u21B6" }, + "⤽": { "codepoints": [10557], "characters": "\u293D" }, + "⩈": { "codepoints": [10824], "characters": "\u2A48" }, + "⩆": { "codepoints": [10822], "characters": "\u2A46" }, + "≍": { "codepoints": [8781], "characters": "\u224D" }, + "∪": { "codepoints": [8746], "characters": "\u222A" }, + "⋓": { "codepoints": [8915], "characters": "\u22D3" }, + "⩊": { "codepoints": [10826], "characters": "\u2A4A" }, + "⊍": { "codepoints": [8845], "characters": "\u228D" }, + "⩅": { "codepoints": [10821], "characters": "\u2A45" }, + "∪︀": { "codepoints": [8746, 65024], "characters": "\u222A\uFE00" }, + "↷": { "codepoints": [8631], "characters": "\u21B7" }, + "⤼": { "codepoints": [10556], "characters": "\u293C" }, + "⋞": { "codepoints": [8926], "characters": "\u22DE" }, + "⋟": { "codepoints": [8927], "characters": "\u22DF" }, + "⋎": { "codepoints": [8910], "characters": "\u22CE" }, + "⋏": { "codepoints": [8911], "characters": "\u22CF" }, + "¤": { "codepoints": [164], "characters": "\u00A4" }, + "¤": { "codepoints": [164], "characters": "\u00A4" }, + "↶": { "codepoints": [8630], "characters": "\u21B6" }, + "↷": { "codepoints": [8631], "characters": "\u21B7" }, + "⋎": { "codepoints": [8910], "characters": "\u22CE" }, + "⋏": { "codepoints": [8911], "characters": "\u22CF" }, + "∲": { "codepoints": [8754], "characters": "\u2232" }, + "∱": { "codepoints": [8753], "characters": "\u2231" }, + "⌭": { "codepoints": [9005], "characters": "\u232D" }, + "†": { "codepoints": [8224], "characters": "\u2020" }, + "‡": { "codepoints": [8225], "characters": "\u2021" }, + "ℸ": { "codepoints": [8504], "characters": "\u2138" }, + "↓": { "codepoints": [8595], "characters": "\u2193" }, + "↡": { "codepoints": [8609], "characters": "\u21A1" }, + "⇓": { "codepoints": [8659], "characters": "\u21D3" }, + "‐": { "codepoints": [8208], "characters": "\u2010" }, + "⫤": { "codepoints": [10980], "characters": "\u2AE4" }, + "⊣": { "codepoints": [8867], "characters": "\u22A3" }, + "⤏": { "codepoints": [10511], "characters": "\u290F" }, + "˝": { "codepoints": [733], "characters": "\u02DD" }, + "Ď": { "codepoints": [270], "characters": "\u010E" }, + "ď": { "codepoints": [271], "characters": "\u010F" }, + "Д": { "codepoints": [1044], "characters": "\u0414" }, + "д": { "codepoints": [1076], "characters": "\u0434" }, + "‡": { "codepoints": [8225], "characters": "\u2021" }, + "⇊": { "codepoints": [8650], "characters": "\u21CA" }, + "ⅅ": { "codepoints": [8517], "characters": "\u2145" }, + "ⅆ": { "codepoints": [8518], "characters": "\u2146" }, + "⤑": { "codepoints": [10513], "characters": "\u2911" }, + "⩷": { "codepoints": [10871], "characters": "\u2A77" }, + "°": { "codepoints": [176], "characters": "\u00B0" }, + "°": { "codepoints": [176], "characters": "\u00B0" }, + "∇": { "codepoints": [8711], "characters": "\u2207" }, + "Δ": { "codepoints": [916], "characters": "\u0394" }, + "δ": { "codepoints": [948], "characters": "\u03B4" }, + "⦱": { "codepoints": [10673], "characters": "\u29B1" }, + "⥿": { "codepoints": [10623], "characters": "\u297F" }, + "𝔇": { "codepoints": [120071], "characters": "\uD835\uDD07" }, + "𝔡": { "codepoints": [120097], "characters": "\uD835\uDD21" }, + "⥥": { "codepoints": [10597], "characters": "\u2965" }, + "⇃": { "codepoints": [8643], "characters": "\u21C3" }, + "⇂": { "codepoints": [8642], "characters": "\u21C2" }, + "´": { "codepoints": [180], "characters": "\u00B4" }, + "˙": { "codepoints": [729], "characters": "\u02D9" }, + "˝": { "codepoints": [733], "characters": "\u02DD" }, + "`": { "codepoints": [96], "characters": "\u0060" }, + "˜": { "codepoints": [732], "characters": "\u02DC" }, + "⋄": { "codepoints": [8900], "characters": "\u22C4" }, + "⋄": { "codepoints": [8900], "characters": "\u22C4" }, + "⋄": { "codepoints": [8900], "characters": "\u22C4" }, + "♦": { "codepoints": [9830], "characters": "\u2666" }, + "♦": { "codepoints": [9830], "characters": "\u2666" }, + "¨": { "codepoints": [168], "characters": "\u00A8" }, + "ⅆ": { "codepoints": [8518], "characters": "\u2146" }, + "ϝ": { "codepoints": [989], "characters": "\u03DD" }, + "⋲": { "codepoints": [8946], "characters": "\u22F2" }, + "÷": { "codepoints": [247], "characters": "\u00F7" }, + "÷": { "codepoints": [247], "characters": "\u00F7" }, + "÷": { "codepoints": [247], "characters": "\u00F7" }, + "⋇": { "codepoints": [8903], "characters": "\u22C7" }, + "⋇": { "codepoints": [8903], "characters": "\u22C7" }, + "Ђ": { "codepoints": [1026], "characters": "\u0402" }, + "ђ": { "codepoints": [1106], "characters": "\u0452" }, + "⌞": { "codepoints": [8990], "characters": "\u231E" }, + "⌍": { "codepoints": [8973], "characters": "\u230D" }, + "$": { "codepoints": [36], "characters": "\u0024" }, + "𝔻": { "codepoints": [120123], "characters": "\uD835\uDD3B" }, + "𝕕": { "codepoints": [120149], "characters": "\uD835\uDD55" }, + "¨": { "codepoints": [168], "characters": "\u00A8" }, + "˙": { "codepoints": [729], "characters": "\u02D9" }, + "⃜": { "codepoints": [8412], "characters": "\u20DC" }, + "≐": { "codepoints": [8784], "characters": "\u2250" }, + "≑": { "codepoints": [8785], "characters": "\u2251" }, + "≐": { "codepoints": [8784], "characters": "\u2250" }, + "∸": { "codepoints": [8760], "characters": "\u2238" }, + "∔": { "codepoints": [8724], "characters": "\u2214" }, + "⊡": { "codepoints": [8865], "characters": "\u22A1" }, + "⌆": { "codepoints": [8966], "characters": "\u2306" }, + "∯": { "codepoints": [8751], "characters": "\u222F" }, + "¨": { "codepoints": [168], "characters": "\u00A8" }, + "⇓": { "codepoints": [8659], "characters": "\u21D3" }, + "⇐": { "codepoints": [8656], "characters": "\u21D0" }, + "⇔": { "codepoints": [8660], "characters": "\u21D4" }, + "⫤": { "codepoints": [10980], "characters": "\u2AE4" }, + "⟸": { "codepoints": [10232], "characters": "\u27F8" }, + "⟺": { "codepoints": [10234], "characters": "\u27FA" }, + "⟹": { "codepoints": [10233], "characters": "\u27F9" }, + "⇒": { "codepoints": [8658], "characters": "\u21D2" }, + "⊨": { "codepoints": [8872], "characters": "\u22A8" }, + "⇑": { "codepoints": [8657], "characters": "\u21D1" }, + "⇕": { "codepoints": [8661], "characters": "\u21D5" }, + "∥": { "codepoints": [8741], "characters": "\u2225" }, + "⤓": { "codepoints": [10515], "characters": "\u2913" }, + "↓": { "codepoints": [8595], "characters": "\u2193" }, + "↓": { "codepoints": [8595], "characters": "\u2193" }, + "⇓": { "codepoints": [8659], "characters": "\u21D3" }, + "⇵": { "codepoints": [8693], "characters": "\u21F5" }, + "̑": { "codepoints": [785], "characters": "\u0311" }, + "⇊": { "codepoints": [8650], "characters": "\u21CA" }, + "⇃": { "codepoints": [8643], "characters": "\u21C3" }, + "⇂": { "codepoints": [8642], "characters": "\u21C2" }, + "⥐": { "codepoints": [10576], "characters": "\u2950" }, + "⥞": { "codepoints": [10590], "characters": "\u295E" }, + "⥖": { "codepoints": [10582], "characters": "\u2956" }, + "↽": { "codepoints": [8637], "characters": "\u21BD" }, + "⥟": { "codepoints": [10591], "characters": "\u295F" }, + "⥗": { "codepoints": [10583], "characters": "\u2957" }, + "⇁": { "codepoints": [8641], "characters": "\u21C1" }, + "↧": { "codepoints": [8615], "characters": "\u21A7" }, + "⊤": { "codepoints": [8868], "characters": "\u22A4" }, + "⤐": { "codepoints": [10512], "characters": "\u2910" }, + "⌟": { "codepoints": [8991], "characters": "\u231F" }, + "⌌": { "codepoints": [8972], "characters": "\u230C" }, + "𝒟": { "codepoints": [119967], "characters": "\uD835\uDC9F" }, + "𝒹": { "codepoints": [119993], "characters": "\uD835\uDCB9" }, + "Ѕ": { "codepoints": [1029], "characters": "\u0405" }, + "ѕ": { "codepoints": [1109], "characters": "\u0455" }, + "⧶": { "codepoints": [10742], "characters": "\u29F6" }, + "Đ": { "codepoints": [272], "characters": "\u0110" }, + "đ": { "codepoints": [273], "characters": "\u0111" }, + "⋱": { "codepoints": [8945], "characters": "\u22F1" }, + "▿": { "codepoints": [9663], "characters": "\u25BF" }, + "▾": { "codepoints": [9662], "characters": "\u25BE" }, + "⇵": { "codepoints": [8693], "characters": "\u21F5" }, + "⥯": { "codepoints": [10607], "characters": "\u296F" }, + "⦦": { "codepoints": [10662], "characters": "\u29A6" }, + "Џ": { "codepoints": [1039], "characters": "\u040F" }, + "џ": { "codepoints": [1119], "characters": "\u045F" }, + "⟿": { "codepoints": [10239], "characters": "\u27FF" }, + "É": { "codepoints": [201], "characters": "\u00C9" }, + "É": { "codepoints": [201], "characters": "\u00C9" }, + "é": { "codepoints": [233], "characters": "\u00E9" }, + "é": { "codepoints": [233], "characters": "\u00E9" }, + "⩮": { "codepoints": [10862], "characters": "\u2A6E" }, + "Ě": { "codepoints": [282], "characters": "\u011A" }, + "ě": { "codepoints": [283], "characters": "\u011B" }, + "Ê": { "codepoints": [202], "characters": "\u00CA" }, + "Ê": { "codepoints": [202], "characters": "\u00CA" }, + "ê": { "codepoints": [234], "characters": "\u00EA" }, + "ê": { "codepoints": [234], "characters": "\u00EA" }, + "≖": { "codepoints": [8790], "characters": "\u2256" }, + "≕": { "codepoints": [8789], "characters": "\u2255" }, + "Э": { "codepoints": [1069], "characters": "\u042D" }, + "э": { "codepoints": [1101], "characters": "\u044D" }, + "⩷": { "codepoints": [10871], "characters": "\u2A77" }, + "Ė": { "codepoints": [278], "characters": "\u0116" }, + "ė": { "codepoints": [279], "characters": "\u0117" }, + "≑": { "codepoints": [8785], "characters": "\u2251" }, + "ⅇ": { "codepoints": [8519], "characters": "\u2147" }, + "≒": { "codepoints": [8786], "characters": "\u2252" }, + "𝔈": { "codepoints": [120072], "characters": "\uD835\uDD08" }, + "𝔢": { "codepoints": [120098], "characters": "\uD835\uDD22" }, + "⪚": { "codepoints": [10906], "characters": "\u2A9A" }, + "È": { "codepoints": [200], "characters": "\u00C8" }, + "È": { "codepoints": [200], "characters": "\u00C8" }, + "è": { "codepoints": [232], "characters": "\u00E8" }, + "è": { "codepoints": [232], "characters": "\u00E8" }, + "⪖": { "codepoints": [10902], "characters": "\u2A96" }, + "⪘": { "codepoints": [10904], "characters": "\u2A98" }, + "⪙": { "codepoints": [10905], "characters": "\u2A99" }, + "∈": { "codepoints": [8712], "characters": "\u2208" }, + "⏧": { "codepoints": [9191], "characters": "\u23E7" }, + "ℓ": { "codepoints": [8467], "characters": "\u2113" }, + "⪕": { "codepoints": [10901], "characters": "\u2A95" }, + "⪗": { "codepoints": [10903], "characters": "\u2A97" }, + "Ē": { "codepoints": [274], "characters": "\u0112" }, + "ē": { "codepoints": [275], "characters": "\u0113" }, + "∅": { "codepoints": [8709], "characters": "\u2205" }, + "∅": { "codepoints": [8709], "characters": "\u2205" }, + "◻": { "codepoints": [9723], "characters": "\u25FB" }, + "∅": { "codepoints": [8709], "characters": "\u2205" }, + "▫": { "codepoints": [9643], "characters": "\u25AB" }, + " ": { "codepoints": [8196], "characters": "\u2004" }, + " ": { "codepoints": [8197], "characters": "\u2005" }, + " ": { "codepoints": [8195], "characters": "\u2003" }, + "Ŋ": { "codepoints": [330], "characters": "\u014A" }, + "ŋ": { "codepoints": [331], "characters": "\u014B" }, + " ": { "codepoints": [8194], "characters": "\u2002" }, + "Ę": { "codepoints": [280], "characters": "\u0118" }, + "ę": { "codepoints": [281], "characters": "\u0119" }, + "𝔼": { "codepoints": [120124], "characters": "\uD835\uDD3C" }, + "𝕖": { "codepoints": [120150], "characters": "\uD835\uDD56" }, + "⋕": { "codepoints": [8917], "characters": "\u22D5" }, + "⧣": { "codepoints": [10723], "characters": "\u29E3" }, + "⩱": { "codepoints": [10865], "characters": "\u2A71" }, + "ε": { "codepoints": [949], "characters": "\u03B5" }, + "Ε": { "codepoints": [917], "characters": "\u0395" }, + "ε": { "codepoints": [949], "characters": "\u03B5" }, + "ϵ": { "codepoints": [1013], "characters": "\u03F5" }, + "≖": { "codepoints": [8790], "characters": "\u2256" }, + "≕": { "codepoints": [8789], "characters": "\u2255" }, + "≂": { "codepoints": [8770], "characters": "\u2242" }, + "⪖": { "codepoints": [10902], "characters": "\u2A96" }, + "⪕": { "codepoints": [10901], "characters": "\u2A95" }, + "⩵": { "codepoints": [10869], "characters": "\u2A75" }, + "=": { "codepoints": [61], "characters": "\u003D" }, + "≂": { "codepoints": [8770], "characters": "\u2242" }, + "≟": { "codepoints": [8799], "characters": "\u225F" }, + "⇌": { "codepoints": [8652], "characters": "\u21CC" }, + "≡": { "codepoints": [8801], "characters": "\u2261" }, + "⩸": { "codepoints": [10872], "characters": "\u2A78" }, + "⧥": { "codepoints": [10725], "characters": "\u29E5" }, + "⥱": { "codepoints": [10609], "characters": "\u2971" }, + "≓": { "codepoints": [8787], "characters": "\u2253" }, + "ℯ": { "codepoints": [8495], "characters": "\u212F" }, + "ℰ": { "codepoints": [8496], "characters": "\u2130" }, + "≐": { "codepoints": [8784], "characters": "\u2250" }, + "⩳": { "codepoints": [10867], "characters": "\u2A73" }, + "≂": { "codepoints": [8770], "characters": "\u2242" }, + "Η": { "codepoints": [919], "characters": "\u0397" }, + "η": { "codepoints": [951], "characters": "\u03B7" }, + "Ð": { "codepoints": [208], "characters": "\u00D0" }, + "Ð": { "codepoints": [208], "characters": "\u00D0" }, + "ð": { "codepoints": [240], "characters": "\u00F0" }, + "ð": { "codepoints": [240], "characters": "\u00F0" }, + "Ë": { "codepoints": [203], "characters": "\u00CB" }, + "Ë": { "codepoints": [203], "characters": "\u00CB" }, + "ë": { "codepoints": [235], "characters": "\u00EB" }, + "ë": { "codepoints": [235], "characters": "\u00EB" }, + "€": { "codepoints": [8364], "characters": "\u20AC" }, + "!": { "codepoints": [33], "characters": "\u0021" }, + "∃": { "codepoints": [8707], "characters": "\u2203" }, + "∃": { "codepoints": [8707], "characters": "\u2203" }, + "ℰ": { "codepoints": [8496], "characters": "\u2130" }, + "ⅇ": { "codepoints": [8519], "characters": "\u2147" }, + "ⅇ": { "codepoints": [8519], "characters": "\u2147" }, + "≒": { "codepoints": [8786], "characters": "\u2252" }, + "Ф": { "codepoints": [1060], "characters": "\u0424" }, + "ф": { "codepoints": [1092], "characters": "\u0444" }, + "♀": { "codepoints": [9792], "characters": "\u2640" }, + "ffi": { "codepoints": [64259], "characters": "\uFB03" }, + "ff": { "codepoints": [64256], "characters": "\uFB00" }, + "ffl": { "codepoints": [64260], "characters": "\uFB04" }, + "𝔉": { "codepoints": [120073], "characters": "\uD835\uDD09" }, + "𝔣": { "codepoints": [120099], "characters": "\uD835\uDD23" }, + "fi": { "codepoints": [64257], "characters": "\uFB01" }, + "◼": { "codepoints": [9724], "characters": "\u25FC" }, + "▪": { "codepoints": [9642], "characters": "\u25AA" }, + "fj": { "codepoints": [102, 106], "characters": "\u0066\u006A" }, + "♭": { "codepoints": [9837], "characters": "\u266D" }, + "fl": { "codepoints": [64258], "characters": "\uFB02" }, + "▱": { "codepoints": [9649], "characters": "\u25B1" }, + "ƒ": { "codepoints": [402], "characters": "\u0192" }, + "𝔽": { "codepoints": [120125], "characters": "\uD835\uDD3D" }, + "𝕗": { "codepoints": [120151], "characters": "\uD835\uDD57" }, + "∀": { "codepoints": [8704], "characters": "\u2200" }, + "∀": { "codepoints": [8704], "characters": "\u2200" }, + "⋔": { "codepoints": [8916], "characters": "\u22D4" }, + "⫙": { "codepoints": [10969], "characters": "\u2AD9" }, + "ℱ": { "codepoints": [8497], "characters": "\u2131" }, + "⨍": { "codepoints": [10765], "characters": "\u2A0D" }, + "½": { "codepoints": [189], "characters": "\u00BD" }, + "½": { "codepoints": [189], "characters": "\u00BD" }, + "⅓": { "codepoints": [8531], "characters": "\u2153" }, + "¼": { "codepoints": [188], "characters": "\u00BC" }, + "¼": { "codepoints": [188], "characters": "\u00BC" }, + "⅕": { "codepoints": [8533], "characters": "\u2155" }, + "⅙": { "codepoints": [8537], "characters": "\u2159" }, + "⅛": { "codepoints": [8539], "characters": "\u215B" }, + "⅔": { "codepoints": [8532], "characters": "\u2154" }, + "⅖": { "codepoints": [8534], "characters": "\u2156" }, + "¾": { "codepoints": [190], "characters": "\u00BE" }, + "¾": { "codepoints": [190], "characters": "\u00BE" }, + "⅗": { "codepoints": [8535], "characters": "\u2157" }, + "⅜": { "codepoints": [8540], "characters": "\u215C" }, + "⅘": { "codepoints": [8536], "characters": "\u2158" }, + "⅚": { "codepoints": [8538], "characters": "\u215A" }, + "⅝": { "codepoints": [8541], "characters": "\u215D" }, + "⅞": { "codepoints": [8542], "characters": "\u215E" }, + "⁄": { "codepoints": [8260], "characters": "\u2044" }, + "⌢": { "codepoints": [8994], "characters": "\u2322" }, + "𝒻": { "codepoints": [119995], "characters": "\uD835\uDCBB" }, + "ℱ": { "codepoints": [8497], "characters": "\u2131" }, + "ǵ": { "codepoints": [501], "characters": "\u01F5" }, + "Γ": { "codepoints": [915], "characters": "\u0393" }, + "γ": { "codepoints": [947], "characters": "\u03B3" }, + "Ϝ": { "codepoints": [988], "characters": "\u03DC" }, + "ϝ": { "codepoints": [989], "characters": "\u03DD" }, + "⪆": { "codepoints": [10886], "characters": "\u2A86" }, + "Ğ": { "codepoints": [286], "characters": "\u011E" }, + "ğ": { "codepoints": [287], "characters": "\u011F" }, + "Ģ": { "codepoints": [290], "characters": "\u0122" }, + "Ĝ": { "codepoints": [284], "characters": "\u011C" }, + "ĝ": { "codepoints": [285], "characters": "\u011D" }, + "Г": { "codepoints": [1043], "characters": "\u0413" }, + "г": { "codepoints": [1075], "characters": "\u0433" }, + "Ġ": { "codepoints": [288], "characters": "\u0120" }, + "ġ": { "codepoints": [289], "characters": "\u0121" }, + "≥": { "codepoints": [8805], "characters": "\u2265" }, + "≧": { "codepoints": [8807], "characters": "\u2267" }, + "⪌": { "codepoints": [10892], "characters": "\u2A8C" }, + "⋛": { "codepoints": [8923], "characters": "\u22DB" }, + "≥": { "codepoints": [8805], "characters": "\u2265" }, + "≧": { "codepoints": [8807], "characters": "\u2267" }, + "⩾": { "codepoints": [10878], "characters": "\u2A7E" }, + "⪩": { "codepoints": [10921], "characters": "\u2AA9" }, + "⩾": { "codepoints": [10878], "characters": "\u2A7E" }, + "⪀": { "codepoints": [10880], "characters": "\u2A80" }, + "⪂": { "codepoints": [10882], "characters": "\u2A82" }, + "⪄": { "codepoints": [10884], "characters": "\u2A84" }, + "⋛︀": { "codepoints": [8923, 65024], "characters": "\u22DB\uFE00" }, + "⪔": { "codepoints": [10900], "characters": "\u2A94" }, + "𝔊": { "codepoints": [120074], "characters": "\uD835\uDD0A" }, + "𝔤": { "codepoints": [120100], "characters": "\uD835\uDD24" }, + "≫": { "codepoints": [8811], "characters": "\u226B" }, + "⋙": { "codepoints": [8921], "characters": "\u22D9" }, + "⋙": { "codepoints": [8921], "characters": "\u22D9" }, + "ℷ": { "codepoints": [8503], "characters": "\u2137" }, + "Ѓ": { "codepoints": [1027], "characters": "\u0403" }, + "ѓ": { "codepoints": [1107], "characters": "\u0453" }, + "⪥": { "codepoints": [10917], "characters": "\u2AA5" }, + "≷": { "codepoints": [8823], "characters": "\u2277" }, + "⪒": { "codepoints": [10898], "characters": "\u2A92" }, + "⪤": { "codepoints": [10916], "characters": "\u2AA4" }, + "⪊": { "codepoints": [10890], "characters": "\u2A8A" }, + "⪊": { "codepoints": [10890], "characters": "\u2A8A" }, + "⪈": { "codepoints": [10888], "characters": "\u2A88" }, + "≩": { "codepoints": [8809], "characters": "\u2269" }, + "⪈": { "codepoints": [10888], "characters": "\u2A88" }, + "≩": { "codepoints": [8809], "characters": "\u2269" }, + "⋧": { "codepoints": [8935], "characters": "\u22E7" }, + "𝔾": { "codepoints": [120126], "characters": "\uD835\uDD3E" }, + "𝕘": { "codepoints": [120152], "characters": "\uD835\uDD58" }, + "`": { "codepoints": [96], "characters": "\u0060" }, + "≥": { "codepoints": [8805], "characters": "\u2265" }, + "⋛": { "codepoints": [8923], "characters": "\u22DB" }, + "≧": { "codepoints": [8807], "characters": "\u2267" }, + "⪢": { "codepoints": [10914], "characters": "\u2AA2" }, + "≷": { "codepoints": [8823], "characters": "\u2277" }, + "⩾": { "codepoints": [10878], "characters": "\u2A7E" }, + "≳": { "codepoints": [8819], "characters": "\u2273" }, + "𝒢": { "codepoints": [119970], "characters": "\uD835\uDCA2" }, + "ℊ": { "codepoints": [8458], "characters": "\u210A" }, + "≳": { "codepoints": [8819], "characters": "\u2273" }, + "⪎": { "codepoints": [10894], "characters": "\u2A8E" }, + "⪐": { "codepoints": [10896], "characters": "\u2A90" }, + "⪧": { "codepoints": [10919], "characters": "\u2AA7" }, + "⩺": { "codepoints": [10874], "characters": "\u2A7A" }, + ">": { "codepoints": [62], "characters": "\u003E" }, + ">": { "codepoints": [62], "characters": "\u003E" }, + ">": { "codepoints": [62], "characters": "\u003E" }, + ">": { "codepoints": [62], "characters": "\u003E" }, + "≫": { "codepoints": [8811], "characters": "\u226B" }, + "⋗": { "codepoints": [8919], "characters": "\u22D7" }, + "⦕": { "codepoints": [10645], "characters": "\u2995" }, + "⩼": { "codepoints": [10876], "characters": "\u2A7C" }, + "⪆": { "codepoints": [10886], "characters": "\u2A86" }, + "⥸": { "codepoints": [10616], "characters": "\u2978" }, + "⋗": { "codepoints": [8919], "characters": "\u22D7" }, + "⋛": { "codepoints": [8923], "characters": "\u22DB" }, + "⪌": { "codepoints": [10892], "characters": "\u2A8C" }, + "≷": { "codepoints": [8823], "characters": "\u2277" }, + "≳": { "codepoints": [8819], "characters": "\u2273" }, + "≩︀": { "codepoints": [8809, 65024], "characters": "\u2269\uFE00" }, + "≩︀": { "codepoints": [8809, 65024], "characters": "\u2269\uFE00" }, + "ˇ": { "codepoints": [711], "characters": "\u02C7" }, + " ": { "codepoints": [8202], "characters": "\u200A" }, + "½": { "codepoints": [189], "characters": "\u00BD" }, + "ℋ": { "codepoints": [8459], "characters": "\u210B" }, + "Ъ": { "codepoints": [1066], "characters": "\u042A" }, + "ъ": { "codepoints": [1098], "characters": "\u044A" }, + "⥈": { "codepoints": [10568], "characters": "\u2948" }, + "↔": { "codepoints": [8596], "characters": "\u2194" }, + "⇔": { "codepoints": [8660], "characters": "\u21D4" }, + "↭": { "codepoints": [8621], "characters": "\u21AD" }, + "^": { "codepoints": [94], "characters": "\u005E" }, + "ℏ": { "codepoints": [8463], "characters": "\u210F" }, + "Ĥ": { "codepoints": [292], "characters": "\u0124" }, + "ĥ": { "codepoints": [293], "characters": "\u0125" }, + "♥": { "codepoints": [9829], "characters": "\u2665" }, + "♥": { "codepoints": [9829], "characters": "\u2665" }, + "…": { "codepoints": [8230], "characters": "\u2026" }, + "⊹": { "codepoints": [8889], "characters": "\u22B9" }, + "𝔥": { "codepoints": [120101], "characters": "\uD835\uDD25" }, + "ℌ": { "codepoints": [8460], "characters": "\u210C" }, + "ℋ": { "codepoints": [8459], "characters": "\u210B" }, + "⤥": { "codepoints": [10533], "characters": "\u2925" }, + "⤦": { "codepoints": [10534], "characters": "\u2926" }, + "⇿": { "codepoints": [8703], "characters": "\u21FF" }, + "∻": { "codepoints": [8763], "characters": "\u223B" }, + "↩": { "codepoints": [8617], "characters": "\u21A9" }, + "↪": { "codepoints": [8618], "characters": "\u21AA" }, + "𝕙": { "codepoints": [120153], "characters": "\uD835\uDD59" }, + "ℍ": { "codepoints": [8461], "characters": "\u210D" }, + "―": { "codepoints": [8213], "characters": "\u2015" }, + "─": { "codepoints": [9472], "characters": "\u2500" }, + "𝒽": { "codepoints": [119997], "characters": "\uD835\uDCBD" }, + "ℋ": { "codepoints": [8459], "characters": "\u210B" }, + "ℏ": { "codepoints": [8463], "characters": "\u210F" }, + "Ħ": { "codepoints": [294], "characters": "\u0126" }, + "ħ": { "codepoints": [295], "characters": "\u0127" }, + "≎": { "codepoints": [8782], "characters": "\u224E" }, + "≏": { "codepoints": [8783], "characters": "\u224F" }, + "⁃": { "codepoints": [8259], "characters": "\u2043" }, + "‐": { "codepoints": [8208], "characters": "\u2010" }, + "Í": { "codepoints": [205], "characters": "\u00CD" }, + "Í": { "codepoints": [205], "characters": "\u00CD" }, + "í": { "codepoints": [237], "characters": "\u00ED" }, + "í": { "codepoints": [237], "characters": "\u00ED" }, + "⁣": { "codepoints": [8291], "characters": "\u2063" }, + "Î": { "codepoints": [206], "characters": "\u00CE" }, + "Î": { "codepoints": [206], "characters": "\u00CE" }, + "î": { "codepoints": [238], "characters": "\u00EE" }, + "î": { "codepoints": [238], "characters": "\u00EE" }, + "И": { "codepoints": [1048], "characters": "\u0418" }, + "и": { "codepoints": [1080], "characters": "\u0438" }, + "İ": { "codepoints": [304], "characters": "\u0130" }, + "Е": { "codepoints": [1045], "characters": "\u0415" }, + "е": { "codepoints": [1077], "characters": "\u0435" }, + "¡": { "codepoints": [161], "characters": "\u00A1" }, + "¡": { "codepoints": [161], "characters": "\u00A1" }, + "⇔": { "codepoints": [8660], "characters": "\u21D4" }, + "𝔦": { "codepoints": [120102], "characters": "\uD835\uDD26" }, + "ℑ": { "codepoints": [8465], "characters": "\u2111" }, + "Ì": { "codepoints": [204], "characters": "\u00CC" }, + "Ì": { "codepoints": [204], "characters": "\u00CC" }, + "ì": { "codepoints": [236], "characters": "\u00EC" }, + "ì": { "codepoints": [236], "characters": "\u00EC" }, + "ⅈ": { "codepoints": [8520], "characters": "\u2148" }, + "⨌": { "codepoints": [10764], "characters": "\u2A0C" }, + "∭": { "codepoints": [8749], "characters": "\u222D" }, + "⧜": { "codepoints": [10716], "characters": "\u29DC" }, + "℩": { "codepoints": [8489], "characters": "\u2129" }, + "IJ": { "codepoints": [306], "characters": "\u0132" }, + "ij": { "codepoints": [307], "characters": "\u0133" }, + "Ī": { "codepoints": [298], "characters": "\u012A" }, + "ī": { "codepoints": [299], "characters": "\u012B" }, + "ℑ": { "codepoints": [8465], "characters": "\u2111" }, + "ⅈ": { "codepoints": [8520], "characters": "\u2148" }, + "ℐ": { "codepoints": [8464], "characters": "\u2110" }, + "ℑ": { "codepoints": [8465], "characters": "\u2111" }, + "ı": { "codepoints": [305], "characters": "\u0131" }, + "ℑ": { "codepoints": [8465], "characters": "\u2111" }, + "⊷": { "codepoints": [8887], "characters": "\u22B7" }, + "Ƶ": { "codepoints": [437], "characters": "\u01B5" }, + "⇒": { "codepoints": [8658], "characters": "\u21D2" }, + "℅": { "codepoints": [8453], "characters": "\u2105" }, + "∈": { "codepoints": [8712], "characters": "\u2208" }, + "∞": { "codepoints": [8734], "characters": "\u221E" }, + "⧝": { "codepoints": [10717], "characters": "\u29DD" }, + "ı": { "codepoints": [305], "characters": "\u0131" }, + "⊺": { "codepoints": [8890], "characters": "\u22BA" }, + "∫": { "codepoints": [8747], "characters": "\u222B" }, + "∬": { "codepoints": [8748], "characters": "\u222C" }, + "ℤ": { "codepoints": [8484], "characters": "\u2124" }, + "∫": { "codepoints": [8747], "characters": "\u222B" }, + "⊺": { "codepoints": [8890], "characters": "\u22BA" }, + "⋂": { "codepoints": [8898], "characters": "\u22C2" }, + "⨗": { "codepoints": [10775], "characters": "\u2A17" }, + "⨼": { "codepoints": [10812], "characters": "\u2A3C" }, + "⁣": { "codepoints": [8291], "characters": "\u2063" }, + "⁢": { "codepoints": [8290], "characters": "\u2062" }, + "Ё": { "codepoints": [1025], "characters": "\u0401" }, + "ё": { "codepoints": [1105], "characters": "\u0451" }, + "Į": { "codepoints": [302], "characters": "\u012E" }, + "į": { "codepoints": [303], "characters": "\u012F" }, + "𝕀": { "codepoints": [120128], "characters": "\uD835\uDD40" }, + "𝕚": { "codepoints": [120154], "characters": "\uD835\uDD5A" }, + "Ι": { "codepoints": [921], "characters": "\u0399" }, + "ι": { "codepoints": [953], "characters": "\u03B9" }, + "⨼": { "codepoints": [10812], "characters": "\u2A3C" }, + "¿": { "codepoints": [191], "characters": "\u00BF" }, + "¿": { "codepoints": [191], "characters": "\u00BF" }, + "𝒾": { "codepoints": [119998], "characters": "\uD835\uDCBE" }, + "ℐ": { "codepoints": [8464], "characters": "\u2110" }, + "∈": { "codepoints": [8712], "characters": "\u2208" }, + "⋵": { "codepoints": [8949], "characters": "\u22F5" }, + "⋹": { "codepoints": [8953], "characters": "\u22F9" }, + "⋴": { "codepoints": [8948], "characters": "\u22F4" }, + "⋳": { "codepoints": [8947], "characters": "\u22F3" }, + "∈": { "codepoints": [8712], "characters": "\u2208" }, + "⁢": { "codepoints": [8290], "characters": "\u2062" }, + "Ĩ": { "codepoints": [296], "characters": "\u0128" }, + "ĩ": { "codepoints": [297], "characters": "\u0129" }, + "І": { "codepoints": [1030], "characters": "\u0406" }, + "і": { "codepoints": [1110], "characters": "\u0456" }, + "Ï": { "codepoints": [207], "characters": "\u00CF" }, + "Ï": { "codepoints": [207], "characters": "\u00CF" }, + "ï": { "codepoints": [239], "characters": "\u00EF" }, + "ï": { "codepoints": [239], "characters": "\u00EF" }, + "Ĵ": { "codepoints": [308], "characters": "\u0134" }, + "ĵ": { "codepoints": [309], "characters": "\u0135" }, + "Й": { "codepoints": [1049], "characters": "\u0419" }, + "й": { "codepoints": [1081], "characters": "\u0439" }, + "𝔍": { "codepoints": [120077], "characters": "\uD835\uDD0D" }, + "𝔧": { "codepoints": [120103], "characters": "\uD835\uDD27" }, + "ȷ": { "codepoints": [567], "characters": "\u0237" }, + "𝕁": { "codepoints": [120129], "characters": "\uD835\uDD41" }, + "𝕛": { "codepoints": [120155], "characters": "\uD835\uDD5B" }, + "𝒥": { "codepoints": [119973], "characters": "\uD835\uDCA5" }, + "𝒿": { "codepoints": [119999], "characters": "\uD835\uDCBF" }, + "Ј": { "codepoints": [1032], "characters": "\u0408" }, + "ј": { "codepoints": [1112], "characters": "\u0458" }, + "Є": { "codepoints": [1028], "characters": "\u0404" }, + "є": { "codepoints": [1108], "characters": "\u0454" }, + "Κ": { "codepoints": [922], "characters": "\u039A" }, + "κ": { "codepoints": [954], "characters": "\u03BA" }, + "ϰ": { "codepoints": [1008], "characters": "\u03F0" }, + "Ķ": { "codepoints": [310], "characters": "\u0136" }, + "ķ": { "codepoints": [311], "characters": "\u0137" }, + "К": { "codepoints": [1050], "characters": "\u041A" }, + "к": { "codepoints": [1082], "characters": "\u043A" }, + "𝔎": { "codepoints": [120078], "characters": "\uD835\uDD0E" }, + "𝔨": { "codepoints": [120104], "characters": "\uD835\uDD28" }, + "ĸ": { "codepoints": [312], "characters": "\u0138" }, + "Х": { "codepoints": [1061], "characters": "\u0425" }, + "х": { "codepoints": [1093], "characters": "\u0445" }, + "Ќ": { "codepoints": [1036], "characters": "\u040C" }, + "ќ": { "codepoints": [1116], "characters": "\u045C" }, + "𝕂": { "codepoints": [120130], "characters": "\uD835\uDD42" }, + "𝕜": { "codepoints": [120156], "characters": "\uD835\uDD5C" }, + "𝒦": { "codepoints": [119974], "characters": "\uD835\uDCA6" }, + "𝓀": { "codepoints": [120000], "characters": "\uD835\uDCC0" }, + "⇚": { "codepoints": [8666], "characters": "\u21DA" }, + "Ĺ": { "codepoints": [313], "characters": "\u0139" }, + "ĺ": { "codepoints": [314], "characters": "\u013A" }, + "⦴": { "codepoints": [10676], "characters": "\u29B4" }, + "ℒ": { "codepoints": [8466], "characters": "\u2112" }, + "Λ": { "codepoints": [923], "characters": "\u039B" }, + "λ": { "codepoints": [955], "characters": "\u03BB" }, + "⟨": { "codepoints": [10216], "characters": "\u27E8" }, + "⟪": { "codepoints": [10218], "characters": "\u27EA" }, + "⦑": { "codepoints": [10641], "characters": "\u2991" }, + "⟨": { "codepoints": [10216], "characters": "\u27E8" }, + "⪅": { "codepoints": [10885], "characters": "\u2A85" }, + "ℒ": { "codepoints": [8466], "characters": "\u2112" }, + "«": { "codepoints": [171], "characters": "\u00AB" }, + "«": { "codepoints": [171], "characters": "\u00AB" }, + "⇤": { "codepoints": [8676], "characters": "\u21E4" }, + "⤟": { "codepoints": [10527], "characters": "\u291F" }, + "←": { "codepoints": [8592], "characters": "\u2190" }, + "↞": { "codepoints": [8606], "characters": "\u219E" }, + "⇐": { "codepoints": [8656], "characters": "\u21D0" }, + "⤝": { "codepoints": [10525], "characters": "\u291D" }, + "↩": { "codepoints": [8617], "characters": "\u21A9" }, + "↫": { "codepoints": [8619], "characters": "\u21AB" }, + "⤹": { "codepoints": [10553], "characters": "\u2939" }, + "⥳": { "codepoints": [10611], "characters": "\u2973" }, + "↢": { "codepoints": [8610], "characters": "\u21A2" }, + "⤙": { "codepoints": [10521], "characters": "\u2919" }, + "⤛": { "codepoints": [10523], "characters": "\u291B" }, + "⪫": { "codepoints": [10923], "characters": "\u2AAB" }, + "⪭": { "codepoints": [10925], "characters": "\u2AAD" }, + "⪭︀": { "codepoints": [10925, 65024], "characters": "\u2AAD\uFE00" }, + "⤌": { "codepoints": [10508], "characters": "\u290C" }, + "⤎": { "codepoints": [10510], "characters": "\u290E" }, + "❲": { "codepoints": [10098], "characters": "\u2772" }, + "{": { "codepoints": [123], "characters": "\u007B" }, + "[": { "codepoints": [91], "characters": "\u005B" }, + "⦋": { "codepoints": [10635], "characters": "\u298B" }, + "⦏": { "codepoints": [10639], "characters": "\u298F" }, + "⦍": { "codepoints": [10637], "characters": "\u298D" }, + "Ľ": { "codepoints": [317], "characters": "\u013D" }, + "ľ": { "codepoints": [318], "characters": "\u013E" }, + "Ļ": { "codepoints": [315], "characters": "\u013B" }, + "ļ": { "codepoints": [316], "characters": "\u013C" }, + "⌈": { "codepoints": [8968], "characters": "\u2308" }, + "{": { "codepoints": [123], "characters": "\u007B" }, + "Л": { "codepoints": [1051], "characters": "\u041B" }, + "л": { "codepoints": [1083], "characters": "\u043B" }, + "⤶": { "codepoints": [10550], "characters": "\u2936" }, + "“": { "codepoints": [8220], "characters": "\u201C" }, + "„": { "codepoints": [8222], "characters": "\u201E" }, + "⥧": { "codepoints": [10599], "characters": "\u2967" }, + "⥋": { "codepoints": [10571], "characters": "\u294B" }, + "↲": { "codepoints": [8626], "characters": "\u21B2" }, + "≤": { "codepoints": [8804], "characters": "\u2264" }, + "≦": { "codepoints": [8806], "characters": "\u2266" }, + "⟨": { "codepoints": [10216], "characters": "\u27E8" }, + "⇤": { "codepoints": [8676], "characters": "\u21E4" }, + "←": { "codepoints": [8592], "characters": "\u2190" }, + "←": { "codepoints": [8592], "characters": "\u2190" }, + "⇐": { "codepoints": [8656], "characters": "\u21D0" }, + "⇆": { "codepoints": [8646], "characters": "\u21C6" }, + "↢": { "codepoints": [8610], "characters": "\u21A2" }, + "⌈": { "codepoints": [8968], "characters": "\u2308" }, + "⟦": { "codepoints": [10214], "characters": "\u27E6" }, + "⥡": { "codepoints": [10593], "characters": "\u2961" }, + "⥙": { "codepoints": [10585], "characters": "\u2959" }, + "⇃": { "codepoints": [8643], "characters": "\u21C3" }, + "⌊": { "codepoints": [8970], "characters": "\u230A" }, + "↽": { "codepoints": [8637], "characters": "\u21BD" }, + "↼": { "codepoints": [8636], "characters": "\u21BC" }, + "⇇": { "codepoints": [8647], "characters": "\u21C7" }, + "↔": { "codepoints": [8596], "characters": "\u2194" }, + "↔": { "codepoints": [8596], "characters": "\u2194" }, + "⇔": { "codepoints": [8660], "characters": "\u21D4" }, + "⇆": { "codepoints": [8646], "characters": "\u21C6" }, + "⇋": { "codepoints": [8651], "characters": "\u21CB" }, + "↭": { "codepoints": [8621], "characters": "\u21AD" }, + "⥎": { "codepoints": [10574], "characters": "\u294E" }, + "↤": { "codepoints": [8612], "characters": "\u21A4" }, + "⊣": { "codepoints": [8867], "characters": "\u22A3" }, + "⥚": { "codepoints": [10586], "characters": "\u295A" }, + "⋋": { "codepoints": [8907], "characters": "\u22CB" }, + "⧏": { "codepoints": [10703], "characters": "\u29CF" }, + "⊲": { "codepoints": [8882], "characters": "\u22B2" }, + "⊴": { "codepoints": [8884], "characters": "\u22B4" }, + "⥑": { "codepoints": [10577], "characters": "\u2951" }, + "⥠": { "codepoints": [10592], "characters": "\u2960" }, + "⥘": { "codepoints": [10584], "characters": "\u2958" }, + "↿": { "codepoints": [8639], "characters": "\u21BF" }, + "⥒": { "codepoints": [10578], "characters": "\u2952" }, + "↼": { "codepoints": [8636], "characters": "\u21BC" }, + "⪋": { "codepoints": [10891], "characters": "\u2A8B" }, + "⋚": { "codepoints": [8922], "characters": "\u22DA" }, + "≤": { "codepoints": [8804], "characters": "\u2264" }, + "≦": { "codepoints": [8806], "characters": "\u2266" }, + "⩽": { "codepoints": [10877], "characters": "\u2A7D" }, + "⪨": { "codepoints": [10920], "characters": "\u2AA8" }, + "⩽": { "codepoints": [10877], "characters": "\u2A7D" }, + "⩿": { "codepoints": [10879], "characters": "\u2A7F" }, + "⪁": { "codepoints": [10881], "characters": "\u2A81" }, + "⪃": { "codepoints": [10883], "characters": "\u2A83" }, + "⋚︀": { "codepoints": [8922, 65024], "characters": "\u22DA\uFE00" }, + "⪓": { "codepoints": [10899], "characters": "\u2A93" }, + "⪅": { "codepoints": [10885], "characters": "\u2A85" }, + "⋖": { "codepoints": [8918], "characters": "\u22D6" }, + "⋚": { "codepoints": [8922], "characters": "\u22DA" }, + "⪋": { "codepoints": [10891], "characters": "\u2A8B" }, + "⋚": { "codepoints": [8922], "characters": "\u22DA" }, + "≦": { "codepoints": [8806], "characters": "\u2266" }, + "≶": { "codepoints": [8822], "characters": "\u2276" }, + "≶": { "codepoints": [8822], "characters": "\u2276" }, + "⪡": { "codepoints": [10913], "characters": "\u2AA1" }, + "≲": { "codepoints": [8818], "characters": "\u2272" }, + "⩽": { "codepoints": [10877], "characters": "\u2A7D" }, + "≲": { "codepoints": [8818], "characters": "\u2272" }, + "⥼": { "codepoints": [10620], "characters": "\u297C" }, + "⌊": { "codepoints": [8970], "characters": "\u230A" }, + "𝔏": { "codepoints": [120079], "characters": "\uD835\uDD0F" }, + "𝔩": { "codepoints": [120105], "characters": "\uD835\uDD29" }, + "≶": { "codepoints": [8822], "characters": "\u2276" }, + "⪑": { "codepoints": [10897], "characters": "\u2A91" }, + "⥢": { "codepoints": [10594], "characters": "\u2962" }, + "↽": { "codepoints": [8637], "characters": "\u21BD" }, + "↼": { "codepoints": [8636], "characters": "\u21BC" }, + "⥪": { "codepoints": [10602], "characters": "\u296A" }, + "▄": { "codepoints": [9604], "characters": "\u2584" }, + "Љ": { "codepoints": [1033], "characters": "\u0409" }, + "љ": { "codepoints": [1113], "characters": "\u0459" }, + "⇇": { "codepoints": [8647], "characters": "\u21C7" }, + "≪": { "codepoints": [8810], "characters": "\u226A" }, + "⋘": { "codepoints": [8920], "characters": "\u22D8" }, + "⌞": { "codepoints": [8990], "characters": "\u231E" }, + "⇚": { "codepoints": [8666], "characters": "\u21DA" }, + "⥫": { "codepoints": [10603], "characters": "\u296B" }, + "◺": { "codepoints": [9722], "characters": "\u25FA" }, + "Ŀ": { "codepoints": [319], "characters": "\u013F" }, + "ŀ": { "codepoints": [320], "characters": "\u0140" }, + "⎰": { "codepoints": [9136], "characters": "\u23B0" }, + "⎰": { "codepoints": [9136], "characters": "\u23B0" }, + "⪉": { "codepoints": [10889], "characters": "\u2A89" }, + "⪉": { "codepoints": [10889], "characters": "\u2A89" }, + "⪇": { "codepoints": [10887], "characters": "\u2A87" }, + "≨": { "codepoints": [8808], "characters": "\u2268" }, + "⪇": { "codepoints": [10887], "characters": "\u2A87" }, + "≨": { "codepoints": [8808], "characters": "\u2268" }, + "⋦": { "codepoints": [8934], "characters": "\u22E6" }, + "⟬": { "codepoints": [10220], "characters": "\u27EC" }, + "⇽": { "codepoints": [8701], "characters": "\u21FD" }, + "⟦": { "codepoints": [10214], "characters": "\u27E6" }, + "⟵": { "codepoints": [10229], "characters": "\u27F5" }, + "⟵": { "codepoints": [10229], "characters": "\u27F5" }, + "⟸": { "codepoints": [10232], "characters": "\u27F8" }, + "⟷": { "codepoints": [10231], "characters": "\u27F7" }, + "⟷": { "codepoints": [10231], "characters": "\u27F7" }, + "⟺": { "codepoints": [10234], "characters": "\u27FA" }, + "⟼": { "codepoints": [10236], "characters": "\u27FC" }, + "⟶": { "codepoints": [10230], "characters": "\u27F6" }, + "⟶": { "codepoints": [10230], "characters": "\u27F6" }, + "⟹": { "codepoints": [10233], "characters": "\u27F9" }, + "↫": { "codepoints": [8619], "characters": "\u21AB" }, + "↬": { "codepoints": [8620], "characters": "\u21AC" }, + "⦅": { "codepoints": [10629], "characters": "\u2985" }, + "𝕃": { "codepoints": [120131], "characters": "\uD835\uDD43" }, + "𝕝": { "codepoints": [120157], "characters": "\uD835\uDD5D" }, + "⨭": { "codepoints": [10797], "characters": "\u2A2D" }, + "⨴": { "codepoints": [10804], "characters": "\u2A34" }, + "∗": { "codepoints": [8727], "characters": "\u2217" }, + "_": { "codepoints": [95], "characters": "\u005F" }, + "↙": { "codepoints": [8601], "characters": "\u2199" }, + "↘": { "codepoints": [8600], "characters": "\u2198" }, + "◊": { "codepoints": [9674], "characters": "\u25CA" }, + "◊": { "codepoints": [9674], "characters": "\u25CA" }, + "⧫": { "codepoints": [10731], "characters": "\u29EB" }, + "(": { "codepoints": [40], "characters": "\u0028" }, + "⦓": { "codepoints": [10643], "characters": "\u2993" }, + "⇆": { "codepoints": [8646], "characters": "\u21C6" }, + "⌟": { "codepoints": [8991], "characters": "\u231F" }, + "⇋": { "codepoints": [8651], "characters": "\u21CB" }, + "⥭": { "codepoints": [10605], "characters": "\u296D" }, + "‎": { "codepoints": [8206], "characters": "\u200E" }, + "⊿": { "codepoints": [8895], "characters": "\u22BF" }, + "‹": { "codepoints": [8249], "characters": "\u2039" }, + "𝓁": { "codepoints": [120001], "characters": "\uD835\uDCC1" }, + "ℒ": { "codepoints": [8466], "characters": "\u2112" }, + "↰": { "codepoints": [8624], "characters": "\u21B0" }, + "↰": { "codepoints": [8624], "characters": "\u21B0" }, + "≲": { "codepoints": [8818], "characters": "\u2272" }, + "⪍": { "codepoints": [10893], "characters": "\u2A8D" }, + "⪏": { "codepoints": [10895], "characters": "\u2A8F" }, + "[": { "codepoints": [91], "characters": "\u005B" }, + "‘": { "codepoints": [8216], "characters": "\u2018" }, + "‚": { "codepoints": [8218], "characters": "\u201A" }, + "Ł": { "codepoints": [321], "characters": "\u0141" }, + "ł": { "codepoints": [322], "characters": "\u0142" }, + "⪦": { "codepoints": [10918], "characters": "\u2AA6" }, + "⩹": { "codepoints": [10873], "characters": "\u2A79" }, + "<": { "codepoints": [60], "characters": "\u003C" }, + "<": { "codepoints": [60], "characters": "\u003C" }, + "<": { "codepoints": [60], "characters": "\u003C" }, + "<": { "codepoints": [60], "characters": "\u003C" }, + "≪": { "codepoints": [8810], "characters": "\u226A" }, + "⋖": { "codepoints": [8918], "characters": "\u22D6" }, + "⋋": { "codepoints": [8907], "characters": "\u22CB" }, + "⋉": { "codepoints": [8905], "characters": "\u22C9" }, + "⥶": { "codepoints": [10614], "characters": "\u2976" }, + "⩻": { "codepoints": [10875], "characters": "\u2A7B" }, + "◃": { "codepoints": [9667], "characters": "\u25C3" }, + "⊴": { "codepoints": [8884], "characters": "\u22B4" }, + "◂": { "codepoints": [9666], "characters": "\u25C2" }, + "⦖": { "codepoints": [10646], "characters": "\u2996" }, + "⥊": { "codepoints": [10570], "characters": "\u294A" }, + "⥦": { "codepoints": [10598], "characters": "\u2966" }, + "≨︀": { "codepoints": [8808, 65024], "characters": "\u2268\uFE00" }, + "≨︀": { "codepoints": [8808, 65024], "characters": "\u2268\uFE00" }, + "¯": { "codepoints": [175], "characters": "\u00AF" }, + "¯": { "codepoints": [175], "characters": "\u00AF" }, + "♂": { "codepoints": [9794], "characters": "\u2642" }, + "✠": { "codepoints": [10016], "characters": "\u2720" }, + "✠": { "codepoints": [10016], "characters": "\u2720" }, + "⤅": { "codepoints": [10501], "characters": "\u2905" }, + "↦": { "codepoints": [8614], "characters": "\u21A6" }, + "↦": { "codepoints": [8614], "characters": "\u21A6" }, + "↧": { "codepoints": [8615], "characters": "\u21A7" }, + "↤": { "codepoints": [8612], "characters": "\u21A4" }, + "↥": { "codepoints": [8613], "characters": "\u21A5" }, + "▮": { "codepoints": [9646], "characters": "\u25AE" }, + "⨩": { "codepoints": [10793], "characters": "\u2A29" }, + "М": { "codepoints": [1052], "characters": "\u041C" }, + "м": { "codepoints": [1084], "characters": "\u043C" }, + "—": { "codepoints": [8212], "characters": "\u2014" }, + "∺": { "codepoints": [8762], "characters": "\u223A" }, + "∡": { "codepoints": [8737], "characters": "\u2221" }, + " ": { "codepoints": [8287], "characters": "\u205F" }, + "ℳ": { "codepoints": [8499], "characters": "\u2133" }, + "𝔐": { "codepoints": [120080], "characters": "\uD835\uDD10" }, + "𝔪": { "codepoints": [120106], "characters": "\uD835\uDD2A" }, + "℧": { "codepoints": [8487], "characters": "\u2127" }, + "µ": { "codepoints": [181], "characters": "\u00B5" }, + "µ": { "codepoints": [181], "characters": "\u00B5" }, + "*": { "codepoints": [42], "characters": "\u002A" }, + "⫰": { "codepoints": [10992], "characters": "\u2AF0" }, + "∣": { "codepoints": [8739], "characters": "\u2223" }, + "·": { "codepoints": [183], "characters": "\u00B7" }, + "·": { "codepoints": [183], "characters": "\u00B7" }, + "⊟": { "codepoints": [8863], "characters": "\u229F" }, + "−": { "codepoints": [8722], "characters": "\u2212" }, + "∸": { "codepoints": [8760], "characters": "\u2238" }, + "⨪": { "codepoints": [10794], "characters": "\u2A2A" }, + "∓": { "codepoints": [8723], "characters": "\u2213" }, + "⫛": { "codepoints": [10971], "characters": "\u2ADB" }, + "…": { "codepoints": [8230], "characters": "\u2026" }, + "∓": { "codepoints": [8723], "characters": "\u2213" }, + "⊧": { "codepoints": [8871], "characters": "\u22A7" }, + "𝕄": { "codepoints": [120132], "characters": "\uD835\uDD44" }, + "𝕞": { "codepoints": [120158], "characters": "\uD835\uDD5E" }, + "∓": { "codepoints": [8723], "characters": "\u2213" }, + "𝓂": { "codepoints": [120002], "characters": "\uD835\uDCC2" }, + "ℳ": { "codepoints": [8499], "characters": "\u2133" }, + "∾": { "codepoints": [8766], "characters": "\u223E" }, + "Μ": { "codepoints": [924], "characters": "\u039C" }, + "μ": { "codepoints": [956], "characters": "\u03BC" }, + "⊸": { "codepoints": [8888], "characters": "\u22B8" }, + "⊸": { "codepoints": [8888], "characters": "\u22B8" }, + "∇": { "codepoints": [8711], "characters": "\u2207" }, + "Ń": { "codepoints": [323], "characters": "\u0143" }, + "ń": { "codepoints": [324], "characters": "\u0144" }, + "∠⃒": { "codepoints": [8736, 8402], "characters": "\u2220\u20D2" }, + "≉": { "codepoints": [8777], "characters": "\u2249" }, + "⩰̸": { "codepoints": [10864, 824], "characters": "\u2A70\u0338" }, + "≋̸": { "codepoints": [8779, 824], "characters": "\u224B\u0338" }, + "ʼn": { "codepoints": [329], "characters": "\u0149" }, + "≉": { "codepoints": [8777], "characters": "\u2249" }, + "♮": { "codepoints": [9838], "characters": "\u266E" }, + "ℕ": { "codepoints": [8469], "characters": "\u2115" }, + "♮": { "codepoints": [9838], "characters": "\u266E" }, + " ": { "codepoints": [160], "characters": "\u00A0" }, + " ": { "codepoints": [160], "characters": "\u00A0" }, + "≎̸": { "codepoints": [8782, 824], "characters": "\u224E\u0338" }, + "≏̸": { "codepoints": [8783, 824], "characters": "\u224F\u0338" }, + "⩃": { "codepoints": [10819], "characters": "\u2A43" }, + "Ň": { "codepoints": [327], "characters": "\u0147" }, + "ň": { "codepoints": [328], "characters": "\u0148" }, + "Ņ": { "codepoints": [325], "characters": "\u0145" }, + "ņ": { "codepoints": [326], "characters": "\u0146" }, + "≇": { "codepoints": [8775], "characters": "\u2247" }, + "⩭̸": { "codepoints": [10861, 824], "characters": "\u2A6D\u0338" }, + "⩂": { "codepoints": [10818], "characters": "\u2A42" }, + "Н": { "codepoints": [1053], "characters": "\u041D" }, + "н": { "codepoints": [1085], "characters": "\u043D" }, + "–": { "codepoints": [8211], "characters": "\u2013" }, + "⤤": { "codepoints": [10532], "characters": "\u2924" }, + "↗": { "codepoints": [8599], "characters": "\u2197" }, + "⇗": { "codepoints": [8663], "characters": "\u21D7" }, + "↗": { "codepoints": [8599], "characters": "\u2197" }, + "≠": { "codepoints": [8800], "characters": "\u2260" }, + "≐̸": { "codepoints": [8784, 824], "characters": "\u2250\u0338" }, + "​": { "codepoints": [8203], "characters": "\u200B" }, + "​": { "codepoints": [8203], "characters": "\u200B" }, + "​": { "codepoints": [8203], "characters": "\u200B" }, + "​": { "codepoints": [8203], "characters": "\u200B" }, + "≢": { "codepoints": [8802], "characters": "\u2262" }, + "⤨": { "codepoints": [10536], "characters": "\u2928" }, + "≂̸": { "codepoints": [8770, 824], "characters": "\u2242\u0338" }, + "≫": { "codepoints": [8811], "characters": "\u226B" }, + "≪": { "codepoints": [8810], "characters": "\u226A" }, + " ": { "codepoints": [10], "characters": "\u000A" }, + "∄": { "codepoints": [8708], "characters": "\u2204" }, + "∄": { "codepoints": [8708], "characters": "\u2204" }, + "𝔑": { "codepoints": [120081], "characters": "\uD835\uDD11" }, + "𝔫": { "codepoints": [120107], "characters": "\uD835\uDD2B" }, + "≧̸": { "codepoints": [8807, 824], "characters": "\u2267\u0338" }, + "≱": { "codepoints": [8817], "characters": "\u2271" }, + "≱": { "codepoints": [8817], "characters": "\u2271" }, + "≧̸": { "codepoints": [8807, 824], "characters": "\u2267\u0338" }, + "⩾̸": { "codepoints": [10878, 824], "characters": "\u2A7E\u0338" }, + "⩾̸": { "codepoints": [10878, 824], "characters": "\u2A7E\u0338" }, + "⋙̸": { "codepoints": [8921, 824], "characters": "\u22D9\u0338" }, + "≵": { "codepoints": [8821], "characters": "\u2275" }, + "≫⃒": { "codepoints": [8811, 8402], "characters": "\u226B\u20D2" }, + "≯": { "codepoints": [8815], "characters": "\u226F" }, + "≯": { "codepoints": [8815], "characters": "\u226F" }, + "≫̸": { "codepoints": [8811, 824], "characters": "\u226B\u0338" }, + "↮": { "codepoints": [8622], "characters": "\u21AE" }, + "⇎": { "codepoints": [8654], "characters": "\u21CE" }, + "⫲": { "codepoints": [10994], "characters": "\u2AF2" }, + "∋": { "codepoints": [8715], "characters": "\u220B" }, + "⋼": { "codepoints": [8956], "characters": "\u22FC" }, + "⋺": { "codepoints": [8954], "characters": "\u22FA" }, + "∋": { "codepoints": [8715], "characters": "\u220B" }, + "Њ": { "codepoints": [1034], "characters": "\u040A" }, + "њ": { "codepoints": [1114], "characters": "\u045A" }, + "↚": { "codepoints": [8602], "characters": "\u219A" }, + "⇍": { "codepoints": [8653], "characters": "\u21CD" }, + "‥": { "codepoints": [8229], "characters": "\u2025" }, + "≦̸": { "codepoints": [8806, 824], "characters": "\u2266\u0338" }, + "≰": { "codepoints": [8816], "characters": "\u2270" }, + "↚": { "codepoints": [8602], "characters": "\u219A" }, + "⇍": { "codepoints": [8653], "characters": "\u21CD" }, + "↮": { "codepoints": [8622], "characters": "\u21AE" }, + "⇎": { "codepoints": [8654], "characters": "\u21CE" }, + "≰": { "codepoints": [8816], "characters": "\u2270" }, + "≦̸": { "codepoints": [8806, 824], "characters": "\u2266\u0338" }, + "⩽̸": { "codepoints": [10877, 824], "characters": "\u2A7D\u0338" }, + "⩽̸": { "codepoints": [10877, 824], "characters": "\u2A7D\u0338" }, + "≮": { "codepoints": [8814], "characters": "\u226E" }, + "⋘̸": { "codepoints": [8920, 824], "characters": "\u22D8\u0338" }, + "≴": { "codepoints": [8820], "characters": "\u2274" }, + "≪⃒": { "codepoints": [8810, 8402], "characters": "\u226A\u20D2" }, + "≮": { "codepoints": [8814], "characters": "\u226E" }, + "⋪": { "codepoints": [8938], "characters": "\u22EA" }, + "⋬": { "codepoints": [8940], "characters": "\u22EC" }, + "≪̸": { "codepoints": [8810, 824], "characters": "\u226A\u0338" }, + "∤": { "codepoints": [8740], "characters": "\u2224" }, + "⁠": { "codepoints": [8288], "characters": "\u2060" }, + " ": { "codepoints": [160], "characters": "\u00A0" }, + "𝕟": { "codepoints": [120159], "characters": "\uD835\uDD5F" }, + "ℕ": { "codepoints": [8469], "characters": "\u2115" }, + "⫬": { "codepoints": [10988], "characters": "\u2AEC" }, + "¬": { "codepoints": [172], "characters": "\u00AC" }, + "¬": { "codepoints": [172], "characters": "\u00AC" }, + "≢": { "codepoints": [8802], "characters": "\u2262" }, + "≭": { "codepoints": [8813], "characters": "\u226D" }, + "∦": { "codepoints": [8742], "characters": "\u2226" }, + "∉": { "codepoints": [8713], "characters": "\u2209" }, + "≠": { "codepoints": [8800], "characters": "\u2260" }, + "≂̸": { "codepoints": [8770, 824], "characters": "\u2242\u0338" }, + "∄": { "codepoints": [8708], "characters": "\u2204" }, + "≯": { "codepoints": [8815], "characters": "\u226F" }, + "≱": { "codepoints": [8817], "characters": "\u2271" }, + "≧̸": { "codepoints": [8807, 824], "characters": "\u2267\u0338" }, + "≫̸": { "codepoints": [8811, 824], "characters": "\u226B\u0338" }, + "≹": { "codepoints": [8825], "characters": "\u2279" }, + "⩾̸": { "codepoints": [10878, 824], "characters": "\u2A7E\u0338" }, + "≵": { "codepoints": [8821], "characters": "\u2275" }, + "≎̸": { "codepoints": [8782, 824], "characters": "\u224E\u0338" }, + "≏̸": { "codepoints": [8783, 824], "characters": "\u224F\u0338" }, + "∉": { "codepoints": [8713], "characters": "\u2209" }, + "⋵̸": { "codepoints": [8949, 824], "characters": "\u22F5\u0338" }, + "⋹̸": { "codepoints": [8953, 824], "characters": "\u22F9\u0338" }, + "∉": { "codepoints": [8713], "characters": "\u2209" }, + "⋷": { "codepoints": [8951], "characters": "\u22F7" }, + "⋶": { "codepoints": [8950], "characters": "\u22F6" }, + "⧏̸": { "codepoints": [10703, 824], "characters": "\u29CF\u0338" }, + "⋪": { "codepoints": [8938], "characters": "\u22EA" }, + "⋬": { "codepoints": [8940], "characters": "\u22EC" }, + "≮": { "codepoints": [8814], "characters": "\u226E" }, + "≰": { "codepoints": [8816], "characters": "\u2270" }, + "≸": { "codepoints": [8824], "characters": "\u2278" }, + "≪̸": { "codepoints": [8810, 824], "characters": "\u226A\u0338" }, + "⩽̸": { "codepoints": [10877, 824], "characters": "\u2A7D\u0338" }, + "≴": { "codepoints": [8820], "characters": "\u2274" }, + "⪢̸": { "codepoints": [10914, 824], "characters": "\u2AA2\u0338" }, + "⪡̸": { "codepoints": [10913, 824], "characters": "\u2AA1\u0338" }, + "∌": { "codepoints": [8716], "characters": "\u220C" }, + "∌": { "codepoints": [8716], "characters": "\u220C" }, + "⋾": { "codepoints": [8958], "characters": "\u22FE" }, + "⋽": { "codepoints": [8957], "characters": "\u22FD" }, + "⊀": { "codepoints": [8832], "characters": "\u2280" }, + "⪯̸": { "codepoints": [10927, 824], "characters": "\u2AAF\u0338" }, + "⋠": { "codepoints": [8928], "characters": "\u22E0" }, + "∌": { "codepoints": [8716], "characters": "\u220C" }, + "⧐̸": { "codepoints": [10704, 824], "characters": "\u29D0\u0338" }, + "⋫": { "codepoints": [8939], "characters": "\u22EB" }, + "⋭": { "codepoints": [8941], "characters": "\u22ED" }, + "⊏̸": { "codepoints": [8847, 824], "characters": "\u228F\u0338" }, + "⋢": { "codepoints": [8930], "characters": "\u22E2" }, + "⊐̸": { "codepoints": [8848, 824], "characters": "\u2290\u0338" }, + "⋣": { "codepoints": [8931], "characters": "\u22E3" }, + "⊂⃒": { "codepoints": [8834, 8402], "characters": "\u2282\u20D2" }, + "⊈": { "codepoints": [8840], "characters": "\u2288" }, + "⊁": { "codepoints": [8833], "characters": "\u2281" }, + "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" }, + "⋡": { "codepoints": [8929], "characters": "\u22E1" }, + "≿̸": { "codepoints": [8831, 824], "characters": "\u227F\u0338" }, + "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" }, + "⊉": { "codepoints": [8841], "characters": "\u2289" }, + "≁": { "codepoints": [8769], "characters": "\u2241" }, + "≄": { "codepoints": [8772], "characters": "\u2244" }, + "≇": { "codepoints": [8775], "characters": "\u2247" }, + "≉": { "codepoints": [8777], "characters": "\u2249" }, + "∤": { "codepoints": [8740], "characters": "\u2224" }, + "∦": { "codepoints": [8742], "characters": "\u2226" }, + "∦": { "codepoints": [8742], "characters": "\u2226" }, + "⫽⃥": { "codepoints": [11005, 8421], "characters": "\u2AFD\u20E5" }, + "∂̸": { "codepoints": [8706, 824], "characters": "\u2202\u0338" }, + "⨔": { "codepoints": [10772], "characters": "\u2A14" }, + "⊀": { "codepoints": [8832], "characters": "\u2280" }, + "⋠": { "codepoints": [8928], "characters": "\u22E0" }, + "⊀": { "codepoints": [8832], "characters": "\u2280" }, + "⪯̸": { "codepoints": [10927, 824], "characters": "\u2AAF\u0338" }, + "⪯̸": { "codepoints": [10927, 824], "characters": "\u2AAF\u0338" }, + "⤳̸": { "codepoints": [10547, 824], "characters": "\u2933\u0338" }, + "↛": { "codepoints": [8603], "characters": "\u219B" }, + "⇏": { "codepoints": [8655], "characters": "\u21CF" }, + "↝̸": { "codepoints": [8605, 824], "characters": "\u219D\u0338" }, + "↛": { "codepoints": [8603], "characters": "\u219B" }, + "⇏": { "codepoints": [8655], "characters": "\u21CF" }, + "⋫": { "codepoints": [8939], "characters": "\u22EB" }, + "⋭": { "codepoints": [8941], "characters": "\u22ED" }, + "⊁": { "codepoints": [8833], "characters": "\u2281" }, + "⋡": { "codepoints": [8929], "characters": "\u22E1" }, + "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" }, + "𝒩": { "codepoints": [119977], "characters": "\uD835\uDCA9" }, + "𝓃": { "codepoints": [120003], "characters": "\uD835\uDCC3" }, + "∤": { "codepoints": [8740], "characters": "\u2224" }, + "∦": { "codepoints": [8742], "characters": "\u2226" }, + "≁": { "codepoints": [8769], "characters": "\u2241" }, + "≄": { "codepoints": [8772], "characters": "\u2244" }, + "≄": { "codepoints": [8772], "characters": "\u2244" }, + "∤": { "codepoints": [8740], "characters": "\u2224" }, + "∦": { "codepoints": [8742], "characters": "\u2226" }, + "⋢": { "codepoints": [8930], "characters": "\u22E2" }, + "⋣": { "codepoints": [8931], "characters": "\u22E3" }, + "⊄": { "codepoints": [8836], "characters": "\u2284" }, + "⫅̸": { "codepoints": [10949, 824], "characters": "\u2AC5\u0338" }, + "⊈": { "codepoints": [8840], "characters": "\u2288" }, + "⊂⃒": { "codepoints": [8834, 8402], "characters": "\u2282\u20D2" }, + "⊈": { "codepoints": [8840], "characters": "\u2288" }, + "⫅̸": { "codepoints": [10949, 824], "characters": "\u2AC5\u0338" }, + "⊁": { "codepoints": [8833], "characters": "\u2281" }, + "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" }, + "⊅": { "codepoints": [8837], "characters": "\u2285" }, + "⫆̸": { "codepoints": [10950, 824], "characters": "\u2AC6\u0338" }, + "⊉": { "codepoints": [8841], "characters": "\u2289" }, + "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" }, + "⊉": { "codepoints": [8841], "characters": "\u2289" }, + "⫆̸": { "codepoints": [10950, 824], "characters": "\u2AC6\u0338" }, + "≹": { "codepoints": [8825], "characters": "\u2279" }, + "Ñ": { "codepoints": [209], "characters": "\u00D1" }, + "Ñ": { "codepoints": [209], "characters": "\u00D1" }, + "ñ": { "codepoints": [241], "characters": "\u00F1" }, + "ñ": { "codepoints": [241], "characters": "\u00F1" }, + "≸": { "codepoints": [8824], "characters": "\u2278" }, + "⋪": { "codepoints": [8938], "characters": "\u22EA" }, + "⋬": { "codepoints": [8940], "characters": "\u22EC" }, + "⋫": { "codepoints": [8939], "characters": "\u22EB" }, + "⋭": { "codepoints": [8941], "characters": "\u22ED" }, + "Ν": { "codepoints": [925], "characters": "\u039D" }, + "ν": { "codepoints": [957], "characters": "\u03BD" }, + "#": { "codepoints": [35], "characters": "\u0023" }, + "№": { "codepoints": [8470], "characters": "\u2116" }, + " ": { "codepoints": [8199], "characters": "\u2007" }, + "≍⃒": { "codepoints": [8781, 8402], "characters": "\u224D\u20D2" }, + "⊬": { "codepoints": [8876], "characters": "\u22AC" }, + "⊭": { "codepoints": [8877], "characters": "\u22AD" }, + "⊮": { "codepoints": [8878], "characters": "\u22AE" }, + "⊯": { "codepoints": [8879], "characters": "\u22AF" }, + "≥⃒": { "codepoints": [8805, 8402], "characters": "\u2265\u20D2" }, + ">⃒": { "codepoints": [62, 8402], "characters": "\u003E\u20D2" }, + "⤄": { "codepoints": [10500], "characters": "\u2904" }, + "⧞": { "codepoints": [10718], "characters": "\u29DE" }, + "⤂": { "codepoints": [10498], "characters": "\u2902" }, + "≤⃒": { "codepoints": [8804, 8402], "characters": "\u2264\u20D2" }, + "<⃒": { "codepoints": [60, 8402], "characters": "\u003C\u20D2" }, + "⊴⃒": { "codepoints": [8884, 8402], "characters": "\u22B4\u20D2" }, + "⤃": { "codepoints": [10499], "characters": "\u2903" }, + "⊵⃒": { "codepoints": [8885, 8402], "characters": "\u22B5\u20D2" }, + "∼⃒": { "codepoints": [8764, 8402], "characters": "\u223C\u20D2" }, + "⤣": { "codepoints": [10531], "characters": "\u2923" }, + "↖": { "codepoints": [8598], "characters": "\u2196" }, + "⇖": { "codepoints": [8662], "characters": "\u21D6" }, + "↖": { "codepoints": [8598], "characters": "\u2196" }, + "⤧": { "codepoints": [10535], "characters": "\u2927" }, + "Ó": { "codepoints": [211], "characters": "\u00D3" }, + "Ó": { "codepoints": [211], "characters": "\u00D3" }, + "ó": { "codepoints": [243], "characters": "\u00F3" }, + "ó": { "codepoints": [243], "characters": "\u00F3" }, + "⊛": { "codepoints": [8859], "characters": "\u229B" }, + "Ô": { "codepoints": [212], "characters": "\u00D4" }, + "Ô": { "codepoints": [212], "characters": "\u00D4" }, + "ô": { "codepoints": [244], "characters": "\u00F4" }, + "ô": { "codepoints": [244], "characters": "\u00F4" }, + "⊚": { "codepoints": [8858], "characters": "\u229A" }, + "О": { "codepoints": [1054], "characters": "\u041E" }, + "о": { "codepoints": [1086], "characters": "\u043E" }, + "⊝": { "codepoints": [8861], "characters": "\u229D" }, + "Ő": { "codepoints": [336], "characters": "\u0150" }, + "ő": { "codepoints": [337], "characters": "\u0151" }, + "⨸": { "codepoints": [10808], "characters": "\u2A38" }, + "⊙": { "codepoints": [8857], "characters": "\u2299" }, + "⦼": { "codepoints": [10684], "characters": "\u29BC" }, + "Œ": { "codepoints": [338], "characters": "\u0152" }, + "œ": { "codepoints": [339], "characters": "\u0153" }, + "⦿": { "codepoints": [10687], "characters": "\u29BF" }, + "𝔒": { "codepoints": [120082], "characters": "\uD835\uDD12" }, + "𝔬": { "codepoints": [120108], "characters": "\uD835\uDD2C" }, + "˛": { "codepoints": [731], "characters": "\u02DB" }, + "Ò": { "codepoints": [210], "characters": "\u00D2" }, + "Ò": { "codepoints": [210], "characters": "\u00D2" }, + "ò": { "codepoints": [242], "characters": "\u00F2" }, + "ò": { "codepoints": [242], "characters": "\u00F2" }, + "⧁": { "codepoints": [10689], "characters": "\u29C1" }, + "⦵": { "codepoints": [10677], "characters": "\u29B5" }, + "Ω": { "codepoints": [937], "characters": "\u03A9" }, + "∮": { "codepoints": [8750], "characters": "\u222E" }, + "↺": { "codepoints": [8634], "characters": "\u21BA" }, + "⦾": { "codepoints": [10686], "characters": "\u29BE" }, + "⦻": { "codepoints": [10683], "characters": "\u29BB" }, + "‾": { "codepoints": [8254], "characters": "\u203E" }, + "⧀": { "codepoints": [10688], "characters": "\u29C0" }, + "Ō": { "codepoints": [332], "characters": "\u014C" }, + "ō": { "codepoints": [333], "characters": "\u014D" }, + "Ω": { "codepoints": [937], "characters": "\u03A9" }, + "ω": { "codepoints": [969], "characters": "\u03C9" }, + "Ο": { "codepoints": [927], "characters": "\u039F" }, + "ο": { "codepoints": [959], "characters": "\u03BF" }, + "⦶": { "codepoints": [10678], "characters": "\u29B6" }, + "⊖": { "codepoints": [8854], "characters": "\u2296" }, + "𝕆": { "codepoints": [120134], "characters": "\uD835\uDD46" }, + "𝕠": { "codepoints": [120160], "characters": "\uD835\uDD60" }, + "⦷": { "codepoints": [10679], "characters": "\u29B7" }, + "“": { "codepoints": [8220], "characters": "\u201C" }, + "‘": { "codepoints": [8216], "characters": "\u2018" }, + "⦹": { "codepoints": [10681], "characters": "\u29B9" }, + "⊕": { "codepoints": [8853], "characters": "\u2295" }, + "↻": { "codepoints": [8635], "characters": "\u21BB" }, + "⩔": { "codepoints": [10836], "characters": "\u2A54" }, + "∨": { "codepoints": [8744], "characters": "\u2228" }, + "⩝": { "codepoints": [10845], "characters": "\u2A5D" }, + "ℴ": { "codepoints": [8500], "characters": "\u2134" }, + "ℴ": { "codepoints": [8500], "characters": "\u2134" }, + "ª": { "codepoints": [170], "characters": "\u00AA" }, + "ª": { "codepoints": [170], "characters": "\u00AA" }, + "º": { "codepoints": [186], "characters": "\u00BA" }, + "º": { "codepoints": [186], "characters": "\u00BA" }, + "⊶": { "codepoints": [8886], "characters": "\u22B6" }, + "⩖": { "codepoints": [10838], "characters": "\u2A56" }, + "⩗": { "codepoints": [10839], "characters": "\u2A57" }, + "⩛": { "codepoints": [10843], "characters": "\u2A5B" }, + "Ⓢ": { "codepoints": [9416], "characters": "\u24C8" }, + "𝒪": { "codepoints": [119978], "characters": "\uD835\uDCAA" }, + "ℴ": { "codepoints": [8500], "characters": "\u2134" }, + "Ø": { "codepoints": [216], "characters": "\u00D8" }, + "Ø": { "codepoints": [216], "characters": "\u00D8" }, + "ø": { "codepoints": [248], "characters": "\u00F8" }, + "ø": { "codepoints": [248], "characters": "\u00F8" }, + "⊘": { "codepoints": [8856], "characters": "\u2298" }, + "Õ": { "codepoints": [213], "characters": "\u00D5" }, + "Õ": { "codepoints": [213], "characters": "\u00D5" }, + "õ": { "codepoints": [245], "characters": "\u00F5" }, + "õ": { "codepoints": [245], "characters": "\u00F5" }, + "⨶": { "codepoints": [10806], "characters": "\u2A36" }, + "⨷": { "codepoints": [10807], "characters": "\u2A37" }, + "⊗": { "codepoints": [8855], "characters": "\u2297" }, + "Ö": { "codepoints": [214], "characters": "\u00D6" }, + "Ö": { "codepoints": [214], "characters": "\u00D6" }, + "ö": { "codepoints": [246], "characters": "\u00F6" }, + "ö": { "codepoints": [246], "characters": "\u00F6" }, + "⌽": { "codepoints": [9021], "characters": "\u233D" }, + "‾": { "codepoints": [8254], "characters": "\u203E" }, + "⏞": { "codepoints": [9182], "characters": "\u23DE" }, + "⎴": { "codepoints": [9140], "characters": "\u23B4" }, + "⏜": { "codepoints": [9180], "characters": "\u23DC" }, + "¶": { "codepoints": [182], "characters": "\u00B6" }, + "¶": { "codepoints": [182], "characters": "\u00B6" }, + "∥": { "codepoints": [8741], "characters": "\u2225" }, + "∥": { "codepoints": [8741], "characters": "\u2225" }, + "⫳": { "codepoints": [10995], "characters": "\u2AF3" }, + "⫽": { "codepoints": [11005], "characters": "\u2AFD" }, + "∂": { "codepoints": [8706], "characters": "\u2202" }, + "∂": { "codepoints": [8706], "characters": "\u2202" }, + "П": { "codepoints": [1055], "characters": "\u041F" }, + "п": { "codepoints": [1087], "characters": "\u043F" }, + "%": { "codepoints": [37], "characters": "\u0025" }, + ".": { "codepoints": [46], "characters": "\u002E" }, + "‰": { "codepoints": [8240], "characters": "\u2030" }, + "⊥": { "codepoints": [8869], "characters": "\u22A5" }, + "‱": { "codepoints": [8241], "characters": "\u2031" }, + "𝔓": { "codepoints": [120083], "characters": "\uD835\uDD13" }, + "𝔭": { "codepoints": [120109], "characters": "\uD835\uDD2D" }, + "Φ": { "codepoints": [934], "characters": "\u03A6" }, + "φ": { "codepoints": [966], "characters": "\u03C6" }, + "ϕ": { "codepoints": [981], "characters": "\u03D5" }, + "ℳ": { "codepoints": [8499], "characters": "\u2133" }, + "☎": { "codepoints": [9742], "characters": "\u260E" }, + "Π": { "codepoints": [928], "characters": "\u03A0" }, + "π": { "codepoints": [960], "characters": "\u03C0" }, + "⋔": { "codepoints": [8916], "characters": "\u22D4" }, + "ϖ": { "codepoints": [982], "characters": "\u03D6" }, + "ℏ": { "codepoints": [8463], "characters": "\u210F" }, + "ℎ": { "codepoints": [8462], "characters": "\u210E" }, + "ℏ": { "codepoints": [8463], "characters": "\u210F" }, + "⨣": { "codepoints": [10787], "characters": "\u2A23" }, + "⊞": { "codepoints": [8862], "characters": "\u229E" }, + "⨢": { "codepoints": [10786], "characters": "\u2A22" }, + "+": { "codepoints": [43], "characters": "\u002B" }, + "∔": { "codepoints": [8724], "characters": "\u2214" }, + "⨥": { "codepoints": [10789], "characters": "\u2A25" }, + "⩲": { "codepoints": [10866], "characters": "\u2A72" }, + "±": { "codepoints": [177], "characters": "\u00B1" }, + "±": { "codepoints": [177], "characters": "\u00B1" }, + "±": { "codepoints": [177], "characters": "\u00B1" }, + "⨦": { "codepoints": [10790], "characters": "\u2A26" }, + "⨧": { "codepoints": [10791], "characters": "\u2A27" }, + "±": { "codepoints": [177], "characters": "\u00B1" }, + "ℌ": { "codepoints": [8460], "characters": "\u210C" }, + "⨕": { "codepoints": [10773], "characters": "\u2A15" }, + "𝕡": { "codepoints": [120161], "characters": "\uD835\uDD61" }, + "ℙ": { "codepoints": [8473], "characters": "\u2119" }, + "£": { "codepoints": [163], "characters": "\u00A3" }, + "£": { "codepoints": [163], "characters": "\u00A3" }, + "⪷": { "codepoints": [10935], "characters": "\u2AB7" }, + "⪻": { "codepoints": [10939], "characters": "\u2ABB" }, + "≺": { "codepoints": [8826], "characters": "\u227A" }, + "≼": { "codepoints": [8828], "characters": "\u227C" }, + "⪷": { "codepoints": [10935], "characters": "\u2AB7" }, + "≺": { "codepoints": [8826], "characters": "\u227A" }, + "≼": { "codepoints": [8828], "characters": "\u227C" }, + "≺": { "codepoints": [8826], "characters": "\u227A" }, + "⪯": { "codepoints": [10927], "characters": "\u2AAF" }, + "≼": { "codepoints": [8828], "characters": "\u227C" }, + "≾": { "codepoints": [8830], "characters": "\u227E" }, + "⪯": { "codepoints": [10927], "characters": "\u2AAF" }, + "⪹": { "codepoints": [10937], "characters": "\u2AB9" }, + "⪵": { "codepoints": [10933], "characters": "\u2AB5" }, + "⋨": { "codepoints": [8936], "characters": "\u22E8" }, + "⪯": { "codepoints": [10927], "characters": "\u2AAF" }, + "⪳": { "codepoints": [10931], "characters": "\u2AB3" }, + "≾": { "codepoints": [8830], "characters": "\u227E" }, + "′": { "codepoints": [8242], "characters": "\u2032" }, + "″": { "codepoints": [8243], "characters": "\u2033" }, + "ℙ": { "codepoints": [8473], "characters": "\u2119" }, + "⪹": { "codepoints": [10937], "characters": "\u2AB9" }, + "⪵": { "codepoints": [10933], "characters": "\u2AB5" }, + "⋨": { "codepoints": [8936], "characters": "\u22E8" }, + "∏": { "codepoints": [8719], "characters": "\u220F" }, + "∏": { "codepoints": [8719], "characters": "\u220F" }, + "⌮": { "codepoints": [9006], "characters": "\u232E" }, + "⌒": { "codepoints": [8978], "characters": "\u2312" }, + "⌓": { "codepoints": [8979], "characters": "\u2313" }, + "∝": { "codepoints": [8733], "characters": "\u221D" }, + "∝": { "codepoints": [8733], "characters": "\u221D" }, + "∷": { "codepoints": [8759], "characters": "\u2237" }, + "∝": { "codepoints": [8733], "characters": "\u221D" }, + "≾": { "codepoints": [8830], "characters": "\u227E" }, + "⊰": { "codepoints": [8880], "characters": "\u22B0" }, + "𝒫": { "codepoints": [119979], "characters": "\uD835\uDCAB" }, + "𝓅": { "codepoints": [120005], "characters": "\uD835\uDCC5" }, + "Ψ": { "codepoints": [936], "characters": "\u03A8" }, + "ψ": { "codepoints": [968], "characters": "\u03C8" }, + " ": { "codepoints": [8200], "characters": "\u2008" }, + "𝔔": { "codepoints": [120084], "characters": "\uD835\uDD14" }, + "𝔮": { "codepoints": [120110], "characters": "\uD835\uDD2E" }, + "⨌": { "codepoints": [10764], "characters": "\u2A0C" }, + "𝕢": { "codepoints": [120162], "characters": "\uD835\uDD62" }, + "ℚ": { "codepoints": [8474], "characters": "\u211A" }, + "⁗": { "codepoints": [8279], "characters": "\u2057" }, + "𝒬": { "codepoints": [119980], "characters": "\uD835\uDCAC" }, + "𝓆": { "codepoints": [120006], "characters": "\uD835\uDCC6" }, + "ℍ": { "codepoints": [8461], "characters": "\u210D" }, + "⨖": { "codepoints": [10774], "characters": "\u2A16" }, + "?": { "codepoints": [63], "characters": "\u003F" }, + "≟": { "codepoints": [8799], "characters": "\u225F" }, + """: { "codepoints": [34], "characters": "\u0022" }, + """: { "codepoints": [34], "characters": "\u0022" }, + """: { "codepoints": [34], "characters": "\u0022" }, + """: { "codepoints": [34], "characters": "\u0022" }, + "⇛": { "codepoints": [8667], "characters": "\u21DB" }, + "∽̱": { "codepoints": [8765, 817], "characters": "\u223D\u0331" }, + "Ŕ": { "codepoints": [340], "characters": "\u0154" }, + "ŕ": { "codepoints": [341], "characters": "\u0155" }, + "√": { "codepoints": [8730], "characters": "\u221A" }, + "⦳": { "codepoints": [10675], "characters": "\u29B3" }, + "⟩": { "codepoints": [10217], "characters": "\u27E9" }, + "⟫": { "codepoints": [10219], "characters": "\u27EB" }, + "⦒": { "codepoints": [10642], "characters": "\u2992" }, + "⦥": { "codepoints": [10661], "characters": "\u29A5" }, + "⟩": { "codepoints": [10217], "characters": "\u27E9" }, + "»": { "codepoints": [187], "characters": "\u00BB" }, + "»": { "codepoints": [187], "characters": "\u00BB" }, + "⥵": { "codepoints": [10613], "characters": "\u2975" }, + "⇥": { "codepoints": [8677], "characters": "\u21E5" }, + "⤠": { "codepoints": [10528], "characters": "\u2920" }, + "⤳": { "codepoints": [10547], "characters": "\u2933" }, + "→": { "codepoints": [8594], "characters": "\u2192" }, + "↠": { "codepoints": [8608], "characters": "\u21A0" }, + "⇒": { "codepoints": [8658], "characters": "\u21D2" }, + "⤞": { "codepoints": [10526], "characters": "\u291E" }, + "↪": { "codepoints": [8618], "characters": "\u21AA" }, + "↬": { "codepoints": [8620], "characters": "\u21AC" }, + "⥅": { "codepoints": [10565], "characters": "\u2945" }, + "⥴": { "codepoints": [10612], "characters": "\u2974" }, + "⤖": { "codepoints": [10518], "characters": "\u2916" }, + "↣": { "codepoints": [8611], "characters": "\u21A3" }, + "↝": { "codepoints": [8605], "characters": "\u219D" }, + "⤚": { "codepoints": [10522], "characters": "\u291A" }, + "⤜": { "codepoints": [10524], "characters": "\u291C" }, + "∶": { "codepoints": [8758], "characters": "\u2236" }, + "ℚ": { "codepoints": [8474], "characters": "\u211A" }, + "⤍": { "codepoints": [10509], "characters": "\u290D" }, + "⤏": { "codepoints": [10511], "characters": "\u290F" }, + "⤐": { "codepoints": [10512], "characters": "\u2910" }, + "❳": { "codepoints": [10099], "characters": "\u2773" }, + "}": { "codepoints": [125], "characters": "\u007D" }, + "]": { "codepoints": [93], "characters": "\u005D" }, + "⦌": { "codepoints": [10636], "characters": "\u298C" }, + "⦎": { "codepoints": [10638], "characters": "\u298E" }, + "⦐": { "codepoints": [10640], "characters": "\u2990" }, + "Ř": { "codepoints": [344], "characters": "\u0158" }, + "ř": { "codepoints": [345], "characters": "\u0159" }, + "Ŗ": { "codepoints": [342], "characters": "\u0156" }, + "ŗ": { "codepoints": [343], "characters": "\u0157" }, + "⌉": { "codepoints": [8969], "characters": "\u2309" }, + "}": { "codepoints": [125], "characters": "\u007D" }, + "Р": { "codepoints": [1056], "characters": "\u0420" }, + "р": { "codepoints": [1088], "characters": "\u0440" }, + "⤷": { "codepoints": [10551], "characters": "\u2937" }, + "⥩": { "codepoints": [10601], "characters": "\u2969" }, + "”": { "codepoints": [8221], "characters": "\u201D" }, + "”": { "codepoints": [8221], "characters": "\u201D" }, + "↳": { "codepoints": [8627], "characters": "\u21B3" }, + "ℜ": { "codepoints": [8476], "characters": "\u211C" }, + "ℛ": { "codepoints": [8475], "characters": "\u211B" }, + "ℜ": { "codepoints": [8476], "characters": "\u211C" }, + "ℝ": { "codepoints": [8477], "characters": "\u211D" }, + "ℜ": { "codepoints": [8476], "characters": "\u211C" }, + "▭": { "codepoints": [9645], "characters": "\u25AD" }, + "®": { "codepoints": [174], "characters": "\u00AE" }, + "®": { "codepoints": [174], "characters": "\u00AE" }, + "®": { "codepoints": [174], "characters": "\u00AE" }, + "®": { "codepoints": [174], "characters": "\u00AE" }, + "∋": { "codepoints": [8715], "characters": "\u220B" }, + "⇋": { "codepoints": [8651], "characters": "\u21CB" }, + "⥯": { "codepoints": [10607], "characters": "\u296F" }, + "⥽": { "codepoints": [10621], "characters": "\u297D" }, + "⌋": { "codepoints": [8971], "characters": "\u230B" }, + "𝔯": { "codepoints": [120111], "characters": "\uD835\uDD2F" }, + "ℜ": { "codepoints": [8476], "characters": "\u211C" }, + "⥤": { "codepoints": [10596], "characters": "\u2964" }, + "⇁": { "codepoints": [8641], "characters": "\u21C1" }, + "⇀": { "codepoints": [8640], "characters": "\u21C0" }, + "⥬": { "codepoints": [10604], "characters": "\u296C" }, + "Ρ": { "codepoints": [929], "characters": "\u03A1" }, + "ρ": { "codepoints": [961], "characters": "\u03C1" }, + "ϱ": { "codepoints": [1009], "characters": "\u03F1" }, + "⟩": { "codepoints": [10217], "characters": "\u27E9" }, + "⇥": { "codepoints": [8677], "characters": "\u21E5" }, + "→": { "codepoints": [8594], "characters": "\u2192" }, + "→": { "codepoints": [8594], "characters": "\u2192" }, + "⇒": { "codepoints": [8658], "characters": "\u21D2" }, + "⇄": { "codepoints": [8644], "characters": "\u21C4" }, + "↣": { "codepoints": [8611], "characters": "\u21A3" }, + "⌉": { "codepoints": [8969], "characters": "\u2309" }, + "⟧": { "codepoints": [10215], "characters": "\u27E7" }, + "⥝": { "codepoints": [10589], "characters": "\u295D" }, + "⥕": { "codepoints": [10581], "characters": "\u2955" }, + "⇂": { "codepoints": [8642], "characters": "\u21C2" }, + "⌋": { "codepoints": [8971], "characters": "\u230B" }, + "⇁": { "codepoints": [8641], "characters": "\u21C1" }, + "⇀": { "codepoints": [8640], "characters": "\u21C0" }, + "⇄": { "codepoints": [8644], "characters": "\u21C4" }, + "⇌": { "codepoints": [8652], "characters": "\u21CC" }, + "⇉": { "codepoints": [8649], "characters": "\u21C9" }, + "↝": { "codepoints": [8605], "characters": "\u219D" }, + "↦": { "codepoints": [8614], "characters": "\u21A6" }, + "⊢": { "codepoints": [8866], "characters": "\u22A2" }, + "⥛": { "codepoints": [10587], "characters": "\u295B" }, + "⋌": { "codepoints": [8908], "characters": "\u22CC" }, + "⧐": { "codepoints": [10704], "characters": "\u29D0" }, + "⊳": { "codepoints": [8883], "characters": "\u22B3" }, + "⊵": { "codepoints": [8885], "characters": "\u22B5" }, + "⥏": { "codepoints": [10575], "characters": "\u294F" }, + "⥜": { "codepoints": [10588], "characters": "\u295C" }, + "⥔": { "codepoints": [10580], "characters": "\u2954" }, + "↾": { "codepoints": [8638], "characters": "\u21BE" }, + "⥓": { "codepoints": [10579], "characters": "\u2953" }, + "⇀": { "codepoints": [8640], "characters": "\u21C0" }, + "˚": { "codepoints": [730], "characters": "\u02DA" }, + "≓": { "codepoints": [8787], "characters": "\u2253" }, + "⇄": { "codepoints": [8644], "characters": "\u21C4" }, + "⇌": { "codepoints": [8652], "characters": "\u21CC" }, + "‏": { "codepoints": [8207], "characters": "\u200F" }, + "⎱": { "codepoints": [9137], "characters": "\u23B1" }, + "⎱": { "codepoints": [9137], "characters": "\u23B1" }, + "⫮": { "codepoints": [10990], "characters": "\u2AEE" }, + "⟭": { "codepoints": [10221], "characters": "\u27ED" }, + "⇾": { "codepoints": [8702], "characters": "\u21FE" }, + "⟧": { "codepoints": [10215], "characters": "\u27E7" }, + "⦆": { "codepoints": [10630], "characters": "\u2986" }, + "𝕣": { "codepoints": [120163], "characters": "\uD835\uDD63" }, + "ℝ": { "codepoints": [8477], "characters": "\u211D" }, + "⨮": { "codepoints": [10798], "characters": "\u2A2E" }, + "⨵": { "codepoints": [10805], "characters": "\u2A35" }, + "⥰": { "codepoints": [10608], "characters": "\u2970" }, + ")": { "codepoints": [41], "characters": "\u0029" }, + "⦔": { "codepoints": [10644], "characters": "\u2994" }, + "⨒": { "codepoints": [10770], "characters": "\u2A12" }, + "⇉": { "codepoints": [8649], "characters": "\u21C9" }, + "⇛": { "codepoints": [8667], "characters": "\u21DB" }, + "›": { "codepoints": [8250], "characters": "\u203A" }, + "𝓇": { "codepoints": [120007], "characters": "\uD835\uDCC7" }, + "ℛ": { "codepoints": [8475], "characters": "\u211B" }, + "↱": { "codepoints": [8625], "characters": "\u21B1" }, + "↱": { "codepoints": [8625], "characters": "\u21B1" }, + "]": { "codepoints": [93], "characters": "\u005D" }, + "’": { "codepoints": [8217], "characters": "\u2019" }, + "’": { "codepoints": [8217], "characters": "\u2019" }, + "⋌": { "codepoints": [8908], "characters": "\u22CC" }, + "⋊": { "codepoints": [8906], "characters": "\u22CA" }, + "▹": { "codepoints": [9657], "characters": "\u25B9" }, + "⊵": { "codepoints": [8885], "characters": "\u22B5" }, + "▸": { "codepoints": [9656], "characters": "\u25B8" }, + "⧎": { "codepoints": [10702], "characters": "\u29CE" }, + "⧴": { "codepoints": [10740], "characters": "\u29F4" }, + "⥨": { "codepoints": [10600], "characters": "\u2968" }, + "℞": { "codepoints": [8478], "characters": "\u211E" }, + "Ś": { "codepoints": [346], "characters": "\u015A" }, + "ś": { "codepoints": [347], "characters": "\u015B" }, + "‚": { "codepoints": [8218], "characters": "\u201A" }, + "⪸": { "codepoints": [10936], "characters": "\u2AB8" }, + "Š": { "codepoints": [352], "characters": "\u0160" }, + "š": { "codepoints": [353], "characters": "\u0161" }, + "⪼": { "codepoints": [10940], "characters": "\u2ABC" }, + "≻": { "codepoints": [8827], "characters": "\u227B" }, + "≽": { "codepoints": [8829], "characters": "\u227D" }, + "⪰": { "codepoints": [10928], "characters": "\u2AB0" }, + "⪴": { "codepoints": [10932], "characters": "\u2AB4" }, + "Ş": { "codepoints": [350], "characters": "\u015E" }, + "ş": { "codepoints": [351], "characters": "\u015F" }, + "Ŝ": { "codepoints": [348], "characters": "\u015C" }, + "ŝ": { "codepoints": [349], "characters": "\u015D" }, + "⪺": { "codepoints": [10938], "characters": "\u2ABA" }, + "⪶": { "codepoints": [10934], "characters": "\u2AB6" }, + "⋩": { "codepoints": [8937], "characters": "\u22E9" }, + "⨓": { "codepoints": [10771], "characters": "\u2A13" }, + "≿": { "codepoints": [8831], "characters": "\u227F" }, + "С": { "codepoints": [1057], "characters": "\u0421" }, + "с": { "codepoints": [1089], "characters": "\u0441" }, + "⊡": { "codepoints": [8865], "characters": "\u22A1" }, + "⋅": { "codepoints": [8901], "characters": "\u22C5" }, + "⩦": { "codepoints": [10854], "characters": "\u2A66" }, + "⤥": { "codepoints": [10533], "characters": "\u2925" }, + "↘": { "codepoints": [8600], "characters": "\u2198" }, + "⇘": { "codepoints": [8664], "characters": "\u21D8" }, + "↘": { "codepoints": [8600], "characters": "\u2198" }, + "§": { "codepoints": [167], "characters": "\u00A7" }, + "§": { "codepoints": [167], "characters": "\u00A7" }, + ";": { "codepoints": [59], "characters": "\u003B" }, + "⤩": { "codepoints": [10537], "characters": "\u2929" }, + "∖": { "codepoints": [8726], "characters": "\u2216" }, + "∖": { "codepoints": [8726], "characters": "\u2216" }, + "✶": { "codepoints": [10038], "characters": "\u2736" }, + "𝔖": { "codepoints": [120086], "characters": "\uD835\uDD16" }, + "𝔰": { "codepoints": [120112], "characters": "\uD835\uDD30" }, + "⌢": { "codepoints": [8994], "characters": "\u2322" }, + "♯": { "codepoints": [9839], "characters": "\u266F" }, + "Щ": { "codepoints": [1065], "characters": "\u0429" }, + "щ": { "codepoints": [1097], "characters": "\u0449" }, + "Ш": { "codepoints": [1064], "characters": "\u0428" }, + "ш": { "codepoints": [1096], "characters": "\u0448" }, + "↓": { "codepoints": [8595], "characters": "\u2193" }, + "←": { "codepoints": [8592], "characters": "\u2190" }, + "∣": { "codepoints": [8739], "characters": "\u2223" }, + "∥": { "codepoints": [8741], "characters": "\u2225" }, + "→": { "codepoints": [8594], "characters": "\u2192" }, + "↑": { "codepoints": [8593], "characters": "\u2191" }, + "­": { "codepoints": [173], "characters": "\u00AD" }, + "­": { "codepoints": [173], "characters": "\u00AD" }, + "Σ": { "codepoints": [931], "characters": "\u03A3" }, + "σ": { "codepoints": [963], "characters": "\u03C3" }, + "ς": { "codepoints": [962], "characters": "\u03C2" }, + "ς": { "codepoints": [962], "characters": "\u03C2" }, + "∼": { "codepoints": [8764], "characters": "\u223C" }, + "⩪": { "codepoints": [10858], "characters": "\u2A6A" }, + "≃": { "codepoints": [8771], "characters": "\u2243" }, + "≃": { "codepoints": [8771], "characters": "\u2243" }, + "⪞": { "codepoints": [10910], "characters": "\u2A9E" }, + "⪠": { "codepoints": [10912], "characters": "\u2AA0" }, + "⪝": { "codepoints": [10909], "characters": "\u2A9D" }, + "⪟": { "codepoints": [10911], "characters": "\u2A9F" }, + "≆": { "codepoints": [8774], "characters": "\u2246" }, + "⨤": { "codepoints": [10788], "characters": "\u2A24" }, + "⥲": { "codepoints": [10610], "characters": "\u2972" }, + "←": { "codepoints": [8592], "characters": "\u2190" }, + "∘": { "codepoints": [8728], "characters": "\u2218" }, + "∖": { "codepoints": [8726], "characters": "\u2216" }, + "⨳": { "codepoints": [10803], "characters": "\u2A33" }, + "⧤": { "codepoints": [10724], "characters": "\u29E4" }, + "∣": { "codepoints": [8739], "characters": "\u2223" }, + "⌣": { "codepoints": [8995], "characters": "\u2323" }, + "⪪": { "codepoints": [10922], "characters": "\u2AAA" }, + "⪬": { "codepoints": [10924], "characters": "\u2AAC" }, + "⪬︀": { "codepoints": [10924, 65024], "characters": "\u2AAC\uFE00" }, + "Ь": { "codepoints": [1068], "characters": "\u042C" }, + "ь": { "codepoints": [1100], "characters": "\u044C" }, + "⌿": { "codepoints": [9023], "characters": "\u233F" }, + "⧄": { "codepoints": [10692], "characters": "\u29C4" }, + "/": { "codepoints": [47], "characters": "\u002F" }, + "𝕊": { "codepoints": [120138], "characters": "\uD835\uDD4A" }, + "𝕤": { "codepoints": [120164], "characters": "\uD835\uDD64" }, + "♠": { "codepoints": [9824], "characters": "\u2660" }, + "♠": { "codepoints": [9824], "characters": "\u2660" }, + "∥": { "codepoints": [8741], "characters": "\u2225" }, + "⊓": { "codepoints": [8851], "characters": "\u2293" }, + "⊓︀": { "codepoints": [8851, 65024], "characters": "\u2293\uFE00" }, + "⊔": { "codepoints": [8852], "characters": "\u2294" }, + "⊔︀": { "codepoints": [8852, 65024], "characters": "\u2294\uFE00" }, + "√": { "codepoints": [8730], "characters": "\u221A" }, + "⊏": { "codepoints": [8847], "characters": "\u228F" }, + "⊑": { "codepoints": [8849], "characters": "\u2291" }, + "⊏": { "codepoints": [8847], "characters": "\u228F" }, + "⊑": { "codepoints": [8849], "characters": "\u2291" }, + "⊐": { "codepoints": [8848], "characters": "\u2290" }, + "⊒": { "codepoints": [8850], "characters": "\u2292" }, + "⊐": { "codepoints": [8848], "characters": "\u2290" }, + "⊒": { "codepoints": [8850], "characters": "\u2292" }, + "□": { "codepoints": [9633], "characters": "\u25A1" }, + "□": { "codepoints": [9633], "characters": "\u25A1" }, + "⊓": { "codepoints": [8851], "characters": "\u2293" }, + "⊏": { "codepoints": [8847], "characters": "\u228F" }, + "⊑": { "codepoints": [8849], "characters": "\u2291" }, + "⊐": { "codepoints": [8848], "characters": "\u2290" }, + "⊒": { "codepoints": [8850], "characters": "\u2292" }, + "⊔": { "codepoints": [8852], "characters": "\u2294" }, + "▪": { "codepoints": [9642], "characters": "\u25AA" }, + "□": { "codepoints": [9633], "characters": "\u25A1" }, + "▪": { "codepoints": [9642], "characters": "\u25AA" }, + "→": { "codepoints": [8594], "characters": "\u2192" }, + "𝒮": { "codepoints": [119982], "characters": "\uD835\uDCAE" }, + "𝓈": { "codepoints": [120008], "characters": "\uD835\uDCC8" }, + "∖": { "codepoints": [8726], "characters": "\u2216" }, + "⌣": { "codepoints": [8995], "characters": "\u2323" }, + "⋆": { "codepoints": [8902], "characters": "\u22C6" }, + "⋆": { "codepoints": [8902], "characters": "\u22C6" }, + "☆": { "codepoints": [9734], "characters": "\u2606" }, + "★": { "codepoints": [9733], "characters": "\u2605" }, + "ϵ": { "codepoints": [1013], "characters": "\u03F5" }, + "ϕ": { "codepoints": [981], "characters": "\u03D5" }, + "¯": { "codepoints": [175], "characters": "\u00AF" }, + "⊂": { "codepoints": [8834], "characters": "\u2282" }, + "⋐": { "codepoints": [8912], "characters": "\u22D0" }, + "⪽": { "codepoints": [10941], "characters": "\u2ABD" }, + "⫅": { "codepoints": [10949], "characters": "\u2AC5" }, + "⊆": { "codepoints": [8838], "characters": "\u2286" }, + "⫃": { "codepoints": [10947], "characters": "\u2AC3" }, + "⫁": { "codepoints": [10945], "characters": "\u2AC1" }, + "⫋": { "codepoints": [10955], "characters": "\u2ACB" }, + "⊊": { "codepoints": [8842], "characters": "\u228A" }, + "⪿": { "codepoints": [10943], "characters": "\u2ABF" }, + "⥹": { "codepoints": [10617], "characters": "\u2979" }, + "⊂": { "codepoints": [8834], "characters": "\u2282" }, + "⋐": { "codepoints": [8912], "characters": "\u22D0" }, + "⊆": { "codepoints": [8838], "characters": "\u2286" }, + "⫅": { "codepoints": [10949], "characters": "\u2AC5" }, + "⊆": { "codepoints": [8838], "characters": "\u2286" }, + "⊊": { "codepoints": [8842], "characters": "\u228A" }, + "⫋": { "codepoints": [10955], "characters": "\u2ACB" }, + "⫇": { "codepoints": [10951], "characters": "\u2AC7" }, + "⫕": { "codepoints": [10965], "characters": "\u2AD5" }, + "⫓": { "codepoints": [10963], "characters": "\u2AD3" }, + "⪸": { "codepoints": [10936], "characters": "\u2AB8" }, + "≻": { "codepoints": [8827], "characters": "\u227B" }, + "≽": { "codepoints": [8829], "characters": "\u227D" }, + "≻": { "codepoints": [8827], "characters": "\u227B" }, + "⪰": { "codepoints": [10928], "characters": "\u2AB0" }, + "≽": { "codepoints": [8829], "characters": "\u227D" }, + "≿": { "codepoints": [8831], "characters": "\u227F" }, + "⪰": { "codepoints": [10928], "characters": "\u2AB0" }, + "⪺": { "codepoints": [10938], "characters": "\u2ABA" }, + "⪶": { "codepoints": [10934], "characters": "\u2AB6" }, + "⋩": { "codepoints": [8937], "characters": "\u22E9" }, + "≿": { "codepoints": [8831], "characters": "\u227F" }, + "∋": { "codepoints": [8715], "characters": "\u220B" }, + "∑": { "codepoints": [8721], "characters": "\u2211" }, + "∑": { "codepoints": [8721], "characters": "\u2211" }, + "♪": { "codepoints": [9834], "characters": "\u266A" }, + "¹": { "codepoints": [185], "characters": "\u00B9" }, + "¹": { "codepoints": [185], "characters": "\u00B9" }, + "²": { "codepoints": [178], "characters": "\u00B2" }, + "²": { "codepoints": [178], "characters": "\u00B2" }, + "³": { "codepoints": [179], "characters": "\u00B3" }, + "³": { "codepoints": [179], "characters": "\u00B3" }, + "⊃": { "codepoints": [8835], "characters": "\u2283" }, + "⋑": { "codepoints": [8913], "characters": "\u22D1" }, + "⪾": { "codepoints": [10942], "characters": "\u2ABE" }, + "⫘": { "codepoints": [10968], "characters": "\u2AD8" }, + "⫆": { "codepoints": [10950], "characters": "\u2AC6" }, + "⊇": { "codepoints": [8839], "characters": "\u2287" }, + "⫄": { "codepoints": [10948], "characters": "\u2AC4" }, + "⊃": { "codepoints": [8835], "characters": "\u2283" }, + "⊇": { "codepoints": [8839], "characters": "\u2287" }, + "⟉": { "codepoints": [10185], "characters": "\u27C9" }, + "⫗": { "codepoints": [10967], "characters": "\u2AD7" }, + "⥻": { "codepoints": [10619], "characters": "\u297B" }, + "⫂": { "codepoints": [10946], "characters": "\u2AC2" }, + "⫌": { "codepoints": [10956], "characters": "\u2ACC" }, + "⊋": { "codepoints": [8843], "characters": "\u228B" }, + "⫀": { "codepoints": [10944], "characters": "\u2AC0" }, + "⊃": { "codepoints": [8835], "characters": "\u2283" }, + "⋑": { "codepoints": [8913], "characters": "\u22D1" }, + "⊇": { "codepoints": [8839], "characters": "\u2287" }, + "⫆": { "codepoints": [10950], "characters": "\u2AC6" }, + "⊋": { "codepoints": [8843], "characters": "\u228B" }, + "⫌": { "codepoints": [10956], "characters": "\u2ACC" }, + "⫈": { "codepoints": [10952], "characters": "\u2AC8" }, + "⫔": { "codepoints": [10964], "characters": "\u2AD4" }, + "⫖": { "codepoints": [10966], "characters": "\u2AD6" }, + "⤦": { "codepoints": [10534], "characters": "\u2926" }, + "↙": { "codepoints": [8601], "characters": "\u2199" }, + "⇙": { "codepoints": [8665], "characters": "\u21D9" }, + "↙": { "codepoints": [8601], "characters": "\u2199" }, + "⤪": { "codepoints": [10538], "characters": "\u292A" }, + "ß": { "codepoints": [223], "characters": "\u00DF" }, + "ß": { "codepoints": [223], "characters": "\u00DF" }, + " ": { "codepoints": [9], "characters": "\u0009" }, + "⌖": { "codepoints": [8982], "characters": "\u2316" }, + "Τ": { "codepoints": [932], "characters": "\u03A4" }, + "τ": { "codepoints": [964], "characters": "\u03C4" }, + "⎴": { "codepoints": [9140], "characters": "\u23B4" }, + "Ť": { "codepoints": [356], "characters": "\u0164" }, + "ť": { "codepoints": [357], "characters": "\u0165" }, + "Ţ": { "codepoints": [354], "characters": "\u0162" }, + "ţ": { "codepoints": [355], "characters": "\u0163" }, + "Т": { "codepoints": [1058], "characters": "\u0422" }, + "т": { "codepoints": [1090], "characters": "\u0442" }, + "⃛": { "codepoints": [8411], "characters": "\u20DB" }, + "⌕": { "codepoints": [8981], "characters": "\u2315" }, + "𝔗": { "codepoints": [120087], "characters": "\uD835\uDD17" }, + "𝔱": { "codepoints": [120113], "characters": "\uD835\uDD31" }, + "∴": { "codepoints": [8756], "characters": "\u2234" }, + "∴": { "codepoints": [8756], "characters": "\u2234" }, + "∴": { "codepoints": [8756], "characters": "\u2234" }, + "Θ": { "codepoints": [920], "characters": "\u0398" }, + "θ": { "codepoints": [952], "characters": "\u03B8" }, + "ϑ": { "codepoints": [977], "characters": "\u03D1" }, + "ϑ": { "codepoints": [977], "characters": "\u03D1" }, + "≈": { "codepoints": [8776], "characters": "\u2248" }, + "∼": { "codepoints": [8764], "characters": "\u223C" }, + "  ": { "codepoints": [8287, 8202], "characters": "\u205F\u200A" }, + " ": { "codepoints": [8201], "characters": "\u2009" }, + " ": { "codepoints": [8201], "characters": "\u2009" }, + "≈": { "codepoints": [8776], "characters": "\u2248" }, + "∼": { "codepoints": [8764], "characters": "\u223C" }, + "Þ": { "codepoints": [222], "characters": "\u00DE" }, + "Þ": { "codepoints": [222], "characters": "\u00DE" }, + "þ": { "codepoints": [254], "characters": "\u00FE" }, + "þ": { "codepoints": [254], "characters": "\u00FE" }, + "˜": { "codepoints": [732], "characters": "\u02DC" }, + "∼": { "codepoints": [8764], "characters": "\u223C" }, + "≃": { "codepoints": [8771], "characters": "\u2243" }, + "≅": { "codepoints": [8773], "characters": "\u2245" }, + "≈": { "codepoints": [8776], "characters": "\u2248" }, + "⨱": { "codepoints": [10801], "characters": "\u2A31" }, + "⊠": { "codepoints": [8864], "characters": "\u22A0" }, + "×": { "codepoints": [215], "characters": "\u00D7" }, + "×": { "codepoints": [215], "characters": "\u00D7" }, + "⨰": { "codepoints": [10800], "characters": "\u2A30" }, + "∭": { "codepoints": [8749], "characters": "\u222D" }, + "⤨": { "codepoints": [10536], "characters": "\u2928" }, + "⌶": { "codepoints": [9014], "characters": "\u2336" }, + "⫱": { "codepoints": [10993], "characters": "\u2AF1" }, + "⊤": { "codepoints": [8868], "characters": "\u22A4" }, + "𝕋": { "codepoints": [120139], "characters": "\uD835\uDD4B" }, + "𝕥": { "codepoints": [120165], "characters": "\uD835\uDD65" }, + "⫚": { "codepoints": [10970], "characters": "\u2ADA" }, + "⤩": { "codepoints": [10537], "characters": "\u2929" }, + "‴": { "codepoints": [8244], "characters": "\u2034" }, + "™": { "codepoints": [8482], "characters": "\u2122" }, + "™": { "codepoints": [8482], "characters": "\u2122" }, + "▵": { "codepoints": [9653], "characters": "\u25B5" }, + "▿": { "codepoints": [9663], "characters": "\u25BF" }, + "◃": { "codepoints": [9667], "characters": "\u25C3" }, + "⊴": { "codepoints": [8884], "characters": "\u22B4" }, + "≜": { "codepoints": [8796], "characters": "\u225C" }, + "▹": { "codepoints": [9657], "characters": "\u25B9" }, + "⊵": { "codepoints": [8885], "characters": "\u22B5" }, + "◬": { "codepoints": [9708], "characters": "\u25EC" }, + "≜": { "codepoints": [8796], "characters": "\u225C" }, + "⨺": { "codepoints": [10810], "characters": "\u2A3A" }, + "⃛": { "codepoints": [8411], "characters": "\u20DB" }, + "⨹": { "codepoints": [10809], "characters": "\u2A39" }, + "⧍": { "codepoints": [10701], "characters": "\u29CD" }, + "⨻": { "codepoints": [10811], "characters": "\u2A3B" }, + "⏢": { "codepoints": [9186], "characters": "\u23E2" }, + "𝒯": { "codepoints": [119983], "characters": "\uD835\uDCAF" }, + "𝓉": { "codepoints": [120009], "characters": "\uD835\uDCC9" }, + "Ц": { "codepoints": [1062], "characters": "\u0426" }, + "ц": { "codepoints": [1094], "characters": "\u0446" }, + "Ћ": { "codepoints": [1035], "characters": "\u040B" }, + "ћ": { "codepoints": [1115], "characters": "\u045B" }, + "Ŧ": { "codepoints": [358], "characters": "\u0166" }, + "ŧ": { "codepoints": [359], "characters": "\u0167" }, + "≬": { "codepoints": [8812], "characters": "\u226C" }, + "↞": { "codepoints": [8606], "characters": "\u219E" }, + "↠": { "codepoints": [8608], "characters": "\u21A0" }, + "Ú": { "codepoints": [218], "characters": "\u00DA" }, + "Ú": { "codepoints": [218], "characters": "\u00DA" }, + "ú": { "codepoints": [250], "characters": "\u00FA" }, + "ú": { "codepoints": [250], "characters": "\u00FA" }, + "↑": { "codepoints": [8593], "characters": "\u2191" }, + "↟": { "codepoints": [8607], "characters": "\u219F" }, + "⇑": { "codepoints": [8657], "characters": "\u21D1" }, + "⥉": { "codepoints": [10569], "characters": "\u2949" }, + "Ў": { "codepoints": [1038], "characters": "\u040E" }, + "ў": { "codepoints": [1118], "characters": "\u045E" }, + "Ŭ": { "codepoints": [364], "characters": "\u016C" }, + "ŭ": { "codepoints": [365], "characters": "\u016D" }, + "Û": { "codepoints": [219], "characters": "\u00DB" }, + "Û": { "codepoints": [219], "characters": "\u00DB" }, + "û": { "codepoints": [251], "characters": "\u00FB" }, + "û": { "codepoints": [251], "characters": "\u00FB" }, + "У": { "codepoints": [1059], "characters": "\u0423" }, + "у": { "codepoints": [1091], "characters": "\u0443" }, + "⇅": { "codepoints": [8645], "characters": "\u21C5" }, + "Ű": { "codepoints": [368], "characters": "\u0170" }, + "ű": { "codepoints": [369], "characters": "\u0171" }, + "⥮": { "codepoints": [10606], "characters": "\u296E" }, + "⥾": { "codepoints": [10622], "characters": "\u297E" }, + "𝔘": { "codepoints": [120088], "characters": "\uD835\uDD18" }, + "𝔲": { "codepoints": [120114], "characters": "\uD835\uDD32" }, + "Ù": { "codepoints": [217], "characters": "\u00D9" }, + "Ù": { "codepoints": [217], "characters": "\u00D9" }, + "ù": { "codepoints": [249], "characters": "\u00F9" }, + "ù": { "codepoints": [249], "characters": "\u00F9" }, + "⥣": { "codepoints": [10595], "characters": "\u2963" }, + "↿": { "codepoints": [8639], "characters": "\u21BF" }, + "↾": { "codepoints": [8638], "characters": "\u21BE" }, + "▀": { "codepoints": [9600], "characters": "\u2580" }, + "⌜": { "codepoints": [8988], "characters": "\u231C" }, + "⌜": { "codepoints": [8988], "characters": "\u231C" }, + "⌏": { "codepoints": [8975], "characters": "\u230F" }, + "◸": { "codepoints": [9720], "characters": "\u25F8" }, + "Ū": { "codepoints": [362], "characters": "\u016A" }, + "ū": { "codepoints": [363], "characters": "\u016B" }, + "¨": { "codepoints": [168], "characters": "\u00A8" }, + "¨": { "codepoints": [168], "characters": "\u00A8" }, + "_": { "codepoints": [95], "characters": "\u005F" }, + "⏟": { "codepoints": [9183], "characters": "\u23DF" }, + "⎵": { "codepoints": [9141], "characters": "\u23B5" }, + "⏝": { "codepoints": [9181], "characters": "\u23DD" }, + "⋃": { "codepoints": [8899], "characters": "\u22C3" }, + "⊎": { "codepoints": [8846], "characters": "\u228E" }, + "Ų": { "codepoints": [370], "characters": "\u0172" }, + "ų": { "codepoints": [371], "characters": "\u0173" }, + "𝕌": { "codepoints": [120140], "characters": "\uD835\uDD4C" }, + "𝕦": { "codepoints": [120166], "characters": "\uD835\uDD66" }, + "⤒": { "codepoints": [10514], "characters": "\u2912" }, + "↑": { "codepoints": [8593], "characters": "\u2191" }, + "↑": { "codepoints": [8593], "characters": "\u2191" }, + "⇑": { "codepoints": [8657], "characters": "\u21D1" }, + "⇅": { "codepoints": [8645], "characters": "\u21C5" }, + "↕": { "codepoints": [8597], "characters": "\u2195" }, + "↕": { "codepoints": [8597], "characters": "\u2195" }, + "⇕": { "codepoints": [8661], "characters": "\u21D5" }, + "⥮": { "codepoints": [10606], "characters": "\u296E" }, + "↿": { "codepoints": [8639], "characters": "\u21BF" }, + "↾": { "codepoints": [8638], "characters": "\u21BE" }, + "⊎": { "codepoints": [8846], "characters": "\u228E" }, + "↖": { "codepoints": [8598], "characters": "\u2196" }, + "↗": { "codepoints": [8599], "characters": "\u2197" }, + "υ": { "codepoints": [965], "characters": "\u03C5" }, + "ϒ": { "codepoints": [978], "characters": "\u03D2" }, + "ϒ": { "codepoints": [978], "characters": "\u03D2" }, + "Υ": { "codepoints": [933], "characters": "\u03A5" }, + "υ": { "codepoints": [965], "characters": "\u03C5" }, + "↥": { "codepoints": [8613], "characters": "\u21A5" }, + "⊥": { "codepoints": [8869], "characters": "\u22A5" }, + "⇈": { "codepoints": [8648], "characters": "\u21C8" }, + "⌝": { "codepoints": [8989], "characters": "\u231D" }, + "⌝": { "codepoints": [8989], "characters": "\u231D" }, + "⌎": { "codepoints": [8974], "characters": "\u230E" }, + "Ů": { "codepoints": [366], "characters": "\u016E" }, + "ů": { "codepoints": [367], "characters": "\u016F" }, + "◹": { "codepoints": [9721], "characters": "\u25F9" }, + "𝒰": { "codepoints": [119984], "characters": "\uD835\uDCB0" }, + "𝓊": { "codepoints": [120010], "characters": "\uD835\uDCCA" }, + "⋰": { "codepoints": [8944], "characters": "\u22F0" }, + "Ũ": { "codepoints": [360], "characters": "\u0168" }, + "ũ": { "codepoints": [361], "characters": "\u0169" }, + "▵": { "codepoints": [9653], "characters": "\u25B5" }, + "▴": { "codepoints": [9652], "characters": "\u25B4" }, + "⇈": { "codepoints": [8648], "characters": "\u21C8" }, + "Ü": { "codepoints": [220], "characters": "\u00DC" }, + "Ü": { "codepoints": [220], "characters": "\u00DC" }, + "ü": { "codepoints": [252], "characters": "\u00FC" }, + "ü": { "codepoints": [252], "characters": "\u00FC" }, + "⦧": { "codepoints": [10663], "characters": "\u29A7" }, + "⦜": { "codepoints": [10652], "characters": "\u299C" }, + "ϵ": { "codepoints": [1013], "characters": "\u03F5" }, + "ϰ": { "codepoints": [1008], "characters": "\u03F0" }, + "∅": { "codepoints": [8709], "characters": "\u2205" }, + "ϕ": { "codepoints": [981], "characters": "\u03D5" }, + "ϖ": { "codepoints": [982], "characters": "\u03D6" }, + "∝": { "codepoints": [8733], "characters": "\u221D" }, + "↕": { "codepoints": [8597], "characters": "\u2195" }, + "⇕": { "codepoints": [8661], "characters": "\u21D5" }, + "ϱ": { "codepoints": [1009], "characters": "\u03F1" }, + "ς": { "codepoints": [962], "characters": "\u03C2" }, + "⊊︀": { "codepoints": [8842, 65024], "characters": "\u228A\uFE00" }, + "⫋︀": { "codepoints": [10955, 65024], "characters": "\u2ACB\uFE00" }, + "⊋︀": { "codepoints": [8843, 65024], "characters": "\u228B\uFE00" }, + "⫌︀": { "codepoints": [10956, 65024], "characters": "\u2ACC\uFE00" }, + "ϑ": { "codepoints": [977], "characters": "\u03D1" }, + "⊲": { "codepoints": [8882], "characters": "\u22B2" }, + "⊳": { "codepoints": [8883], "characters": "\u22B3" }, + "⫨": { "codepoints": [10984], "characters": "\u2AE8" }, + "⫫": { "codepoints": [10987], "characters": "\u2AEB" }, + "⫩": { "codepoints": [10985], "characters": "\u2AE9" }, + "В": { "codepoints": [1042], "characters": "\u0412" }, + "в": { "codepoints": [1074], "characters": "\u0432" }, + "⊢": { "codepoints": [8866], "characters": "\u22A2" }, + "⊨": { "codepoints": [8872], "characters": "\u22A8" }, + "⊩": { "codepoints": [8873], "characters": "\u22A9" }, + "⊫": { "codepoints": [8875], "characters": "\u22AB" }, + "⫦": { "codepoints": [10982], "characters": "\u2AE6" }, + "⊻": { "codepoints": [8891], "characters": "\u22BB" }, + "∨": { "codepoints": [8744], "characters": "\u2228" }, + "⋁": { "codepoints": [8897], "characters": "\u22C1" }, + "≚": { "codepoints": [8794], "characters": "\u225A" }, + "⋮": { "codepoints": [8942], "characters": "\u22EE" }, + "|": { "codepoints": [124], "characters": "\u007C" }, + "‖": { "codepoints": [8214], "characters": "\u2016" }, + "|": { "codepoints": [124], "characters": "\u007C" }, + "‖": { "codepoints": [8214], "characters": "\u2016" }, + "∣": { "codepoints": [8739], "characters": "\u2223" }, + "|": { "codepoints": [124], "characters": "\u007C" }, + "❘": { "codepoints": [10072], "characters": "\u2758" }, + "≀": { "codepoints": [8768], "characters": "\u2240" }, + " ": { "codepoints": [8202], "characters": "\u200A" }, + "𝔙": { "codepoints": [120089], "characters": "\uD835\uDD19" }, + "𝔳": { "codepoints": [120115], "characters": "\uD835\uDD33" }, + "⊲": { "codepoints": [8882], "characters": "\u22B2" }, + "⊂⃒": { "codepoints": [8834, 8402], "characters": "\u2282\u20D2" }, + "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" }, + "𝕍": { "codepoints": [120141], "characters": "\uD835\uDD4D" }, + "𝕧": { "codepoints": [120167], "characters": "\uD835\uDD67" }, + "∝": { "codepoints": [8733], "characters": "\u221D" }, + "⊳": { "codepoints": [8883], "characters": "\u22B3" }, + "𝒱": { "codepoints": [119985], "characters": "\uD835\uDCB1" }, + "𝓋": { "codepoints": [120011], "characters": "\uD835\uDCCB" }, + "⫋︀": { "codepoints": [10955, 65024], "characters": "\u2ACB\uFE00" }, + "⊊︀": { "codepoints": [8842, 65024], "characters": "\u228A\uFE00" }, + "⫌︀": { "codepoints": [10956, 65024], "characters": "\u2ACC\uFE00" }, + "⊋︀": { "codepoints": [8843, 65024], "characters": "\u228B\uFE00" }, + "⊪": { "codepoints": [8874], "characters": "\u22AA" }, + "⦚": { "codepoints": [10650], "characters": "\u299A" }, + "Ŵ": { "codepoints": [372], "characters": "\u0174" }, + "ŵ": { "codepoints": [373], "characters": "\u0175" }, + "⩟": { "codepoints": [10847], "characters": "\u2A5F" }, + "∧": { "codepoints": [8743], "characters": "\u2227" }, + "⋀": { "codepoints": [8896], "characters": "\u22C0" }, + "≙": { "codepoints": [8793], "characters": "\u2259" }, + "℘": { "codepoints": [8472], "characters": "\u2118" }, + "𝔚": { "codepoints": [120090], "characters": "\uD835\uDD1A" }, + "𝔴": { "codepoints": [120116], "characters": "\uD835\uDD34" }, + "𝕎": { "codepoints": [120142], "characters": "\uD835\uDD4E" }, + "𝕨": { "codepoints": [120168], "characters": "\uD835\uDD68" }, + "℘": { "codepoints": [8472], "characters": "\u2118" }, + "≀": { "codepoints": [8768], "characters": "\u2240" }, + "≀": { "codepoints": [8768], "characters": "\u2240" }, + "𝒲": { "codepoints": [119986], "characters": "\uD835\uDCB2" }, + "𝓌": { "codepoints": [120012], "characters": "\uD835\uDCCC" }, + "⋂": { "codepoints": [8898], "characters": "\u22C2" }, + "◯": { "codepoints": [9711], "characters": "\u25EF" }, + "⋃": { "codepoints": [8899], "characters": "\u22C3" }, + "▽": { "codepoints": [9661], "characters": "\u25BD" }, + "𝔛": { "codepoints": [120091], "characters": "\uD835\uDD1B" }, + "𝔵": { "codepoints": [120117], "characters": "\uD835\uDD35" }, + "⟷": { "codepoints": [10231], "characters": "\u27F7" }, + "⟺": { "codepoints": [10234], "characters": "\u27FA" }, + "Ξ": { "codepoints": [926], "characters": "\u039E" }, + "ξ": { "codepoints": [958], "characters": "\u03BE" }, + "⟵": { "codepoints": [10229], "characters": "\u27F5" }, + "⟸": { "codepoints": [10232], "characters": "\u27F8" }, + "⟼": { "codepoints": [10236], "characters": "\u27FC" }, + "⋻": { "codepoints": [8955], "characters": "\u22FB" }, + "⨀": { "codepoints": [10752], "characters": "\u2A00" }, + "𝕏": { "codepoints": [120143], "characters": "\uD835\uDD4F" }, + "𝕩": { "codepoints": [120169], "characters": "\uD835\uDD69" }, + "⨁": { "codepoints": [10753], "characters": "\u2A01" }, + "⨂": { "codepoints": [10754], "characters": "\u2A02" }, + "⟶": { "codepoints": [10230], "characters": "\u27F6" }, + "⟹": { "codepoints": [10233], "characters": "\u27F9" }, + "𝒳": { "codepoints": [119987], "characters": "\uD835\uDCB3" }, + "𝓍": { "codepoints": [120013], "characters": "\uD835\uDCCD" }, + "⨆": { "codepoints": [10758], "characters": "\u2A06" }, + "⨄": { "codepoints": [10756], "characters": "\u2A04" }, + "△": { "codepoints": [9651], "characters": "\u25B3" }, + "⋁": { "codepoints": [8897], "characters": "\u22C1" }, + "⋀": { "codepoints": [8896], "characters": "\u22C0" }, + "Ý": { "codepoints": [221], "characters": "\u00DD" }, + "Ý": { "codepoints": [221], "characters": "\u00DD" }, + "ý": { "codepoints": [253], "characters": "\u00FD" }, + "ý": { "codepoints": [253], "characters": "\u00FD" }, + "Я": { "codepoints": [1071], "characters": "\u042F" }, + "я": { "codepoints": [1103], "characters": "\u044F" }, + "Ŷ": { "codepoints": [374], "characters": "\u0176" }, + "ŷ": { "codepoints": [375], "characters": "\u0177" }, + "Ы": { "codepoints": [1067], "characters": "\u042B" }, + "ы": { "codepoints": [1099], "characters": "\u044B" }, + "¥": { "codepoints": [165], "characters": "\u00A5" }, + "¥": { "codepoints": [165], "characters": "\u00A5" }, + "𝔜": { "codepoints": [120092], "characters": "\uD835\uDD1C" }, + "𝔶": { "codepoints": [120118], "characters": "\uD835\uDD36" }, + "Ї": { "codepoints": [1031], "characters": "\u0407" }, + "ї": { "codepoints": [1111], "characters": "\u0457" }, + "𝕐": { "codepoints": [120144], "characters": "\uD835\uDD50" }, + "𝕪": { "codepoints": [120170], "characters": "\uD835\uDD6A" }, + "𝒴": { "codepoints": [119988], "characters": "\uD835\uDCB4" }, + "𝓎": { "codepoints": [120014], "characters": "\uD835\uDCCE" }, + "Ю": { "codepoints": [1070], "characters": "\u042E" }, + "ю": { "codepoints": [1102], "characters": "\u044E" }, + "ÿ": { "codepoints": [255], "characters": "\u00FF" }, + "ÿ": { "codepoints": [255], "characters": "\u00FF" }, + "Ÿ": { "codepoints": [376], "characters": "\u0178" }, + "Ź": { "codepoints": [377], "characters": "\u0179" }, + "ź": { "codepoints": [378], "characters": "\u017A" }, + "Ž": { "codepoints": [381], "characters": "\u017D" }, + "ž": { "codepoints": [382], "characters": "\u017E" }, + "З": { "codepoints": [1047], "characters": "\u0417" }, + "з": { "codepoints": [1079], "characters": "\u0437" }, + "Ż": { "codepoints": [379], "characters": "\u017B" }, + "ż": { "codepoints": [380], "characters": "\u017C" }, + "ℨ": { "codepoints": [8488], "characters": "\u2128" }, + "​": { "codepoints": [8203], "characters": "\u200B" }, + "Ζ": { "codepoints": [918], "characters": "\u0396" }, + "ζ": { "codepoints": [950], "characters": "\u03B6" }, + "𝔷": { "codepoints": [120119], "characters": "\uD835\uDD37" }, + "ℨ": { "codepoints": [8488], "characters": "\u2128" }, + "Ж": { "codepoints": [1046], "characters": "\u0416" }, + "ж": { "codepoints": [1078], "characters": "\u0436" }, + "⇝": { "codepoints": [8669], "characters": "\u21DD" }, + "𝕫": { "codepoints": [120171], "characters": "\uD835\uDD6B" }, + "ℤ": { "codepoints": [8484], "characters": "\u2124" }, + "𝒵": { "codepoints": [119989], "characters": "\uD835\uDCB5" }, + "𝓏": { "codepoints": [120015], "characters": "\uD835\uDCCF" }, + "‍": { "codepoints": [8205], "characters": "\u200D" }, + "‌": { "codepoints": [8204], "characters": "\u200C" } +} diff --git a/test/fixtures/web-platform-tests/common/form-submission.py b/test/fixtures/web-platform-tests/common/form-submission.py new file mode 100644 index 00000000000000..467875453c9dc6 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/form-submission.py @@ -0,0 +1,10 @@ +def main(request, response): + if request.headers.get('Content-Type') == 'application/x-www-form-urlencoded': + result = request.body == 'foo=bara' + elif request.headers.get('Content-Type') == 'text/plain': + result = request.body == 'qux=baz\r\n' + else: + result = request.POST.first('foo') == 'bar' + + return ([("Content-Type", "text/plain")], + "OK" if result else "FAIL") diff --git a/test/fixtures/web-platform-tests/common/get-host-info.sub.js b/test/fixtures/web-platform-tests/common/get-host-info.sub.js new file mode 100644 index 00000000000000..35acad33a8fa42 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/get-host-info.sub.js @@ -0,0 +1,40 @@ +function get_host_info() { + + var HTTP_PORT = '{{ports[http][0]}}'; + var HTTP_PORT2 = '{{ports[http][1]}}'; + var HTTPS_PORT = '{{ports[https][0]}}'; + var ORIGINAL_HOST = '{{host}}'; + var REMOTE_HOST = (ORIGINAL_HOST === 'localhost') ? '127.0.0.1' : ('www1.' + ORIGINAL_HOST); + var OTHER_HOST = '{{domains[www2]}}'; + var NOTSAMESITE_HOST = (ORIGINAL_HOST === 'localhost') ? '127.0.0.1' : ('not-' + ORIGINAL_HOST); + + return { + HTTP_PORT: HTTP_PORT, + HTTP_PORT2: HTTP_PORT2, + HTTPS_PORT: HTTPS_PORT, + ORIGINAL_HOST: ORIGINAL_HOST, + REMOTE_HOST: REMOTE_HOST, + + HTTP_ORIGIN: 'http://' + ORIGINAL_HOST + ':' + HTTP_PORT, + HTTPS_ORIGIN: 'https://' + ORIGINAL_HOST + ':' + HTTPS_PORT, + HTTPS_ORIGIN_WITH_CREDS: 'https://foo:bar@' + ORIGINAL_HOST + ':' + HTTPS_PORT, + HTTP_ORIGIN_WITH_DIFFERENT_PORT: 'http://' + ORIGINAL_HOST + ':' + HTTP_PORT2, + HTTP_REMOTE_ORIGIN: 'http://' + REMOTE_HOST + ':' + HTTP_PORT, + HTTP_NOTSAMESITE_ORIGIN: 'http://' + NOTSAMESITE_HOST + ':' + HTTP_PORT, + HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT: 'http://' + REMOTE_HOST + ':' + HTTP_PORT2, + HTTPS_REMOTE_ORIGIN: 'https://' + REMOTE_HOST + ':' + HTTPS_PORT, + HTTPS_REMOTE_ORIGIN_WITH_CREDS: 'https://foo:bar@' + REMOTE_HOST + ':' + HTTPS_PORT, + UNAUTHENTICATED_ORIGIN: 'http://' + OTHER_HOST + ':' + HTTP_PORT, + AUTHENTICATED_ORIGIN: 'https://' + OTHER_HOST + ':' + HTTPS_PORT + }; +} + +function get_port(loc) { + // When a default port is used, location.port returns the empty string. + // To compare with wptserve `ports` substitution we need a port... + // loc can be Location///URL, but assumes http/https only. + if (loc.port) { + return loc.port; + } + return loc.protocol === 'https:' ? '443' : '80'; +} diff --git a/test/fixtures/web-platform-tests/common/get-host-info.sub.js.headers b/test/fixtures/web-platform-tests/common/get-host-info.sub.js.headers new file mode 100644 index 00000000000000..6805c323df5a97 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/get-host-info.sub.js.headers @@ -0,0 +1 @@ +Content-Type: text/javascript; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/large.py b/test/fixtures/web-platform-tests/common/large.py new file mode 100644 index 00000000000000..0db4e4bbd1cf08 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/large.py @@ -0,0 +1,45 @@ +def main(request, response): + """Code for generating large responses where the actual response data + isn't very important. + + Two request parameters: + size (required): An integer number of bytes (no suffix) or kilobytes + ("kb" suffix) or megabytes ("Mb" suffix). + string (optional): The string to repeat in the response. Defaults to "a". + + Example: + /resources/large.py?size=32Mb&string=ab + """ + if not "size" in request.GET: + 400, "Need an integer bytes parameter" + + bytes_value = request.GET.first("size") + + chunk_size = 1024 + + multipliers = {"kb": 1024, + "Mb": 1024*1024} + + suffix = bytes_value[-2:] + if suffix in multipliers: + multiplier = multipliers[suffix] + bytes_value = bytes_value[:-2] * multiplier + + try: + num_bytes = int(bytes_value) + except ValueError: + return 400, "Bytes must be an integer possibly with a kb or Mb suffix" + + string = str(request.GET.first("string", "a")) + + chunk = string * chunk_size + + def content(): + bytes_sent = 0 + while bytes_sent < num_bytes: + if num_bytes - bytes_sent < len(chunk): + yield chunk[num_bytes - bytes_sent] + else: + yield chunk + bytes_sent += len(chunk) + return [("Content-Type", "text/plain")], content() diff --git a/test/fixtures/web-platform-tests/common/media.js b/test/fixtures/web-platform-tests/common/media.js new file mode 100644 index 00000000000000..7cea1ac9b3cace --- /dev/null +++ b/test/fixtures/web-platform-tests/common/media.js @@ -0,0 +1,46 @@ +// +// Returns the URI of a supported video source based on the user agent +// +function getVideoURI(base) +{ + var extension = '.mp4'; + + var videotag = document.createElement("video"); + + if ( videotag.canPlayType && + videotag.canPlayType('video/ogg; codecs="theora, vorbis"') ) + { + extension = '.ogv'; + } + + return base + extension; +} + +// +// Returns the URI of a supported audio source based on the user agent +// +function getAudioURI(base) +{ + var extension = '.mp3'; + + var audiotag = document.createElement("audio"); + + if ( audiotag.canPlayType && + audiotag.canPlayType('audio/ogg') ) + { + extension = '.oga'; + } + + return base + extension; +} + +function getMediaContentType(url) { + var extension = new URL(url, location).pathname.split(".").pop(); + var map = { + "mp4": "video/mp4", + "ogv": "video/ogg", + "mp3": "audio/mp3", + "oga": "audio/ogg", + }; + return map[extension]; +} diff --git a/test/fixtures/web-platform-tests/common/media.js.headers b/test/fixtures/web-platform-tests/common/media.js.headers new file mode 100644 index 00000000000000..6805c323df5a97 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/media.js.headers @@ -0,0 +1 @@ +Content-Type: text/javascript; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/namespaces.js b/test/fixtures/web-platform-tests/common/namespaces.js new file mode 100644 index 00000000000000..741bdc1d88a133 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/namespaces.js @@ -0,0 +1,4 @@ +var NAMESPACES = { + "svg": "http://www.w3.org/2000/svg", + "xlink": "http://www.w3.org/1999/xlink", +}; diff --git a/test/fixtures/web-platform-tests/common/object-association.js b/test/fixtures/web-platform-tests/common/object-association.js new file mode 100644 index 00000000000000..e1ab07e7108882 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/object-association.js @@ -0,0 +1,66 @@ +"use strict"; + +// For now this only has per-Window tests, but we could expand it to also test per-Document + +window.testIsPerWindow = propertyName => { + test(t => { + const iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + const frame = iframe.contentWindow; + + const before = frame[propertyName]; + assert_true(before !== undefined && before !== null, `window.${propertyName} must be implemented`); + + iframe.remove(); + + const after = frame[propertyName]; + assert_equals(after, before, `window.${propertyName} should not change after iframe.remove()`); + }, `Discarding the browsing context must not change window.${propertyName}`); + + async_test(t => { + const iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + const frame = iframe.contentWindow; + + const before = frame[propertyName]; + assert_true(before !== undefined && before !== null, `window.${propertyName} must be implemented`); + + // Note: cannot use step_func_done for this because it might be called twice, per the below comment. + iframe.onload = t.step_func(() => { + if (frame.location.href === "about:blank") { + // Browsers are not reliable on whether about:blank fires the load event; see + // https://github.com/whatwg/html/issues/490 + return; + } + + const after = frame[propertyName]; + assert_equals(after, before); + t.done(); + }); + + iframe.src = "/common/blank.html"; + }, `Navigating from the initial about:blank must not replace window.${propertyName}`); + + // Note: document.open()'s spec doesn't match most browsers; see https://github.com/whatwg/html/issues/1698. + // However, as explained in https://github.com/whatwg/html/issues/1698#issuecomment-298748641, even an updated spec + // will probably still reset Window-associated properties. + async_test(t => { + const iframe = document.createElement("iframe"); + + iframe.onload = t.step_func_done(() => { + const frame = iframe.contentWindow; + const before = frame[propertyName]; + assert_true(before !== undefined && before !== null, `window.${propertyName} must be implemented`); + + frame.document.open(); + + const after = frame[propertyName]; + assert_not_equals(after, before); + + frame.document.close(); + }); + + iframe.src = "/common/blank.html"; + document.body.appendChild(iframe); + }, `document.open() must replace window.${propertyName}`); +}; diff --git a/test/fixtures/web-platform-tests/common/object-association.js.headers b/test/fixtures/web-platform-tests/common/object-association.js.headers new file mode 100644 index 00000000000000..6805c323df5a97 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/object-association.js.headers @@ -0,0 +1 @@ +Content-Type: text/javascript; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/performance-timeline-utils.js b/test/fixtures/web-platform-tests/common/performance-timeline-utils.js new file mode 100644 index 00000000000000..6845d6cbc689fc --- /dev/null +++ b/test/fixtures/web-platform-tests/common/performance-timeline-utils.js @@ -0,0 +1,44 @@ +var performanceNamespace = window.performance; +var namespace_check = false; +function wp_test(func, msg, properties) +{ + // only run the namespace check once + if (!namespace_check) + { + namespace_check = true; + + if (performanceNamespace === undefined || performanceNamespace == null) + { + // show a single error that window.performance is undefined + test(function() { assert_true(performanceNamespace !== undefined && performanceNamespace != null, "window.performance is defined and not null"); }, "window.performance is defined and not null.", {author:"W3C http://www.w3.org/",help:"http://www.w3.org/TR/navigation-timing/#sec-window.performance-attribute",assert:"The window.performance attribute provides a hosting area for performance related attributes. "}); + } + } + + test(func, msg, properties); +} + +function test_true(value, msg, properties) +{ + wp_test(function () { assert_true(value, msg); }, msg, properties); +} + +function test_equals(value, equals, msg, properties) +{ + wp_test(function () { assert_equals(value, equals, msg); }, msg, properties); +} + +// assert for every entry in `expectedEntries`, there is a matching entry _somewhere_ in `actualEntries` +function test_entries(actualEntries, expectedEntries) { + test_equals(actualEntries.length, expectedEntries.length) + expectedEntries.forEach(function (expectedEntry) { + var foundEntry = actualEntries.find(function (actualEntry) { + return typeof Object.keys(expectedEntry).find(function (key) { + return actualEntry[key] !== expectedEntry[key] + }) === 'undefined' + }) + test_true(!!foundEntry, `Entry ${JSON.stringify(expectedEntry)} could not be found.`) + if (foundEntry) { + assert_object_equals(foundEntry.toJSON(), expectedEntry) + } + }) +} diff --git a/test/fixtures/web-platform-tests/common/performance-timeline-utils.js.headers b/test/fixtures/web-platform-tests/common/performance-timeline-utils.js.headers new file mode 100644 index 00000000000000..6805c323df5a97 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/performance-timeline-utils.js.headers @@ -0,0 +1 @@ +Content-Type: text/javascript; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/redirect-opt-in.py b/test/fixtures/web-platform-tests/common/redirect-opt-in.py new file mode 100644 index 00000000000000..ff5744b181547a --- /dev/null +++ b/test/fixtures/web-platform-tests/common/redirect-opt-in.py @@ -0,0 +1,20 @@ +def main(request, response): + """Simple handler that causes redirection. + + The request should typically have two query parameters: + status - The status to use for the redirection. Defaults to 302. + location - The resource to redirect to. + """ + status = 302 + if "status" in request.GET: + try: + status = int(request.GET.first("status")) + except ValueError: + pass + + response.status = status + + location = request.GET.first("location") + + response.headers.set("Location", location) + response.headers.set("Timing-Allow-Origin", "*") diff --git a/test/fixtures/web-platform-tests/common/redirect.py b/test/fixtures/web-platform-tests/common/redirect.py new file mode 100644 index 00000000000000..3f15effc059e7f --- /dev/null +++ b/test/fixtures/web-platform-tests/common/redirect.py @@ -0,0 +1,19 @@ +def main(request, response): + """Simple handler that causes redirection. + + The request should typically have two query parameters: + status - The status to use for the redirection. Defaults to 302. + location - The resource to redirect to. + """ + status = 302 + if "status" in request.GET: + try: + status = int(request.GET.first("status")) + except ValueError: + pass + + response.status = status + + location = request.GET.first("location") + + response.headers.set("Location", location) diff --git a/test/fixtures/web-platform-tests/common/reftest-wait.js b/test/fixtures/web-platform-tests/common/reftest-wait.js new file mode 100644 index 00000000000000..87816f8345225d --- /dev/null +++ b/test/fixtures/web-platform-tests/common/reftest-wait.js @@ -0,0 +1,9 @@ +function takeScreenshot() { + document.documentElement.classList.remove("reftest-wait"); +} + +function takeScreenshotDelayed(timeout) { + setTimeout(function() { + takeScreenshot(); + }, timeout); +} diff --git a/test/fixtures/web-platform-tests/common/reftest-wait.js.headers b/test/fixtures/web-platform-tests/common/reftest-wait.js.headers new file mode 100644 index 00000000000000..6805c323df5a97 --- /dev/null +++ b/test/fixtures/web-platform-tests/common/reftest-wait.js.headers @@ -0,0 +1 @@ +Content-Type: text/javascript; charset=utf-8 diff --git a/test/fixtures/web-platform-tests/common/stringifiers.js b/test/fixtures/web-platform-tests/common/stringifiers.js new file mode 100644 index 00000000000000..b59ca9c246f75a --- /dev/null +++ b/test/fixtures/web-platform-tests/common/stringifiers.js @@ -0,0 +1,52 @@ +// Tests . +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 + +``` + +### 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 + ""; + 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(/" + + "ResultTest 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
+*
+* +* +* +* +* +* +* +* +*
thisis
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
+*
+* +* +* +* +* +* +* +* +*
thisis
valid
+*

This is
valid.

+*

This is
valid.

+*

This is
valid.

+*

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 }); +} From e0117e7963aaf46d5a83056812ef72ff6c558b6a Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Mon, 20 Aug 2018 09:52:22 -0500 Subject: [PATCH 3/3] streams: add whatwg stream interop --- doc/api/stream.md | 62 ++++++++++++++++++++++++++++++++++++++++ lib/_stream_duplex.js | 8 ++++++ lib/_stream_readable.js | 23 +++++++++++++++ lib/_stream_transform.js | 27 +++++++++++++++++ lib/_stream_writable.js | 27 +++++++++++++++++ tools/doc/type-parser.js | 4 +++ 6 files changed, 151 insertions(+) diff --git a/doc/api/stream.md b/doc/api/stream.md index c42df4ee7c757d..dcca45a1ce7f71 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -341,6 +341,23 @@ reader.pipe(writer); reader.unpipe(writer); ``` +##### writable.acquireStandardStream() + + +> Stability: 1 - Experimental + +* Returns: {WritableStream} to feed this stream. + +```js +const fs = require('fs'); + +const stream = fs.createWriteStream('file').acquireStandardStream(); +const writer = stream.getWriter(); +writer.write('hi!'); +``` + ##### writable.cork() + +> Stability: 1 - Experimental + +* Returns: {ReadableStream} to fully consume the stream. + +```js +const fs = require('fs'); + +const stream = fs.createReadStream('file').acquireStandardStream(); +const reader = stream.getReader(); +``` + ##### readable.destroy([error]) + +> Stability: 1 - Experimental + +* Returns {Object} + * `readable` {ReadableStream} + * `writable` {WritableStream} + +Creates a WHATWG stream pair to represent this duplex stream. + +```js +const stream = getDuplexSomehow(); +const { readable, writable } = stream.acquireStandardStream(); +readable.getReader(); +writable.getWriter(); +``` + #### Class: stream.Transform + +> Stability: 1 - Experimental + +* Returns: {TransformStream} + #### transform.\_flush(callback) * `callback` {Function} A callback function (optionally with an error diff --git a/lib/_stream_duplex.js b/lib/_stream_duplex.js index 7059757dbd44b1..be5ff2df756d67 100644 --- a/lib/_stream_duplex.js +++ b/lib/_stream_duplex.js @@ -31,6 +31,7 @@ module.exports = Duplex; const util = require('util'); const Readable = require('_stream_readable'); const Writable = require('_stream_writable'); +const { emitExperimentalWarning } = require('internal/util'); util.inherits(Duplex, Readable); @@ -66,6 +67,13 @@ function Duplex(options) { } } +Duplex.prototype.acquireStandardStream = function() { + emitExperimentalWarning('Duplex.acquireStandardStream'); + const readable = Readable.prototype.acquireStandardStream.call(this); + const writable = Writable.prototype.acquireStandardStream.call(this); + return { readable, writable }; +}; + Object.defineProperty(Duplex.prototype, 'writableHighWaterMark', { // making it explicit this property is not enumerable // because otherwise some prototype manipulation in diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js index 029faaa6142d96..47e329901b19ab 100644 --- a/lib/_stream_readable.js +++ b/lib/_stream_readable.js @@ -988,6 +988,29 @@ Readable.prototype[Symbol.asyncIterator] = function() { return new ReadableAsyncIterator(this); }; +Readable.prototype.acquireStandardStream = function() { + emitExperimentalWarning('Readable.acquireStandardStream'); + return new ReadableStream({ + start: (controller) => { + this.pause(); + this.on('data', (chunk) => { + controller.enqueue(chunk); + this.pause(); + }); + this.once('end', () => controller.close()); + this.once('error', (e) => controller.error(e)); + }, + pull: () => { + this.resume(); + }, + cancel: () => { + this.destroy(); + }, + }, { + highWaterMark: this.readableHighWaterMark, + }); +}; + Object.defineProperty(Readable.prototype, 'readableHighWaterMark', { // making it explicit this property is not enumerable // because otherwise some prototype manipulation in diff --git a/lib/_stream_transform.js b/lib/_stream_transform.js index 679f79b80dfb35..c13230953d65ed 100644 --- a/lib/_stream_transform.js +++ b/lib/_stream_transform.js @@ -72,6 +72,7 @@ const { } = require('internal/errors').codes; const Duplex = require('_stream_duplex'); const util = require('util'); +const { emitExperimentalWarning } = require('internal/util'); util.inherits(Transform, Duplex); @@ -202,6 +203,32 @@ Transform.prototype._destroy = function(err, cb) { }); }; +Transform.prototype.acquireStandardStream = function() { + emitExperimentalWarning('Transform.acquireStandardStream'); + return new TransformStream({ + start: (controller) => { + this.on('data', (chunk) => { + controller.enqueue(chunk); + }); + this.once('end', () => controller.close()); + this.once('error', (e) => controller.error(e)); + }, + transform: (chunk) => { + return new Promise((resolve) => { + const underHighWaterMark = this.write(chunk); + if (!underHighWaterMark) { + this.once('drain', resolve); + } else { + resolve(); + } + }); + }, + flush: () => { + this.end(); + }, + }); +}; + function done(stream, er, data) { if (er) diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js index 3bad957912b323..aee44bc7e577e1 100644 --- a/lib/_stream_writable.js +++ b/lib/_stream_writable.js @@ -591,6 +591,33 @@ Writable.prototype.end = function(chunk, encoding, cb) { return this; }; +Writable.prototype.acquireStandardStream = function() { + internalUtil.emitExperimentalWarning('Writable.acquireStandardStream'); + return new WritableStream({ + start: (controller) => { + this.once('error', (e) => controller.error(e)); + }, + write: (chunk) => { + return new Promise((resolve) => { + const underHighWaterMark = this.write(chunk); + if (!underHighWaterMark) { + this.once('drain', resolve); + } else { + resolve(); + } + }); + }, + close: (controller) => { + this.end(); + }, + abort: (reason) => { + this.destroy(reason); + }, + }, { + highWaterMark: this.writableHighWaterMark, + }); +}; + Object.defineProperty(Writable.prototype, 'writableLength', { // making it explicit this property is not enumerable // because otherwise some prototype manipulation in 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',