From d3237c0b2c9334234951e58d14bdb2bf3d0190f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Sat, 6 Jun 2020 12:20:54 +0200 Subject: [PATCH] test: update WPT harness --- test/fixtures/wpt/README.md | 14 +- .../console/console-label-conversion.any.js | 2 +- .../wpt/encoding/api-invalid-label.any.js | 2 +- .../encoding/api-replacement-encodings.any.js | 3 +- test/fixtures/wpt/encoding/encodeInto.any.js | 86 +- .../eucjp-decode-cseucpkdfmtjapanese.html | 1 + .../euc-jp/eucjp-decode-errors.html | 1 + .../euc-jp/eucjp-decode-x-euc-jp.html | 1 + .../euc-jp/eucjp-decode.html | 1 + .../iso-2022-jp/iso2022jp-decode-errors.html | 1 + .../shift_jis/sjis-encode-href.html | 1 + .../euc-kr/euckr-encode-form-windows-949.html | 1 + .../big5/big5-encode-form-cn-big5.html | 1 + .../big5/big5-encode-href-errors-han.html | 1 + .../big5/big5-encode-href-errors-hangul.html | 1 + .../wpt/encoding/streams/backpressure.any.js | 12 +- .../encoding/streams/decode-attributes.any.js | 22 +- .../encoding/streams/decode-bad-chunks.any.js | 18 +- .../streams/decode-incomplete-input.any.js | 2 +- .../encoding/streams/decode-non-utf8.any.js | 4 +- .../wpt/encoding/streams/decode-utf8.any.js | 69 +- .../wpt/encoding/textdecoder-copy.any.js | 32 +- .../textdecoder-fatal-single-byte.any.js | 2 +- .../textdecoder-fatal-streaming.any.js | 6 +- .../wpt/encoding/textdecoder-fatal.any.js | 8 +- .../wpt/encoding/textdecoder-ignorebom.any.js | 12 + .../wpt/encoding/textdecoder-streaming.any.js | 38 +- .../textdecoder-utf16-surrogates.any.js | 2 +- .../microtask-queuing/queue-microtask.any.js | 12 +- .../timers/cleartimeout-clearinterval.any.js | 29 + .../timers/negative-setinterval.html | 1 + .../timers/negative-settimeout.html | 1 + .../timers/type-long-setinterval.html | 1 + .../timers/type-long-settimeout.html | 1 + test/fixtures/wpt/interfaces/html.idl | 1 + test/fixtures/wpt/resources/META.yml | 1 - .../fixtures/wpt/resources/check-layout-th.js | 48 +- test/fixtures/wpt/resources/idlharness.js | 498 +- test/fixtures/wpt/resources/readme.md | 22 +- test/fixtures/wpt/resources/sriharness.js | 108 + .../wpt/resources/testdriver-actions.js | 44 +- test/fixtures/wpt/resources/testdriver.js | 270 +- test/fixtures/wpt/resources/testharness.js | 636 ++- .../wpt/resources/webidl2/lib/webidl2.js | 3999 +++++++++++++---- test/fixtures/wpt/url/failure.html | 12 +- test/fixtures/wpt/url/historical.any.js | 2 +- .../wpt/url/resources/setters_tests.json | 11 + .../wpt/url/resources/urltestdata.json | 77 +- test/fixtures/wpt/url/toascii.window.js | 2 +- test/fixtures/wpt/url/url-constructor.html | 2 +- test/fixtures/wpt/url/url-searchparams.any.js | 2 +- .../url/urlsearchparams-constructor.any.js | 6 +- test/fixtures/wpt/versions.json | 14 +- 53 files changed, 4736 insertions(+), 1408 deletions(-) create mode 100644 test/fixtures/wpt/html/webappapis/timers/cleartimeout-clearinterval.any.js diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index db10cdaf87fa86..b915b53099e076 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -10,13 +10,13 @@ See [test/wpt](../../wpt/README.md) for information on how these tests are run. Last update: -- console: https://github.com/web-platform-tests/wpt/tree/9786a4b131/console -- encoding: https://github.com/web-platform-tests/wpt/tree/5059d2c777/encoding -- url: https://github.com/web-platform-tests/wpt/tree/43feb7f612/url -- resources: https://github.com/web-platform-tests/wpt/tree/e1fddfbf80/resources -- interfaces: https://github.com/web-platform-tests/wpt/tree/8ada332aea/interfaces -- html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/0c3bed38df/html/webappapis/microtask-queuing -- html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/ddfe9c089b/html/webappapis/timers +- console: https://github.com/web-platform-tests/wpt/tree/b0daaa6b86/console +- encoding: https://github.com/web-platform-tests/wpt/tree/ab733fd9f5/encoding +- url: https://github.com/web-platform-tests/wpt/tree/3696f2233a/url +- resources: https://github.com/web-platform-tests/wpt/tree/abfd8c9729/resources +- interfaces: https://github.com/web-platform-tests/wpt/tree/3cbf8e150b/interfaces +- html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing +- html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/264f12bc7b/html/webappapis/timers - hr-time: https://github.com/web-platform-tests/wpt/tree/a5d1774ecf/hr-time [Web Platform Tests]: https://github.com/web-platform-tests/wpt diff --git a/test/fixtures/wpt/console/console-label-conversion.any.js b/test/fixtures/wpt/console/console-label-conversion.any.js index 1fb269d4061c61..4d48df3b1d0d00 100644 --- a/test/fixtures/wpt/console/console-label-conversion.any.js +++ b/test/fixtures/wpt/console/console-label-conversion.any.js @@ -18,7 +18,7 @@ for (const method of methods) { }, `console.${method}()'s label gets converted to string via label.toString() when label is an object`); test(() => { - assert_throws({name: 'Error'}, () => { + assert_throws_js(Error, () => { console[method]({ toString() { throw new Error('conversion error'); diff --git a/test/fixtures/wpt/encoding/api-invalid-label.any.js b/test/fixtures/wpt/encoding/api-invalid-label.any.js index 38c0e9a55fd480..2dafb67c07d464 100644 --- a/test/fixtures/wpt/encoding/api-invalid-label.any.js +++ b/test/fixtures/wpt/encoding/api-invalid-label.any.js @@ -19,6 +19,6 @@ setup(function() { tests.forEach(function(input) { test(function() { - assert_throws(new RangeError(), function() { new TextDecoder(input); }); + assert_throws_js(RangeError, function() { new TextDecoder(input); }); }, 'Invalid label ' + format_value(input) + ' should be rejected by TextDecoder.'); }); diff --git a/test/fixtures/wpt/encoding/api-replacement-encodings.any.js b/test/fixtures/wpt/encoding/api-replacement-encodings.any.js index 506ed09bac0183..9031cf6638cad8 100644 --- a/test/fixtures/wpt/encoding/api-replacement-encodings.any.js +++ b/test/fixtures/wpt/encoding/api-replacement-encodings.any.js @@ -7,8 +7,9 @@ encodings_table.forEach(function(section) { }).forEach(function(encoding) { encoding.labels.forEach(function(label) { test(function() { - assert_throws(new RangeError(), function() { new TextDecoder(label); }); + assert_throws_js(RangeError, function() { new TextDecoder(label); }); }, 'Label for "replacement" should be rejected by API: ' + label); }); }); }); + diff --git a/test/fixtures/wpt/encoding/encodeInto.any.js b/test/fixtures/wpt/encoding/encodeInto.any.js index 8b3e743f71b9ce..7e18c812499a52 100644 --- a/test/fixtures/wpt/encoding/encodeInto.any.js +++ b/test/fixtures/wpt/encoding/encodeInto.any.js @@ -74,44 +74,46 @@ "filler": "random" } ].forEach(destinationData => { - test(() => { - // Setup - const bufferLength = testData.destinationLength + destinationData.bufferIncrease, - destinationOffset = destinationData.destinationOffset, - destinationLength = testData.destinationLength, - destinationFiller = destinationData.filler, - encoder = new TextEncoder(), - buffer = new ArrayBuffer(bufferLength), - view = new Uint8Array(buffer, destinationOffset, destinationLength), - fullView = new Uint8Array(buffer), - control = new Array(bufferLength); - let byte = destinationFiller; - for (let i = 0; i < bufferLength; i++) { - if (destinationFiller === "random") { - byte = Math.floor(Math.random() * 256); + ["ArrayBuffer", "SharedArrayBuffer"].forEach(arrayBufferOrSharedArrayBuffer => { + test(() => { + // Setup + const bufferLength = testData.destinationLength + destinationData.bufferIncrease, + destinationOffset = destinationData.destinationOffset, + destinationLength = testData.destinationLength, + destinationFiller = destinationData.filler, + encoder = new TextEncoder(), + buffer = new self[arrayBufferOrSharedArrayBuffer](bufferLength), + view = new Uint8Array(buffer, destinationOffset, destinationLength), + fullView = new Uint8Array(buffer), + control = new Array(bufferLength); + let byte = destinationFiller; + for (let i = 0; i < bufferLength; i++) { + if (destinationFiller === "random") { + byte = Math.floor(Math.random() * 256); + } + control[i] = byte; + fullView[i] = byte; } - control[i] = byte; - fullView[i] = byte; - } - // It's happening - const result = encoder.encodeInto(testData.input, view); + // It's happening + const result = encoder.encodeInto(testData.input, view); - // Basics - assert_equals(view.byteLength, destinationLength); - assert_equals(view.length, destinationLength); + // Basics + assert_equals(view.byteLength, destinationLength); + assert_equals(view.length, destinationLength); - // Remainder - assert_equals(result.read, testData.read); - assert_equals(result.written, testData.written.length); - for (let i = 0; i < bufferLength; i++) { - if (i < destinationOffset || i >= (destinationOffset + testData.written.length)) { - assert_equals(fullView[i], control[i]); - } else { - assert_equals(fullView[i], testData.written[i - destinationOffset]); + // Remainder + assert_equals(result.read, testData.read); + assert_equals(result.written, testData.written.length); + for (let i = 0; i < bufferLength; i++) { + if (i < destinationOffset || i >= (destinationOffset + testData.written.length)) { + assert_equals(fullView[i], control[i]); + } else { + assert_equals(fullView[i], testData.written[i - destinationOffset]); + } } - } - }, "encodeInto() with " + testData.input + " and destination length " + testData.destinationLength + ", offset " + destinationData.destinationOffset + ", filler " + destinationData.filler); + }, "encodeInto() into " + arrayBufferOrSharedArrayBuffer + " with " + testData.input + " and destination length " + testData.destinationLength + ", offset " + destinationData.destinationOffset + ", filler " + destinationData.filler); + }) }); }); @@ -124,14 +126,20 @@ Uint8ClampedArray, Float32Array, Float64Array].forEach(view => { + ["ArrayBuffer", "SharedArrayBuffer"].forEach((arrayBufferOrSharedArrayBuffer) => { + test(() => { + assert_throws_js(TypeError, () => new TextEncoder().encodeInto("", new view(new self[arrayBufferOrSharedArrayBuffer](0)))); + }, "Invalid encodeInto() destination: " + view.name + ", backed by: " + arrayBufferOrSharedArrayBuffer); + }); +}); + +["ArrayBuffer", "SharedArrayBuffer"].forEach((arrayBufferOrSharedArrayBuffer) => { test(() => { - assert_throws(new TypeError(), () => new TextEncoder().encodeInto("", new view(new ArrayBuffer(0)))); - }, "Invalid encodeInto() destination: " + view.name); - }); + assert_throws_js(TypeError, () => new TextEncoder().encodeInto("", new self[arrayBufferOrSharedArrayBuffer](10))); + }, "Invalid encodeInto() destination: " + arrayBufferOrSharedArrayBuffer); +}); + -test(() => { - assert_throws(new TypeError(), () => new TextEncoder().encodeInto("", new ArrayBuffer(10))); -}, "Invalid encodeInto() destination: ArrayBuffer"); test(() => { const buffer = new ArrayBuffer(10), diff --git a/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-cseucpkdfmtjapanese.html b/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-cseucpkdfmtjapanese.html index 4cbef941f50d6e..8448bf48a77b42 100644 --- a/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-cseucpkdfmtjapanese.html +++ b/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-cseucpkdfmtjapanese.html @@ -41,3 +41,4 @@ + diff --git a/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-errors.html b/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-errors.html index 1069587e9182d2..ea1a35f7e15ff1 100644 --- a/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-errors.html +++ b/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-errors.html @@ -129,3 +129,4 @@ + diff --git a/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-x-euc-jp.html b/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-x-euc-jp.html index 35cdbc9932dca4..b66bb2d96d1845 100644 --- a/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-x-euc-jp.html +++ b/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode-x-euc-jp.html @@ -41,3 +41,4 @@ + diff --git a/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode.html b/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode.html index 6b37534ab2e3f3..088985682e1bc4 100644 --- a/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode.html +++ b/test/fixtures/wpt/encoding/legacy-mb-japanese/euc-jp/eucjp-decode.html @@ -41,3 +41,4 @@ + diff --git a/test/fixtures/wpt/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-decode-errors.html b/test/fixtures/wpt/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-decode-errors.html index b0de2c1c7937cb..23ea8481258075 100644 --- a/test/fixtures/wpt/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-decode-errors.html +++ b/test/fixtures/wpt/encoding/legacy-mb-japanese/iso-2022-jp/iso2022jp-decode-errors.html @@ -104,3 +104,4 @@ + diff --git a/test/fixtures/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-encode-href.html b/test/fixtures/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-encode-href.html index 5efdd166b58b57..288e4da4b31b96 100644 --- a/test/fixtures/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-encode-href.html +++ b/test/fixtures/wpt/encoding/legacy-mb-japanese/shift_jis/sjis-encode-href.html @@ -40,3 +40,4 @@ + diff --git a/test/fixtures/wpt/encoding/legacy-mb-korean/euc-kr/euckr-encode-form-windows-949.html b/test/fixtures/wpt/encoding/legacy-mb-korean/euc-kr/euckr-encode-form-windows-949.html index d6e4dbdf8e41dc..aac30680495447 100644 --- a/test/fixtures/wpt/encoding/legacy-mb-korean/euc-kr/euckr-encode-form-windows-949.html +++ b/test/fixtures/wpt/encoding/legacy-mb-korean/euc-kr/euckr-encode-form-windows-949.html @@ -51,3 +51,4 @@ + diff --git a/test/fixtures/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-form-cn-big5.html b/test/fixtures/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-form-cn-big5.html index e108258e9c3c31..4495f1ba7e9934 100644 --- a/test/fixtures/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-form-cn-big5.html +++ b/test/fixtures/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-form-cn-big5.html @@ -47,3 +47,4 @@ + diff --git a/test/fixtures/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-href-errors-han.html b/test/fixtures/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-href-errors-han.html index c0c2755d67d37e..afa99a4cf58bcc 100644 --- a/test/fixtures/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-href-errors-han.html +++ b/test/fixtures/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-href-errors-han.html @@ -47,3 +47,4 @@ + diff --git a/test/fixtures/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-href-errors-hangul.html b/test/fixtures/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-href-errors-hangul.html index c00ac0582b1e75..977d7760e4d385 100644 --- a/test/fixtures/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-href-errors-hangul.html +++ b/test/fixtures/wpt/encoding/legacy-mb-tchinese/big5/big5-encode-href-errors-hangul.html @@ -43,3 +43,4 @@ + diff --git a/test/fixtures/wpt/encoding/streams/backpressure.any.js b/test/fixtures/wpt/encoding/streams/backpressure.any.js index f17e149ed9e5b9..718942fd8f42c9 100644 --- a/test/fixtures/wpt/encoding/streams/backpressure.any.js +++ b/test/fixtures/wpt/encoding/streams/backpressure.any.js @@ -4,11 +4,11 @@ const classes = [ { - constructor: TextDecoderStream, + name: 'TextDecoderStream', input: new Uint8Array([65]) }, { - constructor: TextEncoderStream, + name: 'TextEncoderStream', input: 'A' } ]; @@ -17,7 +17,7 @@ const microtasksRun = () => new Promise(resolve => step_timeout(resolve, 0)); for (const streamClass of classes) { promise_test(async () => { - const stream = new streamClass.constructor(); + const stream = new self[streamClass.name](); const writer = stream.writable.getWriter(); const reader = stream.readable.getReader(); const events = []; @@ -32,10 +32,10 @@ for (const streamClass of classes) { assert_array_equals(events, ['paused', 'read', 'write'], 'write should happen after read'); }, 'write() should not complete until read relieves backpressure for ' + - `${streamClass.constructor.name}`); + `${streamClass.name}`); promise_test(async () => { - const stream = new streamClass.constructor(); + const stream = new self[streamClass.name](); const writer = stream.writable.getWriter(); const reader = stream.readable.getReader(); const events = []; @@ -56,5 +56,5 @@ for (const streamClass of classes) { 'write2'], 'writes should not happen before read2'); }, 'additional writes should wait for backpressure to be relieved for ' + - `class ${streamClass.constructor.name}`); + `class ${streamClass.name}`); } diff --git a/test/fixtures/wpt/encoding/streams/decode-attributes.any.js b/test/fixtures/wpt/encoding/streams/decode-attributes.any.js index 21b70201271a20..6552a91c6e281a 100644 --- a/test/fixtures/wpt/encoding/streams/decode-attributes.any.js +++ b/test/fixtures/wpt/encoding/streams/decode-attributes.any.js @@ -46,26 +46,26 @@ for (const trueValue of [true, 1, {}, [], 'yes']) { } test(() => { - assert_throws(new RangeError(), () => new TextDecoderStream(''), - 'the constructor should throw'); + assert_throws_js(RangeError, () => new TextDecoderStream(''), + 'the constructor should throw'); }, 'constructing with an invalid encoding should throw'); test(() => { - assert_throws(new TypeError(), () => new TextDecoderStream({ + assert_throws_js(TypeError, () => new TextDecoderStream({ toString() { return {}; } }), 'the constructor should throw'); }, 'constructing with a non-stringifiable encoding should throw'); test(() => { - assert_throws(new Error(), - () => new TextDecoderStream('utf-8', { - get fatal() { throw new Error(); } - }), 'the constructor should throw'); + assert_throws_js(Error, + () => new TextDecoderStream('utf-8', { + get fatal() { throw new Error(); } + }), 'the constructor should throw'); }, 'a throwing fatal member should cause the constructor to throw'); test(() => { - assert_throws(new Error(), - () => new TextDecoderStream('utf-8', { - get ignoreBOM() { throw new Error(); } - }), 'the constructor should throw'); + assert_throws_js(Error, + () => new TextDecoderStream('utf-8', { + get ignoreBOM() { throw new Error(); } + }), 'the constructor should throw'); }, 'a throwing ignoreBOM member should cause the constructor to throw'); diff --git a/test/fixtures/wpt/encoding/streams/decode-bad-chunks.any.js b/test/fixtures/wpt/encoding/streams/decode-bad-chunks.any.js index de2ba74c1016a1..911131ae0709db 100644 --- a/test/fixtures/wpt/encoding/streams/decode-bad-chunks.any.js +++ b/test/fixtures/wpt/encoding/streams/decode-bad-chunks.any.js @@ -22,20 +22,6 @@ const badChunks = [ { name: 'array', value: [65] - }, - { - name: 'SharedArrayBuffer', - // Use a getter to postpone construction so that all tests don't fail where - // SharedArrayBuffer is not yet implemented. - get value() { - return new SharedArrayBuffer(); - } - }, - { - name: 'shared Uint8Array', - get value() { - new Uint8Array(new SharedArrayBuffer()) - } } ]; @@ -46,7 +32,7 @@ for (const chunk of badChunks) { const writer = tds.writable.getWriter(); const writePromise = writer.write(chunk.value); const readPromise = reader.read(); - await promise_rejects(t, new TypeError(), writePromise, 'write should reject'); - await promise_rejects(t, new TypeError(), readPromise, 'read should reject'); + await promise_rejects_js(t, TypeError, writePromise, 'write should reject'); + await promise_rejects_js(t, TypeError, readPromise, 'read should reject'); }, `chunk of type ${chunk.name} should error the stream`); } diff --git a/test/fixtures/wpt/encoding/streams/decode-incomplete-input.any.js b/test/fixtures/wpt/encoding/streams/decode-incomplete-input.any.js index 80dd016d4918da..633a2deb8476c0 100644 --- a/test/fixtures/wpt/encoding/streams/decode-incomplete-input.any.js +++ b/test/fixtures/wpt/encoding/streams/decode-incomplete-input.any.js @@ -19,6 +19,6 @@ promise_test(async t => { const output = input.pipeThrough(new TextDecoderStream( 'utf-8', {fatal: true})); const reader = output.getReader(); - await promise_rejects(t, new TypeError(), reader.read(), + await promise_rejects_js(t, TypeError, reader.read(), 'read should reject'); }, 'incomplete input with error mode "fatal" should error the stream'); diff --git a/test/fixtures/wpt/encoding/streams/decode-non-utf8.any.js b/test/fixtures/wpt/encoding/streams/decode-non-utf8.any.js index 7f39cdc04ca76b..6e5b03a1248404 100644 --- a/test/fixtures/wpt/encoding/streams/decode-non-utf8.any.js +++ b/test/fixtures/wpt/encoding/streams/decode-non-utf8.any.js @@ -67,9 +67,9 @@ for (const encoding of encodings) { const writer = stream.writable.getWriter(); const writePromise = writer.write(new Uint8Array(encoding.invalid)); const closePromise = writer.close(); - await promise_rejects(t, new TypeError(), reader.read(), + await promise_rejects_js(t, TypeError, reader.read(), 'readable should be errored'); - await promise_rejects(t, new TypeError(), + await promise_rejects_js(t, TypeError, Promise.all([writePromise, closePromise]), 'writable should be errored'); }, `TextDecoderStream should be able to reject invalid sequences in ` + diff --git a/test/fixtures/wpt/encoding/streams/decode-utf8.any.js b/test/fixtures/wpt/encoding/streams/decode-utf8.any.js index 266899c6c965f7..421b98c2eea537 100644 --- a/test/fixtures/wpt/encoding/streams/decode-utf8.any.js +++ b/test/fixtures/wpt/encoding/streams/decode-utf8.any.js @@ -4,41 +4,48 @@ 'use strict'; -const emptyChunk = new Uint8Array([]); -const inputChunk = new Uint8Array([73, 32, 240, 159, 146, 153, 32, 115, 116, - 114, 101, 97, 109, 115]); -const expectedOutputString = 'I \u{1F499} streams'; +["ArrayBuffer", "SharedArrayBuffer"].forEach((arrayBufferOrSharedArrayBuffer) => { + const inputChunkData = [73, 32, 240, 159, 146, 153, 32, 115, 116, + 114, 101, 97, 109, 115] -promise_test(async () => { - const input = readableStreamFromArray([inputChunk]); - const output = input.pipeThrough(new TextDecoderStream()); - const array = await readableStreamToArray(output); - assert_array_equals(array, [expectedOutputString], - 'the output should be in one chunk'); -}, 'decoding one UTF-8 chunk should give one output string'); + const emptyChunk = new Uint8Array(new self[arrayBufferOrSharedArrayBuffer](0)); + const inputChunk = new Uint8Array(new self[arrayBufferOrSharedArrayBuffer](inputChunkData.length)); -promise_test(async () => { - const input = readableStreamFromArray([emptyChunk]); - const output = input.pipeThrough(new TextDecoderStream()); - const array = await readableStreamToArray(output); - assert_array_equals(array, [], 'no chunks should be output'); -}, 'decoding an empty chunk should give no output chunks'); + inputChunk.set(inputChunkData); -promise_test(async () => { - const input = readableStreamFromArray([emptyChunk, inputChunk]); - const output = input.pipeThrough(new TextDecoderStream()); - const array = await readableStreamToArray(output); - assert_array_equals(array, [expectedOutputString], - 'the output should be in one chunk'); -}, 'an initial empty chunk should be ignored'); + const expectedOutputString = 'I \u{1F499} streams'; -promise_test(async () => { - const input = readableStreamFromArray([inputChunk, emptyChunk]); - const output = input.pipeThrough(new TextDecoderStream()); - const array = await readableStreamToArray(output); - assert_array_equals(array, [expectedOutputString], - 'the output should be in one chunk'); -}, 'a trailing empty chunk should be ignored'); + promise_test(async () => { + const input = readableStreamFromArray([inputChunk]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, [expectedOutputString], + 'the output should be in one chunk'); + }, 'decoding one UTF-8 chunk should give one output string - ' + arrayBufferOrSharedArrayBuffer); + + promise_test(async () => { + const input = readableStreamFromArray([emptyChunk]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, [], 'no chunks should be output'); + }, 'decoding an empty chunk should give no output chunks - ' + arrayBufferOrSharedArrayBuffer); + + promise_test(async () => { + const input = readableStreamFromArray([emptyChunk, inputChunk]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, [expectedOutputString], + 'the output should be in one chunk'); + }, 'an initial empty chunk should be ignored - ' + arrayBufferOrSharedArrayBuffer); + + promise_test(async () => { + const input = readableStreamFromArray([inputChunk, emptyChunk]); + const output = input.pipeThrough(new TextDecoderStream()); + const array = await readableStreamToArray(output); + assert_array_equals(array, [expectedOutputString], + 'the output should be in one chunk'); + }, 'a trailing empty chunk should be ignored- ' + arrayBufferOrSharedArrayBuffer); +}); promise_test(async () => { const buffer = new ArrayBuffer(3); diff --git a/test/fixtures/wpt/encoding/textdecoder-copy.any.js b/test/fixtures/wpt/encoding/textdecoder-copy.any.js index ef8e4fdb3b03eb..6ae65119db687c 100644 --- a/test/fixtures/wpt/encoding/textdecoder-copy.any.js +++ b/test/fixtures/wpt/encoding/textdecoder-copy.any.js @@ -1,15 +1,17 @@ -test(() => { - const buf = new ArrayBuffer(2), - view = new Uint8Array(buf), - buf2 = new ArrayBuffer(2), - view2 = new Uint8Array(buf2), - decoder = new TextDecoder("utf-8") - view[0] = 0xEF - view[1] = 0xBB - view2[0] = 0xBF - view2[1] = 0x40 - assert_equals(decoder.decode(buf, {stream:true}), "") - view[0] = 0x01 - view[1] = 0x02 - assert_equals(decoder.decode(buf2), "@") -}, "Modify buffer after passing it in") +["ArrayBuffer", "SharedArrayBuffer"].forEach(arrayBufferOrSharedArrayBuffer => { + test(() => { + const buf = new self[arrayBufferOrSharedArrayBuffer](2), + view = new Uint8Array(buf), + buf2 = new self[arrayBufferOrSharedArrayBuffer](2), + view2 = new Uint8Array(buf2), + decoder = new TextDecoder("utf-8"); + view[0] = 0xEF; + view[1] = 0xBB; + view2[0] = 0xBF; + view2[1] = 0x40; + assert_equals(decoder.decode(buf, {stream:true}), ""); + view[0] = 0x01; + view[1] = 0x02; + assert_equals(decoder.decode(buf2), "@"); + }, "Modify buffer after passing it in (" + arrayBufferOrSharedArrayBuffer + ")"); +}); diff --git a/test/fixtures/wpt/encoding/textdecoder-fatal-single-byte.any.js b/test/fixtures/wpt/encoding/textdecoder-fatal-single-byte.any.js index d3e9ae9c9a7774..87e3707be4bfee 100644 --- a/test/fixtures/wpt/encoding/textdecoder-fatal-single-byte.any.js +++ b/test/fixtures/wpt/encoding/textdecoder-fatal-single-byte.any.js @@ -37,7 +37,7 @@ singleByteEncodings.forEach(function(t) { for (var i = 0; i < 256; ++i) { if (t.bad.indexOf(i) != -1) { test(function() { - assert_throws(new TypeError(), function() { + assert_throws_js(TypeError, function() { new TextDecoder(t.encoding, {fatal: true}).decode(new Uint8Array([i])); }); }, 'Throw due to fatal flag: ' + t.encoding + ' doesn\'t have a pointer ' + i); diff --git a/test/fixtures/wpt/encoding/textdecoder-fatal-streaming.any.js b/test/fixtures/wpt/encoding/textdecoder-fatal-streaming.any.js index e4fa64dbd7c433..0d58b2e1d7d8a1 100644 --- a/test/fixtures/wpt/encoding/textdecoder-fatal-streaming.any.js +++ b/test/fixtures/wpt/encoding/textdecoder-fatal-streaming.any.js @@ -7,7 +7,7 @@ test(function() { {encoding: 'utf-16be', sequence: [0x00]} ].forEach(function(testCase) { - assert_throws(new TypeError(), function() { + assert_throws_js(TypeError, function() { var decoder = new TextDecoder(testCase.encoding, {fatal: true}); decoder.decode(new Uint8Array(testCase.sequence)); }, 'Unterminated ' + testCase.encoding + ' sequence should throw if fatal flag is set'); @@ -28,12 +28,12 @@ test(function() { assert_equals(decoder.decode(odd, {stream: true}), ''); assert_equals(decoder.decode(odd), '\u0000'); - assert_throws(new TypeError(), function() { + assert_throws_js(TypeError, function() { decoder.decode(even, {stream: true}); decoder.decode(odd) }); - assert_throws(new TypeError(), function() { + assert_throws_js(TypeError, function() { decoder.decode(odd, {stream: true}); decoder.decode(even); }); diff --git a/test/fixtures/wpt/encoding/textdecoder-fatal.any.js b/test/fixtures/wpt/encoding/textdecoder-fatal.any.js index 5884f11ec3cf14..f9ccb2dfa7608b 100644 --- a/test/fixtures/wpt/encoding/textdecoder-fatal.any.js +++ b/test/fixtures/wpt/encoding/textdecoder-fatal.any.js @@ -51,7 +51,7 @@ var bad = [ bad.forEach(function(t) { test(function() { - assert_throws(new TypeError(), function() { + assert_throws_js(TypeError, function() { new TextDecoder(t.encoding, {fatal: true}).decode(new Uint8Array(t.input)) }); }, 'Fatal flag: ' + t.encoding + ' - ' + t.name); @@ -71,9 +71,9 @@ test(() => { assert_equals(decoder.decode(new DataView(bytes.buffer, 0, 3)), '♥', 'decode() should decode full sequence'); - assert_throws(new TypeError, - () => decoder.decode(new DataView(bytes.buffer, 0, 2)), - 'decode() should throw on incomplete sequence'); + assert_throws_js(TypeError, + () => decoder.decode(new DataView(bytes.buffer, 0, 2)), + 'decode() should throw on incomplete sequence'); assert_equals(decoder.decode(new DataView(bytes.buffer, 0, 3)), '♥', 'decode() should not throw on subsequent call'); diff --git a/test/fixtures/wpt/encoding/textdecoder-ignorebom.any.js b/test/fixtures/wpt/encoding/textdecoder-ignorebom.any.js index 1f5dabd7b51547..81f210eec89b4e 100644 --- a/test/fixtures/wpt/encoding/textdecoder-ignorebom.any.js +++ b/test/fixtures/wpt/encoding/textdecoder-ignorebom.any.js @@ -15,18 +15,30 @@ cases.forEach(function(testCase) { decoder.decode(bytes), BOM + 'abc', testCase.encoding + ': BOM should be present in decoded string if ignored'); + assert_equals( + decoder.decode(bytes), + BOM + 'abc', + testCase.encoding + ': BOM should be present in decoded string if ignored by a reused decoder'); decoder = new TextDecoder(testCase.encoding, {ignoreBOM: false}); assert_equals( decoder.decode(bytes), 'abc', testCase.encoding + ': BOM should be absent from decoded string if not ignored'); + assert_equals( + decoder.decode(bytes), + 'abc', + testCase.encoding + ': BOM should be absent from decoded string if not ignored by a reused decoder'); decoder = new TextDecoder(testCase.encoding); assert_equals( decoder.decode(bytes), 'abc', testCase.encoding + ': BOM should be absent from decoded string by default'); + assert_equals( + decoder.decode(bytes), + 'abc', + testCase.encoding + ': BOM should be absent from decoded string by default with a reused decoder'); }, 'BOM is ignored if ignoreBOM option is specified: ' + testCase.encoding); }); diff --git a/test/fixtures/wpt/encoding/textdecoder-streaming.any.js b/test/fixtures/wpt/encoding/textdecoder-streaming.any.js index 0863385b9af42d..e0c59472598fbd 100644 --- a/test/fixtures/wpt/encoding/textdecoder-streaming.any.js +++ b/test/fixtures/wpt/encoding/textdecoder-streaming.any.js @@ -16,21 +16,25 @@ var octets = { 0xDF,0xFF] }; -Object.keys(octets).forEach(function(encoding) { - for (var len = 1; len <= 5; ++len) { - test(function() { - var encoded = octets[encoding]; +["ArrayBuffer", "SharedArrayBuffer"].forEach((arrayBufferOrSharedArrayBuffer) => { + Object.keys(octets).forEach(function(encoding) { + for (var len = 1; len <= 5; ++len) { + test(function() { + var encoded = octets[encoding]; - var out = ''; - var decoder = new TextDecoder(encoding); - for (var i = 0; i < encoded.length; i += len) { - var sub = []; - for (var j = i; j < encoded.length && j < i + len; ++j) - sub.push(encoded[j]); - out += decoder.decode(new Uint8Array(sub), {stream: true}); - } - out += decoder.decode(); - assert_equals(out, string); - }, 'Streaming decode: ' + encoding + ', ' + len + ' byte window'); - } -}); + var out = ''; + var decoder = new TextDecoder(encoding); + for (var i = 0; i < encoded.length; i += len) { + var sub = []; + for (var j = i; j < encoded.length && j < i + len; ++j) + sub.push(encoded[j]); + var uintArray = new Uint8Array(new self[arrayBufferOrSharedArrayBuffer](sub.length)); + uintArray.set(sub); + out += decoder.decode(uintArray, {stream: true}); + } + out += decoder.decode(); + assert_equals(out, string); + }, 'Streaming decode: ' + encoding + ', ' + len + ' byte window (' + arrayBufferOrSharedArrayBuffer + ')'); + } + }); +}) diff --git a/test/fixtures/wpt/encoding/textdecoder-utf16-surrogates.any.js b/test/fixtures/wpt/encoding/textdecoder-utf16-surrogates.any.js index 3b8418703da77d..b08ea56cee38a2 100644 --- a/test/fixtures/wpt/encoding/textdecoder-utf16-surrogates.any.js +++ b/test/fixtures/wpt/encoding/textdecoder-utf16-surrogates.any.js @@ -38,7 +38,7 @@ bad.forEach(function(t) { assert_equals(new TextDecoder(t.encoding).decode(new Uint8Array(t.input)), t.expected); }, t.encoding + ' - ' + t.name); test(function() { - assert_throws(new TypeError(), function() { + assert_throws_js(TypeError, function() { new TextDecoder(t.encoding, {fatal: true}).decode(new Uint8Array(t.input)) }); }, t.encoding + ' - ' + t.name + ' (fatal flag set)'); diff --git a/test/fixtures/wpt/html/webappapis/microtask-queuing/queue-microtask.any.js b/test/fixtures/wpt/html/webappapis/microtask-queuing/queue-microtask.any.js index b39931719a88ad..e67765fade3bc6 100644 --- a/test/fixtures/wpt/html/webappapis/microtask-queuing/queue-microtask.any.js +++ b/test/fixtures/wpt/html/webappapis/microtask-queuing/queue-microtask.any.js @@ -6,12 +6,12 @@ test(() => { }, "It exists and is a function"); test(() => { - assert_throws(new TypeError(), () => queueMicrotask(), "no argument"); - assert_throws(new TypeError(), () => queueMicrotask(undefined), "undefined"); - assert_throws(new TypeError(), () => queueMicrotask(null), "null"); - assert_throws(new TypeError(), () => queueMicrotask(0), "0"); - assert_throws(new TypeError(), () => queueMicrotask({ handleEvent() { } }), "an event handler object"); - assert_throws(new TypeError(), () => queueMicrotask("window.x = 5;"), "a string"); + assert_throws_js(TypeError, () => queueMicrotask(), "no argument"); + assert_throws_js(TypeError, () => queueMicrotask(undefined), "undefined"); + assert_throws_js(TypeError, () => queueMicrotask(null), "null"); + assert_throws_js(TypeError, () => queueMicrotask(0), "0"); + assert_throws_js(TypeError, () => queueMicrotask({ handleEvent() { } }), "an event handler object"); + assert_throws_js(TypeError, () => queueMicrotask("window.x = 5;"), "a string"); }, "It throws when given non-functions"); async_test(t => { diff --git a/test/fixtures/wpt/html/webappapis/timers/cleartimeout-clearinterval.any.js b/test/fixtures/wpt/html/webappapis/timers/cleartimeout-clearinterval.any.js new file mode 100644 index 00000000000000..44551aa8a1bb80 --- /dev/null +++ b/test/fixtures/wpt/html/webappapis/timers/cleartimeout-clearinterval.any.js @@ -0,0 +1,29 @@ +async_test((t) => { + const handle = setTimeout( + t.step_func(() => { + assert_unreached("Timeout was not canceled"); + }), + 0 + ); + + clearInterval(handle); + + setTimeout(() => { + t.done(); + }, 100); +}, "Clear timeout with clearInterval"); + +async_test((t) => { + const handle = setInterval( + t.step_func(() => { + assert_unreached("Interval was not canceled"); + }), + 0 + ); + + clearTimeout(handle); + + setTimeout(() => { + t.done(); + }, 100); +}, "Clear interval with clearTimeout"); diff --git a/test/fixtures/wpt/html/webappapis/timers/negative-setinterval.html b/test/fixtures/wpt/html/webappapis/timers/negative-setinterval.html index 430d13c58aab69..663872cd1f3850 100644 --- a/test/fixtures/wpt/html/webappapis/timers/negative-setinterval.html +++ b/test/fixtures/wpt/html/webappapis/timers/negative-setinterval.html @@ -3,6 +3,7 @@ diff --git a/test/fixtures/wpt/html/webappapis/timers/type-long-setinterval.html b/test/fixtures/wpt/html/webappapis/timers/type-long-setinterval.html index af029959984668..7fb81ff29ac3a9 100644 --- a/test/fixtures/wpt/html/webappapis/timers/type-long-setinterval.html +++ b/test/fixtures/wpt/html/webappapis/timers/type-long-setinterval.html @@ -3,6 +3,7 @@ diff --git a/test/fixtures/wpt/interfaces/html.idl b/test/fixtures/wpt/interfaces/html.idl index 7b2a96ce6760a0..09e1a29043b5bb 100644 --- a/test/fixtures/wpt/interfaces/html.idl +++ b/test/fixtures/wpt/interfaces/html.idl @@ -194,6 +194,7 @@ interface HTMLLinkElement : HTMLElement { [CEReactions] attribute USVString imageSrcset; [CEReactions] attribute DOMString imageSizes; [CEReactions] attribute DOMString referrerPolicy; + [CEReactions] attribute boolean disabled; }; HTMLLinkElement includes LinkStyle; diff --git a/test/fixtures/wpt/resources/META.yml b/test/fixtures/wpt/resources/META.yml index 8f988f99a82e09..64a240ccbe8116 100644 --- a/test/fixtures/wpt/resources/META.yml +++ b/test/fixtures/wpt/resources/META.yml @@ -1,3 +1,2 @@ suggested_reviewers: - jgraham - - gsnedders diff --git a/test/fixtures/wpt/resources/check-layout-th.js b/test/fixtures/wpt/resources/check-layout-th.js index 928b0cf2a1041e..5d4236d1cedb0e 100644 --- a/test/fixtures/wpt/resources/check-layout-th.js +++ b/test/fixtures/wpt/resources/check-layout-th.js @@ -25,8 +25,41 @@ function assert_tolerance(actual, expected, message) } } +function checkDataKeys(node) { + var validData = new Set([ + "data-expected-width", + "data-expected-height", + "data-offset-x", + "data-offset-y", + "data-expected-client-width", + "data-expected-client-height", + "data-expected-scroll-width", + "data-expected-scroll-height", + "data-expected-bounding-client-rect-width", + "data-total-x", + "data-total-y", + "data-expected-display", + "data-expected-padding-top", + "data-expected-padding-bottom", + "data-expected-padding-left", + "data-expected-padding-right", + "data-expected-margin-top", + "data-expected-margin-bottom", + "data-expected-margin-left", + "data-expected-margin-right" + ]); + if (!node || !node.getAttributeNames) + return; + // Use "data-test" prefix if you need custom-named data elements. + for (let name of node.getAttributeNames()) { + if (name.startsWith("data-") && !name.startsWith("data-test")) + assert_true(validData.has(name), name + " is a valid data attribute"); + } +} + function checkExpectedValues(t, node, prefix) { + checkDataKeys(node); var output = { checked: false }; var expectedWidth = checkAttribute(output, node, "data-expected-width"); @@ -162,6 +195,8 @@ function checkExpectedValues(t, node, prefix) } var testNumber = 0; +var highlightError = false; // displays outline around failed test element. +var printDomOnError = true; // prints dom when test fails. window.checkLayout = function(selectorList, callDone = true) { @@ -175,13 +210,24 @@ window.checkLayout = function(selectorList, callDone = true) 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 prefix = + printDomOnError ? '\n' + container.outerHTML + '\n' : ''; var passed = false; try { checkedLayout |= checkExpectedValues(t, node.parentNode, prefix); checkedLayout |= checkSubtreeExpectedValues(t, node, prefix); passed = true; } finally { + if (!passed && highlightError) { + if (!document.getElementById('testharness_error_css')) { + var style = document.createElement('style'); + style.id = 'testharness_error_css'; + style.textContent = '.testharness_error { outline: red dotted 2px !important; }'; + document.body.appendChild(style); + } + if (node) + node.classList.add('testharness_error'); + } checkedLayout |= !passed; } }, selectorList + ' ' + String(++testNumber)); diff --git a/test/fixtures/wpt/resources/idlharness.js b/test/fixtures/wpt/resources/idlharness.js index c7a040996b9994..cb66fcaa98c06d 100644 --- a/test/fixtures/wpt/resources/idlharness.js +++ b/test/fixtures/wpt/resources/idlharness.js @@ -84,16 +84,33 @@ function minOverloadLength(overloads) .reduce(function(m, n) { return Math.min(m, n); }); } +// A helper to get the global of a Function object. This is needed to determine +// which global exceptions the function throws will come from. +function globalOf(func) +{ + try { + // Use the fact that .constructor for a Function object is normally the + // Function constructor, which can be used to mint a new function in the + // right global. + return func.constructor("return this;")(); + } catch (e) { + } + // If the above fails, because someone gave us a non-function, or a function + // with a weird proto chain or weird .constructor property, just fall back + // to 'self'. + return self; +} + function throwOrReject(a_test, operation, fn, obj, args, message, cb) { if (operation.idlType.generic !== "Promise") { - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(fn).TypeError, function() { fn.apply(obj, args); }, message); cb(); } else { try { - promise_rejects(a_test, new TypeError(), fn.apply(obj, args), message).then(cb, cb); + promise_rejects_js(a_test, TypeError, fn.apply(obj, args), message).then(cb, cb); } catch (e){ a_test.step(function() { assert_unreached("Throws \"" + e + "\" instead of rejecting promise"); @@ -193,6 +210,12 @@ self.IdlArray = function() this["implements"] = {}; this["includes"] = {}; this["inheritance"] = {}; + + /** + * Record of skipped IDL items, in case we later realize that they are a + * dependency (to retroactively process them). + */ + this.skipped = new Map(); }; IdlArray.prototype.add_idls = function(raw_idls, options) @@ -230,7 +253,11 @@ IdlArray.prototype.is_excluded_by_options = function (name, options) IdlArray.prototype.add_dependency_idls = function(raw_idls, options) { - const parsed_idls = WebIDL2.parse(raw_idls); + return this.internal_add_dependency_idls(WebIDL2.parse(raw_idls), options); +}; + +IdlArray.prototype.internal_add_dependency_idls = function(parsed_idls, options) +{ const new_options = { only: [] } const all_deps = new Set(); @@ -244,22 +271,50 @@ IdlArray.prototype.add_dependency_idls = function(raw_idls, options) all_deps.add(k); this.includes[k].forEach(v => all_deps.add(v)); }); - this.partials.map(p => p.name).forEach(v => all_deps.add(v)); - // Add the attribute idlTypes of all the nested members of all tested idls. - for (const obj of [this.members, this.partials]) { - const tested = Object.values(obj).filter(m => !m.untested && m.members); - for (const parsed of tested) { - for (const attr of Object.values(parsed.members).filter(m => !m.untested && m.type === 'attribute')) { - all_deps.add(attr.idlType.idlType); - } + this.partials.forEach(p => all_deps.add(p.name)); + // Add 'TypeOfType' for each "typedef TypeOfType MyType;" entry. + Object.entries(this.members).forEach(([k, v]) => { + if (v instanceof IdlTypedef) { + let defs = v.idlType.union + ? v.idlType.idlType.map(t => t.idlType) + : [v.idlType.idlType]; + defs.forEach(d => all_deps.add(d)); } - } + }); + + // Add the attribute idlTypes of all the nested members of idls. + const attrDeps = parsedIdls => { + return parsedIdls.reduce((deps, parsed) => { + if (parsed.members) { + for (const attr of Object.values(parsed.members).filter(m => m.type === 'attribute')) { + let attrType = attr.idlType; + // Check for generic members (e.g. FrozenArray) + if (attrType.generic) { + deps.add(attrType.generic); + attrType = attrType.idlType; + } + deps.add(attrType.idlType); + } + } + if (parsed.base in this.members) { + attrDeps([this.members[parsed.base]]).forEach(dep => deps.add(dep)); + } + return deps; + }, new Set()); + }; + + const testedMembers = Object.values(this.members).filter(m => !m.untested && m.members); + attrDeps(testedMembers).forEach(dep => all_deps.add(dep)); + + const testedPartials = this.partials.filter(m => !m.untested && m.members); + attrDeps(testedPartials).forEach(dep => all_deps.add(dep)); + if (options && options.except && options.only) { throw new IdlHarnessError("The only and except options can't be used together."); } - const should_skip = name => { + const defined_or_untested = name => { // NOTE: Deps are untested, so we're lenient, and skip re-encountered definitions. // e.g. for 'idl' containing A:B, B:C, C:D // array.add_idls(idl, {only: ['A','B']}). @@ -268,9 +323,7 @@ IdlArray.prototype.add_dependency_idls = function(raw_idls, options) return name in this.members || this.is_excluded_by_options(name, options); } - // Record of skipped items, in case we later determine they are a dependency. // Maps name -> [parsed_idl, ...] - const skipped = new Map(); const process = function(parsed) { var deps = []; if (parsed.name) { @@ -284,13 +337,15 @@ IdlArray.prototype.add_dependency_idls = function(raw_idls, options) } deps = deps.filter(function(name) { - if (!name || should_skip(name) || !all_deps.has(name)) { + if (!name + || name === parsed.name && defined_or_untested(name) + || !all_deps.has(name)) { // Flag as skipped, if it's not already processed, so we can // come back to it later if we retrospectively call it a dep. if (name && !(name in this.members)) { - skipped.has(name) - ? skipped.get(name).push(parsed) - : skipped.set(name, [parsed]); + this.skipped.has(name) + ? this.skipped.get(name).push(parsed) + : this.skipped.set(name, [parsed]); } return false; } @@ -328,9 +383,9 @@ IdlArray.prototype.add_dependency_idls = function(raw_idls, options) } for (const deferred of follow_up) { - if (skipped.has(deferred)) { - const next = skipped.get(deferred); - skipped.delete(deferred); + if (this.skipped.has(deferred)) { + const next = this.skipped.get(deferred); + this.skipped.delete(deferred); next.forEach(process); } } @@ -463,8 +518,7 @@ IdlArray.prototype.internal_add_idls = function(parsed_idls, options) break; case "callback": - // TODO - console.log("callback not yet supported"); + this.members[parsed_idl.name] = new IdlCallback(parsed_idl); break; case "enum": @@ -586,7 +640,7 @@ IdlArray.prototype.is_json_type = function(type) // sequence types if (type.generic == "sequence" || type.generic == "FrozenArray") { - return this.is_json_type(idlType); + return this.is_json_type(idlType[0]); } if (typeof idlType != "string") { throw new Error("Unexpected type " + JSON.stringify(idlType)); } @@ -625,6 +679,7 @@ IdlArray.prototype.is_json_type = function(type) case "Uint32Array": case "Uint8ClampedArray": case "Float32Array": + case "Float64Array": case "ArrayBuffer": case "DataView": case "any": @@ -694,11 +749,11 @@ function exposure_set(object, default_set) { result = new Set(result); } if (exposed && exposed.length) { - var set = exposed[0].rhs.value; + const { rhs } = exposed[0]; // Could be a list or a string. - if (typeof set == "string") { - set = [ set ]; - } + const set = rhs.type === "identifier-list" ? + rhs.value.map(id => id.value) : + [ rhs.value ]; result = new Set(set); } if (result && result.has("Worker")) { @@ -771,10 +826,20 @@ IdlArray.prototype.test = function() if (!(this.members[lhs] instanceof IdlInterface)) throw errStr + lhs + " is not an interface."; if (!(rhs in this.members)) throw errStr + rhs + " is undefined."; if (!(this.members[rhs] instanceof IdlInterface)) throw errStr + rhs + " is not an interface."; - this.members[rhs].members.forEach(function(member) - { - this.members[lhs].members.push(new IdlInterfaceMember(member)); - }.bind(this)); + + if (this.members[rhs].members.length) { + test(function () { + var clash = this.members[rhs].members.find(function(member) { + return this.members[lhs].members.find(function(m) { + return this.are_duplicate_members(m, member); + }.bind(this)); + }.bind(this)); + this.members[rhs].members.forEach(function(member) { + this.members[lhs].members.push(new IdlInterfaceMember(member)); + }.bind(this)); + assert_true(!clash, "member " + (clash && clash.name) + " is unique"); + }.bind(this), lhs + " implements " + rhs + ": member names are unique"); + } }.bind(this)); } this["implements"] = {}; @@ -788,10 +853,23 @@ IdlArray.prototype.test = function() if (!(this.members[lhs] instanceof IdlInterface)) throw errStr + lhs + " is not an interface."; if (!(rhs in this.members)) throw errStr + rhs + " is undefined."; if (!(this.members[rhs] instanceof IdlInterface)) throw errStr + rhs + " is not an interface."; - this.members[rhs].members.forEach(function(member) - { - this.members[lhs].members.push(new IdlInterfaceMember(member)); - }.bind(this)); + + if (this.members[rhs].members.length) { + test(function () { + var clash = this.members[rhs].members.find(function(member) { + return this.members[lhs].members.find(function(m) { + return this.are_duplicate_members(m, member); + }.bind(this)); + }.bind(this)); + this.members[rhs].members.forEach(function(member) { + assert_true( + this.members[lhs].members.every(m => !this.are_duplicate_members(m, member)), + "member " + member.name + " is unique"); + this.members[lhs].members.push(new IdlInterfaceMember(member)); + }.bind(this)); + assert_true(!clash, "member " + (clash && clash.name) + " is unique"); + }.bind(this), lhs + " includes " + rhs + ": member names are unique"); + } }.bind(this)); } this["includes"] = {}; @@ -853,24 +931,27 @@ IdlArray.prototype.collapse_partials = function() || this.members[parsed_idl.name] instanceof IdlDictionary || this.members[parsed_idl.name] instanceof IdlNamespace); + // Ensure unique test name in case of multiple partials. let partialTestName = parsed_idl.name; - if (!parsed_idl.untested) { - // Ensure unique test name in case of multiple partials. - let partialTestCount = 1; - if (testedPartials.has(parsed_idl.name)) { - partialTestCount += testedPartials.get(parsed_idl.name); - partialTestName = `${partialTestName}[${partialTestCount}]`; - } - testedPartials.set(parsed_idl.name, partialTestCount); + let partialTestCount = 1; + if (testedPartials.has(parsed_idl.name)) { + partialTestCount += testedPartials.get(parsed_idl.name); + partialTestName = `${partialTestName}[${partialTestCount}]`; + } + testedPartials.set(parsed_idl.name, partialTestCount); + if (!parsed_idl.untested) { test(function () { assert_true(originalExists, `Original ${parsed_idl.type} should be defined`); - var expected = IdlInterface; + var expected; switch (parsed_idl.type) { - case 'interface': expected = IdlInterface; break; case 'dictionary': expected = IdlDictionary; break; case 'namespace': expected = IdlNamespace; break; + case 'interface': + case 'interface mixin': + default: + expected = IdlInterface; break; } assert_true( expected.prototype.isPrototypeOf(this.members[parsed_idl.name]), @@ -916,14 +997,36 @@ IdlArray.prototype.collapse_partials = function() this.members[parsed_idl.name].extAttrs.push(extAttr); }.bind(this)); } - parsed_idl.members.forEach(function(member) - { - this.members[parsed_idl.name].members.push(new IdlInterfaceMember(member)); - }.bind(this)); + if (parsed_idl.members.length) { + test(function () { + var clash = parsed_idl.members.find(function(member) { + return this.members[parsed_idl.name].members.find(function(m) { + return this.are_duplicate_members(m, member); + }.bind(this)); + }.bind(this)); + parsed_idl.members.forEach(function(member) + { + this.members[parsed_idl.name].members.push(new IdlInterfaceMember(member)); + }.bind(this)); + assert_true(!clash, "member " + (clash && clash.name) + " is unique"); + }.bind(this), `Partial ${parsed_idl.type} ${partialTestName}: member names are unique`); + } }.bind(this)); this.partials = []; } +IdlArray.prototype.are_duplicate_members = function(m1, m2) { + if (m1.name !== m2.name) { + return false; + } + if (m1.type === 'operation' && m2.type === 'operation' + && m1.arguments.length !== m2.arguments.length) { + // Method overload. TODO: Deep comparison of arguments. + return false; + } + return true; +} + IdlArray.prototype.assert_type_is = function(value, type) { if (type.idlType in this.members @@ -931,6 +1034,13 @@ IdlArray.prototype.assert_type_is = function(value, type) this.assert_type_is(value, this.members[type.idlType].idlType); return; } + + if (type.nullable && value === null) + { + // This is fine + return; + } + if (type.union) { for (var i = 0; i < type.idlType.length; i++) { try { @@ -964,12 +1074,6 @@ IdlArray.prototype.assert_type_is = function(value, type) return; } - if (type.nullable && value === null) - { - // This is fine - return; - } - if (type.array) { // TODO: not supported yet @@ -984,7 +1088,7 @@ IdlArray.prototype.assert_type_is = function(value, type) // Nothing we can do. return; } - this.assert_type_is(value[0], type.idlType); + this.assert_type_is(value[0], type.idlType[0]); return; } @@ -1004,11 +1108,11 @@ IdlArray.prototype.assert_type_is = function(value, type) // Nothing we can do. return; } - this.assert_type_is(value[0], type.idlType); + this.assert_type_is(value[0], type.idlType[0]); return; } - type = type.idlType; + type = Array.isArray(type.idlType) ? type.idlType[0] : type.idlType; switch(type) { @@ -1110,9 +1214,17 @@ IdlArray.prototype.assert_type_is = function(value, type) return; } + // This is a catch-all for any IDL type name which follows JS class + // semantics. This includes some non-interface IDL types (e.g. Int8Array, + // Function, ...), as well as any interface types that are not in the IDL + // that is fed to the harness. If an IDL type does not follow JS class + // semantics then it should go in the switch statement above. If an IDL + // type needs full checking, then the test should include it in the IDL it + // feeds to the harness. if (!(type in this.members)) { - throw new IdlHarnessError("Unrecognized type " + type); + assert_true(value instanceof self[type], "wrong type: not a " + type); + return; } if (this.members[type] instanceof IdlInterface) @@ -1138,9 +1250,13 @@ IdlArray.prototype.assert_type_is = function(value, type) { // TODO: Test when we actually have something to test this on } + else if (this.members[type] instanceof IdlCallback) + { + assert_equals(typeof value, "function"); + } else { - throw new IdlHarnessError("Type " + type + " isn't an interface or dictionary"); + throw new IdlHarnessError("Type " + type + " isn't an interface, callback or dictionary"); } }; @@ -1223,9 +1339,10 @@ function IdlInterface(obj, is_callback, is_mixin) /** An array of IdlInterfaceMembers. */ this.members = obj.members.map(function(m){return new IdlInterfaceMember(m); }); - if (this.has_extended_attribute("Unforgeable")) { + if (this.has_extended_attribute("Unforgeable") || + this.has_extended_attribute("LegacyUnforgeable")) { this.members - .filter(function(m) { return !m["static"] && (m.type == "attribute" || m.type == "operation"); }) + .filter(function(m) { return m.special !== "static" && (m.type == "attribute" || m.type == "operation"); }) .forEach(function(m) { return m.isUnforgeable = true; }); } @@ -1376,7 +1493,7 @@ IdlInterface.prototype.default_to_json_operation = function(callback) { if (I.has_default_to_json_regular_operation()) { isDefault = true; I.members.forEach(function(m) { - if (!m.static && m.type == "attribute" && I.array.is_json_type(m.idlType)) { + if (m.special !== "static" && m.type == "attribute" && I.array.is_json_type(m.idlType)) { map.set(m.name, m.idlType); } }); @@ -1470,6 +1587,17 @@ IdlInterface.prototype.test = function() this.test_members(); }; +// This supports both Constructor extended attributes and constructor +// operations until all idl fragments have been updated. +IdlInterface.prototype.constructors = function() +{ + var extendedAttributes = this.extAttrs + .filter(function(attr) { return attr.name == "Constructor"; }); + var operations = this.members + .filter(function(m) { return m.type == "constructor"; }); + return extendedAttributes.concat(operations); +} + IdlInterface.prototype.test_self = function() { subsetTestByKey(this.name, test, function() @@ -1557,17 +1685,17 @@ IdlInterface.prototype.test_self = function() "prototype of self's property " + format_value(this.name) + " is not Function.prototype"); } - if (!this.has_extended_attribute("Constructor")) { + if (!this.constructors().length) { // "The internal [[Call]] method of the interface object behaves as // follows . . . // // "If I was not declared with a [Constructor] extended attribute, // then throw a TypeError." var interface_object = this.get_interface_object(); - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(interface_object).TypeError, function() { interface_object(); }, "interface object didn't throw TypeError when called as a function"); - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(interface_object).TypeError, function() { new interface_object(); }, "interface object didn't throw TypeError when called as a constructor"); } @@ -1592,8 +1720,7 @@ IdlInterface.prototype.test_self = function() assert_false(desc.enumerable, this.name + ".length should not be enumerable"); assert_true(desc.configurable, this.name + ".length should be configurable"); - var constructors = this.extAttrs - .filter(function(attr) { return attr.name == "Constructor"; }); + var constructors = this.constructors(); var expected_length = minOverloadLength(constructors); assert_equals(this.get_interface_object().length, expected_length, "wrong value for " + this.name + ".length"); }.bind(this), this.name + " interface object length"); @@ -1647,7 +1774,7 @@ IdlInterface.prototype.test_self = function() } var aliases; if (rhs.type === "identifier-list") { - aliases = rhs.value; + aliases = rhs.value.map(id => id.value); } else { // rhs.type === identifier aliases = [ rhs.value ]; } @@ -1675,11 +1802,12 @@ IdlInterface.prototype.test_self = function() }.bind(this), this.name + " interface: legacy window alias"); } - if (this.has_extended_attribute("NamedConstructor")) { + if (this.has_extended_attribute("NamedConstructor") || + this.has_extended_attribute("LegacyFactoryFunction")) { var constructors = this.extAttrs - .filter(function(attr) { return attr.name == "NamedConstructor"; }); + .filter(function(attr) { return attr.name == "NamedConstructor" || attr.name == "LegacyFactoryFunction"; }); if (constructors.length !== 1) { - throw new IdlHarnessError("Internal error: missing support for multiple NamedConstructor extended attributes"); + throw new IdlHarnessError("Internal error: missing support for multiple LegacyFactoryFunction extended attributes"); } var constructor = constructors[0]; var min_length = minOverloadLength([constructor]); @@ -1688,10 +1816,10 @@ IdlInterface.prototype.test_self = function() { // This function tests WebIDL as of 2019-01-14. - // "for every [NamedConstructor] extended attribute on an exposed + // "for every [LegacyFactoryFunction] extended attribute on an exposed // interface, a corresponding property must exist on the ECMAScript // global object. The name of the property is the - // [NamedConstructor]'s identifier, and its value is an object + // [LegacyFactoryFunction]'s identifier, and its value is an object // called a named constructor, ... . The property has the attributes // { [[Writable]]: true, [[Enumerable]]: false, // [[Configurable]]: true }." @@ -1785,7 +1913,7 @@ IdlInterface.prototype.test_self = function() var args = constructor.arguments.map(function(arg) { return create_suitable_object(arg.idlType); }); - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(self[name]).TypeError, function() { self[name](...args); }.bind(this)); }.bind(this), this.name + " interface: named constructor without 'new'"); @@ -2030,7 +2158,7 @@ IdlInterface.prototype.test_immutable_prototype = function(type, obj) } catch (err) {} }); - assert_throws(new TypeError(), function() { + assert_throws_js(TypeError, function() { Object.setPrototypeOf(obj, newValue); }); @@ -2048,7 +2176,7 @@ IdlInterface.prototype.test_immutable_prototype = function(type, obj) var newValue = Object.create(null); t.add_cleanup(function() { - var setter = Object.getOwnPropertyDescriptor( + let setter = Object.getOwnPropertyDescriptor( Object.prototype, '__proto__' ).set; @@ -2057,7 +2185,22 @@ IdlInterface.prototype.test_immutable_prototype = function(type, obj) } catch (err) {} }); - assert_throws(new TypeError(), function() { + // We need to find the actual setter for the '__proto__' property, so we + // can determine the right global for it. Walk up the prototype chain + // looking for that property until we find it. + let setter; + { + let cur = obj; + while (cur) { + const desc = Object.getOwnPropertyDescriptor(cur, "__proto__"); + if (desc) { + setter = desc.set; + break; + } + cur = Object.getPrototypeOf(cur); + } + } + assert_throws_js(globalOf(setter).TypeError, function() { obj.__proto__ = newValue; }); @@ -2190,7 +2333,7 @@ IdlInterface.prototype.test_member_attribute = function(member) assert_own_property(this.get_interface_object(), "prototype", 'interface "' + this.name + '" does not have own property "prototype"'); - if (member["static"]) { + if (member.special === "static") { assert_own_property(this.get_interface_object(), member.name, "The interface object must have a property " + format_value(member.name)); @@ -2236,16 +2379,18 @@ IdlInterface.prototype.test_member_attribute = function(member) "The prototype object must have a property " + format_value(member.name)); - if (!member.has_extended_attribute("LenientThis")) { + if (!member.has_extended_attribute("LenientThis") && + !member.has_extended_attribute("LegacyLenientThis")) { if (member.idlType.generic !== "Promise") { - assert_throws(new TypeError(), function() { + // this.get_interface_object() returns a thing in our global + assert_throws_js(TypeError, function() { this.get_interface_object().prototype[member.name]; }.bind(this), "getting property on prototype object must throw TypeError"); // do_interface_attribute_asserts must be the last thing we // do, since it will call done() on a_test. this.do_interface_attribute_asserts(this.get_interface_object().prototype, member, a_test); } else { - promise_rejects(a_test, new TypeError(), + promise_rejects_js(a_test, TypeError, this.get_interface_object().prototype[member.name]) .then(function() { // do_interface_attribute_asserts must be the last @@ -2270,10 +2415,7 @@ IdlInterface.prototype.test_member_operation = function(member) if (!shouldRunSubTest(this.name)) { return; } - var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: operation " + member.name + - "(" + member.arguments.map( - function(m) {return m.idlType.idlType; } ).join(", ") - +")"); + var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: operation " + member); a_test.step(function() { // This function tests WebIDL as of 2015-12-29. @@ -2307,7 +2449,7 @@ IdlInterface.prototype.test_member_operation = function(member) var memberHolderObject; // "* If the operation is static, then the property exists on the // interface object." - if (member["static"]) { + if (member.special === "static") { assert_own_property(this.get_interface_object(), member.name, "interface object missing static operation"); memberHolderObject = this.get_interface_object(); @@ -2400,7 +2542,7 @@ IdlInterface.prototype.do_member_operation_asserts = function(memberHolderObject // check for globals, since otherwise we'll invoke window.close(). And we // have to skip this test for anything that on the proto chain of "self", // since that does in fact have implicit-this behavior. - if (!member["static"]) { + if (member.special !== "static") { var cb; if (!this.is_global() && memberHolderObject[member.name] != self[member.name]) @@ -2522,7 +2664,7 @@ IdlInterface.prototype.test_member_stringifier = function(member) "property has wrong .length"); // "Let O be the result of calling ToObject on the this value." - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(interfacePrototypeObject.toString).TypeError, function() { interfacePrototypeObject.toString.apply(null, []); }, "calling stringifier with this = null didn't throw TypeError"); @@ -2531,7 +2673,7 @@ IdlInterface.prototype.test_member_stringifier = function(member) // // TODO: Test a platform object that implements some other // interface. (Have to be sure to get inheritance right.) - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(interfacePrototypeObject.toString).TypeError, function() { interfacePrototypeObject.toString.apply({}, []); }, "calling stringifier with this = {} didn't throw TypeError"); }.bind(this), this.name + " interface: stringifier"); @@ -2571,7 +2713,7 @@ IdlInterface.prototype.test_members = function() { this.test_member_attribute(member); } - if (member.stringifier) { + if (member.special === "stringifier") { this.test_member_stringifier(member); } break; @@ -2586,7 +2728,7 @@ IdlInterface.prototype.test_members = function() { this.test_member_operation(member); } - } else if (member.stringifier) { + } else if (member.special === "stringifier") { this.test_member_stringifier(member); } break; @@ -2613,10 +2755,19 @@ IdlInterface.prototype.test_object = function(desc) exception = e; } - var expected_typeof = - this.members.some(function(member) { return member.legacycaller; }) - ? "function" - : "object"; + var expected_typeof; + if (this.name == "HTMLAllCollection") + { + // Result of [[IsHTMLDDA]] slot + expected_typeof = "undefined"; + } else if (this.members.some(function(member) { return member.legacycaller; })) + { + expected_typeof = "function"; + } + else + { + expected_typeof = "object"; + } this.test_primary_interface_of(desc, obj, exception, expected_typeof); @@ -2747,16 +2898,11 @@ IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expect || member.type == "operation") && member.name) { - var described_name = member.name; - if (member.type == "operation") - { - described_name += "(" + member.arguments.map(arg => arg.idlType.idlType).join(", ") + ")"; - } subsetTestByKey(this.name, test, function() { assert_equals(exception, null, "Unexpected exception when evaluating object"); assert_equals(typeof obj, expected_typeof, "wrong typeof object"); - if (!member["static"]) { + if (member.special !== "static") { if (!this.is_global()) { assert_inherits(obj, member.name); } else { @@ -2783,7 +2929,15 @@ IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expect } if (!thrown) { - this.array.assert_type_is(property, member.idlType); + if (this.name == "Document" && member.name == "all") + { + // Result of [[IsHTMLDDA]] slot + assert_equals(typeof property, "undefined"); + } + else + { + this.array.assert_type_is(property, member.idlType); + } } } if (member.type == "operation") @@ -2791,22 +2945,23 @@ IdlInterface.prototype.test_interface_of = function(desc, obj, exception, expect assert_equals(typeof obj[member.name], "function"); } } - }.bind(this), this.name + " interface: " + desc + ' must inherit property "' + described_name + '" with the proper type'); + }.bind(this), this.name + " interface: " + desc + ' must inherit property "' + member + '" with the proper type'); } // TODO: This is wrong if there are multiple operations with the same // identifier. // TODO: Test passing arguments of the wrong type. if (member.type == "operation" && member.name && member.arguments.length) { - var a_test = subsetTestByKey(this.name, async_test, this.name + " interface: calling " + member.name + - "(" + member.arguments.map(function(m) { return m.idlType.idlType; }).join(", ") + - ") on " + desc + " with too few arguments must throw TypeError"); + var description = + this.name + " interface: calling " + member + " on " + desc + + " with too few arguments must throw TypeError"; + var a_test = subsetTestByKey(this.name, async_test, description); a_test.step(function() { assert_equals(exception, null, "Unexpected exception when evaluating object"); assert_equals(typeof obj, expected_typeof, "wrong typeof object"); var fn; - if (!member["static"]) { + if (member.special !== "static") { if (!this.is_global() && !member.isUnforgeable) { assert_inherits(obj, member.name); } else { @@ -2849,7 +3004,7 @@ IdlInterface.prototype.has_stringifier = function() // default stringifer return true; } - if (this.members.some(function(member) { return member.stringifier; })) { + if (this.members.some(function(member) { return member.special === "stringifier"; })) { return true; } if (this.base && @@ -2881,7 +3036,7 @@ IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member, a_ // "The property has attributes { [[Get]]: G, [[Set]]: S, [[Enumerable]]: // true, [[Configurable]]: configurable }, where: // "configurable is false if the attribute was declared with the - // [Unforgeable] extended attribute and true otherwise; + // [LegacyUnforgeable] extended attribute and true otherwise; // "G is the attribute getter, defined below; and // "S is the attribute setter, also defined below." var desc = Object.getOwnPropertyDescriptor(obj, member.name); @@ -2890,7 +3045,7 @@ IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member, a_ assert_true(desc.enumerable, "property should be enumerable"); if (member.isUnforgeable) { - assert_false(desc.configurable, "[Unforgeable] property must not be configurable"); + assert_false(desc.configurable, "[LegacyUnforgeable] property must not be configurable"); } else { @@ -2903,19 +3058,20 @@ IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member, a_ assert_equals(typeof desc.get, "function", "getter must be Function"); // "If the attribute is a regular attribute, then:" - if (!member["static"]) { + if (member.special !== "static") { // "If O is not a platform object that implements I, then: - // "If the attribute was specified with the [LenientThis] extended + // "If the attribute was specified with the [LegacyLenientThis] extended // attribute, then return undefined. // "Otherwise, throw a TypeError." - if (!member.has_extended_attribute("LenientThis")) { + if (!member.has_extended_attribute("LenientThis") && + !member.has_extended_attribute("LegacyLenientThis")) { if (member.idlType.generic !== "Promise") { - assert_throws(new TypeError(), function() { + assert_throws_js(globalOf(desc.get).TypeError, function() { desc.get.call({}); }.bind(this), "calling getter on wrong object type must throw TypeError"); } else { pendingPromises.push( - promise_rejects(a_test, new TypeError(), desc.get.call({}), + promise_rejects_js(a_test, TypeError, desc.get.call({}), "calling getter on wrong object type must reject the return promise with TypeError")); } } else { @@ -2938,6 +3094,7 @@ IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member, a_ // TypeError in most cases). if (member.readonly && !member.has_extended_attribute("LenientSetter") + && !member.has_extended_attribute("LegacyLenientSetter") && !member.has_extended_attribute("PutForwards") && !member.has_extended_attribute("Replaceable")) { @@ -2953,15 +3110,16 @@ IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member, a_ assert_equals(typeof desc.set, "function", "setter must be function for PutForwards, Replaceable, or non-readonly attributes"); // "If the attribute is a regular attribute, then:" - if (!member["static"]) { + if (member.special !== "static") { // "If /validThis/ is false and the attribute was not specified - // with the [LenientThis] extended attribute, then throw a + // with the [LegacyLenientThis] extended attribute, then throw a // TypeError." // "If the attribute is declared with a [Replaceable] extended // attribute, then: ..." // "If validThis is false, then return." - if (!member.has_extended_attribute("LenientThis")) { - assert_throws(new TypeError(), function() { + if (!member.has_extended_attribute("LenientThis") && + !member.has_extended_attribute("LegacyLenientThis")) { + assert_throws_js(globalOf(desc.set).TypeError, function() { desc.set.call({}); }.bind(this), "calling setter on wrong object type must throw TypeError"); } else { @@ -2991,7 +3149,7 @@ function IdlInterfaceMember(obj) * We just forward all properties to this object without modification, * except for special extAttrs handling. */ - for (var k in obj) + for (var k in obj.toJSON()) { this[k] = obj[k]; } @@ -3000,16 +3158,51 @@ function IdlInterfaceMember(obj) this.extAttrs = []; } - this.isUnforgeable = this.has_extended_attribute("Unforgeable"); + this.isUnforgeable = this.has_extended_attribute("Unforgeable") || + this.has_extended_attribute("LegacyUnforgeable"); this.isUnscopable = this.has_extended_attribute("Unscopable"); } IdlInterfaceMember.prototype = Object.create(IdlObject.prototype); +IdlInterfaceMember.prototype.toJSON = function() { + return this; +}; + IdlInterfaceMember.prototype.is_to_json_regular_operation = function() { - return this.type == "operation" && !this.static && this.name == "toJSON"; + return this.type == "operation" && this.special !== "static" && this.name == "toJSON"; }; +IdlInterfaceMember.prototype.toString = function() { + function formatType(type) { + var result; + if (type.generic) { + result = type.generic + "<" + type.idlType.map(formatType).join(", ") + ">"; + } else if (type.union) { + result = "(" + type.subtype.map(formatType).join(" or ") + ")"; + } else { + result = type.idlType; + } + if (type.nullable) { + result += "?" + } + return result; + } + + if (this.type === "operation") { + var args = this.arguments.map(function(m) { + return [ + m.optional ? "optional " : "", + formatType(m.idlType), + m.variadic ? "..." : "", + ].join(""); + }).join(", "); + return this.name + "(" + args + ")"; + } + + return this.name; +} + /// Internal helper functions /// function create_suitable_object(type) { @@ -3067,6 +3260,24 @@ function IdlEnum(obj) IdlEnum.prototype = Object.create(IdlObject.prototype); +/// IdlCallback /// +// Used for IdlArray.prototype.assert_type_is +function IdlCallback(obj) +{ + /** + * obj is an object produced by the WebIDLParser.js "callback" + * production. + */ + + /** Self-explanatory. */ + this.name = obj.name; + + /** Arguments for the callback. */ + this.arguments = obj.arguments; +} + +IdlCallback.prototype = Object.create(IdlObject.prototype); + /// IdlTypedef /// // Used for IdlArray.prototype.assert_type_is function IdlTypedef(obj) @@ -3136,17 +3347,10 @@ IdlNamespace.prototype.test_member_operation = function(member) if (!shouldRunSubTest(this.name)) { return; } - var args = member.arguments.map(function(a) { - var s = a.idlType.idlType; - if (a.variadic) { - s += '...'; - } - return s; - }).join(", "); var a_test = subsetTestByKey( this.name, async_test, - this.name + ' namespace: operation ' + member.name + '(' + args + ')'); + this.name + ' namespace: operation ' + member); a_test.step(function() { assert_own_property( self[this.name], @@ -3228,16 +3432,30 @@ function idl_test(srcs, deps, idl_setup_func) { srcs = (srcs instanceof Array) ? srcs : [srcs] || []; deps = (deps instanceof Array) ? deps : [deps] || []; var setup_error = null; + const validationIgnored = [ + "constructor-member", + "dict-arg-default", + "require-exposed" + ]; return Promise.all( - srcs.concat(deps).map(function(spec) { - return fetch_spec(spec); - })) - .then(function(idls) { + srcs.concat(deps).map(fetch_spec)) + .then(function(results) { + const astArray = results.map(result => + WebIDL2.parse(result.idl, { sourceName: result.spec }) + ); + test(() => { + const validations = WebIDL2.validate(astArray) + .filter(v => !validationIgnored.includes(v.ruleName)); + if (validations.length) { + const message = validations.map(v => v.message).join("\n\n"); + throw new Error(message); + } + }, "idl_test validation"); for (var i = 0; i < srcs.length; i++) { - idl_array.add_idls(idls[i]); + idl_array.internal_add_idls(astArray[i]); } for (var i = srcs.length; i < srcs.length + deps.length; i++) { - idl_array.add_dependency_idls(idls[i]); + idl_array.internal_add_dependency_idls(astArray[i]); } }) .then(function() { @@ -3272,6 +3490,6 @@ function fetch_spec(spec) { throw new IdlHarnessError("Error fetching " + url + "."); } return r.text(); - }); + }).then(idl => ({ spec, idl })); } // vim: set expandtab shiftwidth=4 tabstop=4 foldmarker=@{,@} foldmethod=marker: diff --git a/test/fixtures/wpt/resources/readme.md b/test/fixtures/wpt/resources/readme.md index 97d2e6f92203d1..09a62fbd7bfd7c 100644 --- a/test/fixtures/wpt/resources/readme.md +++ b/test/fixtures/wpt/resources/readme.md @@ -1,5 +1,8 @@ # Resources +This directory contains utilities intended for use by tests and maintained as project infrastructure. +It does not contain tests. + ## `testharness.js` `testharness.js` is a framework for writing low-level tests of @@ -7,20 +10,5 @@ browser functionality in javascript. It provides a convenient API for making assertions and is intended to work for both simple synchronous tests, and tests of asynchronous behaviour. -### Getting started - -To use `testharness.js` you must include two scripts, in the order given: - -``` html - - -``` - -### 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). +Complete documentation is available in the `docs/` directory of this repository +and on the web at https://web-platform-tests.org/writing-tests/. diff --git a/test/fixtures/wpt/resources/sriharness.js b/test/fixtures/wpt/resources/sriharness.js index b36d29223585ec..d57a1b38465d64 100644 --- a/test/fixtures/wpt/resources/sriharness.js +++ b/test/fixtures/wpt/resources/sriharness.js @@ -32,6 +32,111 @@ SRIScriptTest.prototype.execute = function() { document.body.appendChild(e); }; +function set_extra_attributes(element, attrs) { + // Apply the rest of the attributes, if any. + for (const [attr_name, attr_val] of Object.entries(attrs)) { + element[attr_name] = attr_val; + } +} + +function buildElementFromDestination(resource_url, destination, attrs) { + // Assert: |destination| is a valid destination. + let element; + + // The below switch is responsible for: + // 1. Creating the correct subresource element + // 2. Setting said element's href, src, or fetch-instigating property + // appropriately. + switch (destination) { + case "script": + element = document.createElement(destination); + set_extra_attributes(element, attrs); + element.src = resource_url; + break; + case "style": + element = document.createElement('link'); + set_extra_attributes(element, attrs); + element.rel = 'stylesheet'; + element.href = resource_url; + break; + case "image": + element = document.createElement('img'); + set_extra_attributes(element, attrs); + element.src = resource_url; + break; + default: + assert_unreached("INVALID DESTINATION"); + } + + return element; +} + +// When using SRIPreloadTest, also include /preload/resources/preload_helper.js +// |number_of_requests| is used to ensure that preload requests are actually +// reused as expected. +const SRIPreloadTest = (preload_sri_success, subresource_sri_success, name, + number_of_requests, destination, resource_url, + link_attrs, subresource_attrs) => { + const test = async_test(name); + const link = document.createElement('link'); + + // Early-fail in UAs that do not support `preload` links. + test.step_func(() => { + assert_true(link.relList.supports('preload'), + "This test is automatically failing because the browser does not" + + "support `preload` links."); + })(); + + // Build up the link. + link.rel = 'preload'; + link.as = destination; + link.href = resource_url; + for (const [attr_name, attr_val] of Object.entries(link_attrs)) { + link[attr_name] = attr_val; // This may override `rel` to modulepreload. + } + + // Preload + subresource success and failure loading functions. + const valid_preload_failed = test.step_func(() => + { assert_unreached("Valid preload fired error handler.") }); + const invalid_preload_succeeded = test.step_func(() => + { assert_unreached("Invalid preload load succeeded.") }); + const valid_subresource_failed = test.step_func(() => + { assert_unreached("Valid subresource fired error handler.") }); + const invalid_subresource_succeeded = test.step_func(() => + { assert_unreached("Invalid subresource load succeeded.") }); + const subresource_pass = test.step_func(() => { + verifyNumberOfResourceTimingEntries(resource_url, number_of_requests); + test.done(); + }); + const preload_pass = test.step_func(() => { + const subresource_element = buildElementFromDestination( + resource_url, + destination, + subresource_attrs + ); + + if (subresource_sri_success) { + subresource_element.onload = subresource_pass; + subresource_element.onerror = valid_subresource_failed; + } else { + subresource_element.onload = invalid_subresource_succeeded; + subresource_element.onerror = subresource_pass; + } + + document.body.append(subresource_element); + }); + + if (preload_sri_success) { + link.onload = preload_pass; + link.onerror = valid_preload_failed; + } else { + link.onload = invalid_preload_succeeded; + link.onerror = preload_pass; + } + + document.head.append(link); +} + // tests // Style tests must be done synchronously because they rely on the presence // and absence of global style, which can affect later tests. Thus, instead @@ -63,6 +168,8 @@ SRIStyleTest.prototype.execute = function() { var div = document.createElement("div"); div.className = "testdiv"; var e = document.createElement("link"); + + // The link relation is guaranteed to not be "preload" or "modulepreload". this.attrs.rel = this.attrs.rel || "stylesheet"; for (var key in this.attrs) { if (this.attrs.hasOwnProperty(key)) { @@ -97,3 +204,4 @@ SRIStyleTest.prototype.execute = function() { container.appendChild(e); this.customCallback(e, container); }; + diff --git a/test/fixtures/wpt/resources/testdriver-actions.js b/test/fixtures/wpt/resources/testdriver-actions.js index 43d8b1df00ae4c..870a2e8e266780 100644 --- a/test/fixtures/wpt/resources/testdriver-actions.js +++ b/test/fixtures/wpt/resources/testdriver-actions.js @@ -3,8 +3,10 @@ /** * Builder for creating a sequence of actions + * The default tick duration is set to 16ms, which is one frame time based on + * 60Hz display. */ - function Actions() { + function Actions(defaultTickDuration=16) { this.sourceTypes = new Map([["key", KeySource], ["pointer", PointerSource], ["none", GeneralSource]]); @@ -19,6 +21,7 @@ } this.createSource("none"); this.tickIdx = 0; + this.defaultTickDuration = defaultTickDuration; } Actions.prototype = { @@ -40,7 +43,7 @@ let actions = []; for (let [sourceType, sourceName] of this.sourceOrder) { let source = this.sources.get(sourceType).get(sourceName); - let serialized = source.serialize(this.tickIdx + 1); + let serialized = source.serialize(this.tickIdx + 1, this.defaultTickDuration); if (serialized) { serialized.id = sourceName; actions.push(serialized); @@ -127,9 +130,9 @@ /** * Add a new pointer input source with the given name * - * @param {String} type - Name of the key source + * @param {String} type - Name of the pointer source * @param {String} pointerType - Type of pointing device - * @param {Bool} set - Set source as the default key source + * @param {Bool} set - Set source as the default pointer source * @returns {Actions} */ addPointer: function(name, pointerType="mouse", set=true) { @@ -192,10 +195,16 @@ * Add a pause to the current tick * * @param {Number?} duration - Minimum length of the tick in ms. + * @param {String} sourceType - source type + * @param {String?} sourceName - Named key or pointer source to use or null for the default + * key or pointer source * @returns {Actions} */ - pause: function(duration) { - this.getSource("none").addPause(this, duration); + pause: function(duration=0, sourceType="none", {sourceName=null}={}) { + if (sourceType=="none") + this.getSource("none").addPause(this, duration); + else + this.getSource(sourceType, sourceName).addPause(this, duration); return this; }, @@ -278,17 +287,14 @@ } GeneralSource.prototype = { - serialize: function(tickCount) { - if (!this.actions.size) { - return undefined; - } + serialize: function(tickCount, defaultTickDuration) { let actions = []; let data = {"type": "none", "actions": actions}; for (let i=0; i 0) { + output.beginEllipsis = true; + } + if (upper_bound < arr.length) { + output.endEllipsis = true; + } + return output; + } + 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}); + "lengths differ, expected array ${expected} length ${expectedLength}, got ${actual} length ${actualLength}", + {expected:shorten_array(expected, expected.length - 1), expectedLength:expected.length, + actual:shorten_array(actual, actual.length - 1), actualLength: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}", + "expected property ${i} to be ${expected} but was ${actual} (expected array ${arrayExpected} got ${arrayActual})", {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing", - actual:actual.hasOwnProperty(i) ? "present" : "missing"}); + actual:actual.hasOwnProperty(i) ? "present" : "missing", + arrayExpected:shorten_array(expected, i), arrayActual:shorten_array(actual, i)}); 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]}); + "expected property ${i} to be ${expected} but got ${actual} (expected array ${arrayExpected} got ${arrayActual})", + {i:i, expected:expected[i], actual:actual[i], + arrayExpected:shorten_array(expected, i), arrayActual:shorten_array(actual, i)}); } } expose(assert_array_equals, "assert_array_equals"); @@ -1124,7 +1300,7 @@ policies and contribution forms [3]. "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"}); + 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}", @@ -1270,8 +1446,11 @@ policies and contribution forms [3]. expose(assert_regexp_match, "assert_regexp_match"); function assert_class_string(object, class_string, description) { - assert_equals({}.toString.call(object), "[object " + class_string + "]", - description); + var actual = {}.toString.call(object); + var expected = "[object " + class_string + "]"; + assert(same_value(actual, expected), "assert_class_string", description, + "expected ${expected} but got ${actual}", + {expected:expected, actual:actual}); } expose(assert_class_string, "assert_class_string"); @@ -1293,7 +1472,9 @@ policies and contribution forms [3]. function _assert_inherits(name) { return function (object, property_name, description) { - assert(typeof object === "object" || typeof object === "function", + assert(typeof object === "object" || typeof object === "function" || + // Or has [[IsHTMLDDA]] slot + String(object) === "[object HTMLAllCollection]", name, description, "provided value is not an object"); @@ -1333,47 +1514,153 @@ policies and contribution forms [3]. expose(assert_readonly, "assert_readonly"); /** - * Assert an Exception with the expected code is thrown. + * Assert a JS Error with the expected constructor is thrown. * - * @param {object|number|string} code The expected exception code. + * @param {object} constructor The expected exception constructor. * @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) + function assert_throws_js(constructor, func, description) + { + assert_throws_js_impl(constructor, func, description, + "assert_throws_js"); + } + expose(assert_throws_js, "assert_throws_js"); + + /** + * Like assert_throws_js but allows specifying the assertion type + * (assert_throws_js or promise_rejects_js, in practice). + */ + function assert_throws_js_impl(constructor, func, description, + assertion_type) { try { func.call(this); - assert(false, "assert_throws", description, + assert(false, assertion_type, description, "${func} did not throw", {func:func}); } catch (e) { if (e instanceof AssertionError) { throw e; } + // Basic sanity-checks on the thrown exception. assert(typeof e === "object", - "assert_throws", description, + assertion_type, description, "${func} threw ${e} with type ${type}, not an object", {func:func, e:e, type:typeof e}); assert(e !== null, - "assert_throws", description, + assertion_type, description, "${func} threw null, not an object", {func:func}); - if (code === null) { - throw new AssertionError('Test bug: need to pass exception to assert_throws()'); + // Basic sanity-check on the passed-in constructor + assert(typeof constructor == "function", + assertion_type, description, + "${constructor} is not a constructor", + {constructor:constructor}); + var obj = constructor; + while (obj) { + if (typeof obj === "function" && + obj.name === "Error") { + break; + } + obj = Object.getPrototypeOf(obj); } - 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; + assert(obj != null, + assertion_type, description, + "${constructor} is not an Error subtype", + {constructor:constructor}); + + // And checking that our exception is reasonable + assert(e.constructor === constructor && + e.name === constructor.name, + assertion_type, description, + "${func} threw ${actual} (${actual_name}) expected instance of ${expected} (${expected_name})", + {func:func, actual:e, actual_name:e.name, + expected:constructor, + expected_name:constructor.name}); + } + } + + /** + * Assert a DOMException with the expected type is thrown. + * + * @param {number|string} type The expected exception name or code. See the + * table of names and codes at + * https://heycam.github.io/webidl/#dfn-error-names-table + * If a number is passed it should be one of the numeric code values + * in that table (e.g. 3, 4, etc). If a string is passed it can + * either be an exception name (e.g. "HierarchyRequestError", + * "WrongDocumentError") or the name of the corresponding error code + * (e.g. "HIERARCHY_REQUEST_ERR", "WRONG_DOCUMENT_ERR"). + * + * For the remaining arguments, there are two ways of calling + * promise_rejects_dom: + * + * 1) If the DOMException is expected to come from the current global, the + * second argument should be the function expected to throw and a third, + * optional, argument is the assertion description. + * + * 2) If the DOMException is expected to come from some other global, the + * second argument should be the DOMException constructor from that global, + * the third argument the function expected to throw, and the fourth, optional, + * argument the assertion description. + */ + function assert_throws_dom(type, funcOrConstructor, descriptionOrFunc, maybeDescription) + { + let constructor, func, description; + if (funcOrConstructor.name === "DOMException") { + constructor = funcOrConstructor; + func = descriptionOrFunc; + description = maybeDescription; + } else { + constructor = self.DOMException; + func = funcOrConstructor; + description = descriptionOrFunc; + assert(maybeDescription === undefined, + "Too many args pased to no-constructor version of assert_throws_dom"); + } + assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor) + } + expose(assert_throws_dom, "assert_throws_dom"); + + /** + * Similar to assert_throws_dom but allows specifying the assertion type + * (assert_throws_dom or promise_rejects_dom, in practice). The + * "constructor" argument must be the DOMException constructor from the + * global we expect the exception to come from. + */ + function assert_throws_dom_impl(type, func, description, assertion_type, constructor) + { + try { + func.call(this); + assert(false, assertion_type, description, + "${func} did not throw", {func:func}); + } catch (e) { + if (e instanceof AssertionError) { + throw e; } - var code_name_map = { + // Basic sanity-checks on the thrown exception. + assert(typeof e === "object", + assertion_type, description, + "${func} threw ${e} with type ${type}, not an object", + {func:func, e:e, type:typeof e}); + + assert(e !== null, + assertion_type, description, + "${func} threw null, not an object", + {func:func}); + + // Sanity-check our type + assert(typeof type == "number" || + typeof type == "string", + assertion_type, description, + "${type} is not a number or string", + {type:type}); + + var codename_name_map = { INDEX_SIZE_ERR: 'IndexSizeError', HIERARCHY_REQUEST_ERR: 'HierarchyRequestError', WRONG_DOCUMENT_ERR: 'WrongDocumentError', @@ -1398,8 +1685,6 @@ policies and contribution forms [3]. DATA_CLONE_ERR: 'DataCloneError' }; - var name = code in code_name_map ? code_name_map[code] : code; - var name_code_map = { IndexSizeError: 1, HierarchyRequestError: 3, @@ -1436,11 +1721,32 @@ policies and contribution forms [3]. NotAllowedError: 0 }; - if (!(name in name_code_map)) { - throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()'); + var code_name_map = {}; + for (var key in name_code_map) { + if (name_code_map[key] > 0) { + code_name_map[name_code_map[key]] = key; + } } - var required_props = { code: name_code_map[name] }; + var required_props = {}; + var name; + + if (typeof type === "number") { + if (type === 0) { + throw new AssertionError('Test bug: ambiguous DOMException code 0 passed to assert_throws_dom()'); + } else if (!(type in code_name_map)) { + throw new AssertionError('Test bug: unrecognized DOMException code "' + type + '" passed to assert_throws_dom()'); + } + name = code_name_map[type]; + required_props.code = type; + } else if (typeof type === "string") { + name = type in codename_name_map ? codename_name_map[type] : type; + if (!(name in name_code_map)) { + throw new AssertionError('Test bug: unrecognized DOMException code name or name "' + type + '" passed to assert_throws_dom()'); + } + + required_props.code = name_code_map[name]; + } if (required_props.code === 0 || ("name" in e && @@ -1450,20 +1756,59 @@ policies and contribution forms [3]. 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}", + assertion_type, description, + "${func} threw ${e} that is not a DOMException " + type + ": property ${prop} is equal to ${actual}, expected ${expected}", {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]}); } + + // Check that the exception is from the right global. This check is last + // so more specific, and more informative, checks on the properties can + // happen in case a totally incorrect exception is thrown. + assert(e.constructor === constructor, + assertion_type, description, + "${func} threw an exception from the wrong global", + {func}); + + } + } + + /** + * Assert the provided value is thrown. + * + * @param {value} exception The expected exception. + * @param {Function} func Function which should throw. + * @param {string} description Error description for the case that the error is not thrown. + */ + function assert_throws_exactly(exception, func, description) + { + assert_throws_exactly_impl(exception, func, description, + "assert_throws_exactly"); + } + expose(assert_throws_exactly, "assert_throws_exactly"); + + /** + * Like assert_throws_exactly but allows specifying the assertion type + * (assert_throws_exactly or promise_rejects_exactly, in practice). + */ + function assert_throws_exactly_impl(exception, func, description, + assertion_type) + { + try { + func.call(this); + assert(false, assertion_type, description, + "${func} did not throw", {func:func}); + } catch (e) { + if (e instanceof AssertionError) { + throw e; + } + + assert(same_value(e, exception), assertion_type, description, + "${func} threw ${e} but we expected it to throw ${exception}", + {func:func, e:e, exception:exception}); } } - expose(assert_throws, "assert_throws"); function assert_unreached(description) { assert(false, "assert_unreached", description, @@ -1492,6 +1837,43 @@ policies and contribution forms [3]. } expose(assert_any, "assert_any"); + /** + * Assert that a feature is implemented, based on a 'truthy' condition. + * + * This function should be used to early-exit from tests in which there is + * no point continuing without support for a non-optional spec or spec + * feature. For example: + * + * assert_implements(window.Foo, 'Foo is not supported'); + * + * @param {object} condition The truthy value to test + * @param {string} description Error description for the case that the condition is not truthy. + */ + function assert_implements(condition, description) { + assert(!!condition, "assert_implements", description); + } + expose(assert_implements, "assert_implements") + + /** + * Assert that an optional feature is implemented, based on a 'truthy' condition. + * + * This function should be used to early-exit from tests in which there is + * no point continuing without support for an explicitly optional spec or + * spec feature. For example: + * + * assert_implements_optional(video.canPlayType("video/webm"), + * "webm video playback not supported"); + * + * @param {object} condition The truthy value to test + * @param {string} description Error description for the case that the condition is not truthy. + */ + function assert_implements_optional(condition, description) { + if (!condition) { + throw new OptionalFeatureUnsupportedError(description); + } + } + expose(assert_implements_optional, "assert_implements_optional") + function Test(name, properties) { if (tests.file_is_test && tests.tests.length) { @@ -1506,7 +1888,7 @@ policies and contribution forms [3]. this.timeout_id = null; this.index = null; - this.properties = properties; + this.properties = properties || {}; this.timeout_length = settings.test_timeout; if (this.timeout_length !== null) { this.timeout_length *= tests.timeout_multiplier; @@ -1536,7 +1918,8 @@ policies and contribution forms [3]. PASS:0, FAIL:1, TIMEOUT:2, - NOTRUN:3 + NOTRUN:3, + PRECONDITION_FAILED:4 }; Test.prototype = merge({}, Test.statuses); @@ -1596,10 +1979,11 @@ policies and contribution forms [3]. if (this.phase >= this.phases.HAS_RESULT) { return; } + var status = e instanceof OptionalFeatureUnsupportedError ? this.PRECONDITION_FAILED : this.FAIL; 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.set_status(status, message, stack); this.phase = this.phases.HAS_RESULT; this.done(); } @@ -2070,7 +2454,8 @@ policies and contribution forms [3]. TestsStatus.statuses = { OK:0, ERROR:1, - TIMEOUT:2 + TIMEOUT:2, + PRECONDITION_FAILED:3 }; TestsStatus.prototype = merge({}, TestsStatus.statuses); @@ -2111,6 +2496,10 @@ policies and contribution forms [3]. this.allow_uncaught_exception = false; this.file_is_test = false; + // This value is lazily initialized in order to avoid introducing a + // dependency on ECMAScript 2015 Promises to all tests. + this.promise_tests = null; + this.promise_setup_called = false; this.timeout_multiplier = 1; this.timeout_length = test_environment.test_timeout(); @@ -2161,6 +2550,8 @@ policies and contribution forms [3]. { clearTimeout(this.timeout_id); } + } else if (p == "single_test" && value) { + this.set_file_is_test(); } else if (p == "timeout_multiplier") { this.timeout_multiplier = value; if (this.timeout_length) { @@ -2174,9 +2565,10 @@ policies and contribution forms [3]. try { func(); } catch (e) { - this.status.status = this.status.ERROR; + this.status.status = e instanceof OptionalFeatureUnsupportedError ? this.status.PRECONDITION_FAILED : this.status.ERROR; this.status.message = String(e); this.status.stack = e.stack ? e.stack : null; + this.complete(); } } this.set_timeout(); @@ -2467,24 +2859,8 @@ policies and contribution forms [3]. 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"}); - } + message_port = navigator.serviceWorker; + worker.postMessage({type: "connect"}); } else if (is_shared_worker(worker)) { message_port = worker.port; message_port.start(); @@ -2731,12 +3107,14 @@ policies and contribution forms [3]. status_text_harness[harness_status.OK] = "OK"; status_text_harness[harness_status.ERROR] = "Error"; status_text_harness[harness_status.TIMEOUT] = "Timeout"; + status_text_harness[harness_status.PRECONDITION_FAILED] = "Optional Feature Unsupported"; 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"; + status_text[Test.prototype.PRECONDITION_FAILED] = "Optional Feature Unsupported"; var status_number = {}; forEach(tests, @@ -3056,9 +3434,6 @@ policies and contribution forms [3]. */ 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); @@ -3121,6 +3496,13 @@ policies and contribution forms [3]. return lines.slice(i).join("\n"); } + function OptionalFeatureUnsupportedError(message) + { + AssertionError.call(this, message); + } + OptionalFeatureUnsupportedError.prototype = Object.create(AssertionError.prototype); + expose(OptionalFeatureUnsupportedError, "OptionalFeatureUnsupportedError"); + function make_message(function_name, description, error, substitutions) { for (var p in substitutions) { @@ -3348,38 +3730,56 @@ policies and contribution forms [3]. var tests = new Tests(); if (global_scope.addEventListener) { - var error_handler = function(e) { - if (tests.tests.length === 0 && !tests.allow_uncaught_exception) { - tests.set_file_is_test(); - } - - var stack; - if (e.error && e.error.stack) { - stack = e.error.stack; - } else { - stack = e.filename + ":" + e.lineno + ":" + e.colno; - } - + var error_handler = function(error, message, stack) { + var optional_unsupported = error instanceof OptionalFeatureUnsupportedError; if (tests.file_is_test) { var test = tests.tests[0]; if (test.phase >= test.phases.HAS_RESULT) { return; } - test.set_status(test.FAIL, e.message, stack); + var status = optional_unsupported ? test.PRECONDITION_FAILED : test.FAIL; + test.set_status(status, message, stack); test.phase = test.phases.HAS_RESULT; - // The following function invocation is superfluous. - // TODO: Remove. - test.done(); } else if (!tests.allow_uncaught_exception) { - tests.status.status = tests.status.ERROR; - tests.status.message = e.message; + var status = optional_unsupported ? tests.status.PRECONDITION_FAILED : tests.status.ERROR; + tests.status.status = status; + tests.status.message = message; tests.status.stack = stack; } - done(); + + // Do not transition to the "complete" phase if the test has been + // configured to allow uncaught exceptions. This gives the test an + // opportunity to define subtests based on the exception reporting + // behavior. + if (!tests.allow_uncaught_exception) { + done(); + } }; - addEventListener("error", error_handler, false); - addEventListener("unhandledrejection", function(e){ error_handler(e.reason); }, false); + addEventListener("error", function(e) { + var message = e.message; + var stack; + if (e.error && e.error.stack) { + stack = e.error.stack; + } else { + stack = e.filename + ":" + e.lineno + ":" + e.colno; + } + error_handler(e.error, message, stack); + }, false); + + addEventListener("unhandledrejection", function(e) { + var message; + if (e.reason && e.reason.message) { + message = "Unhandled rejection: " + e.reason.message; + } else { + message = "Unhandled rejection"; + } + var stack; + if (e.reason && e.reason.stack) { + stack = e.reason.stack; + } + error_handler(e.reason, message, stack); + }, false); } test_environment.on_tests_ready(); @@ -3416,7 +3816,7 @@ table#results {\ \ table#results th:first-child,\ table#results td:first-child {\ - width:4em;\ + width:8em;\ }\ \ table#results th:last-child,\ @@ -3457,7 +3857,11 @@ tr.notrun > td:first-child {\ color:blue;\ }\ \ -.pass > td:first-child, .fail > td:first-child, .timeout > td:first-child, .notrun > td:first-child {\ +tr.optionalunsupported > td:first-child {\ + color:blue;\ +}\ +\ +.pass > td:first-child, .fail > td:first-child, .timeout > td:first-child, .notrun > td:first-child, .optionalunsupported > td:first-child {\ font-variant:small-caps;\ }\ \ diff --git a/test/fixtures/wpt/resources/webidl2/lib/webidl2.js b/test/fixtures/wpt/resources/webidl2/lib/webidl2.js index ef519c09df6d6d..d707905fa63e16 100644 --- a/test/fixtures/wpt/resources/webidl2/lib/webidl2.js +++ b/test/fixtures/wpt/resources/webidl2/lib/webidl2.js @@ -1,970 +1,3223 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["WebIDL2"] = factory(); + else + root["WebIDL2"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + "use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _lib_webidl2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "parse", function() { return _lib_webidl2_js__WEBPACK_IMPORTED_MODULE_0__["parse"]; }); -(() => { - // 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 - }; +/* harmony import */ var _lib_writer_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "write", function() { return _lib_writer_js__WEBPACK_IMPORTED_MODULE_1__["write"]; }); - 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 }); - } +/* harmony import */ var _lib_validator_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(31); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "validate", function() { return _lib_validator_js__WEBPACK_IMPORTED_MODULE_2__["validate"]; }); - 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"); - } +/* harmony import */ var _lib_tokeniser_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "WebIDLParseError", function() { return _lib_tokeniser_js__WEBPACK_IMPORTED_MODULE_3__["WebIDLParseError"]; }); - 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"; +/***/ }), +/* 1 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - const EMPTY_OPERATION = Object.freeze({ - type: "operation", - getter: false, - setter: false, - deleter: false, - static: false, - stringifier: false - }); +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "parse", function() { return parse; }); +/* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var _productions_enum_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(15); +/* harmony import */ var _productions_includes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(16); +/* harmony import */ var _productions_extended_attributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8); +/* harmony import */ var _productions_typedef_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(17); +/* harmony import */ var _productions_callback_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(18); +/* harmony import */ var _productions_interface_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(19); +/* harmony import */ var _productions_mixin_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(25); +/* harmony import */ var _productions_dictionary_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(26); +/* harmony import */ var _productions_namespace_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(28); +/* harmony import */ var _productions_callback_interface_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(29); +/* harmony import */ var _productions_helpers_js__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(4); - 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 }; - } +/** + * @param {Tokeniser} tokeniser + * @param {object} options + * @param {boolean} [options.concrete] + */ +function parseByTokens(tokeniser, options) { + const source = tokeniser.source; - function type_suffix(obj) { - obj.nullable = !!consume("?"); - if (probe("?")) error("Can't nullable more than once"); - } + function error(str) { + tokeniser.error(str); + } - 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 consume(...candidates) { + return tokeniser.consume(...candidates); + } - 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 callback() { + const callback = consume("callback"); + if (!callback) return; + if (tokeniser.probe("interface")) { + return _productions_callback_interface_js__WEBPACK_IMPORTED_MODULE_10__["CallbackInterface"].parse(tokeniser, callback); } + return _productions_callback_js__WEBPACK_IMPORTED_MODULE_5__["CallbackFunction"].parse(tokeniser, callback); + } - function type(typeName) { - return single_type(typeName) || union_type(typeName); - } + function interface_(opts) { + const base = consume("interface"); + if (!base) return; + const ret = _productions_mixin_js__WEBPACK_IMPORTED_MODULE_7__["Mixin"].parse(tokeniser, base, opts) || + _productions_interface_js__WEBPACK_IMPORTED_MODULE_6__["Interface"].parse(tokeniser, base, opts) || + error("Interface has no proper body"); + return ret; + } - 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 partial() { + const partial = consume("partial"); + if (!partial) return; + return _productions_dictionary_js__WEBPACK_IMPORTED_MODULE_8__["Dictionary"].parse(tokeniser, { partial }) || + interface_({ partial }) || + _productions_namespace_js__WEBPACK_IMPORTED_MODULE_9__["Namespace"].parse(tokeniser, { partial }) || + error("Partial doesn't apply to anything"); + } - 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; + function definition() { + return callback() || + interface_() || + partial() || + _productions_dictionary_js__WEBPACK_IMPORTED_MODULE_8__["Dictionary"].parse(tokeniser) || + _productions_enum_js__WEBPACK_IMPORTED_MODULE_1__["Enum"].parse(tokeniser) || + _productions_typedef_js__WEBPACK_IMPORTED_MODULE_4__["Typedef"].parse(tokeniser) || + _productions_includes_js__WEBPACK_IMPORTED_MODULE_2__["Includes"].parse(tokeniser) || + _productions_namespace_js__WEBPACK_IMPORTED_MODULE_9__["Namespace"].parse(tokeniser); + } + + function definitions() { + if (!source.length) return []; + const defs = []; + while (true) { + const ea = _productions_extended_attributes_js__WEBPACK_IMPORTED_MODULE_3__["ExtendedAttributes"].parse(tokeniser); + const def = definition(); + if (!def) { + if (ea.length) error("Stray extended attributes"); + break; } - return ret; + Object(_productions_helpers_js__WEBPACK_IMPORTED_MODULE_11__["autoParenter"])(def).extAttrs = ea; + defs.push(def); + } + const eof = consume("eof"); + if (options.concrete) { + defs.push(eof); } + return defs; + } + const res = definitions(); + if (tokeniser.position < source.length) error("Unrecognised tokens"); + return res; +} + +/** + * @param {string} str + * @param {object} [options] + * @param {*} [options.sourceName] + * @param {boolean} [options.concrete] + */ +function parse(str, options = {}) { + const tokeniser = new _tokeniser_js__WEBPACK_IMPORTED_MODULE_0__["Tokeniser"](str); + if (typeof options.sourceName !== "undefined") { + tokeniser.source.name = options.sourceName; + } + return parseByTokens(tokeniser, options); +} - 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); - } + +/***/ }), +/* 2 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "typeNameKeywords", function() { return typeNameKeywords; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "stringTypes", function() { return stringTypes; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "argumentNameKeywords", function() { return argumentNameKeywords; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Tokeniser", function() { return Tokeniser; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WebIDLParseError", function() { return WebIDLParseError; }); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3); +/* harmony import */ var _productions_helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +// 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. + "decimal": /-?(?=[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 typeNameKeywords = [ + "ArrayBuffer", + "DataView", + "Int8Array", + "Int16Array", + "Int32Array", + "Uint8Array", + "Uint16Array", + "Uint32Array", + "Uint8ClampedArray", + "Float32Array", + "Float64Array", + "any", + "object", + "symbol" +]; + +const stringTypes = [ + "ByteString", + "DOMString", + "USVString" +]; + +const argumentNameKeywords = [ + "async", + "attribute", + "callback", + "const", + "constructor", + "deleter", + "dictionary", + "enum", + "getter", + "includes", + "inherit", + "interface", + "iterable", + "maplike", + "namespace", + "partial", + "required", + "setlike", + "setter", + "static", + "stringifier", + "typedef", + "unrestricted" +]; + +const nonRegexTerminals = [ + "-Infinity", + "FrozenArray", + "Infinity", + "NaN", + "Promise", + "boolean", + "byte", + "double", + "false", + "float", + "long", + "mixin", + "null", + "octet", + "optional", + "or", + "readonly", + "record", + "sequence", + "short", + "true", + "unsigned", + "void" +].concat(argumentNameKeywords, stringTypes, typeNameKeywords); + +const punctuations = [ + "(", + ")", + ",", + "...", + ":", + ";", + "<", + "=", + ">", + "?", + "[", + "]", + "{", + "}" +]; + +const reserved = [ + // "constructor" is now a keyword + "_constructor", + "toString", + "_toString", +]; + +/** + * @typedef {ArrayItemType>} Token + * @param {string} str + */ +function tokenise(str) { + const tokens = []; + let lastCharIndex = 0; + let trivia = ""; + let line = 1; + let index = 0; + while (lastCharIndex < str.length) { + const nextChar = str.charAt(lastCharIndex); + let result = -1; + + if (/[\t\n\r ]/.test(nextChar)) { + result = attemptTokenMatch("whitespace", { noFlushTrivia: true }); + } else if (nextChar === '/') { + result = attemptTokenMatch("comment", { noFlushTrivia: true }); } - 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")); + if (result !== -1) { + const currentTrivia = tokens.pop().value; + line += (currentTrivia.match(/\n/g) || []).length; + trivia += currentTrivia; + index -= 1; + } else if (/[-0-9.A-Z_a-z]/.test(nextChar)) { + result = attemptTokenMatch("decimal"); + if (result === -1) { + result = attemptTokenMatch("integer"); } - 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; + if (result === -1) { + result = attemptTokenMatch("identifier"); + const lastIndex = tokens.length - 1; + const token = tokens[lastIndex]; + if (result !== -1) { + if (reserved.includes(token.value)) { + const message = `${Object(_productions_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(token.value)} is a reserved identifier and must not be used.`; + throw new WebIDLParseError(Object(_error_js__WEBPACK_IMPORTED_MODULE_0__["syntaxError"])(tokens, lastIndex, null, message)); + } else if (nonRegexTerminals.includes(token.value)) { + token.type = token.value; + } } } + } else if (nextChar === '"') { + result = attemptTokenMatch("string"); } - 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; + for (const punctuation of punctuations) { + if (str.startsWith(punctuation, lastCharIndex)) { + tokens.push({ type: punctuation, value: punctuation, trivia, line, index }); + trivia = ""; + lastCharIndex += punctuation.length; + result = lastCharIndex; + break; } } - 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; + // other as the last try + if (result === -1) { + result = attemptTokenMatch("other"); } + if (result === -1) { + throw new Error("Token stream not progressing"); + } + lastCharIndex = result; + index += 1; + } - 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" }); + // remaining trivia as eof + tokens.push({ + type: "eof", + value: "", + trivia + }); + + return tokens; + + /** + * @param {keyof typeof tokenRe} type + * @param {object} options + * @param {boolean} [options.noFlushTrivia] + */ + function attemptTokenMatch(type, { noFlushTrivia } = {}) { + const re = tokenRe[type]; + re.lastIndex = lastCharIndex; + const result = re.exec(str); + if (result) { + tokens.push({ type, value: result[0], trivia, line, index }); + if (!noFlushTrivia) { + trivia = ""; } + return re.lastIndex; } + return -1; + } +} + +class Tokeniser { + /** + * @param {string} idl + */ + constructor(idl) { + this.source = tokenise(idl); + this.position = 0; + } - 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; + /** + * @param {string} message + * @return {never} + */ + error(message) { + throw new WebIDLParseError(Object(_error_js__WEBPACK_IMPORTED_MODULE_0__["syntaxError"])(this.source, this.position, this.current, message)); + } + + /** + * @param {string} type + */ + probe(type) { + return this.source.length > this.position && this.source[this.position].type === type; + } + + /** + * @param {...string} candidates + */ + consume(...candidates) { + for (const type of candidates) { + if (!this.probe(type)) continue; + const token = this.source[this.position]; + this.position++; + return token; } + } + + /** + * @param {number} position + */ + unconsume(position) { + this.position = position; + } +} + +class WebIDLParseError extends Error { + /** + * @param {object} options + * @param {string} options.message + * @param {string} options.bareMessage + * @param {string} options.context + * @param {number} options.line + * @param {*} options.sourceName + * @param {string} options.input + * @param {*[]} options.tokens + */ + constructor({ message, bareMessage, context, line, sourceName, input, tokens }) { + super(message); + + this.name = "WebIDLParseError"; // not to be mangled + this.bareMessage = bareMessage; + this.context = context; + this.line = line; + this.sourceName = sourceName; + this.input = input; + this.tokens = tokens; + } +} + + +/***/ }), +/* 3 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "syntaxError", function() { return syntaxError; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "validationError", function() { return validationError; }); +/** + * @param {string} text + */ +function lastLine(text) { + const splitted = text.split("\n"); + return splitted[splitted.length - 1]; +} + +/** + * @typedef {object} WebIDL2ErrorOptions + * @property {"error" | "warning"} [level] + * @property {Function} [autofix] + * + * @param {string} message error message + * @param {"Syntax" | "Validation"} kind error type + * @param {WebIDL2ErrorOptions} [options] + */ +function error(source, position, current, message, kind, { level = "error", autofix, ruleName } = {}) { + /** + * @param {number} count + */ + function sliceTokens(count) { + return count > 0 ? + source.slice(position, position + count) : + source.slice(Math.max(position + count, 0), position); + } - 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 tokensToText(inputs, { precedes } = {}) { + const text = inputs.map(t => t.trivia + t.value).join(""); + const nextToken = source[position]; + if (nextToken.type === "eof") { + return text; } + if (precedes) { + return text + nextToken.trivia; + } + return text.slice(nextToken.trivia.length); + } - 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; + const maxTokens = 5; // arbitrary but works well enough + const line = + source[position].type !== "eof" ? source[position].line : + source.length > 1 ? source[position - 1].line : + 1; + + const precedingLastLine = lastLine( + tokensToText(sliceTokens(-maxTokens), { precedes: true }) + ); + + const subsequentTokens = sliceTokens(maxTokens); + const subsequentText = tokensToText(subsequentTokens); + const subsequentFirstLine = subsequentText.split("\n")[0]; + + const spaced = " ".repeat(precedingLastLine.length) + "^"; + const sourceContext = precedingLastLine + subsequentFirstLine + "\n" + spaced; + + const contextType = kind === "Syntax" ? "since" : "inside"; + const inSourceName = source.name ? ` in ${source.name}` : ""; + const grammaticalContext = (current && current.name) ? `, ${contextType} \`${current.partial ? "partial " : ""}${current.type} ${current.name}\`` : ""; + const context = `${kind} error at line ${line}${inSourceName}${grammaticalContext}:\n${sourceContext}`; + return { + message: `${context} ${message}`, + bareMessage: message, + context, + line, + sourceName: source.name, + level, + ruleName, + autofix, + input: subsequentText, + tokens: subsequentTokens + }; +} + +/** + * @param {string} message error message + */ +function syntaxError(source, position, current, message) { + return error(source, position, current, message, "Syntax"); +} + +/** + * @param {string} message error message + * @param {WebIDL2ErrorOptions} [options] + */ +function validationError(token, current, ruleName, message, options = {}) { + options.ruleName = ruleName; + return error(current.source, token.index, current, message, "Validation", options); +} + + +/***/ }), +/* 4 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "unescape", function() { return unescape; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "list", function() { return list; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "const_value", function() { return const_value; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "const_data", function() { return const_data; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "primitive_type", function() { return primitive_type; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "argument_list", function() { return argument_list; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "type_with_extended_attributes", function() { return type_with_extended_attributes; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "return_type", function() { return return_type; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "stringifier", function() { return stringifier; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getLastIndentation", function() { return getLastIndentation; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getMemberIndentation", function() { return getMemberIndentation; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "autofixAddExposedWindow", function() { return autofixAddExposedWindow; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getFirstToken", function() { return getFirstToken; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findLastIndex", function() { return findLastIndex; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "autoParenter", function() { return autoParenter; }); +/* harmony import */ var _type_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5); +/* harmony import */ var _argument_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8); +/* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(13); +/* harmony import */ var _attribute_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(14); +/* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(2); + + + + + + + +/** + * @param {string} identifier + */ +function unescape(identifier) { + return identifier.startsWith('_') ? identifier.slice(1) : identifier; +} + +/** + * Parses comma-separated list + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {object} args + * @param {Function} args.parser parser function for each item + * @param {boolean} [args.allowDangler] whether to allow dangling comma + * @param {string} [args.listName] the name to be shown on error messages + */ +function list(tokeniser, { parser, allowDangler, listName = "list" }) { + const first = parser(tokeniser); + if (!first) { + return []; + } + first.tokens.separator = tokeniser.consume(","); + const items = [first]; + while (first.tokens.separator) { + const item = parser(tokeniser); + if (!item) { + if (!allowDangler) { + tokeniser.error(`Trailing comma in ${listName}`); } - return arr; + break; } - - 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; + item.tokens.separator = tokeniser.consume(","); + items.push(item); + if (!item.tokens.separator) break; + } + return items; +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function const_value(tokeniser) { + return tokeniser.consume("true", "false", "Infinity", "-Infinity", "NaN", "decimal", "integer"); +} + +/** + * @param {object} token + * @param {string} token.type + * @param {string} token.value + */ +function const_data({ type, value }) { + switch (type) { + case "true": + case "false": + return { type: "boolean", value: type === "true" }; + case "Infinity": + case "-Infinity": + return { type: "Infinity", negative: type.startsWith("-") }; + case "[": + return { type: "sequence", value: [] }; + case "{": + return { type: "dictionary" }; + case "decimal": + case "integer": + return { type: "number", value }; + case "string": + return { type: "string", value: value.slice(1, -1) }; + default: + return { type }; + } +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function primitive_type(tokeniser) { + function integer_type() { + const prefix = tokeniser.consume("unsigned"); + const base = tokeniser.consume("short", "long"); + if (base) { + const postfix = tokeniser.consume("long"); + return new _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"]({ source, tokens: { prefix, base, postfix } }); } + if (prefix) tokeniser.error("Failed to parse integer type"); + } - function readonly_iterable_type() { - if (consume("maplike")) return "maplike"; - else if (consume("setlike")) return "setlike"; - else return; + function decimal_type() { + const prefix = tokeniser.consume("unrestricted"); + const base = tokeniser.consume("float", "double"); + if (base) { + return new _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"]({ source, tokens: { prefix, base } }); } + if (prefix) tokeniser.error("Failed to parse float type"); + } - function iterable() { - const start_position = consume_position; - const ret = { type: null, idlType: null, readonly: false }; - if (consume("readonly")) { - ret.readonly = true; + const { source } = tokeniser; + const num_type = integer_type(tokeniser) || decimal_type(tokeniser); + if (num_type) return num_type; + const base = tokeniser.consume("boolean", "byte", "octet"); + if (base) { + return new _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"]({ source, tokens: { base } }); + } +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function argument_list(tokeniser) { + return list(tokeniser, { parser: _argument_js__WEBPACK_IMPORTED_MODULE_1__["Argument"].parse, listName: "arguments list" }); +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} typeName + */ +function type_with_extended_attributes(tokeniser, typeName) { + const extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["ExtendedAttributes"].parse(tokeniser); + const ret = _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"].parse(tokeniser, typeName); + if (ret) autoParenter(ret).extAttrs = extAttrs; + return ret; +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} typeName + */ +function return_type(tokeniser, typeName) { + const typ = _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"].parse(tokeniser, typeName || "return-type"); + if (typ) { + return typ; + } + const voidToken = tokeniser.consume("void"); + if (voidToken) { + const ret = new _type_js__WEBPACK_IMPORTED_MODULE_0__["Type"]({ source: tokeniser.source, tokens: { base: voidToken } }); + ret.type = "return-type"; + return ret; + } +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function stringifier(tokeniser) { + const special = tokeniser.consume("stringifier"); + if (!special) return; + const member = _attribute_js__WEBPACK_IMPORTED_MODULE_4__["Attribute"].parse(tokeniser, { special }) || + _operation_js__WEBPACK_IMPORTED_MODULE_3__["Operation"].parse(tokeniser, { special }) || + tokeniser.error("Unterminated stringifier"); + return member; +} + +/** + * @param {string} str + */ +function getLastIndentation(str) { + const lines = str.split("\n"); + // the first line visually binds to the preceding token + if (lines.length) { + const match = lines[lines.length - 1].match(/^\s+/); + if (match) { + return match[0]; + } + } + return ""; +} + +/** + * @param {string} parentTrivia + */ +function getMemberIndentation(parentTrivia) { + const indentation = getLastIndentation(parentTrivia); + const indentCh = indentation.includes("\t") ? "\t" : " "; + return indentation + indentCh; +} + +/** + * @param {object} def + * @param {import("./extended-attributes.js").ExtendedAttributes} def.extAttrs + */ +function autofixAddExposedWindow(def) { + return () => { + if (def.extAttrs.length){ + const tokeniser = new _tokeniser_js__WEBPACK_IMPORTED_MODULE_5__["Tokeniser"]("Exposed=Window,"); + const exposed = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["SimpleExtendedAttribute"].parse(tokeniser); + exposed.tokens.separator = tokeniser.consume(","); + const existing = def.extAttrs[0]; + if (!/^\s/.test(existing.tokens.name.trivia)) { + existing.tokens.name.trivia = ` ${existing.tokens.name.trivia}`; } - const consumeItType = ret.readonly ? readonly_iterable_type : iterable_type; - - const ittype = consumeItType(); - if (!ittype) { - unconsume(start_position); - return; + def.extAttrs.unshift(exposed); + } else { + autoParenter(def).extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["ExtendedAttributes"].parse(new _tokeniser_js__WEBPACK_IMPORTED_MODULE_5__["Tokeniser"]("[Exposed=Window]")); + const trivia = def.tokens.base.trivia; + def.extAttrs.tokens.open.trivia = trivia; + def.tokens.base.trivia = `\n${getLastIndentation(trivia)}`; + } + }; +} + +/** + * Get the first syntax token for the given IDL object. + * @param {*} data + */ +function getFirstToken(data) { + if (data.extAttrs.length) { + return data.extAttrs.tokens.open; + } + if (data.type === "operation" && !data.special) { + return getFirstToken(data.idlType); + } + const tokens = Object.values(data.tokens).sort((x, y) => x.index - y.index); + return tokens[0]; +} + +/** + * @template T + * @param {T[]} array + * @param {(item: T) => boolean} predicate + */ +function findLastIndex(array, predicate) { + const index = array.slice().reverse().findIndex(predicate); + if (index === -1) { + return index; + } + return array.length - index - 1; +} + +/** + * Returns a proxy that auto-assign `parent` field. + * @template T + * @param {T} data + * @param {*} [parent] The object that will be assigned to `parent`. + * If absent, it will be `data` by default. + * @return {T} + */ +function autoParenter(data, parent) { + if (!parent) { + // Defaults to `data` unless specified otherwise. + parent = data; + } + if (!data) { + // This allows `autoParenter(undefined)` which again allows + // `autoParenter(parse())` where the function may return nothing. + return data; + } + return new Proxy(data, { + get(target, p) { + const value = target[p]; + if (Array.isArray(value)) { + // Wraps the array so that any added items will also automatically + // get their `parent` values. + return autoParenter(value, target); } - - 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()); + return value; + }, + set(target, p, value) { + target[p] = value; + if (!value) { + return true; + } else if (Array.isArray(value)) { + // Assigning an array will add `parent` to its items. + for (const item of value) { + if (typeof item.parent !== "undefined") { + item.parent = parent; } - 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); + } else if (typeof value.parent !== "undefined") { + value.parent = parent; } + return true; } + }); +} - 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); - } + +/***/ }), +/* 5 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Type", function() { return Type; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +/* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3); +/* harmony import */ var _validators_helpers_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7); +/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(8); + + + + + + + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} typeName + */ +function generic_type(tokeniser, typeName) { + const base = tokeniser.consume("FrozenArray", "Promise", "sequence", "record"); + if (!base) { + return; + } + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Type({ source: tokeniser.source, tokens: { base } })); + ret.tokens.open = tokeniser.consume("<") || tokeniser.error(`No opening bracket after ${base.type}`); + switch (base.type) { + case "Promise": { + if (tokeniser.probe("[")) tokeniser.error("Promise type cannot have extended attribute"); + const subtype = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["return_type"])(tokeniser, typeName) || tokeniser.error("Missing Promise subtype"); + ret.subtype.push(subtype); + break; + } + case "sequence": + case "FrozenArray": { + const subtype = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, typeName) || tokeniser.error(`Missing ${base.type} subtype`); + ret.subtype.push(subtype); + break; + } + case "record": { + if (tokeniser.probe("[")) tokeniser.error("Record key cannot have extended attribute"); + const keyType = tokeniser.consume(..._tokeniser_js__WEBPACK_IMPORTED_MODULE_2__["stringTypes"]) || tokeniser.error(`Record key must be one of: ${_tokeniser_js__WEBPACK_IMPORTED_MODULE_2__["stringTypes"].join(", ")}`); + const keyIdlType = new Type({ source: tokeniser.source, tokens: { base: keyType }}); + keyIdlType.tokens.separator = tokeniser.consume(",") || tokeniser.error("Missing comma after record key type"); + keyIdlType.type = typeName; + const valueType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, typeName) || tokeniser.error("Error parsing generic type record"); + ret.subtype.push(keyIdlType, valueType); + break; + } + } + if (!ret.idlType) tokeniser.error(`Error parsing generic type ${base.type}`); + ret.tokens.close = tokeniser.consume(">") || tokeniser.error(`Missing closing bracket after ${base.type}`); + return ret.this; +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function type_suffix(tokeniser, obj) { + const nullable = tokeniser.consume("?"); + if (nullable) { + obj.tokens.nullable = nullable; + } + if (tokeniser.probe("?")) tokeniser.error("Can't nullable more than once"); +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} typeName + */ +function single_type(tokeniser, typeName) { + let ret = generic_type(tokeniser, typeName) || Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["primitive_type"])(tokeniser); + if (!ret) { + const base = tokeniser.consume("identifier", ..._tokeniser_js__WEBPACK_IMPORTED_MODULE_2__["stringTypes"], ..._tokeniser_js__WEBPACK_IMPORTED_MODULE_2__["typeNameKeywords"]); + if (!base) { + return; + } + ret = new Type({ source: tokeniser.source, tokens: { base } }); + if (tokeniser.probe("<")) tokeniser.error(`Unsupported generic type ${base.value}`); + } + if (ret.generic === "Promise" && tokeniser.probe("?")) { + tokeniser.error("Promise type cannot be nullable"); + } + ret.type = typeName || null; + type_suffix(tokeniser, ret); + if (ret.nullable && ret.idlType === "any") tokeniser.error("Type `any` cannot be made nullable"); + return ret; +} + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} type + */ +function union_type(tokeniser, type) { + const tokens = {}; + tokens.open = tokeniser.consume("("); + if (!tokens.open) return; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Type({ source: tokeniser.source, tokens })); + ret.type = type || null; + while (true) { + const typ = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser) || tokeniser.error("No type after open parenthesis or 'or' in union type"); + if (typ.idlType === "any") tokeniser.error("Type `any` cannot be included in a union type"); + if (typ.generic === "Promise") tokeniser.error("Type `Promise` cannot be included in a union type"); + ret.subtype.push(typ); + const or = tokeniser.consume("or"); + if (or) { + typ.tokens.separator = or; } + else break; + } + if (ret.idlType.length < 2) { + tokeniser.error("At least two types are expected in a union type but found less"); + } + tokens.close = tokeniser.consume(")") || tokeniser.error("Unterminated union type"); + type_suffix(tokeniser, ret); + return ret.this; +} + +class Type extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} typeName + */ + static parse(tokeniser, typeName) { + return single_type(tokeniser, typeName) || union_type(tokeniser, typeName); + } + + constructor({ source, tokens }) { + super({ source, tokens }); + Object.defineProperty(this, "subtype", { value: [], writable: true }); + this.extAttrs = new _extended_attributes_js__WEBPACK_IMPORTED_MODULE_5__["ExtendedAttributes"]({}); + } - function interface_(isPartial) { - if (!consume("interface")) return; - return mixin_rest(isPartial) || - interface_rest(isPartial) || - error("Interface has no proper body"); + get generic() { + if (this.subtype.length && this.tokens.base) { + return this.tokens.base.value; + } + return ""; + } + get nullable() { + return Boolean(this.tokens.nullable); + } + get union() { + return Boolean(this.subtype.length) && !this.tokens.base; + } + get idlType() { + if (this.subtype.length) { + return this.subtype; } + // Adding prefixes/postfixes for "unrestricted float", etc. + const name = [ + this.tokens.prefix, + this.tokens.base, + this.tokens.postfix + ].filter(t => t).map(t => t.value).join(" "); + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(name); + } - 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); + *validate(defs) { + yield* this.extAttrs.validate(defs); + /* + * If a union is nullable, its subunions cannot include a dictionary + * If not, subunions may include dictionaries if each union is not nullable + */ + const typedef = !this.union && defs.unique.get(this.idlType); + const target = + this.union ? this : + (typedef && typedef.type === "typedef") ? typedef.idlType : + undefined; + if (target && this.nullable) { + // do not allow any dictionary + const { reference } = Object(_validators_helpers_js__WEBPACK_IMPORTED_MODULE_4__["idlTypeIncludesDictionary"])(target, defs) || {}; + if (reference) { + const targetToken = (this.union ? reference : this).tokens.base; + const message = `Nullable union cannot include a dictionary type`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_3__["validationError"])(targetToken, this, "no-nullable-union-dict", message); + } + } else { + // allow some dictionary + for (const subtype of this.subtype) { + yield* subtype.validate(defs); } } + } +} - 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; + +/***/ }), +/* 6 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Base", function() { return Base; }); +// @ts-check + +class Base { + /** + * @param {object} initializer + * @param {Base["source"]} initializer.source + * @param {Base["tokens"]} initializer.tokens + */ + constructor({ source, tokens }) { + Object.defineProperties(this, { + source: { value: source }, + tokens: { value: tokens, writable: true }, + parent: { value: null, writable: true }, + this: { value: this } // useful when escaping from proxy + }); + } + + toJSON() { + const json = { type: undefined, name: undefined, inheritance: undefined }; + let proto = this; + while (proto !== Object.prototype) { + const descMap = Object.getOwnPropertyDescriptors(proto); + for (const [key, value] of Object.entries(descMap)) { + if (value.enumerable || value.get) { + // @ts-ignore - allow indexing here + json[key] = this[key]; } - 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"); } + proto = Object.getPrototypeOf(proto); } + return json; + } +} - 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(","); + +/***/ }), +/* 7 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "idlTypeIncludesDictionary", function() { return idlTypeIncludesDictionary; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dictionaryIncludesRequiredField", function() { return dictionaryIncludesRequiredField; }); +// @ts-check + +/** + * @typedef {import("../productions/dictionary.js").Dictionary} Dictionary + * + * @param {*} idlType + * @param {import("../validator.js").Definitions} defs + * @param {object} [options] + * @param {boolean} [options.useNullableInner] use when the input idlType is nullable and you want to use its inner type + * @return {{ reference: *, dictionary: Dictionary }} the type reference that ultimately includes dictionary. + */ +function idlTypeIncludesDictionary(idlType, defs, { useNullableInner } = {}) { + if (!idlType.union) { + const def = defs.unique.get(idlType.idlType); + if (!def) { + return; + } + if (def.type === "typedef") { + const { typedefIncludesDictionary } = defs.cache; + if (typedefIncludesDictionary.has(def)) { + // Note that this also halts when it met indeterminate state + // to prevent infinite recursion + return typedefIncludesDictionary.get(def); + } + defs.cache.typedefIncludesDictionary.set(def, undefined); // indeterminate state + const result = idlTypeIncludesDictionary(def.idlType, defs); + defs.cache.typedefIncludesDictionary.set(def, result); + if (result) { + return { + reference: idlType, + dictionary: result.dictionary + }; } } - - function typedef() { - if (!consume("typedef")) return; - const ret = { - type: "typedef" + if (def.type === "dictionary" && (useNullableInner || !idlType.nullable)) { + return { + reference: idlType, + dictionary: def }; - 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); + } + } + for (const subtype of idlType.subtype) { + const result = idlTypeIncludesDictionary(subtype, defs); + if (result) { + if (subtype.union) { + return result; } + return { + reference: subtype, + dictionary: result.dictionary + }; } + } +} + +/** + * @param {*} dict dictionary type + * @param {import("../validator.js").Definitions} defs + * @return {boolean} + */ +function dictionaryIncludesRequiredField(dict, defs) { + if (defs.cache.dictionaryIncludesRequiredField.has(dict)) { + return defs.cache.dictionaryIncludesRequiredField.get(dict); + } + defs.cache.dictionaryIncludesRequiredField.set(dict, undefined); // indeterminate + if (dict.inheritance) { + const superdict = defs.unique.get(dict.inheritance); + if (!superdict) { + return true; + } + if (dictionaryIncludesRequiredField(superdict, defs)) { + return true; + } + } + const result = dict.members.some(field => field.required); + defs.cache.dictionaryIncludesRequiredField.set(dict, result); + return result; +} - 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); - } + +/***/ }), +/* 8 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SimpleExtendedAttribute", function() { return SimpleExtendedAttribute; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ExtendedAttributes", function() { return ExtendedAttributes; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _array_base_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9); +/* harmony import */ var _token_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(10); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(3); + + + + + + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} tokenName + */ +function tokens(tokeniser, tokenName) { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["list"])(tokeniser, { + parser: _token_js__WEBPACK_IMPORTED_MODULE_2__["Token"].parser(tokeniser, tokenName), + listName: tokenName + " list" + }); +} + +const extAttrValueSyntax = ["identifier", "decimal", "integer", "string"]; + +const shouldBeLegacyPrefixed = [ + "NoInterfaceObject", + "LenientSetter", + "LenientThis", + "TreatNonObjectAsNull", + "Unforgeable", +]; + +const renamedLegacies = new Map([ + ...shouldBeLegacyPrefixed.map(name => [name, `Legacy${name}`]), + ["NamedConstructor", "LegacyFactoryFunction"], + ["OverrideBuiltins", "LegacyOverrideBuiltIns"], + ["TreatNullAs", "LegacyNullToEmptyString"], +]); + +/** + * This will allow a set of extended attribute values to be parsed. + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function extAttrListItems(tokeniser) { + for (const syntax of extAttrValueSyntax) { + const toks = tokens(tokeniser, syntax); + if (toks.length) { + return toks; } + } + tokeniser.error(`Expected identifiers, strings, decimals, or integers but none found`); +} + + +class ExtendedAttributeParameters extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const tokens = { assign: tokeniser.consume("=") }; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["autoParenter"])(new ExtendedAttributeParameters({ source: tokeniser.source, tokens })); + if (tokens.assign) { + tokens.secondaryName = tokeniser.consume(...extAttrValueSyntax); + } + tokens.open = tokeniser.consume("("); + if (tokens.open) { + ret.list = ret.rhsIsList ? + // [Exposed=(Window,Worker)] + extAttrListItems(tokeniser) : + // [LegacyFactoryFunction=Audio(DOMString src)] or [Constructor(DOMString str)] + Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["argument_list"])(tokeniser); + tokens.close = tokeniser.consume(")") || tokeniser.error("Unexpected token in extended attribute argument list"); + } else if (ret.hasRhs && !tokens.secondaryName) { + tokeniser.error("No right hand side to extended attribute assignment"); + } + return ret.this; + } - function definition() { - return callback() || - interface_(false) || - partial() || - dictionary(false) || - enum_() || - typedef() || - implements_() || - includes() || - namespace(false); + get rhsIsList() { + return this.tokens.assign && !this.tokens.secondaryName; + } + + get rhsType() { + if (this.rhsIsList) { + return this.list[0].tokens.value.type + "-list"; + } + if (this.tokens.secondaryName) { + return this.tokens.secondaryName.type; + } + return null; + } +} + +class SimpleExtendedAttribute extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const name = tokeniser.consume("identifier"); + if (name) { + return new SimpleExtendedAttribute({ + source: tokeniser.source, + tokens: { name }, + params: ExtendedAttributeParameters.parse(tokeniser) + }); } + } - 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; + constructor({ source, tokens, params }) { + super({ source, tokens }); + params.parent = this; + Object.defineProperty(this, "params", { value: params }); + } + + get type() { + return "extended-attribute"; + } + get name() { + return this.tokens.name.value; + } + get rhs() { + const { rhsType: type, tokens, list } = this.params; + if (!type) { + return null; + } + const value = this.params.rhsIsList ? list : Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["unescape"])(tokens.secondaryName.value); + return { type, value }; + } + get arguments() { + const { rhsIsList, list } = this.params; + if (!list || rhsIsList) { + return []; } - const res = definitions(); - if (consume_position < tokens.length) error("Unrecognised tokens"); - return res; + return list; } - const obj = { - parse(str) { - const tokens = tokenise(str); - return parse(tokens); + *validate(defs) { + const { name } = this; + if (name === "LegacyNoInterfaceObject") { + const message = `\`[LegacyNoInterfaceObject]\` extended attribute is an \ +undesirable feature that may be removed from Web IDL in the future. Refer to the \ +[relevant upstream PR](https://github.com/heycam/webidl/pull/609) for more \ +information.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_4__["validationError"])(this.tokens.name, this, "no-nointerfaceobject", message, { level: "warning" }); + } else if (renamedLegacies.has(name)) { + const message = `\`[${name}]\` extended attribute is a legacy feature \ +that is now renamed to \`[${renamedLegacies.get(name)}]\`. Refer to the \ +[relevant upstream PR](https://github.com/heycam/webidl/pull/870) for more \ +information.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_4__["validationError"])(this.tokens.name, this, "renamed-legacy", message, { + level: "warning", + autofix: renameLegacyExtendedAttribute(this) + }); + } + for (const arg of this.arguments) { + yield* arg.validate(defs); + } + } +} + +/** + * @param {SimpleExtendedAttribute} extAttr + */ +function renameLegacyExtendedAttribute(extAttr) { + return () => { + const { name } = extAttr; + extAttr.tokens.name.value = renamedLegacies.get(name); + if (name === "TreatNullAs") { + extAttr.params.tokens = {}; } }; +} + +// Note: we parse something simpler than the official syntax. It's all that ever +// seems to be used +class ExtendedAttributes extends _array_base_js__WEBPACK_IMPORTED_MODULE_1__["ArrayBase"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const tokens = {}; + tokens.open = tokeniser.consume("["); + if (!tokens.open) return new ExtendedAttributes({}); + const ret = new ExtendedAttributes({ source: tokeniser.source, tokens }); + ret.push(...Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["list"])(tokeniser, { + parser: SimpleExtendedAttribute.parse, + listName: "extended attribute" + })); + tokens.close = tokeniser.consume("]") || tokeniser.error("Unexpected closing token of extended attribute"); + if (!ret.length) { + tokeniser.error("Found an empty extended attribute"); + } + if (tokeniser.probe("[")) { + tokeniser.error("Illegal double extended attribute lists, consider merging them"); + } + return ret; + } - 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; + *validate(defs) { + for (const extAttr of this) { + yield* extAttr.validate(defs); + } } -})(); +} + + +/***/ }), +/* 9 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ArrayBase", function() { return ArrayBase; }); +// @ts-check + +class ArrayBase extends Array { + constructor({ source, tokens }) { + super(); + Object.defineProperties(this, { + source: { value: source }, + tokens: { value: tokens }, + parent: { value: null, writable: true } + }); + } +} + + +/***/ }), +/* 10 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Token", function() { return Token; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +// @ts-check + + + + +class Token extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {string} type + */ + static parser(tokeniser, type) { + return () => { + const value = tokeniser.consume(type); + if (value) { + return new Token({ source: tokeniser.source, tokens: { value } }); + } + }; + } + + get value() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.value.value); + } +} + + +/***/ }), +/* 11 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Argument", function() { return Argument; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _default_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(12); +/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4); +/* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(2); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(3); +/* harmony import */ var _validators_helpers_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(7); +// @ts-check + + + + + + + + + +class Argument extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const start_position = tokeniser.position; + /** @type {Base["tokens"]} */ + const tokens = {}; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["autoParenter"])(new Argument({ source: tokeniser.source, tokens })); + ret.extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["ExtendedAttributes"].parse(tokeniser); + tokens.optional = tokeniser.consume("optional"); + ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["type_with_extended_attributes"])(tokeniser, "argument-type"); + if (!ret.idlType) { + return tokeniser.unconsume(start_position); + } + if (!tokens.optional) { + tokens.variadic = tokeniser.consume("..."); + } + tokens.name = tokeniser.consume("identifier", ..._tokeniser_js__WEBPACK_IMPORTED_MODULE_4__["argumentNameKeywords"]); + if (!tokens.name) { + return tokeniser.unconsume(start_position); + } + ret.default = tokens.optional ? _default_js__WEBPACK_IMPORTED_MODULE_1__["Default"].parse(tokeniser) : null; + return ret.this; + } + + get type() { + return "argument"; + } + get optional() { + return !!this.tokens.optional; + } + get variadic() { + return !!this.tokens.variadic; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["unescape"])(this.tokens.name.value); + } + + /** + * @param {import("../validator.js").Definitions} defs + */ + *validate(defs) { + yield* this.idlType.validate(defs); + const result = Object(_validators_helpers_js__WEBPACK_IMPORTED_MODULE_6__["idlTypeIncludesDictionary"])(this.idlType, defs, { useNullableInner: true }); + if (result) { + if (this.idlType.nullable) { + const message = `Dictionary arguments cannot be nullable.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_5__["validationError"])(this.tokens.name, this, "no-nullable-dict-arg", message); + } else if (!this.optional) { + if (this.parent && !Object(_validators_helpers_js__WEBPACK_IMPORTED_MODULE_6__["dictionaryIncludesRequiredField"])(result.dictionary, defs) && isLastRequiredArgument(this)) { + const message = `Dictionary argument must be optional if it has no required fields`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_5__["validationError"])(this.tokens.name, this, "dict-arg-optional", message, { + autofix: autofixDictionaryArgumentOptionality(this) + }); + } + } else if (!this.default) { + const message = `Optional dictionary arguments must have a default value of \`{}\`.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_5__["validationError"])(this.tokens.name, this, "dict-arg-default", message, { + autofix: autofixOptionalDictionaryDefaultValue(this) + }); + } + } + } +} + +/** + * @param {Argument} arg + */ +function isLastRequiredArgument(arg) { + const list = arg.parent.arguments || arg.parent.list; + const index = list.indexOf(arg); + const requiredExists = list.slice(index + 1).some(a => !a.optional); + return !requiredExists; +} + +/** + * @param {Argument} arg + */ +function autofixDictionaryArgumentOptionality(arg) { + return () => { + const firstToken = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_3__["getFirstToken"])(arg.idlType); + arg.tokens.optional = { type: "optional", value: "optional", trivia: firstToken.trivia }; + firstToken.trivia = " "; + autofixOptionalDictionaryDefaultValue(arg)(); + }; +} + +/** + * @param {Argument} arg + */ +function autofixOptionalDictionaryDefaultValue(arg) { + return () => { + arg.default = _default_js__WEBPACK_IMPORTED_MODULE_1__["Default"].parse(new _tokeniser_js__WEBPACK_IMPORTED_MODULE_4__["Tokeniser"](" = {}")); + }; +} + + +/***/ }), +/* 12 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Default", function() { return Default; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +class Default extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const assign = tokeniser.consume("="); + if (!assign) { + return null; + } + const def = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["const_value"])(tokeniser) || tokeniser.consume("string", "null", "[", "{") || tokeniser.error("No value for default"); + const expression = [def]; + if (def.type === "[") { + const close = tokeniser.consume("]") || tokeniser.error("Default sequence value must be empty"); + expression.push(close); + } else if (def.type === "{") { + const close = tokeniser.consume("}") || tokeniser.error("Default dictionary value must be empty"); + expression.push(close); + } + return new Default({ source: tokeniser.source, tokens: { assign }, expression }); + } + + constructor({ source, tokens, expression }) { + super({ source, tokens }); + expression.parent = this; + Object.defineProperty(this, "expression", { value: expression }); + } + + get type() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["const_data"])(this.expression[0]).type; + } + get value() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["const_data"])(this.expression[0]).value; + } + get negative() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["const_data"])(this.expression[0]).negative; + } +} + + +/***/ }), +/* 13 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Operation", function() { return Operation; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3); + + + + +class Operation extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @typedef {import("../tokeniser.js").Token} Token + * + * @param {import("../tokeniser.js").Tokeniser} tokeniser + * @param {object} [options] + * @param {Token} [options.special] + * @param {Token} [options.regular] + */ + static parse(tokeniser, { special, regular } = {}) { + const tokens = { special }; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Operation({ source: tokeniser.source, tokens })); + if (special && special.value === "stringifier") { + tokens.termination = tokeniser.consume(";"); + if (tokens.termination) { + ret.arguments = []; + return ret; + } + } + if (!special && !regular) { + tokens.special = tokeniser.consume("getter", "setter", "deleter"); + } + ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["return_type"])(tokeniser) || tokeniser.error("Missing return type"); + tokens.name = tokeniser.consume("identifier", "includes"); + tokens.open = tokeniser.consume("(") || tokeniser.error("Invalid operation"); + ret.arguments = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["argument_list"])(tokeniser); + tokens.close = tokeniser.consume(")") || tokeniser.error("Unterminated operation"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated operation, expected `;`"); + return ret.this; + } + + get type() { + return "operation"; + } + get name() { + const { name } = this.tokens; + if (!name) { + return ""; + } + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(name.value); + } + get special() { + if (!this.tokens.special) { + return ""; + } + return this.tokens.special.value; + } + + *validate(defs) { + if (!this.name && ["", "static"].includes(this.special)) { + const message = `Regular or static operations must have both a return type and an identifier.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_2__["validationError"])(this.tokens.open, this, "incomplete-op", message); + } + if (this.idlType) { + yield* this.idlType.validate(defs); + } + for (const argument of this.arguments) { + yield* argument.validate(defs); + } + } +} + + +/***/ }), +/* 14 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Attribute", function() { return Attribute; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +class Attribute extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser.js").Tokeniser} tokeniser + */ + static parse(tokeniser, { special, noInherit = false, readonly = false } = {}) { + const start_position = tokeniser.position; + const tokens = { special }; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Attribute({ source: tokeniser.source, tokens })); + if (!special && !noInherit) { + tokens.special = tokeniser.consume("inherit"); + } + if (ret.special === "inherit" && tokeniser.probe("readonly")) { + tokeniser.error("Inherited attributes cannot be read-only"); + } + tokens.readonly = tokeniser.consume("readonly"); + if (readonly && !tokens.readonly && tokeniser.probe("attribute")) { + tokeniser.error("Attributes must be readonly in this context"); + } + tokens.base = tokeniser.consume("attribute"); + if (!tokens.base) { + tokeniser.unconsume(start_position); + return; + } + ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, "attribute-type") || tokeniser.error("Attribute lacks a type"); + switch (ret.idlType.generic) { + case "sequence": + case "record": tokeniser.error(`Attributes cannot accept ${ret.idlType.generic} types`); + } + tokens.name = tokeniser.consume("identifier", "async", "required") || tokeniser.error("Attribute lacks a name"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated attribute, expected `;`"); + return ret.this; + } + + get type() { + return "attribute"; + } + get special() { + if (!this.tokens.special) { + return ""; + } + return this.tokens.special.value; + } + get readonly() { + return !!this.tokens.readonly; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.name.value); + } + + *validate(defs) { + yield* this.extAttrs.validate(defs); + yield* this.idlType.validate(defs); + } +} + + +/***/ }), +/* 15 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Enum", function() { return Enum; }); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); +/* harmony import */ var _token_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(10); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6); + + + + +class EnumValue extends _token_js__WEBPACK_IMPORTED_MODULE_1__["Token"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const value = tokeniser.consume("string"); + if (value) { + return new EnumValue({ source: tokeniser.source, tokens: { value } }); + } + } + + get type() { + return "enum-value"; + } + get value() { + return super.value.slice(1, -1); + } +} + +class Enum extends _base_js__WEBPACK_IMPORTED_MODULE_2__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + /** @type {Base["tokens"]} */ + const tokens = {}; + tokens.base = tokeniser.consume("enum"); + if (!tokens.base) { + return; + } + tokens.name = tokeniser.consume("identifier") || tokeniser.error("No name for enum"); + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_0__["autoParenter"])(new Enum({ source: tokeniser.source, tokens })); + tokeniser.current = ret.this; + tokens.open = tokeniser.consume("{") || tokeniser.error("Bodyless enum"); + ret.values = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_0__["list"])(tokeniser, { + parser: EnumValue.parse, + allowDangler: true, + listName: "enumeration" + }); + if (tokeniser.probe("string")) { + tokeniser.error("No comma between enum values"); + } + tokens.close = tokeniser.consume("}") || tokeniser.error("Unexpected value in enum"); + if (!ret.values.length) { + tokeniser.error("No value in enum"); + } + tokens.termination = tokeniser.consume(";") || tokeniser.error("No semicolon after enum"); + return ret.this; + } + + get type() { + return "enum"; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_0__["unescape"])(this.tokens.name.value); + } +} + + +/***/ }), +/* 16 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Includes", function() { return Includes; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +// @ts-check + + + + +class Includes extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const target = tokeniser.consume("identifier"); + if (!target) { + return; + } + const tokens = { target }; + tokens.includes = tokeniser.consume("includes"); + if (!tokens.includes) { + tokeniser.unconsume(target.index); + return; + } + tokens.mixin = tokeniser.consume("identifier") || tokeniser.error("Incomplete includes statement"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("No terminating ; for includes statement"); + return new Includes({ source: tokeniser.source, tokens }); + } + + get type() { + return "includes"; + } + get target() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.target.value); + } + get includes() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.mixin.value); + } +} + + +/***/ }), +/* 17 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Typedef", function() { return Typedef; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +class Typedef extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + /** @type {Base["tokens"]} */ + const tokens = {}; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Typedef({ source: tokeniser.source, tokens })); + tokens.base = tokeniser.consume("typedef"); + if (!tokens.base) { + return; + } + ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, "typedef-type") || tokeniser.error("Typedef lacks a type"); + tokens.name = tokeniser.consume("identifier") || tokeniser.error("Typedef lacks a name"); + tokeniser.current = ret.this; + tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated typedef, expected `;`"); + return ret.this; + } + + get type() { + return "typedef"; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.name.value); + } + + *validate(defs) { + yield* this.idlType.validate(defs); + } +} + + +/***/ }), +/* 18 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CallbackFunction", function() { return CallbackFunction; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +class CallbackFunction extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser.js").Tokeniser} tokeniser + */ + static parse(tokeniser, base) { + const tokens = { base }; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new CallbackFunction({ source: tokeniser.source, tokens })); + tokens.name = tokeniser.consume("identifier") || tokeniser.error("Callback lacks a name"); + tokeniser.current = ret.this; + tokens.assign = tokeniser.consume("=") || tokeniser.error("Callback lacks an assignment"); + ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["return_type"])(tokeniser) || tokeniser.error("Callback lacks a return type"); + tokens.open = tokeniser.consume("(") || tokeniser.error("Callback lacks parentheses for arguments"); + ret.arguments = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["argument_list"])(tokeniser); + tokens.close = tokeniser.consume(")") || tokeniser.error("Unterminated callback"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated callback, expected `;`"); + return ret.this; + } + + get type() { + return "callback"; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.name.value); + } + + *validate(defs) { + yield* this.extAttrs.validate(defs); + yield* this.idlType.validate(defs); + } +} + + +/***/ }), +/* 19 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Interface", function() { return Interface; }); +/* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); +/* harmony import */ var _attribute_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(14); +/* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(13); +/* harmony import */ var _constant_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(21); +/* harmony import */ var _iterable_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(22); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3); +/* harmony import */ var _validators_interface_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(23); +/* harmony import */ var _constructor_js__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(24); +/* harmony import */ var _tokeniser_js__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(2); +/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(8); + + + + + + + + + + + + +/** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ +function static_member(tokeniser) { + const special = tokeniser.consume("static"); + if (!special) return; + const member = _attribute_js__WEBPACK_IMPORTED_MODULE_1__["Attribute"].parse(tokeniser, { special }) || + _operation_js__WEBPACK_IMPORTED_MODULE_2__["Operation"].parse(tokeniser, { special }) || + tokeniser.error("No body in static member"); + return member; +} + +class Interface extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser, base, { partial = null } = {}) { + const tokens = { partial, base }; + return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new Interface({ source: tokeniser.source, tokens }), { + type: "interface", + inheritable: !partial, + allowedMembers: [ + [_constant_js__WEBPACK_IMPORTED_MODULE_3__["Constant"].parse], + [_constructor_js__WEBPACK_IMPORTED_MODULE_8__["Constructor"].parse], + [static_member], + [_helpers_js__WEBPACK_IMPORTED_MODULE_5__["stringifier"]], + [_iterable_js__WEBPACK_IMPORTED_MODULE_4__["IterableLike"].parse], + [_attribute_js__WEBPACK_IMPORTED_MODULE_1__["Attribute"].parse], + [_operation_js__WEBPACK_IMPORTED_MODULE_2__["Operation"].parse] + ] + }); + } + + get type() { + return "interface"; + } + + *validate(defs) { + yield* this.extAttrs.validate(defs); + if ( + !this.partial && + this.extAttrs.every(extAttr => extAttr.name !== "Exposed") && + this.extAttrs.every(extAttr => extAttr.name !== "LegacyNoInterfaceObject") + ) { + const message = `Interfaces must have \`[Exposed]\` extended attribute. \ +To fix, add, for example, \`[Exposed=Window]\`. Please also consider carefully \ +if your interface should also be exposed in a Worker scope. Refer to the \ +[WebIDL spec section on Exposed](https://heycam.github.io/webidl/#Exposed) \ +for more information.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_6__["validationError"])(this.tokens.name, this, "require-exposed", message, { + autofix: Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["autofixAddExposedWindow"])(this) + }); + } + const oldConstructors = this.extAttrs.filter(extAttr => extAttr.name === "Constructor"); + for (const constructor of oldConstructors) { + const message = `Constructors should now be represented as a \`constructor()\` operation on the interface \ +instead of \`[Constructor]\` extended attribute. Refer to the \ +[WebIDL spec section on constructor operations](https://heycam.github.io/webidl/#idl-constructors) \ +for more information.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_6__["validationError"])(constructor.tokens.name, this, "constructor-member", message, { + autofix: autofixConstructor(this, constructor) + }); + } + + const isGlobal = this.extAttrs.some(extAttr => extAttr.name === "Global"); + if (isGlobal) { + const factoryFunctions = this.extAttrs.filter(extAttr => extAttr.name === "LegacyFactoryFunction"); + for (const named of factoryFunctions) { + const message = `Interfaces marked as \`[Global]\` cannot have factory functions.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_6__["validationError"])(named.tokens.name, this, "no-constructible-global", message); + } + + const constructors = this.members.filter(member => member.type === "constructor"); + for (const named of constructors) { + const message = `Interfaces marked as \`[Global]\` cannot have constructors.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_6__["validationError"])(named.tokens.base, this, "no-constructible-global", message); + } + } + + yield* super.validate(defs); + if (!this.partial) { + yield* Object(_validators_interface_js__WEBPACK_IMPORTED_MODULE_7__["checkInterfaceMemberDuplication"])(defs, this); + } + } +} + +function autofixConstructor(interfaceDef, constructorExtAttr) { + interfaceDef = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["autoParenter"])(interfaceDef); + return () => { + const indentation = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["getLastIndentation"])(interfaceDef.extAttrs.tokens.open.trivia); + const memberIndent = interfaceDef.members.length ? + Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["getLastIndentation"])(Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["getFirstToken"])(interfaceDef.members[0]).trivia) : + Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["getMemberIndentation"])(indentation); + const constructorOp = _constructor_js__WEBPACK_IMPORTED_MODULE_8__["Constructor"].parse(new _tokeniser_js__WEBPACK_IMPORTED_MODULE_9__["Tokeniser"](`\n${memberIndent}constructor();`)); + constructorOp.extAttrs = new _extended_attributes_js__WEBPACK_IMPORTED_MODULE_10__["ExtendedAttributes"]({}); + Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["autoParenter"])(constructorOp).arguments = constructorExtAttr.arguments; + + const existingIndex = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_5__["findLastIndex"])(interfaceDef.members, m => m.type === "constructor"); + interfaceDef.members.splice(existingIndex + 1, 0, constructorOp); + + const { close } = interfaceDef.tokens; + if (!close.trivia.includes("\n")) { + close.trivia += `\n${indentation}`; + } + + const { extAttrs } = interfaceDef; + const index = extAttrs.indexOf(constructorExtAttr); + const removed = extAttrs.splice(index, 1); + if (!extAttrs.length) { + extAttrs.tokens.open = extAttrs.tokens.close = undefined; + } else if (extAttrs.length === index) { + extAttrs[index - 1].tokens.separator = undefined; + } else if (!extAttrs[index].tokens.name.trivia.trim()) { + extAttrs[index].tokens.name.trivia = removed[0].tokens.name.trivia; + } + }; +} + + +/***/ }), +/* 20 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Container", function() { return Container; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); + + + + +/** + * @param {import("../tokeniser.js").Tokeniser} tokeniser + */ +function inheritance(tokeniser) { + const colon = tokeniser.consume(":"); + if (!colon) { + return {}; + } + const inheritance = tokeniser.consume("identifier") || tokeniser.error("Inheritance lacks a type"); + return { colon, inheritance }; +} + +class Container extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @template T + * @param {import("../tokeniser.js").Tokeniser} tokeniser + * @param {T} instance + * @param {*} args + */ + static parse(tokeniser, instance, { type, inheritable, allowedMembers }) { + const { tokens } = instance; + tokens.name = tokeniser.consume("identifier") || tokeniser.error(`Missing name in ${instance.type}`); + tokeniser.current = instance; + instance = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["autoParenter"])(instance); + if (inheritable) { + Object.assign(tokens, inheritance(tokeniser)); + } + tokens.open = tokeniser.consume("{") || tokeniser.error(`Bodyless ${type}`); + instance.members = []; + while (true) { + tokens.close = tokeniser.consume("}"); + if (tokens.close) { + tokens.termination = tokeniser.consume(";") || tokeniser.error(`Missing semicolon after ${type}`); + return instance.this; + } + const ea = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_1__["ExtendedAttributes"].parse(tokeniser); + let mem; + for (const [parser, ...args] of allowedMembers) { + mem = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["autoParenter"])(parser(tokeniser, ...args)); + if (mem) { + break; + } + } + if (!mem) { + tokeniser.error("Unknown member"); + } + mem.extAttrs = ea; + instance.members.push(mem.this); + } + } + + get partial() { + return !!this.tokens.partial; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["unescape"])(this.tokens.name.value); + } + get inheritance() { + if (!this.tokens.inheritance) { + return null; + } + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["unescape"])(this.tokens.inheritance.value); + } + + *validate(defs) { + for (const member of this.members) { + if (member.validate) { + yield* member.validate(defs); + } + } + } + } + + +/***/ }), +/* 21 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Constant", function() { return Constant; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _type_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); + + + + +class Constant extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser.js").Tokeniser} tokeniser + */ + static parse(tokeniser) { + /** @type {Base["tokens"]} */ + const tokens = {}; + tokens.base = tokeniser.consume("const"); + if (!tokens.base) { + return; + } + let idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["primitive_type"])(tokeniser); + if (!idlType) { + const base = tokeniser.consume("identifier") || tokeniser.error("Const lacks a type"); + idlType = new _type_js__WEBPACK_IMPORTED_MODULE_1__["Type"]({ source: tokeniser.source, tokens: { base } }); + } + if (tokeniser.probe("?")) { + tokeniser.error("Unexpected nullable constant type"); + } + idlType.type = "const-type"; + tokens.name = tokeniser.consume("identifier") || tokeniser.error("Const lacks a name"); + tokens.assign = tokeniser.consume("=") || tokeniser.error("Const lacks value assignment"); + tokens.value = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["const_value"])(tokeniser) || tokeniser.error("Const lacks a value"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated const, expected `;`"); + const ret = new Constant({ source: tokeniser.source, tokens }); + Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["autoParenter"])(ret).idlType = idlType; + return ret; + } + + get type() { + return "const"; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["unescape"])(this.tokens.name.value); + } + get value() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_2__["const_data"])(this.tokens.value); + } +} + + +/***/ }), +/* 22 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "IterableLike", function() { return IterableLike; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +class IterableLike extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser.js").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const start_position = tokeniser.position; + const tokens = {}; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new IterableLike({ source: tokeniser.source, tokens })); + tokens.readonly = tokeniser.consume("readonly"); + if (!tokens.readonly) { + tokens.async = tokeniser.consume("async"); + } + tokens.base = + tokens.readonly ? tokeniser.consume("maplike", "setlike") : + tokens.async ? tokeniser.consume("iterable") : + tokeniser.consume("iterable", "maplike", "setlike"); + if (!tokens.base) { + tokeniser.unconsume(start_position); + return; + } + + const { type } = ret; + const secondTypeRequired = type === "maplike"; + const secondTypeAllowed = secondTypeRequired || type === "iterable"; + const argumentAllowed = ret.async && type === "iterable"; + + tokens.open = tokeniser.consume("<") || tokeniser.error(`Missing less-than sign \`<\` in ${type} declaration`); + const first = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser) || tokeniser.error(`Missing a type argument in ${type} declaration`); + ret.idlType = [first]; + ret.arguments = []; + + if (secondTypeAllowed) { + first.tokens.separator = tokeniser.consume(","); + if (first.tokens.separator) { + ret.idlType.push(Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser)); + } + else if (secondTypeRequired) { + tokeniser.error(`Missing second type argument in ${type} declaration`); + } + } + + tokens.close = tokeniser.consume(">") || tokeniser.error(`Missing greater-than sign \`>\` in ${type} declaration`); + + if (tokeniser.probe("(")) { + if (argumentAllowed) { + tokens.argsOpen = tokeniser.consume("("); + ret.arguments.push(...Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["argument_list"])(tokeniser)); + tokens.argsClose = tokeniser.consume(")") || tokeniser.error("Unterminated async iterable argument list"); + } else { + tokeniser.error(`Arguments are only allowed for \`async iterable\``); + } + } + + tokens.termination = tokeniser.consume(";") || tokeniser.error(`Missing semicolon after ${type} declaration`); + + return ret.this; + } + + get type() { + return this.tokens.base.value; + } + get readonly() { + return !!this.tokens.readonly; + } + get async() { + return !!this.tokens.async; + } + + *validate(defs) { + for (const type of this.idlType) { + yield* type.validate(defs); + } + for (const argument of this.arguments) { + yield* argument.validate(defs); + } + } +} + + +/***/ }), +/* 23 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "checkInterfaceMemberDuplication", function() { return checkInterfaceMemberDuplication; }); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3); +// @ts-check + + + +function* checkInterfaceMemberDuplication(defs, i) { + const opNames = new Set(getOperations(i).map(op => op.name)); + const partials = defs.partials.get(i.name) || []; + const mixins = defs.mixinMap.get(i.name) || []; + for (const ext of [...partials, ...mixins]) { + const additions = getOperations(ext); + yield* forEachExtension(additions, opNames, ext, i); + for (const addition of additions) { + opNames.add(addition.name); + } + } + + function* forEachExtension(additions, existings, ext, base) { + for (const addition of additions) { + const { name } = addition; + if (name && existings.has(name)) { + const message = `The operation "${name}" has already been defined for the base interface "${base.name}" either in itself or in a mixin`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_0__["validationError"])(addition.tokens.name, ext, "no-cross-overload", message); + } + } + } + + function getOperations(i) { + return i.members + .filter(({type}) => type === "operation"); + } +} + + +/***/ }), +/* 24 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Constructor", function() { return Constructor; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); + + + +class Constructor extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + const base = tokeniser.consume("constructor"); + if (!base) { + return; + } + /** @type {Base["tokens"]} */ + const tokens = { base }; + tokens.open = tokeniser.consume("(") || tokeniser.error("No argument list in constructor"); + const args = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["argument_list"])(tokeniser); + tokens.close = tokeniser.consume(")") || tokeniser.error("Unterminated constructor"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("No semicolon after constructor"); + const ret = new Constructor({ source: tokeniser.source, tokens }); + Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(ret).arguments = args; + return ret; + } + + get type() { + return "constructor"; + } + + *validate(defs) { + if (this.idlType) { + yield* this.idlType.validate(defs); + } + for (const argument of this.arguments) { + yield* argument.validate(defs); + } + } +} + + +/***/ }), +/* 25 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Mixin", function() { return Mixin; }); +/* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); +/* harmony import */ var _constant_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(21); +/* harmony import */ var _attribute_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(14); +/* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(13); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(4); + + + + + + +class Mixin extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] { + /** + * @typedef {import("../tokeniser.js").Token} Token + * + * @param {import("../tokeniser.js").Tokeniser} tokeniser + * @param {Token} base + * @param {object} [options] + * @param {Token} [options.partial] + */ + static parse(tokeniser, base, { partial } = {}) { + const tokens = { partial, base }; + tokens.mixin = tokeniser.consume("mixin"); + if (!tokens.mixin) { + return; + } + return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new Mixin({ source: tokeniser.source, tokens }), { + type: "interface mixin", + allowedMembers: [ + [_constant_js__WEBPACK_IMPORTED_MODULE_1__["Constant"].parse], + [_helpers_js__WEBPACK_IMPORTED_MODULE_4__["stringifier"]], + [_attribute_js__WEBPACK_IMPORTED_MODULE_2__["Attribute"].parse, { noInherit: true }], + [_operation_js__WEBPACK_IMPORTED_MODULE_3__["Operation"].parse, { regular: true }] + ] + }); + } + + get type() { + return "interface mixin"; + } +} + + +/***/ }), +/* 26 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Dictionary", function() { return Dictionary; }); +/* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); +/* harmony import */ var _field_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); +// @ts-check + + + + +class Dictionary extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {object} [options] + * @param {import("../tokeniser.js").Token} [options.partial] + */ + static parse(tokeniser, { partial } = {}) { + const tokens = { partial }; + tokens.base = tokeniser.consume("dictionary"); + if (!tokens.base) { + return; + } + return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new Dictionary({ source: tokeniser.source, tokens }), { + type: "dictionary", + inheritable: !partial, + allowedMembers: [ + [_field_js__WEBPACK_IMPORTED_MODULE_1__["Field"].parse], + ] + }); + } + + get type() { + return "dictionary"; + } +} + + +/***/ }), +/* 27 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Field", function() { return Field; }); +/* harmony import */ var _base_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +/* harmony import */ var _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8); +/* harmony import */ var _default_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(12); + + + + + +class Field extends _base_js__WEBPACK_IMPORTED_MODULE_0__["Base"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser) { + /** @type {Base["tokens"]} */ + const tokens = {}; + const ret = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["autoParenter"])(new Field({ source: tokeniser.source, tokens })); + ret.extAttrs = _extended_attributes_js__WEBPACK_IMPORTED_MODULE_2__["ExtendedAttributes"].parse(tokeniser); + tokens.required = tokeniser.consume("required"); + ret.idlType = Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["type_with_extended_attributes"])(tokeniser, "dictionary-type") || tokeniser.error("Dictionary member lacks a type"); + tokens.name = tokeniser.consume("identifier") || tokeniser.error("Dictionary member lacks a name"); + ret.default = _default_js__WEBPACK_IMPORTED_MODULE_3__["Default"].parse(tokeniser); + if (tokens.required && ret.default) tokeniser.error("Required member must not have a default"); + tokens.termination = tokeniser.consume(";") || tokeniser.error("Unterminated dictionary member, expected `;`"); + return ret.this; + } + + get type() { + return "field"; + } + get name() { + return Object(_helpers_js__WEBPACK_IMPORTED_MODULE_1__["unescape"])(this.tokens.name.value); + } + get required() { + return !!this.tokens.required; + } + + *validate(defs) { + yield* this.idlType.validate(defs); + } +} + + +/***/ }), +/* 28 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Namespace", function() { return Namespace; }); +/* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); +/* harmony import */ var _attribute_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(14); +/* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(13); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3); +/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(4); + + + + + + +class Namespace extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + * @param {object} [options] + * @param {import("../tokeniser.js").Token} [options.partial] + */ + static parse(tokeniser, { partial } = {}) { + const tokens = { partial }; + tokens.base = tokeniser.consume("namespace"); + if (!tokens.base) { + return; + } + return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new Namespace({ source: tokeniser.source, tokens }), { + type: "namespace", + allowedMembers: [ + [_attribute_js__WEBPACK_IMPORTED_MODULE_1__["Attribute"].parse, { noInherit: true, readonly: true }], + [_operation_js__WEBPACK_IMPORTED_MODULE_2__["Operation"].parse, { regular: true }] + ] + }); + } + + get type() { + return "namespace"; + } + + *validate(defs) { + if (!this.partial && this.extAttrs.every(extAttr => extAttr.name !== "Exposed")) { + const message = `Namespaces must have [Exposed] extended attribute. \ +To fix, add, for example, [Exposed=Window]. Please also consider carefully \ +if your namespace should also be exposed in a Worker scope. Refer to the \ +[WebIDL spec section on Exposed](https://heycam.github.io/webidl/#Exposed) \ +for more information.`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_3__["validationError"])(this.tokens.name, this, "require-exposed", message, { + autofix: Object(_helpers_js__WEBPACK_IMPORTED_MODULE_4__["autofixAddExposedWindow"])(this) + }); + } + yield* super.validate(defs); + } +} + + +/***/ }), +/* 29 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CallbackInterface", function() { return CallbackInterface; }); +/* harmony import */ var _container_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(20); +/* harmony import */ var _operation_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(13); +/* harmony import */ var _constant_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(21); +// @ts-check + + + + + +class CallbackInterface extends _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"] { + /** + * @param {import("../tokeniser").Tokeniser} tokeniser + */ + static parse(tokeniser, callback, { partial = null } = {}) { + const tokens = { callback }; + tokens.base = tokeniser.consume("interface"); + if (!tokens.base) { + return; + } + return _container_js__WEBPACK_IMPORTED_MODULE_0__["Container"].parse(tokeniser, new CallbackInterface({ source: tokeniser.source, tokens }), { + type: "callback interface", + inheritable: !partial, + allowedMembers: [ + [_constant_js__WEBPACK_IMPORTED_MODULE_2__["Constant"].parse], + [_operation_js__WEBPACK_IMPORTED_MODULE_1__["Operation"].parse, { regular: true }] + ] + }); + } + + get type() { + return "callback interface"; + } +} + + +/***/ }), +/* 30 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "write", function() { return write; }); + + +function noop(arg) { + return arg; +} + +const templates = { + wrap: items => items.join(""), + trivia: noop, + name: noop, + reference: noop, + type: noop, + generic: noop, + nameless: noop, + inheritance: noop, + definition: noop, + extendedAttribute: noop, + extendedAttributeReference: noop +}; + +function write(ast, { templates: ts = templates } = {}) { + ts = Object.assign({}, templates, ts); + + function reference(raw, { unescaped, context }) { + if (!unescaped) { + unescaped = raw.startsWith("_") ? raw.slice(1) : raw; + } + return ts.reference(raw, unescaped, context); + } + + function token(t, wrapper = noop, ...args) { + if (!t) { + return ""; + } + const value = wrapper(t.value, ...args); + return ts.wrap([ts.trivia(t.trivia), value]); + } + + function reference_token(t, context) { + return token(t, reference, { context }); + } + + function name_token(t, arg) { + return token(t, ts.name, arg); + } + + function type_body(it) { + if (it.union || it.generic) { + return ts.wrap([ + token(it.tokens.base, ts.generic), + token(it.tokens.open), + ...it.subtype.map(type), + token(it.tokens.close) + ]); + } + const firstToken = it.tokens.prefix || it.tokens.base; + const prefix = it.tokens.prefix ? [ + it.tokens.prefix.value, + ts.trivia(it.tokens.base.trivia) + ] : []; + const ref = reference(ts.wrap([ + ...prefix, + it.tokens.base.value, + token(it.tokens.postfix) + ]), { unescaped: it.idlType, context: it }); + return ts.wrap([ts.trivia(firstToken.trivia), ref]); + } + function type(it) { + return ts.wrap([ + extended_attributes(it.extAttrs), + type_body(it), + token(it.tokens.nullable), + token(it.tokens.separator) + ]); + } + function default_(def) { + if (!def) { + return ""; + } + return ts.wrap([ + token(def.tokens.assign), + ...def.expression.map(t => token(t)) + ]); + } + function argument(arg) { + return ts.wrap([ + extended_attributes(arg.extAttrs), + token(arg.tokens.optional), + ts.type(type(arg.idlType)), + token(arg.tokens.variadic), + name_token(arg.tokens.name, { data: arg }), + default_(arg.default), + token(arg.tokens.separator) + ]); + } + function extended_attribute_listitem(str) { + return ts.wrap([ + token(str.tokens.value), + token(str.tokens.separator) + ]); + } + function identifier(id, context) { + return ts.wrap([ + reference_token(id.tokens.value, context), + token(id.tokens.separator) + ]); + } + function make_ext_at(it) { + const { rhsType } = it.params; + return ts.wrap([ + ts.trivia(it.tokens.name.trivia), + ts.extendedAttribute(ts.wrap([ + ts.extendedAttributeReference(it.name), + token(it.params.tokens.assign), + reference_token(it.params.tokens.secondaryName, it), + token(it.params.tokens.open), + ...!it.params.list ? [] : + it.params.list.map( + rhsType === "identifier-list" ? id => identifier(id, it) : + rhsType && rhsType.endsWith("-list") ? extended_attribute_listitem : + argument + ), + token(it.params.tokens.close) + ])), + token(it.tokens.separator) + ]); + } + function extended_attributes(eats) { + if (!eats.length) return ""; + return ts.wrap([ + token(eats.tokens.open), + ...eats.map(make_ext_at), + token(eats.tokens.close) + ]); + } + + function operation(it, parent) { + const body = it.idlType ? [ + ts.type(type(it.idlType)), + name_token(it.tokens.name, { data: it, parent }), + token(it.tokens.open), + ts.wrap(it.arguments.map(argument)), + token(it.tokens.close), + ] : []; + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + it.tokens.name ? token(it.tokens.special) : token(it.tokens.special, ts.nameless, { data: it, parent }), + ...body, + token(it.tokens.termination) + ]), { data: it, parent }); + } + + function attribute(it, parent) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.special), + token(it.tokens.readonly), + token(it.tokens.base), + ts.type(type(it.idlType)), + name_token(it.tokens.name, { data: it, parent }), + token(it.tokens.termination) + ]), { data: it, parent }); + } + + function constructor(it, parent) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.base, ts.nameless, { data: it, parent }), + token(it.tokens.open), + ts.wrap(it.arguments.map(argument)), + token(it.tokens.close), + token(it.tokens.termination) + ]), { data: it, parent }); + } + + function inheritance(inh) { + if (!inh.tokens.inheritance) { + return ""; + } + return ts.wrap([ + token(inh.tokens.colon), + ts.trivia(inh.tokens.inheritance.trivia), + ts.inheritance(reference(inh.tokens.inheritance.value, { context: inh })) + ]); + } + + function container(it) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.callback), + token(it.tokens.partial), + token(it.tokens.base), + token(it.tokens.mixin), + name_token(it.tokens.name, { data: it }), + inheritance(it), + token(it.tokens.open), + iterate(it.members, it), + token(it.tokens.close), + token(it.tokens.termination) + ]), { data: it }); + } + + function field(it, parent) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.required), + ts.type(type(it.idlType)), + name_token(it.tokens.name, { data: it, parent }), + default_(it.default), + token(it.tokens.termination) + ]), { data: it, parent }); + } + function const_(it, parent) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.base), + ts.type(type(it.idlType)), + name_token(it.tokens.name, { data: it, parent }), + token(it.tokens.assign), + token(it.tokens.value), + token(it.tokens.termination) + ]), { data: it, parent }); + } + function typedef(it) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.base), + ts.type(type(it.idlType)), + name_token(it.tokens.name, { data: it }), + token(it.tokens.termination) + ]), { data: it }); + } + function includes(it) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + reference_token(it.tokens.target, it), + token(it.tokens.includes), + reference_token(it.tokens.mixin, it), + token(it.tokens.termination) + ]), { data: it }); + } + function callback(it) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.base), + name_token(it.tokens.name, { data: it }), + token(it.tokens.assign), + ts.type(type(it.idlType)), + token(it.tokens.open), + ...it.arguments.map(argument), + token(it.tokens.close), + token(it.tokens.termination), + ]), { data: it }); + } + function enum_(it) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.base), + name_token(it.tokens.name, { data: it }), + token(it.tokens.open), + iterate(it.values, it), + token(it.tokens.close), + token(it.tokens.termination) + ]), { data: it }); + } + function enum_value(v, parent) { + return ts.wrap([ + ts.trivia(v.tokens.value.trivia), + ts.definition( + ts.wrap(['"', ts.name(v.value, { data: v, parent }), '"']), + { data: v, parent } + ), + token(v.tokens.separator) + ]); + } + function iterable_like(it, parent) { + return ts.definition(ts.wrap([ + extended_attributes(it.extAttrs), + token(it.tokens.readonly), + token(it.tokens.async), + token(it.tokens.base, ts.generic), + token(it.tokens.open), + ts.wrap(it.idlType.map(type)), + token(it.tokens.close), + token(it.tokens.argsOpen), + ts.wrap(it.arguments.map(argument)), + token(it.tokens.argsClose), + token(it.tokens.termination) + ]), { data: it, parent }); + } + function eof(it) { + return ts.trivia(it.trivia); + } + + const table = { + interface: container, + "interface mixin": container, + namespace: container, + operation, + attribute, + constructor, + dictionary: container, + field, + const: const_, + typedef, + includes, + callback, + enum: enum_, + "enum-value": enum_value, + iterable: iterable_like, + maplike: iterable_like, + setlike: iterable_like, + "callback interface": container, + eof + }; + function dispatch(it, parent) { + const dispatcher = table[it.type]; + if (!dispatcher) { + throw new Error(`Type "${it.type}" is unsupported`); + } + return table[it.type](it, parent); + } + function iterate(things, parent) { + if (!things) return; + const results = things.map(thing => dispatch(thing, parent)); + return ts.wrap(results); + } + return iterate(ast); +} + + +/***/ }), +/* 31 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "validate", function() { return validate; }); +/* harmony import */ var _error_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3); + + + + +function getMixinMap(all, unique) { + const map = new Map(); + const includes = all.filter(def => def.type === "includes"); + for (const include of includes) { + const mixin = unique.get(include.includes); + if (!mixin) { + continue; + } + const array = map.get(include.target); + if (array) { + array.push(mixin); + } else { + map.set(include.target, [mixin]); + } + } + return map; +} + +/** + * @typedef {ReturnType} Definitions + */ +function groupDefinitions(all) { + const unique = new Map(); + const duplicates = new Set(); + const partials = new Map(); + for (const def of all) { + if (def.partial) { + const array = partials.get(def.name); + if (array) { + array.push(def); + } else { + partials.set(def.name, [def]); + } + continue; + } + if (!def.name) { + continue; + } + if (!unique.has(def.name)) { + unique.set(def.name, def); + } else { + duplicates.add(def); + } + } + return { + all, + unique, + partials, + duplicates, + mixinMap: getMixinMap(all, unique), + cache: { + typedefIncludesDictionary: new WeakMap(), + dictionaryIncludesRequiredField: new WeakMap() + }, + }; +} + +function* checkDuplicatedNames({ unique, duplicates }) { + for (const dup of duplicates) { + const { name } = dup; + const message = `The name "${name}" of type "${unique.get(name).type}" was already seen`; + yield Object(_error_js__WEBPACK_IMPORTED_MODULE_0__["validationError"])(dup.tokens.name, dup, "no-duplicate", message); + } +} + +function* validateIterable(ast) { + const defs = groupDefinitions(ast); + for (const def of defs.all) { + if (def.validate) { + yield* def.validate(defs); + } + } + yield* checkDuplicatedNames(defs); +} + +// Remove this once all of our support targets expose `.flat()` by default +function flatten(array) { + if (array.flat) { + return array.flat(); + } + return [].concat(...array); +} + +/** + * @param {*} ast AST or array of ASTs + */ +function validate(ast) { + return [...validateIterable(flatten(ast))]; +} + + +/***/ }) +/******/ ]); +}); +//# sourceMappingURL=webidl2.js.map \ No newline at end of file diff --git a/test/fixtures/wpt/url/failure.html b/test/fixtures/wpt/url/failure.html index bebdb3dcc4df6b..8f3d0299a40fdb 100644 --- a/test/fixtures/wpt/url/failure.html +++ b/test/fixtures/wpt/url/failure.html @@ -20,29 +20,29 @@ self.test(() => { // URL's constructor's first argument is tested by url-constructor.html // If a URL fails to parse with any valid base, it must also fail to parse with no base, i.e. // when used as a base URL itself. - assert_throws(new TypeError(), () => new URL("about:blank", test.input)); + assert_throws_js(TypeError, () => new URL("about:blank", test.input)); }, "URL's constructor's base argument: " + name) self.test(() => { const url = new URL("about:blank") - assert_throws(new TypeError, () => url.href = test.input) + assert_throws_js(TypeError, () => url.href = test.input) }, "URL's href: " + name) self.test(() => { const client = new XMLHttpRequest() - assert_throws("SyntaxError", () => client.open("GET", test.input)) + assert_throws_dom("SyntaxError", () => client.open("GET", test.input)) }, "XHR: " + name) self.test(() => { - assert_throws(new TypeError, () => self.navigator.sendBeacon(test.input)) + assert_throws_js(TypeError, () => self.navigator.sendBeacon(test.input)) }, "sendBeacon(): " + name) self.test(() => { - assert_throws(new TypeError, () => self[0].location = test.input) + assert_throws_js(self[0].TypeError, () => self[0].location = test.input) }, "Location's href: " + name) self.test(() => { - assert_throws("SyntaxError", () => self.open(test.input).close()) + assert_throws_dom("SyntaxError", () => self.open(test.input).close()) }, "window.open(): " + name) } } diff --git a/test/fixtures/wpt/url/historical.any.js b/test/fixtures/wpt/url/historical.any.js index 407e118f3a05f9..c3067dfd730123 100644 --- a/test/fixtures/wpt/url/historical.any.js +++ b/test/fixtures/wpt/url/historical.any.js @@ -15,7 +15,7 @@ if(self.GLOBAL.isWindow()) { test(function() { var url = new URL("./foo", "http://www.example.org"); assert_equals(url.href, "http://www.example.org/foo"); - assert_throws(new TypeError(), function() { + assert_throws_js(TypeError, function() { url.href = "./bar"; }); }, "Setting URL's href attribute and base URLs"); diff --git a/test/fixtures/wpt/url/resources/setters_tests.json b/test/fixtures/wpt/url/resources/setters_tests.json index 6b7d19b10164a3..d54335f1badeca 100644 --- a/test/fixtures/wpt/url/resources/setters_tests.json +++ b/test/fixtures/wpt/url/resources/setters_tests.json @@ -686,6 +686,17 @@ "port": "2" } }, + { + "comment": "IPv6 literal address with port, crbug.com/1012416", + "href": "http://example.net", + "new_value": "[2001:db8::2]:4002", + "expected": { + "href": "http://[2001:db8::2]:4002/", + "host": "[2001:db8::2]:4002", + "hostname": "[2001:db8::2]", + "port": "4002" + } + }, { "comment": "Default port number is removed", "href": "http://example.net", diff --git a/test/fixtures/wpt/url/resources/urltestdata.json b/test/fixtures/wpt/url/resources/urltestdata.json index 61249e14996297..32ed1959430c13 100644 --- a/test/fixtures/wpt/url/resources/urltestdata.json +++ b/test/fixtures/wpt/url/resources/urltestdata.json @@ -4481,6 +4481,21 @@ "search": "", "hash": "" }, + { + "input": "sc://\u001F!\"$&'()*+,-.;<=>^_`{|}~/", + "base": "about:blank", + "href": "sc://%1F!\"$&'()*+,-.;<=>^_`{|}~/", + "origin": "null", + "protocol": "sc:", + "username": "", + "password": "", + "host": "%1F!\"$&'()*+,-.;<=>^_`{|}~", + "hostname": "%1F!\"$&'()*+,-.;<=>^_`{|}~", + "port": "", + "pathname": "/", + "search": "", + "hash": "" + }, { "input": "sc://\u0000/", "base": "about:blank", @@ -4634,68 +4649,6 @@ "search": "", "hash": "" }, - "Forbidden host code points", - { - "input": "http://ab", - "base": "about:blank", - "failure": true - }, - { - "input": "http://a^b", - "base": "about:blank", - "failure": true - }, - { - "input": "non-special://ab", - "base": "about:blank", - "failure": true - }, - { - "input": "non-special://a^b", - "base": "about:blank", - "failure": true - }, - "Allowed host code points", - { - "input": "http://\u001F!\"$&'()*+,-.;=_`{|}~/", - "base": "about:blank", - "href": "http://\u001F!\"$&'()*+,-.;=_`{|}~/", - "origin": "http://\u001F!\"$&'()*+,-.;=_`{|}~", - "protocol": "http:", - "username": "", - "password": "", - "host": "\u001F!\"$&'()*+,-.;=_`{|}~", - "hostname": "\u001F!\"$&'()*+,-.;=_`{|}~", - "port": "", - "pathname": "/", - "search": "", - "hash": "" - }, - { - "input": "sc://\u001F!\"$&'()*+,-.;=_`{|}~/", - "base": "about:blank", - "href": "sc://%1F!\"$&'()*+,-.;=_`{|}~/", - "origin": "null", - "protocol": "sc:", - "username": "", - "password": "", - "host": "%1F!\"$&'()*+,-.;=_`{|}~", - "hostname": "%1F!\"$&'()*+,-.;=_`{|}~", - "port": "", - "pathname": "/", - "search": "", - "hash": "" - }, "# Hosts and percent-encoding", { "input": "ftp://example.com%80/", diff --git a/test/fixtures/wpt/url/toascii.window.js b/test/fixtures/wpt/url/toascii.window.js index b28c664479a26a..cdc488ec28526f 100644 --- a/test/fixtures/wpt/url/toascii.window.js +++ b/test/fixtures/wpt/url/toascii.window.js @@ -28,7 +28,7 @@ function runTests(tests) { assert_equals(url.href, "https://" + hostTest.output + "/x") } else { if(type === "url") { - assert_throws(new TypeError, () => makeURL("url", hostTest.input)) + assert_throws_js(TypeError, () => makeURL("url", hostTest.input)) } else { const url = makeURL(type, hostTest.input) assert_equals(url.host, "") diff --git a/test/fixtures/wpt/url/url-constructor.html b/test/fixtures/wpt/url/url-constructor.html index cb4c0db3571d2f..7ddcdc88170c20 100644 --- a/test/fixtures/wpt/url/url-constructor.html +++ b/test/fixtures/wpt/url/url-constructor.html @@ -15,7 +15,7 @@ test(function() { if (expected.failure) { - assert_throws(new TypeError(), function() { + assert_throws_js(TypeError, function() { bURL(expected.input, expected.base) }) return diff --git a/test/fixtures/wpt/url/url-searchparams.any.js b/test/fixtures/wpt/url/url-searchparams.any.js index c55ae58d3b1e5b..34d4a4b0689831 100644 --- a/test/fixtures/wpt/url/url-searchparams.any.js +++ b/test/fixtures/wpt/url/url-searchparams.any.js @@ -29,7 +29,7 @@ function runURLSearchParamTests() { 'use strict' var urlString = 'http://example.org' var url = bURL(urlString) - assert_throws(TypeError(), function() { url.searchParams = new URLSearchParams(urlString) }) + assert_throws_js(TypeError, function() { url.searchParams = new URLSearchParams(urlString) }) }, 'URL.searchParams setter, invalid values') test(function() { diff --git a/test/fixtures/wpt/url/urlsearchparams-constructor.any.js b/test/fixtures/wpt/url/urlsearchparams-constructor.any.js index 398021dde2f35f..f7989f70ea0071 100644 --- a/test/fixtures/wpt/url/urlsearchparams-constructor.any.js +++ b/test/fixtures/wpt/url/urlsearchparams-constructor.any.js @@ -22,7 +22,7 @@ test(function () { test(() => { var params = new URLSearchParams(DOMException); assert_equals(params.toString(), "INDEX_SIZE_ERR=1&DOMSTRING_SIZE_ERR=2&HIERARCHY_REQUEST_ERR=3&WRONG_DOCUMENT_ERR=4&INVALID_CHARACTER_ERR=5&NO_DATA_ALLOWED_ERR=6&NO_MODIFICATION_ALLOWED_ERR=7&NOT_FOUND_ERR=8&NOT_SUPPORTED_ERR=9&INUSE_ATTRIBUTE_ERR=10&INVALID_STATE_ERR=11&SYNTAX_ERR=12&INVALID_MODIFICATION_ERR=13&NAMESPACE_ERR=14&INVALID_ACCESS_ERR=15&VALIDATION_ERR=16&TYPE_MISMATCH_ERR=17&SECURITY_ERR=18&NETWORK_ERR=19&ABORT_ERR=20&URL_MISMATCH_ERR=21"A_EXCEEDED_ERR=22&TIMEOUT_ERR=23&INVALID_NODE_TYPE_ERR=24&DATA_CLONE_ERR=25") - assert_throws(new TypeError(), () => new URLSearchParams(DOMException.prototype), + assert_throws_js(TypeError, () => new URLSearchParams(DOMException.prototype), "Constructing a URLSearchParams from DOMException.prototype should throw due to branding checks"); }, "URLSearchParams constructor, DOMException as argument") @@ -153,8 +153,8 @@ test(function() { params = new URLSearchParams([['a', 'b'], ['c', 'd']]); assert_equals(params.get("a"), "b"); assert_equals(params.get("c"), "d"); - assert_throws(new TypeError(), function() { new URLSearchParams([[1]]); }); - assert_throws(new TypeError(), function() { new URLSearchParams([[1,2,3]]); }); + assert_throws_js(TypeError, function() { new URLSearchParams([[1]]); }); + assert_throws_js(TypeError, function() { new URLSearchParams([[1,2,3]]); }); }, "Constructor with sequence of sequences of strings"); [ diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 047ac440d8e5d7..39c6dce26652e6 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -1,30 +1,30 @@ { "console": { - "commit": "9786a4b1317c03b89ea3bf2e997f2e2b6a3690ae", + "commit": "b0daaa6b86c3dc2f9c16aa362136655c0db7308b", "path": "console" }, "encoding": { - "commit": "5059d2c77703d67d2f76931b44e6d2437526b6e9", + "commit": "ab733fd9f53eefdc034a2b96d08f080b355b6b10", "path": "encoding" }, "url": { - "commit": "43feb7f612fe9160639e09a47933a29834904d69", + "commit": "3696f2233a37437896505b7187968aa605be9255", "path": "url" }, "resources": { - "commit": "e1fddfbf801e7cce9cac5645e992194e4059ef56", + "commit": "abfd8c97298f0ead66597a37b8b87a79347df4f3", "path": "resources" }, "interfaces": { - "commit": "8ada332aeac03764f73ec7ff66f682c5c0b454b4", + "commit": "3cbf8e150b2cbfc1efedcaf350ec464f0d234cca", "path": "interfaces" }, "html/webappapis/microtask-queuing": { - "commit": "0c3bed38df6d9dcd1441873728fb5c1bb59c92df", + "commit": "2c5c3c4c27d27a419c1fdba3e9879c2d22037074", "path": "html/webappapis/microtask-queuing" }, "html/webappapis/timers": { - "commit": "ddfe9c089bab565a9d3aa37bdef63d8012c1a94c", + "commit": "264f12bc7bf5db0c6dd064842a5d39ccbf9208c5", "path": "html/webappapis/timers" }, "hr-time": {