diff --git a/Cargo.lock b/Cargo.lock index 3aa25c2cad..6a60a3467f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -792,20 +792,20 @@ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" -version = "0.14.0-rc.32" +version = "0.14.0-rc.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda94f31325c4275e9706adecbb6f0650dee2f904c915a98e3d81adaaaa757aa" +checksum = "102d3643d30dd8b559613c5cced68317199597fffb278cdc88daa2ef7fafc935" dependencies = [ "base16ct", "crypto-bigint", "crypto-common", "digest", + "ff", + "group", "hkdf", "hybrid-array", "pkcs8", "rand_core", - "rustcrypto-ff", - "rustcrypto-group", "sec1", "subtle", "zeroize", @@ -853,6 +853,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.3.0" @@ -1083,6 +1093,17 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "group" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" version = "0.4.14" @@ -2492,9 +2513,9 @@ checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" [[package]] name = "p256" -version = "0.14.0-rc.9" +version = "0.14.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b97e3bf0465157ae90975ff52dbeb1362ba618924878c9f74c25baa27a65f9a" +checksum = "41adc63effe99d48837a8cc0e6d7a77e32ae6a07f6000df466178dbc2193093e" dependencies = [ "ecdsa", "elliptic-curve", @@ -2505,9 +2526,9 @@ dependencies = [ [[package]] name = "p384" -version = "0.14.0-rc.9" +version = "0.14.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437f30ebcb1e16ff48acead5f08bd69fbcdbc82421687bb48af5c315a0bfab03" +checksum = "9bd5333afa5ae0347f39e6a0f2c9c155da431583fd71fe5555bd0521b4ccaf02" dependencies = [ "ecdsa", "elliptic-curve", @@ -2519,9 +2540,9 @@ dependencies = [ [[package]] name = "p521" -version = "0.14.0-rc.9" +version = "0.14.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9fd792bab86ecf6249561752fb5a413511f999887107dd054bbda5143743d7" +checksum = "a3a5297f53dc16d35909060ba3032cff7867e8809f01e273ff325579d5f0ceae" dependencies = [ "base16ct", "ecdsa", @@ -2739,23 +2760,23 @@ dependencies = [ [[package]] name = "primefield" -version = "0.14.0-rc.9" +version = "0.14.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b52e6ee42db392378a95622b463c9740631171d1efce43fa445a569c1600cb6" +checksum = "f845ec3240cd5ed5e1e31cf3ff633a5bf47c698dc4092ba9e767415b3d393406" dependencies = [ "crypto-bigint", "crypto-common", + "ff", "rand_core", - "rustcrypto-ff", "subtle", "zeroize", ] [[package]] name = "primeorder" -version = "0.14.0-rc.9" +version = "0.14.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0556580e42c19833f5d232aca11a7687a503ee41f937b54f5ae1d50fc2a6a36a" +checksum = "7d2793f22b9b6fd11ef3ac1d59bf003c2573593e4968702341605c2748fd90bf" dependencies = [ "elliptic-curve", ] @@ -3011,27 +3032,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustcrypto-ff" -version = "0.14.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd2a8adb347447693cd2ba0d218c4b66c62da9b0a5672b17b981e4291ec65ff6" -dependencies = [ - "rand_core", - "subtle", -] - -[[package]] -name = "rustcrypto-group" -version = "0.14.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "369f9b61aa45933c062c9f6b5c3c50ab710687eca83dd3802653b140b43f85ed" -dependencies = [ - "rand_core", - "rustcrypto-ff", - "subtle", -] - [[package]] name = "rustix" version = "1.1.4" diff --git a/llrt_core/src/modules/js/@llrt/test/index.ts b/llrt_core/src/modules/js/@llrt/test/index.ts index 5e7938b9cb..d2713c9160 100644 --- a/llrt_core/src/modules/js/@llrt/test/index.ts +++ b/llrt_core/src/modules/js/@llrt/test/index.ts @@ -31,6 +31,7 @@ type SuiteResult = TestProps & { tests: TestResult[]; children: SuiteResult[]; parent: SuiteResult | null; + error?: Error | null; }; type RootSuite = TestProps & { @@ -414,6 +415,14 @@ class TestServer { const test = workerData.currentTest!; test.ended = ended; test.success = true; + // A test has completed successfully; clear the reference so a later + // suite-level hook failure (e.g. a child suite's beforeAll) is not + // misattributed to this passing test. + workerData.currentTest = null; + // Clear captured output so stderr/stdout from this passing test does + // not leak into a later failure's report. + workerData.stdErrBuffer.clear(); + workerData.stdOutBuffer.clear(); } workerData.currentPath.pop(); @@ -495,13 +504,6 @@ class TestServer { } handleTestError(workerId: number, error: any, ended: number) { const workerData = this.workerData[workerId]; - const test = workerData.currentTest || { - desc: "", - success: false, - started: 0, - ended: 0, - error, - }; workerData.success = false; const results = this.results.get(workerData.file!); if (results) { @@ -518,9 +520,22 @@ class TestServer { workerData.stdOutBuffer.clear(); this.filesFailed.set(workerData.file!, testFailures); this.totalFailed++; - test.ended = ended; - test.error = error; - test.success = false; + + const test = workerData.currentTest; + if (test) { + // The error occurred inside a running test. + test.ended = ended; + test.error = error; + test.success = false; + workerData.currentTest = null; + } else if (workerData.result) { + // No test is running, so the error came from a suite-level hook + // (e.g. beforeAll/afterAll). Mark the currently-open suite as failed + // instead of misattributing it to a previously passing test. + workerData.result.ended = ended; + workerData.result.error = error; + workerData.result.success = false; + } workerData.currentPath.pop(); } @@ -726,6 +741,9 @@ class TestServer { const results = result.children; for (let result of results) { output += `${indent}${Color.BOLD(result.desc)} ${Color.DIM(TestServer.elapsed(result))}\n`; + if (result.error) { + output += this.formattedError(result.error) + "\n"; + } output += this.printSuiteResult(result, depth + 1); } return output; diff --git a/modules/llrt_crypto/Cargo.toml b/modules/llrt_crypto/Cargo.toml index 6a99896a33..e721ec2576 100644 --- a/modules/llrt_crypto/Cargo.toml +++ b/modules/llrt_crypto/Cargo.toml @@ -87,7 +87,7 @@ ed25519-dalek = { version = "3.0.0-pre.7", features = [ "pkcs8", "rand_core", ], default-features = false, optional = true } -elliptic-curve = { version = "0.14.0-rc.30", features = [ +elliptic-curve = { version = "0.14.0-rc.33", features = [ "alloc", "sec1", ], default-features = false, optional = true } @@ -98,17 +98,17 @@ rsa = { version = "0.10.0-rc.18", features = [ "sha2", "encoding", ], default-features = false, optional = true } -p256 = { version = "0.14.0-rc.9", features = [ +p256 = { version = "0.14.0-rc.10", features = [ "ecdh", "ecdsa", "pkcs8", ], default-features = false, optional = true } -p384 = { version = "0.14.0-rc.9", features = [ +p384 = { version = "0.14.0-rc.10", features = [ "ecdh", "ecdsa", "pkcs8", ], default-features = false, optional = true } -p521 = { version = "0.14.0-rc.9", features = [ +p521 = { version = "0.14.0-rc.10", features = [ "ecdh", "ecdsa", "pkcs8", diff --git a/tests/wpt/FileAPI/support/Blob.js b/tests/wpt/FileAPI/support/Blob.js deleted file mode 100644 index d93b29fe43..0000000000 --- a/tests/wpt/FileAPI/support/Blob.js +++ /dev/null @@ -1,65 +0,0 @@ -export default function (self) { - "use strict"; - - self.test_blob = (fn, expectations) => { - var expected = expectations.expected, - type = expectations.type, - desc = expectations.desc; - - promise_test(async (t) => { - var blob = fn(); - assert_true(blob instanceof Blob); - assert_false(blob instanceof File); - assert_equals(blob.type, type); - assert_equals(blob.size, expected.length); - - const text = await blob.text(); - assert_equals(text, expected); - }, desc); - }; - - self.test_blob_binary = (fn, expectations) => { - var expected = expectations.expected, - type = expectations.type, - desc = expectations.desc; - - promise_test(async (t) => { - var blob = fn(); - assert_true(blob instanceof Blob); - assert_false(blob instanceof File); - assert_equals(blob.type, type); - assert_equals(blob.size, expected.length); - - const ab = await blob.arrayBuffer(); - assert_true(ab instanceof ArrayBuffer, "Result should be an ArrayBuffer"); - assert_array_equals(new Uint8Array(ab), expected); - }, desc); - }; - - // Assert that two TypedArray objects have the same byte values - self.assert_equals_typed_array = (array1, array2) => { - const [view1, view2] = [array1, array2].map((array) => { - self.assert_true( - array.buffer instanceof ArrayBuffer, - "Expect input ArrayBuffers to contain field `buffer`" - ); - return new DataView(array.buffer, array.byteOffset, array.byteLength); - }); - - self.assert_equals( - view1.byteLength, - view2.byteLength, - "Expect both arrays to be of the same byte length" - ); - - const byteLength = view1.byteLength; - - for (let i = 0; i < byteLength; ++i) { - self.assert_equals( - view1.getUint8(i), - view2.getUint8(i), - `Expect byte at buffer position ${i} to be equal` - ); - } - }; -} diff --git a/tests/wpt/FileAPI/support/send-file-formdata-helper.js b/tests/wpt/FileAPI/support/send-file-formdata-helper.js deleted file mode 100644 index 87cb6ab8b2..0000000000 --- a/tests/wpt/FileAPI/support/send-file-formdata-helper.js +++ /dev/null @@ -1,97 +0,0 @@ -export default function (self) { - "use strict"; - - const kTestChars = "ABC~‾¥≈¤・・•∙·☼★星🌟星★☼·∙•・・¤≈¥‾~XYZ"; - - // formDataPostFileUploadTest - verifies multipart upload structure and - // numeric character reference replacement for filenames, field names, - // and field values using FormData and fetch(). - // - // Uses /fetch/api/resources/echo-content.py to echo the upload - // POST (unlike in send-file-form-helper.js, here we expect all - // multipart/form-data request bodies to be UTF-8, so we don't need to - // escape controls and non-ASCII bytes). - // - // Fields in the parameter object: - // - // - fileNameSource: purely explanatory and gives a clue about which - // character encoding is the source for the non-7-bit-ASCII parts of - // the fileBaseName, or Unicode if no smaller-than-Unicode source - // contains all the characters. Used in the test name. - // - fileBaseName: the not-necessarily-just-7-bit-ASCII file basename - // used for the constructed test file. Used in the test name. - const formDataPostFileUploadTest = ({ fileNameSource, fileBaseName }) => { - promise_test(async (testCase) => { - const formData = new FormData(); - let file = new Blob([kTestChars], { type: "text/plain" }); - try { - // Switch to File in browsers that allow this - file = new File([file], fileBaseName, { type: file.type }); - } catch (ignoredException) {} - - // Used to verify that the browser agrees with the test about - // field value replacement and encoding independently of file system - // idiosyncracies. - formData.append("filename", fileBaseName); - - // Same, but with name and value reversed to ensure field names - // get the same treatment. - formData.append(fileBaseName, "filename"); - - formData.append("file", file, fileBaseName); - - const formDataText = await ( - await fetch(`/fetch/api/resources/echo-content.py`, { - method: "POST", - body: formData, - }) - ).text(); - const formDataLines = formDataText.split("\r\n"); - if (formDataLines.length && !formDataLines[formDataLines.length - 1]) { - --formDataLines.length; - } - assert_greater_than( - formDataLines.length, - 2, - `${fileBaseName}: multipart form data must have at least 3 lines: ${JSON.stringify( - formDataText - )}` - ); - const boundary = formDataLines[0]; - assert_equals( - formDataLines[formDataLines.length - 1], - boundary + "--", - `${fileBaseName}: multipart form data must end with ${boundary}--: ${JSON.stringify( - formDataText - )}` - ); - - const asValue = fileBaseName.replace(/\r\n?|\n/g, "\r\n"); - const asName = asValue.replace(/[\r\n"]/g, encodeURIComponent); - const asFilename = fileBaseName.replace(/[\r\n"]/g, encodeURIComponent); - const expectedText = [ - boundary, - 'Content-Disposition: form-data; name="filename"', - "", - asValue, - boundary, - `Content-Disposition: form-data; name="${asName}"`, - "", - "filename", - boundary, - `Content-Disposition: form-data; name="file"; ` + - `filename="${asFilename}"`, - "Content-Type: text/plain", - "", - kTestChars, - boundary + "--", - ].join("\r\n"); - - assert_true( - formDataText.startsWith(expectedText), - `Unexpected multipart-shaped form data received:\n${formDataText}\nExpected:\n${expectedText}` - ); - }, `Upload ${fileBaseName} (${fileNameSource}) in fetch with FormData`); - }; - self.formDataPostFileUploadTest = formDataPostFileUploadTest; -} diff --git a/tests/wpt/common/gc.js b/tests/wpt/common/gc.js deleted file mode 100644 index 574476d951..0000000000 --- a/tests/wpt/common/gc.js +++ /dev/null @@ -1,55 +0,0 @@ -export default function ({}) { -/** - * Does a best-effort attempt at invoking garbage collection. Attempts to use - * the standardized `TestUtils.gc()` function, but falls back to other - * environment-specific nonstandard functions, with a final result of just - * creating a lot of garbage (in which case you will get a console warning). - * - * This should generally only be used to attempt to trigger bugs and crashes - * inside tests, i.e. cases where if garbage collection happened, then this - * should not trigger some misbehavior. You cannot rely on garbage collection - * successfully trigger, or that any particular unreachable object will be - * collected. - * - * @returns {Promise} A promise you should await to ensure garbage - * collection has had a chance to complete. - */ -self.garbageCollect = async () => { - // https://testutils.spec.whatwg.org/#the-testutils-namespace - if (self.TestUtils?.gc) { - return TestUtils.gc(); - } - - // Use --expose_gc for V8 (and Node.js) - // to pass this flag at chrome launch use: --js-flags="--expose-gc" - // Exposed in SpiderMonkey shell as well - if (self.gc) { - return self.gc(); - } - - // Present in some WebKit development environments - if (self.GCController) { - return GCController.collect(); - } - - console.warn( - "Tests are running without the ability to do manual garbage collection. " + - "They will still work, but coverage will be suboptimal." - ); - - for (var i = 0; i < 1000; i++) { - gcRec(10); - } - - function gcRec(n) { - if (n < 1) { - return {}; - } - - let temp = { i: "ab" + i + i / 100000 }; - temp += "foo"; - - gcRec(n - 1); - } -}; -} diff --git a/tests/wpt/common/get-host-info.sub.js b/tests/wpt/common/get-host-info.sub.js deleted file mode 100644 index a0408fa9c1..0000000000 --- a/tests/wpt/common/get-host-info.sub.js +++ /dev/null @@ -1,77 +0,0 @@ -export default function (self) { - (function () { - /** - * Host information for cross-origin tests. - * @returns {Object} with properties for different host information. - */ - function get_host_info() { - var HTTP_PORT = "{{ports[http][0]}}"; - var HTTP_PORT2 = "{{ports[http][1]}}"; - var HTTPS_PORT = "{{ports[https][0]}}"; - var HTTPS_PORT2 = "{{ports[https][1]}}"; - var PROTOCOL = self.location.protocol; - var IS_HTTPS = PROTOCOL == "https:"; - var PORT = IS_HTTPS ? HTTPS_PORT : HTTP_PORT; - var PORT2 = IS_HTTPS ? HTTPS_PORT2 : HTTP_PORT2; - var HTTP_PORT_ELIDED = HTTP_PORT == "80" ? "" : ":" + HTTP_PORT; - var HTTP_PORT2_ELIDED = HTTP_PORT2 == "80" ? "" : ":" + HTTP_PORT2; - var HTTPS_PORT_ELIDED = HTTPS_PORT == "443" ? "" : ":" + HTTPS_PORT; - var PORT_ELIDED = IS_HTTPS ? HTTPS_PORT_ELIDED : HTTP_PORT_ELIDED; - var ORIGINAL_HOST = "{{host}}"; - var REMOTE_HOST = - ORIGINAL_HOST === "localhost" ? "127.0.0.1" : "www1." + ORIGINAL_HOST; - var OTHER_HOST = "{{domains[www2]}}"; - var NOTSAMESITE_HOST = - ORIGINAL_HOST === "localhost" ? "127.0.0.1" : "{{hosts[alt][]}}"; - - return { - HTTP_PORT: HTTP_PORT, - HTTP_PORT2: HTTP_PORT2, - HTTPS_PORT: HTTPS_PORT, - HTTPS_PORT2: HTTPS_PORT2, - PORT: PORT, - PORT2: PORT2, - ORIGINAL_HOST: ORIGINAL_HOST, - REMOTE_HOST: REMOTE_HOST, - NOTSAMESITE_HOST, - - ORIGIN: PROTOCOL + "//" + ORIGINAL_HOST + PORT_ELIDED, - HTTP_ORIGIN: "http://" + ORIGINAL_HOST + HTTP_PORT_ELIDED, - HTTPS_ORIGIN: "https://" + ORIGINAL_HOST + HTTPS_PORT_ELIDED, - HTTPS_ORIGIN_WITH_CREDS: - "https://foo:bar@" + ORIGINAL_HOST + HTTPS_PORT_ELIDED, - HTTP_ORIGIN_WITH_DIFFERENT_PORT: - "http://" + ORIGINAL_HOST + HTTP_PORT2_ELIDED, - REMOTE_ORIGIN: PROTOCOL + "//" + REMOTE_HOST + PORT_ELIDED, - OTHER_ORIGIN: PROTOCOL + "//" + OTHER_HOST + PORT_ELIDED, - HTTP_REMOTE_ORIGIN: "http://" + REMOTE_HOST + HTTP_PORT_ELIDED, - HTTP_NOTSAMESITE_ORIGIN: - "http://" + NOTSAMESITE_HOST + HTTP_PORT_ELIDED, - HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT: - "http://" + REMOTE_HOST + HTTP_PORT2_ELIDED, - HTTPS_REMOTE_ORIGIN: "https://" + REMOTE_HOST + HTTPS_PORT_ELIDED, - HTTPS_REMOTE_ORIGIN_WITH_CREDS: - "https://foo:bar@" + REMOTE_HOST + HTTPS_PORT_ELIDED, - HTTPS_NOTSAMESITE_ORIGIN: - "https://" + NOTSAMESITE_HOST + HTTPS_PORT_ELIDED, - UNAUTHENTICATED_ORIGIN: "http://" + OTHER_HOST + HTTP_PORT_ELIDED, - AUTHENTICATED_ORIGIN: "https://" + OTHER_HOST + HTTPS_PORT_ELIDED, - }; - } - - /** - * When a default port is used, location.port returns the empty string. - * This function attempts to provide an exact port, assuming we are running under wptserve. - * @param {*} loc - can be Location///URL, but assumes http/https only. - * @returns {string} The port number. - */ - function get_port(loc) { - if (loc.port) { - return loc.port; - } - return loc.protocol === "https:" ? "443" : "80"; - } - self.get_host_info = get_host_info; - self.get_port = get_port; - })(); -} diff --git a/tests/wpt/common/subset-tests.js b/tests/wpt/common/subset-tests.js deleted file mode 100644 index 2095260b67..0000000000 --- a/tests/wpt/common/subset-tests.js +++ /dev/null @@ -1,70 +0,0 @@ -export default function (self) { -const location = self.location; - -(function () { - var subTestStart = 0; - var subTestEnd = Infinity; - var match; - if (location.search) { - match = /(?:^\?|&)(\d+)-(\d+|last)(?:&|$)/.exec(location.search); - if (match) { - subTestStart = parseInt(match[1], 10); - if (match[2] !== "last") { - subTestEnd = parseInt(match[2], 10); - } - } - // Below is utility code to generate for copy/paste into tests. - // Sample usage: - // test.html?split=1000 - match = /(?:^\?|&)split=(\d+)(?:&|$)/.exec(location.search); - if (match) { - var testsPerVariant = parseInt(match[1], 10); - add_completion_callback((tests) => { - var total = tests.length; - var template = ''; - var metas = []; - for ( - var i = 1; - i < total - testsPerVariant; - i = i + testsPerVariant - ) { - metas.push( - template.replace("%s", i).replace("%s", i + testsPerVariant - 1) - ); - } - metas.push(template.replace("%s", i).replace("%s", "last")); - var pre = document.createElement("pre"); - pre.textContent = metas.join("\n"); - document.body.insertBefore(pre, document.body.firstChild); - document.getSelection().selectAllChildren(pre); - }); - } - } - /** - * Check if `currentSubTest` is in the subset specified in the URL. - * @param {number} currentSubTest - * @returns {boolean} - */ - function shouldRunSubTest(currentSubTest) { - return currentSubTest >= subTestStart && currentSubTest <= subTestEnd; - } - var currentSubTest = 0; - /** - * Only test a subset of tests with, e.g., `?1-10` in the URL. - * Can be used together with `` - * Sample usage: - * for (const test of tests) { - * subsetTest(async_test, test.fn, test.name); - * } - */ - function subsetTest(testFunc, ...args) { - currentSubTest++; - if (shouldRunSubTest(currentSubTest)) { - return testFunc(...args); - } - return null; - } - self.shouldRunSubTest = shouldRunSubTest; - self.subsetTest = subsetTest; -})(); -} diff --git a/tests/wpt/encoding/resources/decoding-helpers.js b/tests/wpt/encoding/resources/decoding-helpers.js deleted file mode 100644 index 2180de9698..0000000000 --- a/tests/wpt/encoding/resources/decoding-helpers.js +++ /dev/null @@ -1,41 +0,0 @@ -export default function (self) { - // Decode an URL encoded string, using XHR and data: URL. Returns a Promise. - function decode(label, url_encoded_string) { - return new Promise((resolve, reject) => { - const req = new XMLHttpRequest(); - req.open("GET", `data:text/plain,${url_encoded_string}`); - req.overrideMimeType(`text/plain; charset="${label}"`); - req.send(); - req.onload = () => resolve(req.responseText); - req.onerror = () => reject(new Error(req.statusText)); - }); - } - - // Convert code units in a decoded string into: "U+0001/U+0002/...' - function to_code_units(string) { - return string - .split("") - .map((unit) => unit.charCodeAt(0)) - .map( - (code) => "U+" + ("0000" + code.toString(16).toUpperCase()).slice(-4) - ) - .join("/"); - } - - function decode_test( - label, - url_encoded_input, - expected_code_units, - description - ) { - promise_test(() => { - return decode(label, url_encoded_input) - .then((decoded) => to_code_units(decoded)) - .then((actual) => { - assert_equals(actual, expected_code_units, `Decoding with ${label}`); - }); - }, description); - } - - self.decode_test = decode_test; -} diff --git a/tests/wpt/encoding/resources/encodings.js b/tests/wpt/encoding/resources/encodings.js deleted file mode 100644 index 4c050af8ee..0000000000 --- a/tests/wpt/encoding/resources/encodings.js +++ /dev/null @@ -1,469 +0,0 @@ -// Straight from https://encoding.spec.whatwg.org/encodings.json -const encodings_table = -[ - { - "encodings": [ - { - "labels": [ - "unicode-1-1-utf-8", - "unicode11utf8", - "unicode20utf8", - "utf-8", - "utf8", - "x-unicode20utf8" - ], - "name": "UTF-8" - } - ], - "heading": "The Encoding" - }, - { - "encodings": [ - // { - // "labels": [ - // "866", - // "cp866", - // "csibm866", - // "ibm866" - // ], - // "name": "IBM866" - // }, - // { - // "labels": [ - // "csisolatin2", - // "iso-8859-2", - // "iso-ir-101", - // "iso8859-2", - // "iso88592", - // "iso_8859-2", - // "iso_8859-2:1987", - // "l2", - // "latin2" - // ], - // "name": "ISO-8859-2" - // }, - // { - // "labels": [ - // "csisolatin3", - // "iso-8859-3", - // "iso-ir-109", - // "iso8859-3", - // "iso88593", - // "iso_8859-3", - // "iso_8859-3:1988", - // "l3", - // "latin3" - // ], - // "name": "ISO-8859-3" - // }, - // { - // "labels": [ - // "csisolatin4", - // "iso-8859-4", - // "iso-ir-110", - // "iso8859-4", - // "iso88594", - // "iso_8859-4", - // "iso_8859-4:1988", - // "l4", - // "latin4" - // ], - // "name": "ISO-8859-4" - // }, - // { - // "labels": [ - // "csisolatincyrillic", - // "cyrillic", - // "iso-8859-5", - // "iso-ir-144", - // "iso8859-5", - // "iso88595", - // "iso_8859-5", - // "iso_8859-5:1988" - // ], - // "name": "ISO-8859-5" - // }, - // { - // "labels": [ - // "arabic", - // "asmo-708", - // "csiso88596e", - // "csiso88596i", - // "csisolatinarabic", - // "ecma-114", - // "iso-8859-6", - // "iso-8859-6-e", - // "iso-8859-6-i", - // "iso-ir-127", - // "iso8859-6", - // "iso88596", - // "iso_8859-6", - // "iso_8859-6:1987" - // ], - // "name": "ISO-8859-6" - // }, - // { - // "labels": [ - // "csisolatingreek", - // "ecma-118", - // "elot_928", - // "greek", - // "greek8", - // "iso-8859-7", - // "iso-ir-126", - // "iso8859-7", - // "iso88597", - // "iso_8859-7", - // "iso_8859-7:1987", - // "sun_eu_greek" - // ], - // "name": "ISO-8859-7" - // }, - // { - // "labels": [ - // "csiso88598e", - // "csisolatinhebrew", - // "hebrew", - // "iso-8859-8", - // "iso-8859-8-e", - // "iso-ir-138", - // "iso8859-8", - // "iso88598", - // "iso_8859-8", - // "iso_8859-8:1988", - // "visual" - // ], - // "name": "ISO-8859-8" - // }, - // { - // "labels": [ - // "csiso88598i", - // "iso-8859-8-i", - // "logical" - // ], - // "name": "ISO-8859-8-I" - // }, - // { - // "labels": [ - // "csisolatin6", - // "iso-8859-10", - // "iso-ir-157", - // "iso8859-10", - // "iso885910", - // "l6", - // "latin6" - // ], - // "name": "ISO-8859-10" - // }, - // { - // "labels": [ - // "iso-8859-13", - // "iso8859-13", - // "iso885913" - // ], - // "name": "ISO-8859-13" - // }, - // { - // "labels": [ - // "iso-8859-14", - // "iso8859-14", - // "iso885914" - // ], - // "name": "ISO-8859-14" - // }, - // { - // "labels": [ - // "csisolatin9", - // "iso-8859-15", - // "iso8859-15", - // "iso885915", - // "iso_8859-15", - // "l9" - // ], - // "name": "ISO-8859-15" - // }, - // { - // "labels": [ - // "iso-8859-16" - // ], - // "name": "ISO-8859-16" - // }, - // { - // "labels": [ - // "cskoi8r", - // "koi", - // "koi8", - // "koi8-r", - // "koi8_r" - // ], - // "name": "KOI8-R" - // }, - // { - // "labels": [ - // "koi8-ru", - // "koi8-u" - // ], - // "name": "KOI8-U" - // }, - // { - // "labels": [ - // "csmacintosh", - // "mac", - // "macintosh", - // "x-mac-roman" - // ], - // "name": "macintosh" - // }, - // { - // "labels": [ - // "dos-874", - // "iso-8859-11", - // "iso8859-11", - // "iso885911", - // "tis-620", - // "windows-874" - // ], - // "name": "windows-874" - // }, - // { - // "labels": [ - // "cp1250", - // "windows-1250", - // "x-cp1250" - // ], - // "name": "windows-1250" - // }, - // { - // "labels": [ - // "cp1251", - // "windows-1251", - // "x-cp1251" - // ], - // "name": "windows-1251" - // }, - { - "labels": [ - "ansi_x3.4-1968", - "ascii", - "cp1252", - "cp819", - "csisolatin1", - "ibm819", - "iso-8859-1", - "iso-ir-100", - "iso8859-1", - "iso88591", - "iso_8859-1", - "iso_8859-1:1987", - "l1", - "latin1", - "us-ascii", - "windows-1252", - "x-cp1252" - ], - "name": "windows-1252" - }, - // { - // "labels": [ - // "cp1253", - // "windows-1253", - // "x-cp1253" - // ], - // "name": "windows-1253" - // }, - // { - // "labels": [ - // "cp1254", - // "csisolatin5", - // "iso-8859-9", - // "iso-ir-148", - // "iso8859-9", - // "iso88599", - // "iso_8859-9", - // "iso_8859-9:1989", - // "l5", - // "latin5", - // "windows-1254", - // "x-cp1254" - // ], - // "name": "windows-1254" - // }, - // { - // "labels": [ - // "cp1255", - // "windows-1255", - // "x-cp1255" - // ], - // "name": "windows-1255" - // }, - // { - // "labels": [ - // "cp1256", - // "windows-1256", - // "x-cp1256" - // ], - // "name": "windows-1256" - // }, - // { - // "labels": [ - // "cp1257", - // "windows-1257", - // "x-cp1257" - // ], - // "name": "windows-1257" - // }, - // { - // "labels": [ - // "cp1258", - // "windows-1258", - // "x-cp1258" - // ], - // "name": "windows-1258" - // }, - // { - // "labels": [ - // "x-mac-cyrillic", - // "x-mac-ukrainian" - // ], - // "name": "x-mac-cyrillic" - // } - ], - "heading": "Legacy single-byte encodings" - }, - // { - // "encodings": [ - // { - // "labels": [ - // "chinese", - // "csgb2312", - // "csiso58gb231280", - // "gb2312", - // "gb_2312", - // "gb_2312-80", - // "gbk", - // "iso-ir-58", - // "x-gbk" - // ], - // "name": "GBK" - // }, - // { - // "labels": [ - // "gb18030" - // ], - // "name": "gb18030" - // } - // ], - // "heading": "Legacy multi-byte Chinese (simplified) encodings" - // }, - // { - // "encodings": [ - // { - // "labels": [ - // "big5", - // "big5-hkscs", - // "cn-big5", - // "csbig5", - // "x-x-big5" - // ], - // "name": "Big5" - // } - // ], - // "heading": "Legacy multi-byte Chinese (traditional) encodings" - // }, - // { - // "encodings": [ - // { - // "labels": [ - // "cseucpkdfmtjapanese", - // "euc-jp", - // "x-euc-jp" - // ], - // "name": "EUC-JP" - // }, - // { - // "labels": [ - // "csiso2022jp", - // "iso-2022-jp" - // ], - // "name": "ISO-2022-JP" - // }, - // { - // "labels": [ - // "csshiftjis", - // "ms932", - // "ms_kanji", - // "shift-jis", - // "shift_jis", - // "sjis", - // "windows-31j", - // "x-sjis" - // ], - // "name": "Shift_JIS" - // } - // ], - // "heading": "Legacy multi-byte Japanese encodings" - // }, - // { - // "encodings": [ - // { - // "labels": [ - // "cseuckr", - // "csksc56011987", - // "euc-kr", - // "iso-ir-149", - // "korean", - // "ks_c_5601-1987", - // "ks_c_5601-1989", - // "ksc5601", - // "ksc_5601", - // "windows-949" - // ], - // "name": "EUC-KR" - // } - // ], - // "heading": "Legacy multi-byte Korean encodings" - // }, - { - "encodings": [ - { - "labels": [ - "csiso2022kr", - "hz-gb-2312", - "iso-2022-cn", - "iso-2022-cn-ext", - "iso-2022-kr", - "replacement" - ], - "name": "replacement" - }, - { - "labels": [ - "unicodefffe", - "utf-16be" - ], - "name": "UTF-16BE" - }, - { - "labels": [ - "csunicode", - "iso-10646-ucs-2", - "ucs-2", - "unicode", - "unicodefeff", - "utf-16", - "utf-16le" - ], - "name": "UTF-16LE" - }, - // { - // "labels": [ - // "x-user-defined" - // ], - // "name": "x-user-defined" - // } - ], - "heading": "Legacy miscellaneous encodings" - } -] -; -export default encodings_table; diff --git a/tests/wpt/fetch/api/cors/resources/not-cors-safelisted.json b/tests/wpt/fetch/api/cors/resources/not-cors-safelisted.json deleted file mode 100644 index 6c2508d3e7..0000000000 --- a/tests/wpt/fetch/api/cors/resources/not-cors-safelisted.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - ["accept", "\""], - [ - "accept", - "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" - ], - ["accept-language", "\u0001"], - ["accept-language", "@"], - ["authorization", "basics"], - ["content-language", "\u0001"], - ["content-language", "@"], - ["content-type", "text/html"], - [ - "content-type", - "text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901" - ], - ["range", "bytes 0-"], - ["test", "hi"] -] diff --git a/tests/wpt/fetch/api/request/request-cache.js b/tests/wpt/fetch/api/request/request-cache.js deleted file mode 100644 index fc03a48d1a..0000000000 --- a/tests/wpt/fetch/api/request/request-cache.js +++ /dev/null @@ -1,278 +0,0 @@ -export default function (self) { - /** - * Each test is run twice: once using etag/If-None-Match and once with - * date/If-Modified-Since. Each test run gets its own URL and randomized - * content and operates independently. - * - * The test steps are run with request_cache.length fetch requests issued - * and their immediate results sanity-checked. The cache.py server script - * stashes an entry containing any If-None-Match, If-Modified-Since, Pragma, - * and Cache-Control observed headers for each request it receives. When - * the test fetches have run, this state is retrieved from cache.py and the - * expected_* lists are checked, including their length. - * - * This means that if a request_* fetch is expected to hit the cache and not - * touch the network, then there will be no entry for it in the expect_* - * lists. AKA (request_cache.length - expected_validation_headers.length) - * should equal the number of cache hits that didn't touch the network. - * - * Test dictionary keys: - * - state: required string that determines whether the Expires response for - * the fetched document should be set in the future ("fresh") or past - * ("stale"). - * - vary: optional string to be passed to the server for it to quote back - * in a Vary header on the response to us. - * - cache_control: optional string to be passed to the server for it to - * quote back in a Cache-Control header on the response to us. - * - redirect: optional string "same-origin" or "cross-origin". If - * provided, the server will issue an absolute redirect to the script on - * the same or a different origin, as appropriate. The redirected - * location is the script with the redirect parameter removed, so the - * content/state/etc. will be as if you hadn't specified a redirect. - * - request_cache: required array of cache modes to use (via `cache`). - * - request_headers: optional array of explicit fetch `headers` arguments. - * If provided, the server will log an empty dictionary for each request - * instead of the request headers it would normally log. - * - response: optional array of specialized response handling. Right now, - * "error" array entries indicate a network error response is expected - * which will reject with a TypeError. - * - expected_validation_headers: required boolean array indicating whether - * the server should have seen an If-None-Match/If-Modified-Since header - * in the request. - * - expected_no_cache_headers: required boolean array indicating whether - * the server should have seen Pragma/Cache-control:no-cache headers in - * the request. - * - expected_max_age_headers: optional boolean array indicating whether - * the server should have seen a Cache-Control:max-age=0 header in the - * request. - */ - - var now = new Date(); - self.now = now; - - function base_path() { - return location.pathname.replace(/\/[^\/]*$/, "/"); - } - self.base_path = base_path; - - function make_url(uuid, id, value, content, info) { - var dates = { - fresh: new Date( - now.getFullYear() + 1, - now.getMonth(), - now.getDay() - ).toGMTString(), - stale: new Date( - now.getFullYear() - 1, - now.getMonth(), - now.getDay() - ).toGMTString(), - }; - var vary = ""; - if ("vary" in info) { - vary = "&vary=" + info.vary; - } - var cache_control = ""; - if ("cache_control" in info) { - cache_control = "&cache_control=" + info.cache_control; - } - var redirect = ""; - - var ignore_request_headers = ""; - if ("request_headers" in info) { - // Ignore the request headers that we send since they may be synthesized by the test. - ignore_request_headers = "&ignore"; - } - var url_sans_redirect = - "resources/cache.py?token=" + - uuid + - "&content=" + - content + - "&" + - id + - "=" + - value + - "&expires=" + - dates[info.state] + - vary + - cache_control + - ignore_request_headers; - // If there's a redirect, the target is the script without any redirect at - // either the same domain or a different domain. - if ("redirect" in info) { - var host_info = get_host_info(); - var origin; - switch (info.redirect) { - case "same-origin": - origin = host_info["HTTP_ORIGIN"]; - break; - case "cross-origin": - origin = host_info["HTTP_REMOTE_ORIGIN"]; - break; - } - var redirected_url = origin + base_path() + url_sans_redirect; - return ( - url_sans_redirect + "&redirect=" + encodeURIComponent(redirected_url) - ); - } else { - return url_sans_redirect; - } - } - self.make_url = make_url; - - function expected_status(type, identifier, init) { - if ( - type == "date" && - init.headers && - init.headers["If-Modified-Since"] == identifier - ) { - // The server will respond with a 304 in this case. - return [304, "Not Modified"]; - } - return [200, "OK"]; - } - self.expected_status = expected_status; - - function expected_response_text(type, identifier, init, content) { - if ( - type == "date" && - init.headers && - init.headers["If-Modified-Since"] == identifier - ) { - // The server will respond with a 304 in this case. - return ""; - } - return content; - } - self.expected_response_text = expected_response_text; - - function server_state(uuid) { - return fetch("resources/cache.py?querystate&token=" + uuid) - .then(function (response) { - return response.text(); - }) - .then(function (text) { - // null will be returned if the server never received any requests - // for the given uuid. Normalize that to an empty list consistent - // with our representation. - return JSON.parse(text) || []; - }); - } - self.server_state = server_state; - - function make_test(type, info) { - return function (test) { - var uuid = token(); - var identifier = type == "tag" ? Math.random() : now.toGMTString(); - var content = Math.random().toString(); - var url = make_url(uuid, type, identifier, content, info); - var fetch_functions = []; - for (var i = 0; i < info.request_cache.length; ++i) { - fetch_functions.push(function (idx) { - var init = { cache: info.request_cache[idx] }; - if ("request_headers" in info) { - init.headers = info.request_headers[idx]; - } - if (init.cache === "only-if-cached") { - // only-if-cached requires we use same-origin mode. - init.mode = "same-origin"; - } - return fetch(url, init) - .then(function (response) { - if ("response" in info && info.response[idx] === "error") { - assert_true(false, "fetch should have been an error"); - return; - } - assert_array_equals( - [response.status, response.statusText], - expected_status(type, identifier, init) - ); - return response.text(); - }) - .then( - function (text) { - assert_equals( - text, - expected_response_text(type, identifier, init, content) - ); - }, - function (reason) { - if ("response" in info && info.response[idx] === "error") { - assert_throws_js(TypeError, function () { - throw reason; - }); - } else { - throw reason; - } - } - ); - }); - } - var i = 0; - function run_next_step() { - if (fetch_functions.length) { - return fetch_functions.shift()(i++).then(run_next_step); - } else { - return Promise.resolve(); - } - } - return run_next_step() - .then(function () { - // Now, query the server state - return server_state(uuid); - }) - .then(function (state) { - var expectedState = []; - info.expected_validation_headers.forEach(function (validate) { - if (validate) { - if (type == "tag") { - expectedState.push({ "If-None-Match": '"' + identifier + '"' }); - } else { - expectedState.push({ "If-Modified-Since": identifier }); - } - } else { - expectedState.push({}); - } - }); - for (var i = 0; i < info.expected_no_cache_headers.length; ++i) { - if (info.expected_no_cache_headers[i]) { - expectedState[i]["Pragma"] = "no-cache"; - expectedState[i]["Cache-Control"] = "no-cache"; - } - } - if ("expected_max_age_headers" in info) { - for (var i = 0; i < info.expected_max_age_headers.length; ++i) { - if (info.expected_max_age_headers[i]) { - expectedState[i]["Cache-Control"] = "max-age=0"; - } - } - } - assert_equals(state.length, expectedState.length); - for (var i = 0; i < state.length; ++i) { - for (var header in state[i]) { - assert_equals(state[i][header], expectedState[i][header]); - delete expectedState[i][header]; - } - for (var header in expectedState[i]) { - assert_false(header in state[i]); - } - } - }); - }; - } - self.make_test = make_test; - - function run_tests(tests) { - tests.forEach(function (info) { - promise_test( - make_test("tag", info), - info.name + " with Etag and " + info.state + " response" - ); - promise_test( - make_test("date", info), - info.name + " with Last-Modified and " + info.state + " response" - ); - }); - } - self.run_tests = run_tests; -} diff --git a/tests/wpt/fetch/api/request/request-error.js b/tests/wpt/fetch/api/request/request-error.js deleted file mode 100644 index b404e02fb8..0000000000 --- a/tests/wpt/fetch/api/request/request-error.js +++ /dev/null @@ -1,64 +0,0 @@ -export default function (self) { - const badRequestArgTests = [ - { - args: ["", { window: "http://test.url" }], - testName: "RequestInit's window is not null", - }, - { - args: ["http://:not a valid URL"], - testName: "Input URL is not valid", - }, - { - args: ["http://user:pass@test.url"], - testName: "Input URL has credentials", - }, - { - args: ["", { mode: "navigate" }], - testName: "RequestInit's mode is navigate", - }, - { - args: ["", { referrer: "http://:not a valid URL" }], - testName: "RequestInit's referrer is invalid", - }, - { - args: ["", { method: "IN VALID" }], - testName: "RequestInit's method is invalid", - }, - { - args: ["", { method: "TRACE" }], - testName: "RequestInit's method is forbidden", - }, - { - args: ["", { mode: "no-cors", method: "PUT" }], - testName: "RequestInit's mode is no-cors and method is not simple", - }, - { - args: ["", { mode: "cors", cache: "only-if-cached" }], - testName: - "RequestInit's cache mode is only-if-cached and mode is not same-origin", - }, - { - args: ["test", { cache: "only-if-cached", mode: "cors" }], - testName: "Request with cache mode: only-if-cached and fetch mode cors", - }, - { - args: ["test", { cache: "only-if-cached", mode: "no-cors" }], - testName: - "Request with cache mode: only-if-cached and fetch mode no-cors", - }, - ]; - - badRequestArgTests.push( - ...["referrerPolicy", "mode", "credentials", "cache", "redirect"].map( - (optionProp) => { - const options = {}; - options[optionProp] = "BAD"; - return { - args: ["", options], - testName: `Bad ${optionProp} init parameter value`, - }; - } - ) - ); - self.badRequestArgTests = badRequestArgTests; -} diff --git a/tests/wpt/fetch/api/resources/data.json b/tests/wpt/fetch/api/resources/data.json deleted file mode 100644 index 7ba1b793e5..0000000000 --- a/tests/wpt/fetch/api/resources/data.json +++ /dev/null @@ -1 +0,0 @@ -{ "key": "value" } diff --git a/tests/wpt/fetch/api/resources/keepalive-helper.js b/tests/wpt/fetch/api/resources/keepalive-helper.js deleted file mode 100644 index 21b9dfe4f4..0000000000 --- a/tests/wpt/fetch/api/resources/keepalive-helper.js +++ /dev/null @@ -1,261 +0,0 @@ -export default function (self) { - // Utility functions to help testing keepalive requests. - - // Returns a URL to an iframe that loads a keepalive URL on iframe loaded. - // - // The keepalive URL points to a target that stores `token`. The token will then - // be posted back on iframe loaded to the parent document. - // `method` defaults to GET. - // `frameOrigin` to specify the origin of the iframe to load. If not set, - // default to a different site origin. - // `requestOrigin` to specify the origin of the fetch request target. - // `sendOn` to specify the name of the event when the keepalive request should - // be sent instead of the default 'load'. - // `mode` to specify the fetch request's CORS mode. - // `disallowCrossOrigin` to ask the iframe to set up a server that disallows - // cross origin requests. - function getKeepAliveIframeUrl( - token, - method, - { - frameOrigin = "DEFAULT", - requestOrigin = "", - sendOn = "load", - mode = "cors", - disallowCrossOrigin = false, - } = {} - ) { - const https = location.protocol.startsWith("https"); - frameOrigin = - frameOrigin === "DEFAULT" - ? get_host_info()[ - https ? "HTTPS_NOTSAMESITE_ORIGIN" : "HTTP_NOTSAMESITE_ORIGIN" - ] - : frameOrigin; - return ( - `${frameOrigin}/fetch/api/resources/keepalive-iframe.html?` + - `token=${token}&` + - `method=${method}&` + - `sendOn=${sendOn}&` + - `mode=${mode}&` + - (disallowCrossOrigin ? `disallowCrossOrigin=1&` : ``) + - `origin=${requestOrigin}` - ); - } - self.getKeepAliveIframeUrl = getKeepAliveIframeUrl; - - // Returns a different-site URL to an iframe that loads a keepalive URL. - // - // By default, the keepalive URL points to a target that redirects to another - // same-origin destination storing `token`. The token will then be posted back - // to parent document. - // - // The URL redirects can be customized from `origin1` to `origin2` if provided. - // Sets `withPreflight` to true to get URL enabling preflight. - function getKeepAliveAndRedirectIframeUrl( - token, - origin1, - origin2, - withPreflight - ) { - const https = location.protocol.startsWith("https"); - const frameOrigin = - get_host_info()[ - https ? "HTTPS_NOTSAMESITE_ORIGIN" : "HTTP_NOTSAMESITE_ORIGIN" - ]; - return ( - `${frameOrigin}/fetch/api/resources/keepalive-redirect-iframe.html?` + - `token=${token}&` + - `origin1=${origin1}&` + - `origin2=${origin2}&` + - (withPreflight ? `with-headers` : ``) - ); - } - - async function iframeLoaded(iframe) { - return new Promise((resolve) => iframe.addEventListener("load", resolve)); - } - - // Obtains the token from the message posted by iframe after loading - // `getKeepAliveAndRedirectIframeUrl()`. - async function getTokenFromMessage() { - return new Promise((resolve) => { - window.addEventListener( - "message", - (event) => { - resolve(event.data); - }, - { once: true } - ); - }); - } - - // Tells if `token` has been stored in the server. - async function queryToken(token) { - const response = await fetch(`../resources/stash-take.py?key=${token}`); - const json = await response.json(); - return json; - } - - // A helper to assert the existence of `token` that should have been stored in - // the server by fetching ../resources/stash-put.py. - // - // This function simply wait for a custom amount of time before trying to - // retrieve `token` from the server. - // `expectTokenExist` tells if `token` should be present or not. - // - // NOTE: - // In order to parallelize the work, we are going to have an async_test - // for the rest of the work. Note that we want the serialized behavior - // for the steps so far, so we don't want to make the entire test case - // an async_test. - function assertStashedTokenAsync( - testName, - token, - { expectTokenExist = true } = {} - ) { - async_test((test) => { - new Promise((resolve) => test.step_timeout(resolve, 3000 /*ms*/)) - .then( - test.step_func(() => { - return queryToken(token); - }) - ) - .then( - test.step_func((result) => { - if (expectTokenExist) { - assert_equals(result, "on", `token should be on (stashed).`); - test.done(); - } else { - assert_not_equals( - result, - "on", - `token should not be on (stashed).` - ); - return Promise.reject(`Failed to retrieve token from server`); - } - }) - ) - .catch( - test.step_func((e) => { - if (expectTokenExist) { - test.unreached_func(e); - } else { - test.done(); - } - }) - ); - }, testName); - } - - /** - * In an iframe, and in `load` event handler, test to fetch a keepalive URL that - * involves in redirect to another URL. - * - * `unloadIframe` to unload the iframe before verifying stashed token to - * simulate the situation that unloads after fetching. Note that this test is - * different from `keepaliveRedirectInUnloadTest()` in that the latter - * performs fetch() call directly in `unload` event handler, while this test - * does it in `load`. - */ - function keepaliveRedirectTest( - desc, - { - origin1 = "", - origin2 = "", - withPreflight = false, - unloadIframe = false, - expectFetchSucceed = true, - } = {} - ) { - desc = - `[keepalive][iframe][load] ${desc}` + - (unloadIframe ? " [unload at end]" : ""); - promise_test(async (test) => { - const tokenToStash = token(); - const iframe = document.createElement("iframe"); - iframe.src = getKeepAliveAndRedirectIframeUrl( - tokenToStash, - origin1, - origin2, - withPreflight - ); - document.body.appendChild(iframe); - await iframeLoaded(iframe); - assert_equals(await getTokenFromMessage(), tokenToStash); - if (unloadIframe) { - iframe.remove(); - } - - assertStashedTokenAsync(desc, tokenToStash, { - expectTokenExist: expectFetchSucceed, - }); - }, `${desc}; setting up`); - } - self.keepaliveRedirectTest = keepaliveRedirectTest; - - /** - * Opens a different site window, and in `unload` event handler, test to fetch - * a keepalive URL that involves in redirect to another URL. - */ - function keepaliveRedirectInUnloadTest( - desc, - { - origin1 = "", - origin2 = "", - url2 = "", - withPreflight = false, - expectFetchSucceed = true, - } = {} - ) { - desc = `[keepalive][new window][unload] ${desc}`; - - promise_test(async (test) => { - const targetUrl = - `${HTTP_NOTSAMESITE_ORIGIN}/fetch/api/resources/keepalive-redirect-window.html?` + - `origin1=${origin1}&` + - `origin2=${origin2}&` + - `url2=${url2}&` + - (withPreflight ? `with-headers` : ``); - const w = window.open(targetUrl); - const token = await getTokenFromMessage(); - w.close(); - - assertStashedTokenAsync(desc, token, { - expectTokenExist: expectFetchSucceed, - }); - }, `${desc}; setting up`); - } - self.keepaliveRedirectInUnloadTest = keepaliveRedirectInUnloadTest; - - /** - * utility to create pending keepalive fetch requests - * The pending request state is achieved by ensuring the server (trickle.py) does not - * immediately respond to the fetch requests. - * The response delay is set as a url parameter. - */ - - function createPendingKeepAliveRequest(delay, remote = false) { - // trickle.py is a script that can make a delayed response to the client request - const trickleRemoteURL = - get_host_info().HTTPS_REMOTE_ORIGIN + - "/fetch/api/resources/trickle.py?count=1&ms="; - const trickleLocalURL = - get_host_info().HTTP_ORIGIN + - "/fetch/api/resources/trickle.py?count=1&ms="; - url = remote ? trickleRemoteURL : trickleLocalURL; - - const body = "*".repeat(10); - return fetch(url + delay, { keepalive: true, body, method: "POST" }) - .then((res) => { - return res.text(); - }) - .then(() => { - return new Promise((resolve) => step_timeout(resolve, 1)); - }) - .catch((error) => { - return Promise.reject(error); - }); - } - self.createPendingKeepAliveRequest = createPendingKeepAliveRequest; -} diff --git a/tests/wpt/fetch/api/resources/keepalive-worker.js b/tests/wpt/fetch/api/resources/keepalive-worker.js deleted file mode 100644 index 9b34b4833a..0000000000 --- a/tests/wpt/fetch/api/resources/keepalive-worker.js +++ /dev/null @@ -1,17 +0,0 @@ -export default function (self) { - /** - * Script that sends keepalive - * fetch request and terminates immediately. - * The request URL is passed as a parameter to this worker - */ - function sendFetchRequest() { - // Parse the query parameter from the worker's script URL - const urlString = self.location.search.replace("?param=", ""); - postMessage("started"); - fetch(`${urlString}`, { keepalive: true }); - } - - sendFetchRequest(); - // Terminate the worker - self.close(); -} diff --git a/tests/wpt/fetch/api/resources/sw-intercept-abort.js b/tests/wpt/fetch/api/resources/sw-intercept-abort.js deleted file mode 100644 index b2bb26c126..0000000000 --- a/tests/wpt/fetch/api/resources/sw-intercept-abort.js +++ /dev/null @@ -1,21 +0,0 @@ -export default function (self) { - async function messageClient(clientId, message) { - const client = await clients.get(clientId); - client.postMessage(message); - } - - addEventListener("fetch", (event) => { - let resolve; - const promise = new Promise((r) => (resolve = r)); - - function onAborted() { - messageClient(event.clientId, event.request.signal.reason); - resolve(); - } - - messageClient(event.clientId, "fetch event has arrived"); - - event.respondWith(promise.then(() => new Response("hello"))); - event.request.signal.addEventListener("abort", onAborted); - }); -} diff --git a/tests/wpt/fetch/api/resources/sw-intercept.js b/tests/wpt/fetch/api/resources/sw-intercept.js deleted file mode 100644 index d74bd3ac6a..0000000000 --- a/tests/wpt/fetch/api/resources/sw-intercept.js +++ /dev/null @@ -1,12 +0,0 @@ -export default function (self) { - async function broadcast(msg) { - for (const client of await clients.matchAll()) { - client.postMessage(msg); - } - } - - addEventListener("fetch", (event) => { - event.waitUntil(broadcast(event.request.url)); - event.respondWith(fetch(event.request)); - }); -} diff --git a/tests/wpt/fetch/api/resources/utils.js b/tests/wpt/fetch/api/resources/utils.js deleted file mode 100644 index ef86c65739..0000000000 --- a/tests/wpt/fetch/api/resources/utils.js +++ /dev/null @@ -1,180 +0,0 @@ -export default function (self) { - var RESOURCES_DIR = "../resources/"; - - function dirname(path) { - if (typeof path !== "string") { - return ""; - } - return path.replace(/\/[^\/]*$/, "/"); - } - self.dirname = dirname; - - function checkRequest(request, ExpectedValuesDict) { - for (var attribute in ExpectedValuesDict) { - switch (attribute) { - case "headers": - for (var key in ExpectedValuesDict["headers"].keys()) { - self.assert_equals( - request["headers"].get(key), - ExpectedValuesDict["headers"].get(key), - "Check headers attribute has " + - key + - ":" + - ExpectedValuesDict["headers"].get(key) - ); - } - break; - - case "body": - //for checking body's content, a dedicated asyncronous/promise test should be used - self.assert_true( - request["headers"].has("Content-Type"), - "Check request has body using Content-Type header" - ); - break; - - case "method": - case "referrer": - case "referrerPolicy": - case "credentials": - case "cache": - case "redirect": - case "integrity": - case "url": - case "destination": - self.assert_equals( - request[attribute], - ExpectedValuesDict[attribute], - "Check " + attribute + " attribute" - ); - break; - - default: - break; - } - } - } - self.checkRequest = checkRequest; - - function stringToArray(str) { - var array = new Uint8Array(str.length); - for (var i = 0, strLen = str.length; i < strLen; i++) - array[i] = str.charCodeAt(i); - return array; - } - self.stringToArray = stringToArray; - - function encode_utf8(str) { - if (self.TextEncoder) return new TextEncoder().encode(str); - return stringToArray(unescape(encodeURIComponent(str))); - } - self.encode_utf8 = encode_utf8; - - function validateBufferFromString(buffer, expectedValue, message) { - return self.assert_array_equals( - new Uint8Array(buffer !== undefined ? buffer : []), - stringToArray(expectedValue), - message - ); - } - self.validateBufferFromString = validateBufferFromString; - - function validateStreamFromString( - reader, - expectedValue, - retrievedArrayBuffer - ) { - // Passing Uint8Array for byte streams; non-byte streams will simply ignore it - return reader.read(new Uint8Array(64)).then(function (data) { - if (!data.done) { - self.assert_true( - data.value instanceof Uint8Array, - "Fetch ReadableStream chunks should be Uint8Array" - ); - var newBuffer; - if (retrievedArrayBuffer) { - newBuffer = new Uint8Array( - data.value.length + retrievedArrayBuffer.length - ); - newBuffer.set(retrievedArrayBuffer, 0); - newBuffer.set(data.value, retrievedArrayBuffer.length); - } else { - newBuffer = data.value; - } - return validateStreamFromString(reader, expectedValue, newBuffer); - } - validateBufferFromString( - retrievedArrayBuffer, - expectedValue, - "Retrieve and verify stream" - ); - }); - } - self.validateStreamFromString = validateStreamFromString; - - function validateStreamFromPartialString( - reader, - expectedValue, - retrievedArrayBuffer - ) { - // Passing Uint8Array for byte streams; non-byte streams will simply ignore it - return reader.read(new Uint8Array(64)).then(function (data) { - if (!data.done) { - self.assert_true( - data.value instanceof Uint8Array, - "Fetch ReadableStream chunks should be Uint8Array" - ); - var newBuffer; - if (retrievedArrayBuffer) { - newBuffer = new Uint8Array( - data.value.length + retrievedArrayBuffer.length - ); - newBuffer.set(retrievedArrayBuffer, 0); - newBuffer.set(data.value, retrievedArrayBuffer.length); - } else { - newBuffer = data.value; - } - return validateStreamFromPartialString( - reader, - expectedValue, - newBuffer - ); - } - - var string = new TextDecoder("utf-8").decode(retrievedArrayBuffer); - return self.assert_true( - string.search(expectedValue) != -1, - "Retrieve and verify stream" - ); - }); - } - self.validateStreamFromPartialString = validateStreamFromPartialString; - - // From streams tests - function delay(milliseconds) { - return new Promise(function (resolve) { - step_timeout(resolve, milliseconds); - }); - } - self.delay = delay; - - function requestForbiddenHeaders(desc, forbiddenHeaders) { - var url = RESOURCES_DIR + "inspect-headers.py"; - var requestInit = { headers: forbiddenHeaders }; - var urlParameters = "?headers=" + Object.keys(forbiddenHeaders).join("|"); - - promise_test(function (test) { - return fetch(url + urlParameters, requestInit).then(function (resp) { - self.assert_equals(resp.status, 200, "HTTP status is 200"); - self.assert_equals(resp.type, "basic", "Response's type is basic"); - for (var header in forbiddenHeaders) - self.assert_not_equals( - resp.headers.get("x-request-" + header), - forbiddenHeaders[header], - header + " does not have the value we defined" - ); - }); - }, desc); - } - self.requestForbiddenHeaders = requestForbiddenHeaders; -} diff --git a/tests/wpt/resources/idlharness.js b/tests/wpt/resources/idlharness.js deleted file mode 100644 index eb92ff7a19..0000000000 --- a/tests/wpt/resources/idlharness.js +++ /dev/null @@ -1,4827 +0,0 @@ -/* For user documentation see docs/_writing-tests/idlharness.md */ - -/** - * Notes for people who want to edit this file (not just use it as a library): - * - * Most of the interesting stuff happens in the derived classes of IdlObject, - * especially IdlInterface. The entry point for all IdlObjects is .test(), - * which is called by IdlArray.test(). An IdlObject is conceptually just - * "thing we want to run tests on", and an IdlArray is an array of IdlObjects - * with some additional data thrown in. - * - * The object model is based on what WebIDLParser.js produces, which is in turn - * based on its pegjs grammar. If you want to figure out what properties an - * object will have from WebIDLParser.js, the best way is to look at the - * grammar: - * - * https://github.com/darobin/webidl.js/blob/master/lib/grammar.peg - * - * So for instance: - * - * // interface definition - * interface - * = extAttrs:extendedAttributeList? S? "interface" S name:identifier w herit:ifInheritance? w "{" w mem:ifMember* w "}" w ";" w - * { return { type: "interface", name: name, inheritance: herit, members: mem, extAttrs: extAttrs }; } - * - * This means that an "interface" object will have a .type property equal to - * the string "interface", a .name property equal to the identifier that the - * parser found, an .inheritance property equal to either null or the result of - * the "ifInheritance" production found elsewhere in the grammar, and so on. - * After each grammatical production is a JavaScript function in curly braces - * that gets called with suitable arguments and returns some JavaScript value. - * - * (Note that the version of WebIDLParser.js we use might sometimes be - * out-of-date or forked.) - * - * The members and methods of the classes defined by this file are all at least - * briefly documented, hopefully. - */ -export default function (self) { - (function () { - "use strict"; - // Support subsetTestByKey from /common/subset-tests-by-key.js, but make it optional - if (!("subsetTestByKey" in self)) { - self.subsetTestByKey = function (key, callback, ...args) { - return callback(...args); - }; - self.shouldRunSubTest = () => true; - } - /// Helpers /// - function constValue(cnt) { - if (cnt.type === "null") return null; - if (cnt.type === "NaN") return NaN; - if (cnt.type === "Infinity") return cnt.negative ? -Infinity : Infinity; - if (cnt.type === "number") return +cnt.value; - return cnt.value; - } - - function minOverloadLength(overloads) { - // "The value of the Function object’s “length” property is - // a Number determined as follows: - // ". . . - // "Return the length of the shortest argument list of the - // entries in S." - if (!overloads.length) { - return 0; - } - - return overloads - .map(function (attr) { - return attr.arguments - ? attr.arguments.filter(function (arg) { - return !arg.optional && !arg.variadic; - }).length - : 0; - }) - .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; - } - - // https://esdiscuss.org/topic/isconstructor#content-11 - function isConstructor(o) { - try { - new new Proxy(o, { construct: () => ({}) })(); - return true; - } catch (e) { - return false; - } - } - - function throwOrReject(a_test, operation, fn, obj, args, message, cb) { - if (operation.idlType.generic !== "Promise") { - assert_throws_js( - globalOf(fn).TypeError, - function () { - fn.apply(obj, args); - }, - message - ); - cb(); - } else { - try { - 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'); - cb(); - }); - } - } - } - - function awaitNCallbacks(n, cb, ctx) { - var counter = 0; - return function () { - counter++; - if (counter >= n) { - cb(); - } - }; - } - - /// IdlHarnessError /// - // Entry point - const IdlHarnessError = function (message) { - /** - * Message to be printed as the error's toString invocation. - */ - this.message = message; - }; - - IdlHarnessError.prototype = Object.create(Error.prototype); - - IdlHarnessError.prototype.toString = function () { - return this.message; - }; - - /// IdlArray /// - // Entry point - const IdlArray = function () { - /** - * A map from strings to the corresponding named IdlObject, such as - * IdlInterface or IdlException. These are the things that test() will run - * tests on. - */ - this.members = {}; - - /** - * A map from strings to arrays of strings. The keys are interface or - * exception names, and are expected to also exist as keys in this.members - * (otherwise they'll be ignored). This is populated by add_objects() -- - * see documentation at the start of the file. The actual tests will be - * run by calling this.members[name].test_object(obj) for each obj in - * this.objects[name]. obj is a string that will be eval'd to produce a - * JavaScript value, which is supposed to be an object implementing the - * given IdlObject (interface, exception, etc.). - */ - this.objects = {}; - - /** - * When adding multiple collections of IDLs one at a time, an earlier one - * might contain a partial interface or includes statement that depends - * on a later one. Save these up and handle them right before we run - * tests. - * - * Both this.partials and this.includes will be the objects as parsed by - * WebIDLParser.js, not wrapped in IdlInterface or similar. - */ - this.partials = []; - this.includes = []; - - /** - * 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) { - /** Entry point. See documentation at beginning of file. */ - this.internal_add_idls(WebIDL2.parse(raw_idls), options); - }; - - IdlArray.prototype.add_untested_idls = function (raw_idls, options) { - /** Entry point. See documentation at beginning of file. */ - var parsed_idls = WebIDL2.parse(raw_idls); - this.mark_as_untested(parsed_idls); - this.internal_add_idls(parsed_idls, options); - }; - - IdlArray.prototype.mark_as_untested = function (parsed_idls) { - for (var i = 0; i < parsed_idls.length; i++) { - parsed_idls[i].untested = true; - if ("members" in parsed_idls[i]) { - for (var j = 0; j < parsed_idls[i].members.length; j++) { - parsed_idls[i].members[j].untested = true; - } - } - } - }; - - IdlArray.prototype.is_excluded_by_options = function (name, options) { - return ( - options && - ((options.except && options.except.includes(name)) || - (options.only && !options.only.includes(name))) - ); - }; - - IdlArray.prototype.add_dependency_idls = function (raw_idls, options) { - 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(); - Object.values(this.members).forEach((v) => { - if (v.base) { - all_deps.add(v.base); - } - }); - // Add both 'A' and 'B' for each 'A includes B' entry. - this.includes.forEach((i) => { - all_deps.add(i.target); - all_deps.add(i.includes); - }); - 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 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']}). - // array.add_dependency_idls(idl); - // B would be encountered as tested, and encountered as a dep, so we ignore. - return ( - name in this.members || this.is_excluded_by_options(name, options) - ); - }; - // Maps name -> [parsed_idl, ...] - const process = function (parsed) { - var deps = []; - if (parsed.name) { - deps.push(parsed.name); - } else if (parsed.type === "includes") { - deps.push(parsed.target); - deps.push(parsed.includes); - } - - deps = deps.filter( - function (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)) { - this.skipped.has(name) - ? this.skipped.get(name).push(parsed) - : this.skipped.set(name, [parsed]); - } - return false; - } - return true; - }.bind(this) - ); - - deps.forEach( - function (name) { - if (!new_options.only.includes(name)) { - new_options.only.push(name); - } - - const follow_up = new Set(); - for (const dep_type of ["inheritance", "includes"]) { - if (parsed[dep_type]) { - const inheriting = parsed[dep_type]; - const inheritor = parsed.name || parsed.target; - const deps = [inheriting]; - // For A includes B, we can ignore A, unless B (or some of its - // members) is being tested. - if ( - dep_type !== "includes" || - (inheriting in this.members && - !this.members[inheriting].untested) || - this.partials.some(function (p) { - return p.name === inheriting; - }) - ) { - deps.push(inheritor); - } - for (const dep of deps) { - if (!new_options.only.includes(dep)) { - new_options.only.push(dep); - } - all_deps.add(dep); - follow_up.add(dep); - } - } - } - - for (const deferred of follow_up) { - if (this.skipped.has(deferred)) { - const next = this.skipped.get(deferred); - this.skipped.delete(deferred); - next.forEach(process); - } - } - }.bind(this) - ); - }.bind(this); - - for (let parsed of parsed_idls) { - process(parsed); - } - - this.mark_as_untested(parsed_idls); - - if (new_options.only.length) { - this.internal_add_idls(parsed_idls, new_options); - } - }; - - IdlArray.prototype.internal_add_idls = function (parsed_idls, options) { - /** - * Internal helper called by add_idls() and add_untested_idls(). - * - * parsed_idls is an array of objects that come from WebIDLParser.js's - * "definitions" production. The add_untested_idls() entry point - * additionally sets an .untested property on each object (and its - * .members) so that they'll be skipped by test() -- they'll only be - * used for base interfaces of tested interfaces, return types, etc. - * - * options is a dictionary that can have an only or except member which are - * arrays. If only is given then only members, partials and interface - * targets listed will be added, and if except is given only those that - * aren't listed will be added. Only one of only and except can be used. - */ - - if (options && options.only && options.except) { - throw new IdlHarnessError( - "The only and except options can't be used together." - ); - } - - var should_skip = (name) => { - return this.is_excluded_by_options(name, options); - }; - - parsed_idls.forEach( - function (parsed_idl) { - var partial_types = [ - "interface", - "interface mixin", - "dictionary", - "namespace", - ]; - if (parsed_idl.partial && partial_types.includes(parsed_idl.type)) { - if (should_skip(parsed_idl.name)) { - return; - } - this.partials.push(parsed_idl); - return; - } - - if (parsed_idl.type == "includes") { - if (should_skip(parsed_idl.target)) { - return; - } - this.includes.push(parsed_idl); - return; - } - - parsed_idl.array = this; - if (should_skip(parsed_idl.name)) { - return; - } - if (parsed_idl.name in this.members) { - throw new IdlHarnessError( - "Duplicate identifier " + parsed_idl.name - ); - } - - switch (parsed_idl.type) { - case "interface": - this.members[parsed_idl.name] = new IdlInterface( - parsed_idl, - /* is_callback = */ false, - /* is_mixin = */ false - ); - break; - - case "interface mixin": - this.members[parsed_idl.name] = new IdlInterface( - parsed_idl, - /* is_callback = */ false, - /* is_mixin = */ true - ); - break; - - case "dictionary": - // Nothing to test, but we need the dictionary info around for type - // checks - this.members[parsed_idl.name] = new IdlDictionary(parsed_idl); - break; - - case "typedef": - this.members[parsed_idl.name] = new IdlTypedef(parsed_idl); - break; - - case "callback": - this.members[parsed_idl.name] = new IdlCallback(parsed_idl); - break; - - case "enum": - this.members[parsed_idl.name] = new IdlEnum(parsed_idl); - break; - - case "callback interface": - this.members[parsed_idl.name] = new IdlInterface( - parsed_idl, - /* is_callback = */ true, - /* is_mixin = */ false - ); - break; - - case "namespace": - this.members[parsed_idl.name] = new IdlNamespace(parsed_idl); - break; - - default: - throw ( - parsed_idl.name + ": " + parsed_idl.type + " not yet supported" - ); - } - }.bind(this) - ); - }; - - IdlArray.prototype.add_objects = function (dict) { - /** Entry point. See documentation at beginning of file. */ - for (var k in dict) { - if (k in this.objects) { - this.objects[k] = this.objects[k].concat(dict[k]); - } else { - this.objects[k] = dict[k]; - } - } - }; - - IdlArray.prototype.prevent_multiple_testing = function (name) { - /** Entry point. See documentation at beginning of file. */ - this.members[name].prevent_multiple_testing = true; - }; - - IdlArray.prototype.is_json_type = function (type) { - /** - * Checks whether type is a JSON type as per - * https://webidl.spec.whatwg.org/#dfn-json-types - */ - - var idlType = type.idlType; - - if (type.generic == "Promise") { - return false; - } - - // nullable and annotated types don't need to be handled separately, - // as webidl2 doesn't represent them wrapped-up (as they're described - // in WebIDL). - - // union and record types - if (type.union || type.generic == "record") { - return idlType.every(this.is_json_type, this); - } - - // sequence types - if (type.generic == "sequence" || type.generic == "FrozenArray") { - return this.is_json_type(idlType[0]); - } - - if (typeof idlType != "string") { - throw new Error("Unexpected type " + JSON.stringify(idlType)); - } - - switch (idlType) { - // Numeric types - case "byte": - case "octet": - case "short": - case "unsigned short": - case "long": - case "unsigned long": - case "long long": - case "unsigned long long": - case "float": - case "double": - case "unrestricted float": - case "unrestricted double": - // boolean - case "boolean": - // string types - case "DOMString": - case "ByteString": - case "USVString": - // object type - case "object": - return true; - case "Error": - case "DOMException": - case "Int8Array": - case "Int16Array": - case "Int32Array": - case "Uint8Array": - case "Uint16Array": - case "Uint32Array": - case "Uint8ClampedArray": - case "BigInt64Array": - case "BigUint64Array": - case "Float16Array": - case "Float32Array": - case "Float64Array": - case "ArrayBuffer": - case "DataView": - case "any": - return false; - default: - var thing = this.members[idlType]; - if (!thing) { - throw new Error("Type " + idlType + " not found"); - } - if (thing instanceof IdlEnum) { - return true; - } - - if (thing instanceof IdlTypedef) { - return this.is_json_type(thing.idlType); - } - - // dictionaries where all of their members are JSON types - if (thing instanceof IdlDictionary) { - const map = new Map(); - for (const dict of thing.get_reverse_inheritance_stack()) { - for (const m of dict.members) { - map.set(m.name, m.idlType); - } - } - return Array.from(map.values()).every(this.is_json_type, this); - } - - // interface types that have a toJSON operation declared on themselves or - // one of their inherited interfaces. - if (thing instanceof IdlInterface) { - var base; - while (thing) { - if (thing.has_to_json_regular_operation()) { - return true; - } - var mixins = this.includes[thing.name]; - if (mixins) { - mixins = mixins.map(function (id) { - var mixin = this.members[id]; - if (!mixin) { - throw new Error( - "Interface " + - id + - " not found (implemented by " + - thing.name + - ")" - ); - } - return mixin; - }, this); - if ( - mixins.some(function (m) { - return m.has_to_json_regular_operation(); - }) - ) { - return true; - } - } - if (!thing.base) { - return false; - } - base = this.members[thing.base]; - if (!base) { - throw new Error( - "Interface " + - thing.base + - " not found (inherited by " + - thing.name + - ")" - ); - } - thing = base; - } - return false; - } - return false; - } - }; - - function exposure_set(object, default_set) { - var exposed = - object.extAttrs && object.extAttrs.filter((a) => a.name === "Exposed"); - if (exposed && exposed.length > 1) { - throw new IdlHarnessError( - `Multiple 'Exposed' extended attributes on ${object.name}` - ); - } - - let result = default_set || ["Window"]; - if (result && !(result instanceof Set)) { - result = new Set(result); - } - if (exposed && exposed.length) { - const { rhs } = exposed[0]; - // Could be a list or a string. - const set = - rhs.type === "*" - ? ["*"] - : rhs.type === "identifier-list" - ? rhs.value.map((id) => id.value) - : [rhs.value]; - result = new Set(set); - } - if (result && result.has("*")) { - return "*"; - } - if (result && result.has("Worker")) { - result.delete("Worker"); - result.add("DedicatedWorker"); - result.add("ServiceWorker"); - result.add("SharedWorker"); - } - return result; - } - - function exposed_in(globals) { - if (globals === "*") { - return true; - } - if ("Window" in self) { - return globals.has("Window"); - } - if ( - "DedicatedWorkerGlobalScope" in self && - self instanceof DedicatedWorkerGlobalScope - ) { - return globals.has("DedicatedWorker"); - } - if ( - "SharedWorkerGlobalScope" in self && - self instanceof SharedWorkerGlobalScope - ) { - return globals.has("SharedWorker"); - } - if ( - "ServiceWorkerGlobalScope" in self && - self instanceof ServiceWorkerGlobalScope - ) { - return globals.has("ServiceWorker"); - } - if (Object.getPrototypeOf(self) === Object.prototype) { - // ShadowRealm - only exposed with `"*"`. - return false; - } - throw new IdlHarnessError("Unexpected global object"); - } - - /** - * Asserts that the given error message is thrown for the given function. - * @param {string|IdlHarnessError} error Expected Error message. - * @param {Function} idlArrayFunc Function operating on an IdlArray that should throw. - */ - IdlArray.prototype.assert_throws = function (error, idlArrayFunc) { - try { - idlArrayFunc.call(this, this); - } catch (e) { - if (e instanceof AssertionError) { - throw e; - } - // Assertions for behaviour of the idlharness.js engine. - if (error instanceof IdlHarnessError) { - error = error.message; - } - if (e.message !== error) { - throw new IdlHarnessError( - `${idlArrayFunc} threw "${e}", not the expected IdlHarnessError "${error}"` - ); - } - return; - } - throw new IdlHarnessError( - `${idlArrayFunc} did not throw the expected IdlHarnessError` - ); - }; - - IdlArray.prototype.test = function () { - /** Entry point. See documentation at beginning of file. */ - - // First merge in all partial definitions and interface mixins. - this.merge_partials(); - this.merge_mixins(); - - // Assert B defined for A : B - for (const member of Object.values(this.members).filter((m) => m.base)) { - const lhs = member.name; - const rhs = member.base; - if (!(rhs in this.members)) - throw new IdlHarnessError( - `${lhs} inherits ${rhs}, but ${rhs} is undefined.` - ); - const lhs_is_interface = this.members[lhs] instanceof IdlInterface; - const rhs_is_interface = this.members[rhs] instanceof IdlInterface; - if (rhs_is_interface != lhs_is_interface) { - if (!lhs_is_interface) - throw new IdlHarnessError( - `${lhs} inherits ${rhs}, but ${lhs} is not an interface.` - ); - if (!rhs_is_interface) - throw new IdlHarnessError( - `${lhs} inherits ${rhs}, but ${rhs} is not an interface.` - ); - } - // Check for circular dependencies. - member.get_reverse_inheritance_stack(); - } - - Object.getOwnPropertyNames(this.members).forEach( - function (memberName) { - var member = this.members[memberName]; - if (!(member instanceof IdlInterface)) { - return; - } - - var globals = exposure_set(member); - member.exposed = exposed_in(globals); - member.exposureSet = globals; - }.bind(this) - ); - - // Now run test() on every member, and test_object() for every object. - for (var name in this.members) { - this.members[name].test(); - if (name in this.objects) { - const objects = this.objects[name]; - if (!objects || !Array.isArray(objects)) { - throw new IdlHarnessError( - `Invalid or empty objects for member ${name}` - ); - } - objects.forEach( - function (str) { - if ( - !this.members[name] || - !(this.members[name] instanceof IdlInterface) - ) { - throw new IdlHarnessError(`Invalid object member name ${name}`); - } - this.members[name].test_object(str); - }.bind(this) - ); - } - } - }; - - IdlArray.prototype.merge_partials = function () { - const testedPartials = new Map(); - this.partials.forEach( - function (parsed_idl) { - const originalExists = - parsed_idl.name in this.members && - (this.members[parsed_idl.name] instanceof IdlInterface || - 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; - 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; - switch (parsed_idl.type) { - 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] - ), - `Original ${parsed_idl.name} definition should have type ${parsed_idl.type}` - ); - }.bind(this), - `Partial ${parsed_idl.type} ${partialTestName}: original ${parsed_idl.type} defined` - ); - } - if (!originalExists) { - // Not good.. but keep calm and carry on. - return; - } - - if (parsed_idl.extAttrs) { - // Special-case "Exposed". Must be a subset of original interface's exposure. - // Exposed on a partial is the equivalent of having the same Exposed on all nested members. - // See https://github.com/heycam/webidl/issues/154 for discrepency between Exposed and - // other extended attributes on partial interfaces. - const exposureAttr = parsed_idl.extAttrs.find( - (a) => a.name === "Exposed" - ); - if (exposureAttr) { - if (!parsed_idl.untested) { - test( - function () { - const partialExposure = exposure_set(parsed_idl); - const memberExposure = exposure_set( - this.members[parsed_idl.name] - ); - if (memberExposure === "*") { - return; - } - if (partialExposure === "*") { - throw new IdlHarnessError( - `Partial ${parsed_idl.name} ${parsed_idl.type} is exposed everywhere, the original ${parsed_idl.type} is not.` - ); - } - partialExposure.forEach((name) => { - if (!memberExposure || !memberExposure.has(name)) { - throw new IdlHarnessError( - `Partial ${parsed_idl.name} ${parsed_idl.type} is exposed to '${name}', the original ${parsed_idl.type} is not.` - ); - } - }); - }.bind(this), - `Partial ${parsed_idl.type} ${partialTestName}: valid exposure set` - ); - } - parsed_idl.members.forEach( - function (member) { - member.extAttrs.push(exposureAttr); - }.bind(this) - ); - } - - parsed_idl.extAttrs.forEach( - function (extAttr) { - // "Exposed" already handled above. - if (extAttr.name === "Exposed") { - return; - } - this.members[parsed_idl.name].extAttrs.push(extAttr); - }.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.merge_mixins = function () { - for (const parsed_idl of this.includes) { - const lhs = parsed_idl.target; - const rhs = parsed_idl.includes; - - var errStr = lhs + " includes " + rhs + ", but "; - if (!(lhs in this.members)) throw errStr + lhs + " is undefined."; - 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."; - - 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" - ); - } - } - this.includes = []; - }; - - 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 && - this.members[type.idlType] instanceof IdlTypedef - ) { - 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 { - this.assert_type_is(value, type.idlType[i]); - // No AssertionError, so we match one type in the union - return; - } catch (e) { - if (e instanceof AssertionError) { - // We didn't match this type, let's try some others - continue; - } - throw e; - } - } - // TODO: Is there a nice way to list the union's types in the message? - assert_true( - false, - "Attribute has value " + - format_value(value) + - " which doesn't match any of the types in the union" - ); - } - - /** - * Helper function that tests that value is an instance of type according - * to the rules of WebIDL. value is any JavaScript value, and type is an - * object produced by WebIDLParser.js' "type" production. That production - * is fairly elaborate due to the complexity of WebIDL's types, so it's - * best to look at the grammar to figure out what properties it might have. - */ - if (type.idlType == "any") { - // No assertions to make - return; - } - - if (type.array) { - // TODO: not supported yet - return; - } - - if (type.generic === "sequence" || type.generic == "ObservableArray") { - assert_true(Array.isArray(value), "should be an Array"); - if (!value.length) { - // Nothing we can do. - return; - } - this.assert_type_is(value[0], type.idlType[0]); - return; - } - - if (type.generic === "Promise") { - assert_true( - "then" in value, - "Attribute with a Promise type should have a then property" - ); - // TODO: Ideally, we would check on project fulfillment - // that we get the right type - // but that would require making the type check async - return; - } - - if (type.generic === "FrozenArray") { - assert_true(Array.isArray(value), "Value should be array"); - assert_true(Object.isFrozen(value), "Value should be frozen"); - if (!value.length) { - // Nothing we can do. - return; - } - this.assert_type_is(value[0], type.idlType[0]); - return; - } - - type = Array.isArray(type.idlType) ? type.idlType[0] : type.idlType; - - switch (type) { - case "undefined": - assert_equals(value, undefined); - return; - - case "boolean": - assert_equals(typeof value, "boolean"); - return; - - case "byte": - assert_equals(typeof value, "number"); - assert_equals(value, Math.floor(value), "should be an integer"); - assert_true( - -128 <= value && value <= 127, - "byte " + value + " should be in range [-128, 127]" - ); - return; - - case "octet": - assert_equals(typeof value, "number"); - assert_equals(value, Math.floor(value), "should be an integer"); - assert_true( - 0 <= value && value <= 255, - "octet " + value + " should be in range [0, 255]" - ); - return; - - case "short": - assert_equals(typeof value, "number"); - assert_equals(value, Math.floor(value), "should be an integer"); - assert_true( - -32768 <= value && value <= 32767, - "short " + value + " should be in range [-32768, 32767]" - ); - return; - - case "unsigned short": - assert_equals(typeof value, "number"); - assert_equals(value, Math.floor(value), "should be an integer"); - assert_true( - 0 <= value && value <= 65535, - "unsigned short " + value + " should be in range [0, 65535]" - ); - return; - - case "long": - assert_equals(typeof value, "number"); - assert_equals(value, Math.floor(value), "should be an integer"); - assert_true( - -2147483648 <= value && value <= 2147483647, - "long " + value + " should be in range [-2147483648, 2147483647]" - ); - return; - - case "unsigned long": - assert_equals(typeof value, "number"); - assert_equals(value, Math.floor(value), "should be an integer"); - assert_true( - 0 <= value && value <= 4294967295, - "unsigned long " + value + " should be in range [0, 4294967295]" - ); - return; - - case "long long": - assert_equals(typeof value, "number"); - return; - - case "unsigned long long": - case "DOMTimeStamp": - assert_equals(typeof value, "number"); - assert_true(0 <= value, "unsigned long long should be positive"); - return; - - case "float": - assert_equals(typeof value, "number"); - assert_equals( - value, - Math.fround(value), - "float rounded to 32-bit float should be itself" - ); - assert_not_equals(value, Infinity); - assert_not_equals(value, -Infinity); - assert_not_equals(value, NaN); - return; - - case "DOMHighResTimeStamp": - case "double": - assert_equals(typeof value, "number"); - assert_not_equals(value, Infinity); - assert_not_equals(value, -Infinity); - assert_not_equals(value, NaN); - return; - - case "unrestricted float": - assert_equals(typeof value, "number"); - assert_equals( - value, - Math.fround(value), - "unrestricted float rounded to 32-bit float should be itself" - ); - return; - - case "unrestricted double": - assert_equals(typeof value, "number"); - return; - - case "DOMString": - assert_equals(typeof value, "string"); - return; - - case "ByteString": - assert_equals(typeof value, "string"); - assert_regexp_match(value, /^[\x00-\x7F]*$/); - return; - - case "USVString": - assert_equals(typeof value, "string"); - assert_regexp_match( - value, - /^([\x00-\ud7ff\ue000-\uffff]|[\ud800-\udbff][\udc00-\udfff])*$/ - ); - return; - - case "ArrayBufferView": - assert_true(ArrayBuffer.isView(value)); - return; - - case "object": - assert_in_array( - typeof value, - ["object", "function"], - "wrong type: not object or function" - ); - 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)) { - assert_true(value instanceof self[type], "wrong type: not a " + type); - return; - } - - if (this.members[type] instanceof IdlInterface) { - // We don't want to run the full - // IdlInterface.prototype.test_instance_of, because that could result - // in an infinite loop. TODO: This means we don't have tests for - // LegacyNoInterfaceObject interfaces, and we also can't test objects - // that come from another self. - assert_in_array( - typeof value, - ["object", "function"], - "wrong type: not object or function" - ); - if ( - value instanceof Object && - !this.members[type].has_extended_attribute( - "LegacyNoInterfaceObject" - ) && - type in self - ) { - assert_true(value instanceof self[type], "instanceof " + type); - } - } else if (this.members[type] instanceof IdlEnum) { - assert_equals(typeof value, "string"); - } else if (this.members[type] instanceof IdlDictionary) { - // 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, callback or dictionary" - ); - } - }; - - /// IdlObject /// - function IdlObject() {} - IdlObject.prototype.test = function () { - /** - * By default, this does nothing, so no actual tests are run for IdlObjects - * that don't define any (e.g., IdlDictionary at the time of this writing). - */ - }; - - IdlObject.prototype.has_extended_attribute = function (name) { - /** - * This is only meaningful for things that support extended attributes, - * such as interfaces, exceptions, and members. - */ - return this.extAttrs.some(function (o) { - return o.name == name; - }); - }; - - /// IdlDictionary /// - // Used for IdlArray.prototype.assert_type_is - function IdlDictionary(obj) { - /** - * obj is an object produced by the WebIDLParser.js "dictionary" - * production. - */ - - /** Self-explanatory. */ - this.name = obj.name; - - /** A back-reference to our IdlArray. */ - this.array = obj.array; - - /** An array of objects produced by the "dictionaryMember" production. */ - this.members = obj.members; - - /** - * The name (as a string) of the dictionary type we inherit from, or null - * if there is none. - */ - this.base = obj.inheritance; - } - - IdlDictionary.prototype = Object.create(IdlObject.prototype); - - IdlDictionary.prototype.get_reverse_inheritance_stack = function () { - return IdlInterface.prototype.get_reverse_inheritance_stack.call(this); - }; - - /// IdlInterface /// - function IdlInterface(obj, is_callback, is_mixin) { - /** - * obj is an object produced by the WebIDLParser.js "interface" production. - */ - - /** Self-explanatory. */ - this.name = obj.name; - - /** A back-reference to our IdlArray. */ - this.array = obj.array; - - /** - * An indicator of whether we should run tests on the interface object and - * interface prototype object. Tests on members are controlled by .untested - * on each member, not this. - */ - this.untested = obj.untested; - - /** An array of objects produced by the "ExtAttr" production. */ - this.extAttrs = obj.extAttrs; - - /** An array of IdlInterfaceMembers. */ - this.members = obj.members.map(function (m) { - return new IdlInterfaceMember(m); - }); - if (this.has_extended_attribute("LegacyUnforgeable")) { - this.members - .filter(function (m) { - return ( - m.special !== "static" && - (m.type == "attribute" || m.type == "operation") - ); - }) - .forEach(function (m) { - return (m.isUnforgeable = true); - }); - } - - /** - * The name (as a string) of the type we inherit from, or null if there is - * none. - */ - this.base = obj.inheritance; - - this._is_callback = is_callback; - this._is_mixin = is_mixin; - } - IdlInterface.prototype = Object.create(IdlObject.prototype); - IdlInterface.prototype.is_callback = function () { - return this._is_callback; - }; - - IdlInterface.prototype.is_mixin = function () { - return this._is_mixin; - }; - - IdlInterface.prototype.has_constants = function () { - return this.members.some(function (member) { - return member.type === "const"; - }); - }; - - IdlInterface.prototype.get_unscopables = function () { - return this.members.filter(function (member) { - return member.isUnscopable; - }); - }; - - IdlInterface.prototype.is_global = function () { - return this.extAttrs.some(function (attribute) { - return attribute.name === "Global"; - }); - }; - - /** - * Value of the LegacyNamespace extended attribute, if any. - * - * https://webidl.spec.whatwg.org/#LegacyNamespace - */ - IdlInterface.prototype.get_legacy_namespace = function () { - var legacyNamespace = this.extAttrs.find(function (attribute) { - return attribute.name === "LegacyNamespace"; - }); - return legacyNamespace ? legacyNamespace.rhs.value : undefined; - }; - - IdlInterface.prototype.get_interface_object_owner = function () { - var legacyNamespace = this.get_legacy_namespace(); - return legacyNamespace ? self[legacyNamespace] : self; - }; - - IdlInterface.prototype.should_have_interface_object = function () { - // "For every interface that is exposed in a given ECMAScript global - // environment and: - // * is a callback interface that has constants declared on it, or - // * is a non-callback interface that is not declared with the - // [LegacyNoInterfaceObject] extended attribute, - // a corresponding property MUST exist on the ECMAScript global object. - - return this.is_callback() - ? this.has_constants() - : !this.has_extended_attribute("LegacyNoInterfaceObject"); - }; - - IdlInterface.prototype.assert_interface_object_exists = function () { - var owner = this.get_legacy_namespace() || "self"; - assert_own_property( - self[owner], - this.name, - owner + " does not have own property " + format_value(this.name) - ); - }; - - IdlInterface.prototype.get_interface_object = function () { - if (!this.should_have_interface_object()) { - var reason = this.is_callback() - ? "lack of declared constants" - : "declared [LegacyNoInterfaceObject] attribute"; - throw new IdlHarnessError( - this.name + " has no interface object due to " + reason - ); - } - - return this.get_interface_object_owner()[this.name]; - }; - - IdlInterface.prototype.get_qualified_name = function () { - // https://webidl.spec.whatwg.org/#qualified-name - var legacyNamespace = this.get_legacy_namespace(); - if (legacyNamespace) { - return legacyNamespace + "." + this.name; - } - return this.name; - }; - - IdlInterface.prototype.has_to_json_regular_operation = function () { - return this.members.some(function (m) { - return m.is_to_json_regular_operation(); - }); - }; - - IdlInterface.prototype.has_default_to_json_regular_operation = function () { - return this.members.some(function (m) { - return ( - m.is_to_json_regular_operation() && - m.has_extended_attribute("Default") - ); - }); - }; - - /** - * Implementation of https://webidl.spec.whatwg.org/#create-an-inheritance-stack - * with the order reversed. - * - * The order is reversed so that the base class comes first in the list, because - * this is what all call sites need. - * - * So given: - * - * A : B {}; - * B : C {}; - * C {}; - * - * then A.get_reverse_inheritance_stack() returns [C, B, A], - * and B.get_reverse_inheritance_stack() returns [C, B]. - * - * Note: as dictionary inheritance is expressed identically by the AST, - * this works just as well for getting a stack of inherited dictionaries. - */ - IdlInterface.prototype.get_reverse_inheritance_stack = function () { - const stack = [this]; - let idl_interface = this; - while (idl_interface.base) { - const base = this.array.members[idl_interface.base]; - if (!base) { - throw new Error( - idl_interface.type + - " " + - idl_interface.base + - " not found (inherited by " + - idl_interface.name + - ")" - ); - } else if (stack.indexOf(base) > -1) { - stack.unshift(base); - const dep_chain = stack.map((i) => i.name).join(","); - throw new IdlHarnessError( - `${this.name} has a circular dependency: ${dep_chain}` - ); - } - idl_interface = base; - stack.unshift(idl_interface); - } - return stack; - }; - - /** - * Implementation of - * https://webidl.spec.whatwg.org/#default-tojson-operation - * for testing purposes. - * - * Collects the IDL types of the attributes that meet the criteria - * for inclusion in the default toJSON operation for easy - * comparison with actual value - */ - IdlInterface.prototype.default_to_json_operation = function () { - const map = new Map(); - let isDefault = false; - for (const I of this.get_reverse_inheritance_stack()) { - if (I.has_default_to_json_regular_operation()) { - isDefault = true; - for (const m of I.members) { - if ( - m.special !== "static" && - m.type == "attribute" && - I.array.is_json_type(m.idlType) - ) { - map.set(m.name, m.idlType); - } - } - } else if (I.has_to_json_regular_operation()) { - isDefault = false; - } - } - return isDefault ? map : null; - }; - - IdlInterface.prototype.test = function () { - if ( - this.has_extended_attribute("LegacyNoInterfaceObject") || - this.is_mixin() - ) { - // No tests to do without an instance. TODO: We should still be able - // to run tests on the prototype object, if we obtain one through some - // other means. - return; - } - - // If the interface object is not exposed, only test that. Members can't be - // tested either, but objects could still be tested in |test_object|. - if (!this.exposed) { - if (!this.untested) { - subsetTestByKey( - this.name, - test, - function () { - assert_false(this.name in self); - }.bind(this), - this.name + - " interface: existence and properties of interface object" - ); - } - return; - } - - if (!this.untested) { - // First test things to do with the exception/interface object and - // exception/interface prototype object. - this.test_self(); - } - // Then test things to do with its members (constants, fields, attributes, - // operations, . . .). These are run even if .untested is true, because - // members might themselves be marked as .untested. This might happen to - // interfaces if the interface itself is untested but a partial interface - // that extends it is tested -- then the interface itself and its initial - // members will be marked as untested, but the members added by the partial - // interface are still tested. - this.test_members(); - }; - - IdlInterface.prototype.constructors = function () { - return this.members.filter(function (m) { - return m.type == "constructor"; - }); - }; - - IdlInterface.prototype.test_self = function () { - subsetTestByKey( - this.name, - test, - function () { - if (!this.should_have_interface_object()) { - return; - } - - // The name of the property is the identifier of the interface, and its - // value is an object called the interface object. - // The property has the attributes { [[Writable]]: true, - // [[Enumerable]]: false, [[Configurable]]: true }." - // TODO: Should we test here that the property is actually writable - // etc., or trust getOwnPropertyDescriptor? - this.assert_interface_object_exists(); - var desc = Object.getOwnPropertyDescriptor( - this.get_interface_object_owner(), - this.name - ); - assert_false( - "get" in desc, - "self's property " + - format_value(this.name) + - " should not have a getter" - ); - assert_false( - "set" in desc, - "self's property " + - format_value(this.name) + - " should not have a setter" - ); - assert_true( - desc.writable, - "self's property " + format_value(this.name) + " should be writable" - ); - assert_false( - desc.enumerable, - "self's property " + - format_value(this.name) + - " should not be enumerable" - ); - assert_true( - desc.configurable, - "self's property " + - format_value(this.name) + - " should be configurable" - ); - - if (this.is_callback()) { - // "The internal [[Prototype]] property of an interface object for - // a callback interface must be the Function.prototype object." - assert_equals( - Object.getPrototypeOf(this.get_interface_object()), - Function.prototype, - "prototype of self's property " + - format_value(this.name) + - " is not Object.prototype" - ); - - return; - } - - // "The interface object for a given non-callback interface is a - // function object." - // "If an object is defined to be a function object, then it has - // characteristics as follows:" - - // Its [[Prototype]] internal property is otherwise specified (see - // below). - - // "* Its [[Get]] internal property is set as described in ECMA-262 - // section 9.1.8." - // Not much to test for this. - - // "* Its [[Construct]] internal property is set as described in - // ECMA-262 section 19.2.2.3." - - // "* Its @@hasInstance property is set as described in ECMA-262 - // section 19.2.3.8, unless otherwise specified." - // TODO - - // ES6 (rev 30) 19.1.3.6: - // "Else, if O has a [[Call]] internal method, then let builtinTag be - // "Function"." - assert_class_string( - this.get_interface_object(), - "Function", - "class string of " + this.name - ); - - // "The [[Prototype]] internal property of an interface object for a - // non-callback interface is determined as follows:" - var prototype = Object.getPrototypeOf(this.get_interface_object()); - if (this.base) { - // "* If the interface inherits from some other interface, the - // value of [[Prototype]] is the interface object for that other - // interface." - var inherited_interface = this.array.members[this.base]; - if ( - !inherited_interface.has_extended_attribute( - "LegacyNoInterfaceObject" - ) - ) { - inherited_interface.assert_interface_object_exists(); - assert_equals( - prototype, - inherited_interface.get_interface_object(), - "prototype of " + this.name + " is not " + this.base - ); - } - } else { - // "If the interface doesn't inherit from any other interface, the - // value of [[Prototype]] is %FunctionPrototype% ([ECMA-262], - // section 6.1.7.4)." - assert_equals( - prototype, - Function.prototype, - "prototype of self's property " + - format_value(this.name) + - " is not Function.prototype" - ); - } - - // Always test for [[Construct]]: - // https://github.com/heycam/webidl/issues/698 - assert_true( - isConstructor(this.get_interface_object()), - "interface object must pass IsConstructor check" - ); - - var interface_object = this.get_interface_object(); - assert_throws_js( - globalOf(interface_object).TypeError, - function () { - interface_object(); - }, - "interface object didn't throw TypeError when called as a function" - ); - - if (!this.constructors().length) { - assert_throws_js( - globalOf(interface_object).TypeError, - function () { - new interface_object(); - }, - "interface object didn't throw TypeError when called as a constructor" - ); - } - }.bind(this), - this.name + " interface: existence and properties of interface object" - ); - - if (this.should_have_interface_object() && !this.is_callback()) { - subsetTestByKey( - this.name, - test, - function () { - // This function tests WebIDL as of 2014-10-25. - // https://webidl.spec.whatwg.org/#es-interface-call - - this.assert_interface_object_exists(); - - // "Interface objects for non-callback interfaces MUST have a - // property named “length” with attributes { [[Writable]]: false, - // [[Enumerable]]: false, [[Configurable]]: true } whose value is - // a Number." - assert_own_property(this.get_interface_object(), "length"); - var desc = Object.getOwnPropertyDescriptor( - this.get_interface_object(), - "length" - ); - assert_false( - "get" in desc, - this.name + ".length should not have a getter" - ); - assert_false( - "set" in desc, - this.name + ".length should not have a setter" - ); - assert_false( - desc.writable, - this.name + ".length should not be writable" - ); - assert_false( - desc.enumerable, - this.name + ".length should not be enumerable" - ); - assert_true( - desc.configurable, - this.name + ".length should be configurable" - ); - - 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" - ); - } - - if (this.should_have_interface_object()) { - subsetTestByKey( - this.name, - test, - function () { - // This function tests WebIDL as of 2015-11-17. - // https://webidl.spec.whatwg.org/#interface-object - - this.assert_interface_object_exists(); - - // "All interface objects must have a property named “name” with - // attributes { [[Writable]]: false, [[Enumerable]]: false, - // [[Configurable]]: true } whose value is the identifier of the - // corresponding interface." - - assert_own_property(this.get_interface_object(), "name"); - var desc = Object.getOwnPropertyDescriptor( - this.get_interface_object(), - "name" - ); - assert_false( - "get" in desc, - this.name + ".name should not have a getter" - ); - assert_false( - "set" in desc, - this.name + ".name should not have a setter" - ); - assert_false( - desc.writable, - this.name + ".name should not be writable" - ); - assert_false( - desc.enumerable, - this.name + ".name should not be enumerable" - ); - assert_true( - desc.configurable, - this.name + ".name should be configurable" - ); - assert_equals( - this.get_interface_object().name, - this.name, - "wrong value for " + this.name + ".name" - ); - }.bind(this), - this.name + " interface object name" - ); - } - - if (this.has_extended_attribute("LegacyWindowAlias")) { - subsetTestByKey( - this.name, - test, - function () { - var aliasAttrs = this.extAttrs.filter(function (o) { - return o.name === "LegacyWindowAlias"; - }); - if (aliasAttrs.length > 1) { - throw new IdlHarnessError( - "Invalid IDL: multiple LegacyWindowAlias extended attributes on " + - this.name - ); - } - if (this.is_callback()) { - throw new IdlHarnessError( - "Invalid IDL: LegacyWindowAlias extended attribute on non-interface " + - this.name - ); - } - if (!(this.exposureSet === "*" || this.exposureSet.has("Window"))) { - throw new IdlHarnessError( - "Invalid IDL: LegacyWindowAlias extended attribute on " + - this.name + - " which is not exposed in Window" - ); - } - // TODO: when testing of [LegacyNoInterfaceObject] interfaces is supported, - // check that it's not specified together with LegacyWindowAlias. - - // TODO: maybe check that [LegacyWindowAlias] is not specified on a partial interface. - - var rhs = aliasAttrs[0].rhs; - if (!rhs) { - throw new IdlHarnessError( - "Invalid IDL: LegacyWindowAlias extended attribute on " + - this.name + - " without identifier" - ); - } - var aliases; - if (rhs.type === "identifier-list") { - aliases = rhs.value.map((id) => id.value); - } else { - // rhs.type === identifier - aliases = [rhs.value]; - } - - // OK now actually check the aliases... - var alias; - if ( - exposed_in(exposure_set(this, this.exposureSet)) && - "document" in self - ) { - for (alias of aliases) { - assert_true(alias in self, alias + " should exist"); - assert_equals( - self[alias], - this.get_interface_object(), - "self." + - alias + - " should be the same value as self." + - this.get_qualified_name() - ); - var desc = Object.getOwnPropertyDescriptor(self, alias); - assert_equals( - desc.value, - this.get_interface_object(), - "wrong value in " + alias + " property descriptor" - ); - assert_true(desc.writable, alias + " should be writable"); - assert_false( - desc.enumerable, - alias + " should not be enumerable" - ); - assert_true( - desc.configurable, - alias + " should be configurable" - ); - assert_false( - "get" in desc, - alias + " should not have a getter" - ); - assert_false( - "set" in desc, - alias + " should not have a setter" - ); - } - } else { - for (alias of aliases) { - assert_false(alias in self, alias + " should not exist"); - } - } - }.bind(this), - this.name + " interface: legacy window alias" - ); - } - - if (this.has_extended_attribute("LegacyFactoryFunction")) { - var constructors = this.extAttrs.filter(function (attr) { - return attr.name == "LegacyFactoryFunction"; - }); - if (constructors.length !== 1) { - throw new IdlHarnessError( - "Internal error: missing support for multiple LegacyFactoryFunction extended attributes" - ); - } - var constructor = constructors[0]; - var min_length = minOverloadLength([constructor]); - - subsetTestByKey( - this.name, - test, - function () { - // This function tests WebIDL as of 2019-01-14. - - // "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 - // [LegacyFactoryFunction]'s identifier, and its value is an object - // called a named constructor, ... . The property has the attributes - // { [[Writable]]: true, [[Enumerable]]: false, - // [[Configurable]]: true }." - var name = constructor.rhs.value; - assert_own_property(self, name); - var desc = Object.getOwnPropertyDescriptor(self, name); - assert_equals( - desc.value, - self[name], - "wrong value in " + name + " property descriptor" - ); - assert_true(desc.writable, name + " should be writable"); - assert_false(desc.enumerable, name + " should not be enumerable"); - assert_true(desc.configurable, name + " should be configurable"); - assert_false("get" in desc, name + " should not have a getter"); - assert_false("set" in desc, name + " should not have a setter"); - }.bind(this), - this.name + " interface: named constructor" - ); - - subsetTestByKey( - this.name, - test, - function () { - // This function tests WebIDL as of 2019-01-14. - - // "2. Let F be ! CreateBuiltinFunction(realm, steps, - // realm.[[Intrinsics]].[[%FunctionPrototype%]])." - var name = constructor.rhs.value; - var value = self[name]; - assert_equals( - typeof value, - "function", - "type of value in " + name + " property descriptor" - ); - assert_not_equals( - value, - this.get_interface_object(), - "wrong value in " + name + " property descriptor" - ); - assert_equals( - Object.getPrototypeOf(value), - Function.prototype, - "wrong value for " + name + "'s prototype" - ); - }.bind(this), - this.name + " interface: named constructor object" - ); - - subsetTestByKey( - this.name, - test, - function () { - // This function tests WebIDL as of 2019-01-14. - - // "7. Let proto be the interface prototype object of interface I - // in realm. - // "8. Perform ! DefinePropertyOrThrow(F, "prototype", - // PropertyDescriptor{ - // [[Value]]: proto, [[Writable]]: false, - // [[Enumerable]]: false, [[Configurable]]: false - // })." - var name = constructor.rhs.value; - var expected = this.get_interface_object().prototype; - var desc = Object.getOwnPropertyDescriptor(self[name], "prototype"); - assert_equals( - desc.value, - expected, - "wrong value for " + name + ".prototype" - ); - assert_false(desc.writable, "prototype should not be writable"); - assert_false(desc.enumerable, "prototype should not be enumerable"); - assert_false( - desc.configurable, - "prototype should not be configurable" - ); - assert_false("get" in desc, "prototype should not have a getter"); - assert_false("set" in desc, "prototype should not have a setter"); - }.bind(this), - this.name + " interface: named constructor prototype property" - ); - - subsetTestByKey( - this.name, - test, - function () { - // This function tests WebIDL as of 2019-01-14. - - // "3. Perform ! SetFunctionName(F, id)." - var name = constructor.rhs.value; - var desc = Object.getOwnPropertyDescriptor(self[name], "name"); - assert_equals( - desc.value, - name, - "wrong value for " + name + ".name" - ); - assert_false(desc.writable, "name should not be writable"); - assert_false(desc.enumerable, "name should not be enumerable"); - assert_true(desc.configurable, "name should be configurable"); - assert_false("get" in desc, "name should not have a getter"); - assert_false("set" in desc, "name should not have a setter"); - }.bind(this), - this.name + " interface: named constructor name" - ); - - subsetTestByKey( - this.name, - test, - function () { - // This function tests WebIDL as of 2019-01-14. - - // "4. Initialize S to the effective overload set for constructors - // with identifier id on interface I and with argument count 0. - // "5. Let length be the length of the shortest argument list of - // the entries in S. - // "6. Perform ! SetFunctionLength(F, length)." - var name = constructor.rhs.value; - var desc = Object.getOwnPropertyDescriptor(self[name], "length"); - assert_equals( - desc.value, - min_length, - "wrong value for " + name + ".length" - ); - assert_false(desc.writable, "length should not be writable"); - assert_false(desc.enumerable, "length should not be enumerable"); - assert_true(desc.configurable, "length should be configurable"); - assert_false("get" in desc, "length should not have a getter"); - assert_false("set" in desc, "length should not have a setter"); - }.bind(this), - this.name + " interface: named constructor length" - ); - - subsetTestByKey( - this.name, - test, - function () { - // This function tests WebIDL as of 2019-01-14. - - // "1. Let steps be the following steps: - // " 1. If NewTarget is undefined, then throw a TypeError." - var name = constructor.rhs.value; - var args = constructor.arguments.map(function (arg) { - return create_suitable_object(arg.idlType); - }); - assert_throws_js( - globalOf(self[name]).TypeError, - function () { - self[name](...args); - }.bind(this) - ); - }.bind(this), - this.name + " interface: named constructor without 'new'" - ); - } - - subsetTestByKey( - this.name, - test, - function () { - // This function tests WebIDL as of 2015-01-21. - // https://webidl.spec.whatwg.org/#interface-object - - if (!this.should_have_interface_object()) { - return; - } - - this.assert_interface_object_exists(); - - if (this.is_callback()) { - assert_false( - "prototype" in this.get_interface_object(), - this.name + ' should not have a "prototype" property' - ); - return; - } - - // "An interface object for a non-callback interface must have a - // property named “prototype” with attributes { [[Writable]]: false, - // [[Enumerable]]: false, [[Configurable]]: false } whose value is an - // object called the interface prototype object. This object has - // properties that correspond to the regular attributes and regular - // operations defined on the interface, and is described in more detail - // in section 4.5.4 below." - assert_own_property( - this.get_interface_object(), - "prototype", - 'interface "' + - this.name + - '" does not have own property "prototype"' - ); - var desc = Object.getOwnPropertyDescriptor( - this.get_interface_object(), - "prototype" - ); - assert_false( - "get" in desc, - this.name + ".prototype should not have a getter" - ); - assert_false( - "set" in desc, - this.name + ".prototype should not have a setter" - ); - assert_false( - desc.writable, - this.name + ".prototype should not be writable" - ); - assert_false( - desc.enumerable, - this.name + ".prototype should not be enumerable" - ); - assert_false( - desc.configurable, - this.name + ".prototype should not be configurable" - ); - - // Next, test that the [[Prototype]] of the interface prototype object - // is correct. (This is made somewhat difficult by the existence of - // [LegacyNoInterfaceObject].) - // TODO: Aryeh thinks there's at least other place in this file where - // we try to figure out if an interface prototype object is - // correct. Consolidate that code. - - // "The interface prototype object for a given interface A must have an - // internal [[Prototype]] property whose value is returned from the - // following steps: - // "If A is declared with the [Global] extended - // attribute, and A supports named properties, then return the named - // properties object for A, as defined in §3.6.4 Named properties - // object. - // "Otherwise, if A is declared to inherit from another interface, then - // return the interface prototype object for the inherited interface. - // "Otherwise, return %ObjectPrototype%. - // - // "In the ECMAScript binding, the DOMException type has some additional - // requirements: - // - // "Unlike normal interface types, the interface prototype object - // for DOMException must have as its [[Prototype]] the intrinsic - // object %ErrorPrototype%." - // - if (this.name === "Window") { - assert_class_string( - Object.getPrototypeOf(this.get_interface_object().prototype), - "WindowProperties", - "Class name for prototype of Window" + - '.prototype is not "WindowProperties"' - ); - } else { - var inherit_interface, inherit_interface_interface_object; - if (this.base) { - inherit_interface = this.base; - var parent = this.array.members[inherit_interface]; - if (!parent.has_extended_attribute("LegacyNoInterfaceObject")) { - parent.assert_interface_object_exists(); - inherit_interface_interface_object = - parent.get_interface_object(); - } - } else if (this.name === "DOMException") { - inherit_interface = "Error"; - inherit_interface_interface_object = self.Error; - } else { - inherit_interface = "Object"; - inherit_interface_interface_object = self.Object; - } - if (inherit_interface_interface_object) { - assert_not_equals( - inherit_interface_interface_object, - undefined, - "should inherit from " + - inherit_interface + - ", but there is no such property" - ); - assert_own_property( - inherit_interface_interface_object, - "prototype", - "should inherit from " + - inherit_interface + - ', but that object has no "prototype" property' - ); - assert_equals( - Object.getPrototypeOf(this.get_interface_object().prototype), - inherit_interface_interface_object.prototype, - "prototype of " + - this.name + - ".prototype is not " + - inherit_interface + - ".prototype" - ); - } else { - // We can't test that we get the correct object, because this is the - // only way to get our hands on it. We only test that its class - // string, at least, is correct. - assert_class_string( - Object.getPrototypeOf(this.get_interface_object().prototype), - inherit_interface + "Prototype", - "Class name for prototype of " + - this.name + - '.prototype is not "' + - inherit_interface + - 'Prototype"' - ); - } - } - - // "The class string of an interface prototype object is the - // concatenation of the interface’s qualified identifier and the string - // “Prototype”." - - // Skip these tests for now due to a specification issue about - // prototype name. - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=28244 - - // assert_class_string(this.get_interface_object().prototype, this.get_qualified_name() + "Prototype", - // "class string of " + this.name + ".prototype"); - - // String() should end up calling {}.toString if nothing defines a - // stringifier. - if (!this.has_stringifier()) { - // assert_equals(String(this.get_interface_object().prototype), "[object " + this.get_qualified_name() + "Prototype]", - // "String(" + this.name + ".prototype)"); - } - }.bind(this), - this.name + - " interface: existence and properties of interface prototype object" - ); - - // "If the interface is declared with the [Global] - // extended attribute, or the interface is in the set of inherited - // interfaces for any other interface that is declared with one of these - // attributes, then the interface prototype object must be an immutable - // prototype exotic object." - // https://webidl.spec.whatwg.org/#interface-prototype-object - if (this.is_global()) { - this.test_immutable_prototype( - "interface prototype object", - this.get_interface_object().prototype - ); - } - - subsetTestByKey( - this.name, - test, - function () { - if (!this.should_have_interface_object()) { - return; - } - - this.assert_interface_object_exists(); - - if (this.is_callback()) { - assert_false( - "prototype" in this.get_interface_object(), - this.name + ' should not have a "prototype" property' - ); - return; - } - - assert_own_property( - this.get_interface_object(), - "prototype", - 'interface "' + - this.name + - '" does not have own property "prototype"' - ); - - // "If the [LegacyNoInterfaceObject] extended attribute was not specified - // on the interface, then the interface prototype object must also have a - // property named “constructor” with attributes { [[Writable]]: true, - // [[Enumerable]]: false, [[Configurable]]: true } whose value is a - // reference to the interface object for the interface." - assert_own_property( - this.get_interface_object().prototype, - "constructor", - this.name + '.prototype does not have own property "constructor"' - ); - var desc = Object.getOwnPropertyDescriptor( - this.get_interface_object().prototype, - "constructor" - ); - assert_false( - "get" in desc, - this.name + ".prototype.constructor should not have a getter" - ); - assert_false( - "set" in desc, - this.name + ".prototype.constructor should not have a setter" - ); - assert_true( - desc.writable, - this.name + ".prototype.constructor should be writable" - ); - assert_false( - desc.enumerable, - this.name + ".prototype.constructor should not be enumerable" - ); - assert_true( - desc.configurable, - this.name + ".prototype.constructor should be configurable" - ); - assert_equals( - this.get_interface_object().prototype.constructor, - this.get_interface_object(), - this.name + - ".prototype.constructor is not the same object as " + - this.name - ); - }.bind(this), - this.name + - ' interface: existence and properties of interface prototype object\'s "constructor" property' - ); - - subsetTestByKey( - this.name, - test, - function () { - if (!this.should_have_interface_object()) { - return; - } - - this.assert_interface_object_exists(); - - if (this.is_callback()) { - assert_false( - "prototype" in this.get_interface_object(), - this.name + ' should not have a "prototype" property' - ); - return; - } - - assert_own_property( - this.get_interface_object(), - "prototype", - 'interface "' + - this.name + - '" does not have own property "prototype"' - ); - - // If the interface has any member declared with the [Unscopable] extended - // attribute, then there must be a property on the interface prototype object - // whose name is the @@unscopables symbol, which has the attributes - // { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }, - // and whose value is an object created as follows... - var unscopables = this.get_unscopables().map((m) => m.name); - var proto = this.get_interface_object().prototype; - if (unscopables.length != 0) { - assert_own_property( - proto, - Symbol.unscopables, - this.name + ".prototype should have an @@unscopables property" - ); - var desc = Object.getOwnPropertyDescriptor( - proto, - Symbol.unscopables - ); - assert_false( - "get" in desc, - this.name + - ".prototype[Symbol.unscopables] should not have a getter" - ); - assert_false( - "set" in desc, - this.name + - ".prototype[Symbol.unscopables] should not have a setter" - ); - assert_false( - desc.writable, - this.name + - ".prototype[Symbol.unscopables] should not be writable" - ); - assert_false( - desc.enumerable, - this.name + - ".prototype[Symbol.unscopables] should not be enumerable" - ); - assert_true( - desc.configurable, - this.name + - ".prototype[Symbol.unscopables] should be configurable" - ); - assert_equals( - desc.value, - proto[Symbol.unscopables], - this.name + - ".prototype[Symbol.unscopables] should be in the descriptor" - ); - assert_equals( - typeof desc.value, - "object", - this.name + ".prototype[Symbol.unscopables] should be an object" - ); - assert_equals( - Object.getPrototypeOf(desc.value), - null, - this.name + - ".prototype[Symbol.unscopables] should have a null prototype" - ); - assert_equals( - Object.getOwnPropertySymbols(desc.value).length, - 0, - this.name + - ".prototype[Symbol.unscopables] should have the right number of symbol-named properties" - ); - - // Check that we do not have _extra_ unscopables. Checking that we - // have all the ones we should will happen in the per-member tests. - var observed = Object.getOwnPropertyNames(desc.value); - for (var prop of observed) { - assert_not_equals( - unscopables.indexOf(prop), - -1, - this.name + - '.prototype[Symbol.unscopables] has unexpected property "' + - prop + - '"' - ); - } - } else { - assert_equals( - Object.getOwnPropertyDescriptor( - this.get_interface_object().prototype, - Symbol.unscopables - ), - undefined, - this.name + ".prototype should not have @@unscopables" - ); - } - }.bind(this), - this.name + - " interface: existence and properties of interface prototype object's @@unscopables property" - ); - }; - - IdlInterface.prototype.test_immutable_prototype = function (type, obj) { - if (typeof Object.setPrototypeOf !== "function") { - return; - } - - subsetTestByKey( - this.name, - test, - function (t) { - var originalValue = Object.getPrototypeOf(obj); - var newValue = Object.create(null); - - t.add_cleanup(function () { - try { - Object.setPrototypeOf(obj, originalValue); - } catch (err) {} - }); - - assert_throws_js(TypeError, function () { - Object.setPrototypeOf(obj, newValue); - }); - - assert_equals( - Object.getPrototypeOf(obj), - originalValue, - "original value not modified" - ); - }.bind(this), - this.name + - " interface: internal [[SetPrototypeOf]] method " + - "of " + - type + - " - setting to a new value via Object.setPrototypeOf " + - "should throw a TypeError" - ); - - subsetTestByKey( - this.name, - test, - function (t) { - var originalValue = Object.getPrototypeOf(obj); - var newValue = Object.create(null); - - t.add_cleanup(function () { - let setter = Object.getOwnPropertyDescriptor( - Object.prototype, - "__proto__" - ).set; - - try { - setter.call(obj, originalValue); - } catch (err) {} - }); - - // 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; - }); - - assert_equals( - Object.getPrototypeOf(obj), - originalValue, - "original value not modified" - ); - }.bind(this), - this.name + - " interface: internal [[SetPrototypeOf]] method " + - "of " + - type + - " - setting to a new value via __proto__ " + - "should throw a TypeError" - ); - - subsetTestByKey( - this.name, - test, - function (t) { - var originalValue = Object.getPrototypeOf(obj); - var newValue = Object.create(null); - - t.add_cleanup(function () { - try { - Reflect.setPrototypeOf(obj, originalValue); - } catch (err) {} - }); - - assert_false(Reflect.setPrototypeOf(obj, newValue)); - - assert_equals( - Object.getPrototypeOf(obj), - originalValue, - "original value not modified" - ); - }.bind(this), - this.name + - " interface: internal [[SetPrototypeOf]] method " + - "of " + - type + - " - setting to a new value via Reflect.setPrototypeOf " + - "should return false" - ); - - subsetTestByKey( - this.name, - test, - function () { - var originalValue = Object.getPrototypeOf(obj); - - Object.setPrototypeOf(obj, originalValue); - }.bind(this), - this.name + - " interface: internal [[SetPrototypeOf]] method " + - "of " + - type + - " - setting to its original value via Object.setPrototypeOf " + - "should not throw" - ); - - subsetTestByKey( - this.name, - test, - function () { - var originalValue = Object.getPrototypeOf(obj); - - obj.__proto__ = originalValue; - }.bind(this), - this.name + - " interface: internal [[SetPrototypeOf]] method " + - "of " + - type + - " - setting to its original value via __proto__ " + - "should not throw" - ); - - subsetTestByKey( - this.name, - test, - function () { - var originalValue = Object.getPrototypeOf(obj); - - assert_true(Reflect.setPrototypeOf(obj, originalValue)); - }.bind(this), - this.name + - " interface: internal [[SetPrototypeOf]] method " + - "of " + - type + - " - setting to its original value via Reflect.setPrototypeOf " + - "should return true" - ); - }; - - IdlInterface.prototype.test_member_const = function (member) { - if (!this.has_constants()) { - throw new IdlHarnessError( - "Internal error: test_member_const called without any constants" - ); - } - - subsetTestByKey( - this.name, - test, - function () { - this.assert_interface_object_exists(); - - // "For each constant defined on an interface A, there must be - // a corresponding property on the interface object, if it - // exists." - assert_own_property(this.get_interface_object(), member.name); - // "The value of the property is that which is obtained by - // converting the constant’s IDL value to an ECMAScript - // value." - assert_equals( - this.get_interface_object()[member.name], - constValue(member.value), - "property has wrong value" - ); - // "The property has attributes { [[Writable]]: false, - // [[Enumerable]]: true, [[Configurable]]: false }." - var desc = Object.getOwnPropertyDescriptor( - this.get_interface_object(), - member.name - ); - assert_false("get" in desc, "property should not have a getter"); - assert_false("set" in desc, "property should not have a setter"); - assert_false(desc.writable, "property should not be writable"); - assert_true(desc.enumerable, "property should be enumerable"); - assert_false( - desc.configurable, - "property should not be configurable" - ); - }.bind(this), - this.name + - " interface: constant " + - member.name + - " on interface object" - ); - - // "In addition, a property with the same characteristics must - // exist on the interface prototype object." - subsetTestByKey( - this.name, - test, - function () { - this.assert_interface_object_exists(); - - if (this.is_callback()) { - assert_false( - "prototype" in this.get_interface_object(), - this.name + ' should not have a "prototype" property' - ); - return; - } - - assert_own_property( - this.get_interface_object(), - "prototype", - 'interface "' + - this.name + - '" does not have own property "prototype"' - ); - - assert_own_property( - this.get_interface_object().prototype, - member.name - ); - assert_equals( - this.get_interface_object().prototype[member.name], - constValue(member.value), - "property has wrong value" - ); - var desc = Object.getOwnPropertyDescriptor( - this.get_interface_object(), - member.name - ); - assert_false("get" in desc, "property should not have a getter"); - assert_false("set" in desc, "property should not have a setter"); - assert_false(desc.writable, "property should not be writable"); - assert_true(desc.enumerable, "property should be enumerable"); - assert_false( - desc.configurable, - "property should not be configurable" - ); - }.bind(this), - this.name + - " interface: constant " + - member.name + - " on interface prototype object" - ); - }; - - IdlInterface.prototype.test_member_attribute = function (member) { - if (!shouldRunSubTest(this.name)) { - return; - } - var a_test = subsetTestByKey( - this.name, - async_test, - this.name + " interface: attribute " + member.name - ); - a_test.step( - function () { - if (!this.should_have_interface_object()) { - a_test.done(); - return; - } - - this.assert_interface_object_exists(); - assert_own_property( - this.get_interface_object(), - "prototype", - 'interface "' + - this.name + - '" does not have own property "prototype"' - ); - - if (member.special === "static") { - assert_own_property( - this.get_interface_object(), - member.name, - "The interface object must have a property " + - format_value(member.name) - ); - a_test.done(); - return; - } - - this.do_member_unscopable_asserts(member); - - if (this.is_global()) { - assert_own_property( - self, - member.name, - "The global object must have a property " + - format_value(member.name) - ); - assert_false( - member.name in this.get_interface_object().prototype, - "The prototype object should not have a property " + - format_value(member.name) - ); - - var getter = Object.getOwnPropertyDescriptor(self, member.name).get; - assert_equals( - typeof getter, - "function", - format_value(member.name) + " must have a getter" - ); - - // Try/catch around the get here, since it can legitimately throw. - // If it does, we obviously can't check for equality with direct - // invocation of the getter. - var gotValue; - var propVal; - try { - propVal = self[member.name]; - gotValue = true; - } catch (e) { - gotValue = false; - } - if (gotValue) { - assert_equals( - propVal, - getter.call(undefined), - "Gets on a global should not require an explicit this" - ); - } - - // do_interface_attribute_asserts must be the last thing we do, - // since it will call done() on a_test. - this.do_interface_attribute_asserts(self, member, a_test); - } else { - assert_true( - member.name in this.get_interface_object().prototype, - "The prototype object must have a property " + - format_value(member.name) - ); - - if (!member.has_extended_attribute("LegacyLenientThis")) { - if (member.idlType.generic !== "Promise") { - // 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_js( - a_test, - TypeError, - this.get_interface_object().prototype[member.name] - ).then( - a_test.step_func( - function () { - // 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 - ); - }.bind(this) - ) - ); - } - } else { - assert_equals( - this.get_interface_object().prototype[member.name], - undefined, - "getting property on prototype object must return undefined" - ); - // 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 - ); - } - } - }.bind(this) - ); - }; - - 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 - ); - a_test.step( - function () { - // This function tests WebIDL as of 2015-12-29. - // https://webidl.spec.whatwg.org/#es-operations - - if (!this.should_have_interface_object()) { - a_test.done(); - return; - } - - this.assert_interface_object_exists(); - - if (this.is_callback()) { - assert_false( - "prototype" in this.get_interface_object(), - this.name + ' should not have a "prototype" property' - ); - a_test.done(); - return; - } - - assert_own_property( - this.get_interface_object(), - "prototype", - 'interface "' + - this.name + - '" does not have own property "prototype"' - ); - - // "For each unique identifier of an exposed operation defined on the - // interface, there must exist a corresponding property, unless the - // effective overload set for that identifier and operation and with an - // argument count of 0 has no entries." - - // TODO: Consider [Exposed]. - - // "The location of the property is determined as follows:" - var memberHolderObject; - // "* If the operation is static, then the property exists on the - // interface object." - if (member.special === "static") { - assert_own_property( - this.get_interface_object(), - member.name, - "interface object missing static operation" - ); - memberHolderObject = this.get_interface_object(); - // "* Otherwise, [...] if the interface was declared with the [Global] - // extended attribute, then the property exists - // on every object that implements the interface." - } else if (this.is_global()) { - assert_own_property( - self, - member.name, - "global object missing non-static operation" - ); - memberHolderObject = self; - // "* Otherwise, the property exists solely on the interface’s - // interface prototype object." - } else { - assert_own_property( - this.get_interface_object().prototype, - member.name, - "interface prototype object missing non-static operation" - ); - memberHolderObject = this.get_interface_object().prototype; - } - this.do_member_unscopable_asserts(member); - this.do_member_operation_asserts(memberHolderObject, member, a_test); - }.bind(this) - ); - }; - - IdlInterface.prototype.do_member_unscopable_asserts = function (member) { - // Check that if the member is unscopable then it's in the - // @@unscopables object properly. - if (!member.isUnscopable) { - return; - } - - var unscopables = - this.get_interface_object().prototype[Symbol.unscopables]; - var prop = member.name; - var propDesc = Object.getOwnPropertyDescriptor(unscopables, prop); - assert_equals( - typeof propDesc, - "object", - this.name + ".prototype[Symbol.unscopables]." + prop + " must exist" - ); - assert_false( - "get" in propDesc, - this.name + - ".prototype[Symbol.unscopables]." + - prop + - " must have no getter" - ); - assert_false( - "set" in propDesc, - this.name + - ".prototype[Symbol.unscopables]." + - prop + - " must have no setter" - ); - assert_true( - propDesc.writable, - this.name + - ".prototype[Symbol.unscopables]." + - prop + - " must be writable" - ); - assert_true( - propDesc.enumerable, - this.name + - ".prototype[Symbol.unscopables]." + - prop + - " must be enumerable" - ); - assert_true( - propDesc.configurable, - this.name + - ".prototype[Symbol.unscopables]." + - prop + - " must be configurable" - ); - assert_equals( - propDesc.value, - true, - this.name + - ".prototype[Symbol.unscopables]." + - prop + - " must have the value `true`" - ); - }; - - IdlInterface.prototype.do_member_operation_asserts = function ( - memberHolderObject, - member, - a_test - ) { - var done = a_test.done.bind(a_test); - var operationUnforgeable = member.isUnforgeable; - var desc = Object.getOwnPropertyDescriptor( - memberHolderObject, - member.name - ); - // "The property has attributes { [[Writable]]: B, - // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the - // operation is unforgeable on the interface, and true otherwise". - assert_false("get" in desc, "property should not have a getter"); - assert_false("set" in desc, "property should not have a setter"); - assert_equals( - desc.writable, - !operationUnforgeable, - "property should be writable if and only if not unforgeable" - ); - assert_true(desc.enumerable, "property should be enumerable"); - assert_equals( - desc.configurable, - !operationUnforgeable, - "property should be configurable if and only if not unforgeable" - ); - // "The value of the property is a Function object whose - // behavior is as follows . . ." - assert_equals( - typeof memberHolderObject[member.name], - "function", - "property must be a function" - ); - - const operationOverloads = this.members.filter(function (m) { - return ( - m.type == "operation" && - m.name == member.name && - (m.special === "static") === (member.special === "static") - ); - }); - assert_equals( - memberHolderObject[member.name].length, - minOverloadLength(operationOverloads), - "property has wrong .length" - ); - assert_equals( - memberHolderObject[member.name].name, - member.name, - "property has wrong .name" - ); - - // Make some suitable arguments - var args = member.arguments.map(function (arg) { - return create_suitable_object(arg.idlType); - }); - - // "Let O be a value determined as follows: - // ". . . - // "Otherwise, throw a TypeError." - // This should be hit if the operation is not static, there is - // no [ImplicitThis] attribute, and the this value is null. - // - // TODO: We currently ignore the [ImplicitThis] case. Except we manually - // 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.special !== "static") { - var cb; - if ( - !this.is_global() && - memberHolderObject[member.name] != self[member.name] - ) { - cb = awaitNCallbacks(2, done); - throwOrReject( - a_test, - member, - memberHolderObject[member.name], - null, - args, - "calling operation with this = null didn't throw TypeError", - cb - ); - } else { - cb = awaitNCallbacks(1, done); - } - - // ". . . If O is not null and is also not a platform object - // that implements interface I, throw a TypeError." - // - // TODO: Test a platform object that implements some other - // interface. (Have to be sure to get inheritance right.) - throwOrReject( - a_test, - member, - memberHolderObject[member.name], - {}, - args, - "calling operation with this = {} didn't throw TypeError", - cb - ); - } else { - done(); - } - }; - - IdlInterface.prototype.test_to_json_operation = function ( - desc, - memberHolderObject, - member - ) { - var instanceName = - (memberHolderObject && memberHolderObject.constructor.name) || - member.name + " object"; - if (member.has_extended_attribute("Default")) { - subsetTestByKey( - this.name, - test, - function () { - var map = this.default_to_json_operation(); - var json = memberHolderObject.toJSON(); - map.forEach(function (type, k) { - assert_true( - k in json, - "property " + - JSON.stringify(k) + - " should be present in the output of " + - this.name + - ".prototype.toJSON()" - ); - var descriptor = Object.getOwnPropertyDescriptor(json, k); - assert_true( - descriptor.writable, - "property " + k + " should be writable" - ); - assert_true( - descriptor.configurable, - "property " + k + " should be configurable" - ); - assert_true( - descriptor.enumerable, - "property " + k + " should be enumerable" - ); - this.array.assert_type_is(json[k], type); - delete json[k]; - }, this); - }.bind(this), - this.name + " interface: default toJSON operation on " + desc - ); - } else { - subsetTestByKey( - this.name, - test, - function () { - assert_true( - this.array.is_json_type(member.idlType), - JSON.stringify(member.idlType) + - " is not an appropriate return value for the toJSON operation of " + - instanceName - ); - this.array.assert_type_is( - memberHolderObject.toJSON(), - member.idlType - ); - }.bind(this), - this.name + " interface: toJSON operation on " + desc - ); - } - }; - - IdlInterface.prototype.test_member_maplike = function (member) { - subsetTestByKey( - this.name, - test, - () => { - const proto = this.get_interface_object().prototype; - - const methods = [ - ["entries", 0], - ["keys", 0], - ["values", 0], - ["forEach", 1], - ["get", 1], - ["has", 1], - ]; - if (!member.readonly) { - methods.push(["set", 2], ["delete", 1], ["clear", 0]); - } - - for (const [name, length] of methods) { - const desc = Object.getOwnPropertyDescriptor(proto, name); - assert_equals( - typeof desc.value, - "function", - `${name} should be a function` - ); - assert_equals(desc.enumerable, true, `${name} enumerable`); - assert_equals(desc.configurable, true, `${name} configurable`); - assert_equals(desc.writable, true, `${name} writable`); - assert_equals( - desc.value.length, - length, - `${name} function object length should be ${length}` - ); - assert_equals( - desc.value.name, - name, - `${name} function object should have the right name` - ); - } - - const iteratorDesc = Object.getOwnPropertyDescriptor( - proto, - Symbol.iterator - ); - assert_equals( - iteratorDesc.value, - proto.entries, - `@@iterator should equal entries` - ); - assert_equals( - iteratorDesc.enumerable, - false, - `@@iterator enumerable` - ); - assert_equals( - iteratorDesc.configurable, - true, - `@@iterator configurable` - ); - assert_equals(iteratorDesc.writable, true, `@@iterator writable`); - - const sizeDesc = Object.getOwnPropertyDescriptor(proto, "size"); - assert_equals( - typeof sizeDesc.get, - "function", - `size getter should be a function` - ); - assert_equals( - sizeDesc.set, - undefined, - `size should not have a setter` - ); - assert_equals(sizeDesc.enumerable, true, `size enumerable`); - assert_equals(sizeDesc.configurable, true, `size configurable`); - assert_equals(sizeDesc.get.length, 0, `size getter length`); - assert_equals(sizeDesc.get.name, "get size", `size getter name`); - }, - `${this.name} interface: maplike<${member.idlType.map((t) => t.idlType).join(", ")}>` - ); - }; - - IdlInterface.prototype.test_member_setlike = function (member) { - subsetTestByKey( - this.name, - test, - () => { - const proto = this.get_interface_object().prototype; - - const methods = [ - ["entries", 0], - ["keys", 0], - ["values", 0], - ["forEach", 1], - ["has", 1], - ]; - if (!member.readonly) { - methods.push(["add", 1], ["delete", 1], ["clear", 0]); - } - - for (const [name, length] of methods) { - const desc = Object.getOwnPropertyDescriptor(proto, name); - assert_equals( - typeof desc.value, - "function", - `${name} should be a function` - ); - assert_equals(desc.enumerable, true, `${name} enumerable`); - assert_equals(desc.configurable, true, `${name} configurable`); - assert_equals(desc.writable, true, `${name} writable`); - assert_equals( - desc.value.length, - length, - `${name} function object length should be ${length}` - ); - assert_equals( - desc.value.name, - name, - `${name} function object should have the right name` - ); - } - - const iteratorDesc = Object.getOwnPropertyDescriptor( - proto, - Symbol.iterator - ); - assert_equals( - iteratorDesc.value, - proto.values, - `@@iterator should equal values` - ); - assert_equals( - iteratorDesc.enumerable, - false, - `@@iterator enumerable` - ); - assert_equals( - iteratorDesc.configurable, - true, - `@@iterator configurable` - ); - assert_equals(iteratorDesc.writable, true, `@@iterator writable`); - - const sizeDesc = Object.getOwnPropertyDescriptor(proto, "size"); - assert_equals( - typeof sizeDesc.get, - "function", - `size getter should be a function` - ); - assert_equals( - sizeDesc.set, - undefined, - `size should not have a setter` - ); - assert_equals(sizeDesc.enumerable, true, `size enumerable`); - assert_equals(sizeDesc.configurable, true, `size configurable`); - assert_equals(sizeDesc.get.length, 0, `size getter length`); - assert_equals(sizeDesc.get.name, "get size", `size getter name`); - }, - `${this.name} interface: setlike<${member.idlType.map((t) => t.idlType).join(", ")}>` - ); - }; - - IdlInterface.prototype.test_member_iterable = function (member) { - subsetTestByKey( - this.name, - test, - () => { - const isPairIterator = member.idlType.length === 2; - const proto = this.get_interface_object().prototype; - - const methods = [ - ["entries", 0], - ["keys", 0], - ["values", 0], - ["forEach", 1], - ]; - - for (const [name, length] of methods) { - const desc = Object.getOwnPropertyDescriptor(proto, name); - assert_equals( - typeof desc.value, - "function", - `${name} should be a function` - ); - assert_equals(desc.enumerable, true, `${name} enumerable`); - assert_equals(desc.configurable, true, `${name} configurable`); - assert_equals(desc.writable, true, `${name} writable`); - assert_equals( - desc.value.length, - length, - `${name} function object length should be ${length}` - ); - assert_equals( - desc.value.name, - name, - `${name} function object should have the right name` - ); - - if (!isPairIterator) { - assert_equals( - desc.value, - Array.prototype[name], - `${name} equality with Array.prototype version` - ); - } - } - - const iteratorDesc = Object.getOwnPropertyDescriptor( - proto, - Symbol.iterator - ); - assert_equals( - iteratorDesc.enumerable, - false, - `@@iterator enumerable` - ); - assert_equals( - iteratorDesc.configurable, - true, - `@@iterator configurable` - ); - assert_equals(iteratorDesc.writable, true, `@@iterator writable`); - - if (isPairIterator) { - assert_equals( - iteratorDesc.value, - proto.entries, - `@@iterator equality with entries` - ); - } else { - assert_equals( - iteratorDesc.value, - Array.prototype[Symbol.iterator], - `@@iterator equality with Array.prototype version` - ); - } - }, - `${this.name} interface: iterable<${member.idlType.map((t) => t.idlType).join(", ")}>` - ); - }; - - IdlInterface.prototype.test_member_async_iterable = function (member) { - subsetTestByKey( - this.name, - test, - () => { - const isPairIterator = member.idlType.length === 2; - const proto = this.get_interface_object().prototype; - - // Note that although the spec allows arguments, which will be passed to the @@asyncIterator - // method (which is either values or entries), those arguments must always be optional. So - // length of 0 is still correct for values and entries. - const methods = [["values", 0]]; - - if (isPairIterator) { - methods.push(["entries", 0], ["keys", 0]); - } - - for (const [name, length] of methods) { - const desc = Object.getOwnPropertyDescriptor(proto, name); - assert_equals( - typeof desc.value, - "function", - `${name} should be a function` - ); - assert_equals(desc.enumerable, true, `${name} enumerable`); - assert_equals(desc.configurable, true, `${name} configurable`); - assert_equals(desc.writable, true, `${name} writable`); - assert_equals( - desc.value.length, - length, - `${name} function object length should be ${length}` - ); - assert_equals( - desc.value.name, - name, - `${name} function object should have the right name` - ); - } - - const iteratorDesc = Object.getOwnPropertyDescriptor( - proto, - Symbol.asyncIterator - ); - assert_equals( - iteratorDesc.enumerable, - false, - `@@iterator enumerable` - ); - assert_equals( - iteratorDesc.configurable, - true, - `@@iterator configurable` - ); - assert_equals(iteratorDesc.writable, true, `@@iterator writable`); - - if (isPairIterator) { - assert_equals( - iteratorDesc.value, - proto.entries, - `@@iterator equality with entries` - ); - } else { - assert_equals( - iteratorDesc.value, - proto.values, - `@@iterator equality with values` - ); - } - }, - `${this.name} interface: async iterable<${member.idlType.map((t) => t.idlType).join(", ")}>` - ); - }; - - IdlInterface.prototype.test_member_stringifier = function (member) { - subsetTestByKey( - this.name, - test, - function () { - if (!this.should_have_interface_object()) { - return; - } - - this.assert_interface_object_exists(); - - if (this.is_callback()) { - assert_false( - "prototype" in this.get_interface_object(), - this.name + ' should not have a "prototype" property' - ); - return; - } - - assert_own_property( - this.get_interface_object(), - "prototype", - 'interface "' + - this.name + - '" does not have own property "prototype"' - ); - - // ". . . the property exists on the interface prototype object." - var interfacePrototypeObject = this.get_interface_object().prototype; - assert_own_property( - interfacePrototypeObject, - "toString", - "interface prototype object missing non-static operation" - ); - - var stringifierUnforgeable = member.isUnforgeable; - var desc = Object.getOwnPropertyDescriptor( - interfacePrototypeObject, - "toString" - ); - // "The property has attributes { [[Writable]]: B, - // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the - // stringifier is unforgeable on the interface, and true otherwise." - assert_false("get" in desc, "property should not have a getter"); - assert_false("set" in desc, "property should not have a setter"); - assert_equals( - desc.writable, - !stringifierUnforgeable, - "property should be writable if and only if not unforgeable" - ); - assert_true(desc.enumerable, "property should be enumerable"); - assert_equals( - desc.configurable, - !stringifierUnforgeable, - "property should be configurable if and only if not unforgeable" - ); - // "The value of the property is a Function object, which behaves as - // follows . . ." - assert_equals( - typeof interfacePrototypeObject.toString, - "function", - "property must be a function" - ); - // "The value of the Function object’s “length” property is the Number - // value 0." - assert_equals( - interfacePrototypeObject.toString.length, - 0, - "property has wrong .length" - ); - - // "Let O be the result of calling ToObject on the this value." - assert_throws_js( - globalOf(interfacePrototypeObject.toString).TypeError, - function () { - interfacePrototypeObject.toString.apply(null, []); - }, - "calling stringifier with this = null didn't throw TypeError" - ); - - // "If O is not an object that implements the interface on which the - // stringifier was declared, then throw a TypeError." - // - // TODO: Test a platform object that implements some other - // interface. (Have to be sure to get inheritance right.) - 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" - ); - }; - - IdlInterface.prototype.test_members = function () { - var unexposed_members = new Set(); - for (var i = 0; i < this.members.length; i++) { - var member = this.members[i]; - if (member.untested) { - continue; - } - - if (!exposed_in(exposure_set(member, this.exposureSet))) { - if (!unexposed_members.has(member.name)) { - unexposed_members.add(member.name); - subsetTestByKey( - this.name, - test, - function () { - // It's not exposed, so we shouldn't find it anywhere. - assert_false( - member.name in this.get_interface_object(), - "The interface object must not have a property " + - format_value(member.name) - ); - assert_false( - member.name in this.get_interface_object().prototype, - "The prototype object must not have a property " + - format_value(member.name) - ); - }.bind(this), - this.name + " interface: member " + member.name - ); - } - continue; - } - - switch (member.type) { - case "const": - this.test_member_const(member); - break; - - case "attribute": - // For unforgeable attributes, we do the checks in - // test_interface_of instead. - if (!member.isUnforgeable) { - this.test_member_attribute(member); - } - if (member.special === "stringifier") { - this.test_member_stringifier(member); - } - break; - - case "operation": - // TODO: Need to correctly handle multiple operations with the same - // identifier. - // For unforgeable operations, we do the checks in - // test_interface_of instead. - if (member.name) { - if (!member.isUnforgeable) { - this.test_member_operation(member); - } - } else if (member.special === "stringifier") { - this.test_member_stringifier(member); - } - break; - - case "iterable": - if (member.async) { - this.test_member_async_iterable(member); - } else { - this.test_member_iterable(member); - } - break; - case "maplike": - this.test_member_maplike(member); - break; - case "setlike": - this.test_member_setlike(member); - break; - default: - // TODO: check more member types. - break; - } - } - }; - - IdlInterface.prototype.test_object = function (desc) { - var obj, - exception = null; - try { - obj = eval(desc); - } catch (e) { - exception = e; - } - - var expected_typeof; - if (this.name == "HTMLAllCollection") { - // Result of [[IsHTMLDDA]] slot - expected_typeof = "undefined"; - } else { - expected_typeof = "object"; - } - - if (this.is_callback()) { - assert_equals( - exception, - null, - "Unexpected exception when evaluating object" - ); - assert_equals(typeof obj, expected_typeof, "wrong typeof object"); - } else { - this.test_primary_interface_of(desc, obj, exception, expected_typeof); - - var current_interface = this; - while (current_interface) { - if (!(current_interface.name in this.array.members)) { - throw new IdlHarnessError( - "Interface " + - current_interface.name + - " not found (inherited by " + - this.name + - ")" - ); - } - if ( - current_interface.prevent_multiple_testing && - current_interface.already_tested - ) { - return; - } - current_interface.test_interface_of( - desc, - obj, - exception, - expected_typeof - ); - current_interface = this.array.members[current_interface.base]; - } - } - }; - - IdlInterface.prototype.test_primary_interface_of = function ( - desc, - obj, - exception, - expected_typeof - ) { - // Only the object itself, not its members, are tested here, so if the - // interface is untested, there is nothing to do. - if (this.untested) { - return; - } - - // "The internal [[SetPrototypeOf]] method of every platform object that - // implements an interface with the [Global] extended - // attribute must execute the same algorithm as is defined for the - // [[SetPrototypeOf]] internal method of an immutable prototype exotic - // object." - // https://webidl.spec.whatwg.org/#platform-object-setprototypeof - if (this.is_global()) { - this.test_immutable_prototype("global platform object", obj); - } - - // We can't easily test that its prototype is correct if there's no - // interface object, or the object is from a different global environment - // (not instanceof Object). TODO: test in this case that its prototype at - // least looks correct, even if we can't test that it's actually correct. - if ( - this.should_have_interface_object() && - (typeof obj != expected_typeof || obj instanceof Object) - ) { - subsetTestByKey( - this.name, - test, - function () { - assert_equals( - exception, - null, - "Unexpected exception when evaluating object" - ); - assert_equals(typeof obj, expected_typeof, "wrong typeof object"); - this.assert_interface_object_exists(); - assert_own_property( - this.get_interface_object(), - "prototype", - 'interface "' + - this.name + - '" does not have own property "prototype"' - ); - - // "The value of the internal [[Prototype]] property of the - // platform object is the interface prototype object of the primary - // interface from the platform object’s associated global - // environment." - assert_equals( - Object.getPrototypeOf(obj), - this.get_interface_object().prototype, - desc + "'s prototype is not " + this.name + ".prototype" - ); - }.bind(this), - this.name + " must be primary interface of " + desc - ); - } - - // "The class string of a platform object that implements one or more - // interfaces must be the qualified name of the primary interface of the - // platform object." - subsetTestByKey( - this.name, - test, - function () { - assert_equals( - exception, - null, - "Unexpected exception when evaluating object" - ); - assert_equals(typeof obj, expected_typeof, "wrong typeof object"); - assert_class_string( - obj, - this.get_qualified_name(), - "class string of " + desc - ); - if (!this.has_stringifier()) { - assert_equals( - String(obj), - "[object " + this.get_qualified_name() + "]", - "String(" + desc + ")" - ); - } - }.bind(this), - "Stringification of " + desc - ); - }; - - IdlInterface.prototype.test_interface_of = function ( - desc, - obj, - exception, - expected_typeof - ) { - // TODO: Indexed and named properties, more checks on interface members - this.already_tested = true; - if (!shouldRunSubTest(this.name)) { - return; - } - - var unexposed_properties = new Set(); - for (var i = 0; i < this.members.length; i++) { - var member = this.members[i]; - if (member.untested) { - continue; - } - if (!exposed_in(exposure_set(member, this.exposureSet))) { - if (!unexposed_properties.has(member.name)) { - unexposed_properties.add(member.name); - subsetTestByKey( - this.name, - test, - function () { - assert_equals( - exception, - null, - "Unexpected exception when evaluating object" - ); - assert_false(member.name in obj); - }.bind(this), - this.name + - " interface: " + - desc + - ' must not have property "' + - member.name + - '"' - ); - } - continue; - } - if (member.type == "attribute" && member.isUnforgeable) { - var a_test = subsetTestByKey( - this.name, - async_test, - this.name + - " interface: " + - desc + - ' must have own property "' + - member.name + - '"' - ); - a_test.step( - function () { - assert_equals( - exception, - null, - "Unexpected exception when evaluating object" - ); - assert_equals(typeof obj, expected_typeof, "wrong typeof object"); - // Call do_interface_attribute_asserts last, since it will call a_test.done() - this.do_interface_attribute_asserts(obj, member, a_test); - }.bind(this) - ); - } else if ( - member.type == "operation" && - member.name && - member.isUnforgeable - ) { - var a_test = subsetTestByKey( - this.name, - async_test, - this.name + - " interface: " + - desc + - ' must have own property "' + - member.name + - '"' - ); - a_test.step( - function () { - assert_equals( - exception, - null, - "Unexpected exception when evaluating object" - ); - assert_equals(typeof obj, expected_typeof, "wrong typeof object"); - assert_own_property( - obj, - member.name, - "Doesn't have the unforgeable operation property" - ); - this.do_member_operation_asserts(obj, member, a_test); - }.bind(this) - ); - } else if ( - (member.type == "const" || - member.type == "attribute" || - member.type == "operation") && - member.name - ) { - 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.special !== "static") { - if (!this.is_global()) { - assert_inherits(obj, member.name); - } else { - assert_own_property(obj, member.name); - } - - if (member.type == "const") { - assert_equals(obj[member.name], constValue(member.value)); - } - if (member.type == "attribute") { - // Attributes are accessor properties, so they might - // legitimately throw an exception rather than returning - // anything. - var property, - thrown = false; - try { - property = obj[member.name]; - } catch (e) { - thrown = true; - } - if (!thrown) { - 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") { - assert_equals(typeof obj[member.name], "function"); - } - } - }.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 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.special !== "static") { - if (!this.is_global() && !member.isUnforgeable) { - assert_inherits(obj, member.name); - } else { - assert_own_property(obj, member.name); - } - fn = obj[member.name]; - } else { - assert_own_property( - obj.constructor, - member.name, - "interface object must have static operation as own property" - ); - fn = obj.constructor[member.name]; - } - - var minLength = minOverloadLength( - this.members.filter(function (m) { - return m.type == "operation" && m.name == member.name; - }) - ); - var args = []; - var cb = awaitNCallbacks(minLength, a_test.done.bind(a_test)); - for (var i = 0; i < minLength; i++) { - throwOrReject( - a_test, - member, - fn, - obj, - args, - "Called with " + i + " arguments", - cb - ); - - args.push(create_suitable_object(member.arguments[i].idlType)); - } - if (minLength === 0) { - cb(); - } - }.bind(this) - ); - } - - if (member.is_to_json_regular_operation()) { - this.test_to_json_operation(desc, obj, member); - } - } - }; - - IdlInterface.prototype.has_stringifier = function () { - if (this.name === "DOMException") { - // toString is inherited from Error, so don't assume we have the - // default stringifer - return true; - } - if ( - this.members.some(function (member) { - return member.special === "stringifier"; - }) - ) { - return true; - } - if (this.base && this.array.members[this.base].has_stringifier()) { - return true; - } - return false; - }; - - IdlInterface.prototype.do_interface_attribute_asserts = function ( - obj, - member, - a_test - ) { - // This function tests WebIDL as of 2015-01-27. - // TODO: Consider [Exposed]. - - // This is called by test_member_attribute() with the prototype as obj if - // it is not a global, and the global otherwise, and by test_interface_of() - // with the object as obj. - - var pendingPromises = []; - - // "The name of the property is the identifier of the attribute." - assert_own_property(obj, member.name); - - // "The property has attributes { [[Get]]: G, [[Set]]: S, [[Enumerable]]: - // true, [[Configurable]]: configurable }, where: - // "configurable is false if the attribute was declared with the - // [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); - assert_false( - "value" in desc, - 'property descriptor should not have a "value" field' - ); - assert_false( - "writable" in desc, - 'property descriptor should not have a "writable" field' - ); - assert_true(desc.enumerable, "property should be enumerable"); - if (member.isUnforgeable) { - assert_false( - desc.configurable, - "[LegacyUnforgeable] property must not be configurable" - ); - } else { - assert_true(desc.configurable, "property must be configurable"); - } - - // "The attribute getter is a Function object whose behavior when invoked - // is as follows:" - assert_equals(typeof desc.get, "function", "getter must be Function"); - - // "If the attribute is a regular attribute, then:" - if (member.special !== "static") { - // "If O is not a platform object that implements I, then: - // "If the attribute was specified with the [LegacyLenientThis] extended - // attribute, then return undefined. - // "Otherwise, throw a TypeError." - if (!member.has_extended_attribute("LegacyLenientThis")) { - if (member.idlType.generic !== "Promise") { - 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_js( - a_test, - TypeError, - desc.get.call({}), - "calling getter on wrong object type must reject the return promise with TypeError" - ) - ); - } - } else { - assert_equals( - desc.get.call({}), - undefined, - "calling getter on wrong object type must return undefined" - ); - } - } - - // "The value of the Function object’s “length” property is the Number - // value 0." - assert_equals(desc.get.length, 0, "getter length must be 0"); - - // "Let name be the string "get " prepended to attribute’s identifier." - // "Perform ! SetFunctionName(F, name)." - assert_equals( - desc.get.name, - "get " + member.name, - "getter must have the name 'get " + member.name + "'" - ); - - // TODO: Test calling setter on the interface prototype (should throw - // TypeError in most cases). - if ( - member.readonly && - !member.has_extended_attribute("LegacyLenientSetter") && - !member.has_extended_attribute("PutForwards") && - !member.has_extended_attribute("Replaceable") - ) { - // "The attribute setter is undefined if the attribute is declared - // readonly and has neither a [PutForwards] nor a [Replaceable] - // extended attribute declared on it." - assert_equals( - desc.set, - undefined, - "setter must be undefined for readonly attributes" - ); - } else { - // "Otherwise, it is a Function object whose behavior when - // invoked is as follows:" - 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.special !== "static") { - // "If /validThis/ is false and the attribute was not specified - // 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("LegacyLenientThis")) { - assert_throws_js( - globalOf(desc.set).TypeError, - function () { - desc.set.call({}); - }.bind(this), - "calling setter on wrong object type must throw TypeError" - ); - } else { - assert_equals( - desc.set.call({}), - undefined, - "calling setter on wrong object type must return undefined" - ); - } - } - - // "The value of the Function object’s “length” property is the Number - // value 1." - assert_equals(desc.set.length, 1, "setter length must be 1"); - - // "Let name be the string "set " prepended to id." - // "Perform ! SetFunctionName(F, name)." - assert_equals( - desc.set.name, - "set " + member.name, - "The attribute setter must have the name 'set " + member.name + "'" - ); - } - - Promise.all(pendingPromises).then(a_test.done.bind(a_test)); - }; - - /// IdlInterfaceMember /// - function IdlInterfaceMember(obj) { - /** - * obj is an object produced by the WebIDLParser.js "ifMember" production. - * We just forward all properties to this object without modification, - * except for special extAttrs handling. - */ - for (var k in obj.toJSON()) { - this[k] = obj[k]; - } - if (!("extAttrs" in this)) { - this.extAttrs = []; - } - - this.isUnforgeable = 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.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) { - /** - * type is an object produced by the WebIDLParser.js "type" production. We - * return a JavaScript value that matches the type, if we can figure out - * how. - */ - if (type.nullable) { - return null; - } - switch (type.idlType) { - case "any": - case "boolean": - return true; - - case "byte": - case "octet": - case "short": - case "unsigned short": - case "long": - case "unsigned long": - case "long long": - case "unsigned long long": - case "float": - case "double": - case "unrestricted float": - case "unrestricted double": - return 7; - - case "DOMString": - case "ByteString": - case "USVString": - return "foo"; - - case "object": - return { a: "b" }; - - case "Node": - return document.createTextNode("abc"); - } - return null; - } - - /// IdlEnum /// - // Used for IdlArray.prototype.assert_type_is - function IdlEnum(obj) { - /** - * obj is an object produced by the WebIDLParser.js "dictionary" - * production. - */ - - /** Self-explanatory. */ - this.name = obj.name; - - /** An array of values produced by the "enum" production. */ - this.values = obj.values; - } - - 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) { - /** - * obj is an object produced by the WebIDLParser.js "typedef" - * production. - */ - - /** Self-explanatory. */ - this.name = obj.name; - - /** The idlType that we are supposed to be typedeffing to. */ - this.idlType = obj.idlType; - } - - IdlTypedef.prototype = Object.create(IdlObject.prototype); - - /// IdlNamespace /// - function IdlNamespace(obj) { - this.name = obj.name; - this.extAttrs = obj.extAttrs; - this.untested = obj.untested; - /** A back-reference to our IdlArray. */ - this.array = obj.array; - - /** An array of IdlInterfaceMembers. */ - this.members = obj.members.map((m) => new IdlInterfaceMember(m)); - } - - IdlNamespace.prototype = Object.create(IdlObject.prototype); - - IdlNamespace.prototype.do_member_operation_asserts = function ( - memberHolderObject, - member, - a_test - ) { - var desc = Object.getOwnPropertyDescriptor( - memberHolderObject, - member.name - ); - - assert_false("get" in desc, "property should not have a getter"); - assert_false("set" in desc, "property should not have a setter"); - assert_equals( - desc.writable, - !member.isUnforgeable, - "property should be writable if and only if not unforgeable" - ); - assert_true(desc.enumerable, "property should be enumerable"); - assert_equals( - desc.configurable, - !member.isUnforgeable, - "property should be configurable if and only if not unforgeable" - ); - - assert_equals( - typeof memberHolderObject[member.name], - "function", - "property must be a function" - ); - - assert_equals( - memberHolderObject[member.name].length, - minOverloadLength( - this.members.filter(function (m) { - return m.type == "operation" && m.name == member.name; - }) - ), - "operation has wrong .length" - ); - a_test.done(); - }; - - IdlNamespace.prototype.test_member_operation = function (member) { - if (!shouldRunSubTest(this.name)) { - return; - } - var a_test = subsetTestByKey( - this.name, - async_test, - this.name + " namespace: operation " + member - ); - a_test.step( - function () { - assert_own_property( - self[this.name], - member.name, - "namespace object missing operation " + format_value(member.name) - ); - - this.do_member_operation_asserts(self[this.name], member, a_test); - }.bind(this) - ); - }; - - IdlNamespace.prototype.test_member_attribute = function (member) { - if (!shouldRunSubTest(this.name)) { - return; - } - var a_test = subsetTestByKey( - this.name, - async_test, - this.name + " namespace: attribute " + member.name - ); - a_test.step( - function () { - assert_own_property( - self[this.name], - member.name, - this.name + " does not have property " + format_value(member.name) - ); - - var desc = Object.getOwnPropertyDescriptor( - self[this.name], - member.name - ); - assert_equals( - desc.set, - undefined, - "setter must be undefined for namespace members" - ); - a_test.done(); - }.bind(this) - ); - }; - - IdlNamespace.prototype.test_self = function () { - /** - * TODO(lukebjerring): Assert: - * - "Note that unlike interfaces or dictionaries, namespaces do not create types." - */ - - subsetTestByKey( - this.name, - test, - () => { - assert_true( - this.extAttrs.every( - (o) => o.name === "Exposed" || o.name === "SecureContext" - ), - "Only the [Exposed] and [SecureContext] extended attributes are applicable to namespaces" - ); - assert_true( - this.has_extended_attribute("Exposed"), - "Namespaces must be annotated with the [Exposed] extended attribute" - ); - }, - `${this.name} namespace: extended attributes` - ); - - const namespaceObject = self[this.name]; - - subsetTestByKey( - this.name, - test, - () => { - const desc = Object.getOwnPropertyDescriptor(self, this.name); - assert_equals( - desc.value, - namespaceObject, - `wrong value for ${this.name} namespace object` - ); - assert_true(desc.writable, "namespace object should be writable"); - assert_false( - desc.enumerable, - "namespace object should not be enumerable" - ); - assert_true( - desc.configurable, - "namespace object should be configurable" - ); - assert_false( - "get" in desc, - "namespace object should not have a getter" - ); - assert_false( - "set" in desc, - "namespace object should not have a setter" - ); - }, - `${this.name} namespace: property descriptor` - ); - - subsetTestByKey( - this.name, - test, - () => { - assert_true(Object.isExtensible(namespaceObject)); - }, - `${this.name} namespace: [[Extensible]] is true` - ); - - subsetTestByKey( - this.name, - test, - () => { - assert_true(namespaceObject instanceof Object); - - if (this.name === "console") { - // https://console.spec.whatwg.org/#console-namespace - const namespacePrototype = Object.getPrototypeOf(namespaceObject); - assert_equals(Reflect.ownKeys(namespacePrototype).length, 0); - assert_equals( - Object.getPrototypeOf(namespacePrototype), - Object.prototype - ); - } else { - assert_equals( - Object.getPrototypeOf(namespaceObject), - Object.prototype - ); - } - }, - `${this.name} namespace: [[Prototype]] is Object.prototype` - ); - - subsetTestByKey( - this.name, - test, - () => { - assert_equals(typeof namespaceObject, "object"); - }, - `${this.name} namespace: typeof is "object"` - ); - - subsetTestByKey( - this.name, - test, - () => { - assert_equals( - Object.getOwnPropertyDescriptor(namespaceObject, "length"), - undefined, - "length property must be undefined" - ); - }, - `${this.name} namespace: has no length property` - ); - - subsetTestByKey( - this.name, - test, - () => { - assert_equals( - Object.getOwnPropertyDescriptor(namespaceObject, "name"), - undefined, - "name property must be undefined" - ); - }, - `${this.name} namespace: has no name property` - ); - }; - - IdlNamespace.prototype.test = function () { - if (!this.untested) { - this.test_self(); - } - - for (const v of Object.values(this.members)) { - switch (v.type) { - case "operation": - this.test_member_operation(v); - break; - - case "attribute": - this.test_member_attribute(v); - break; - - default: - throw ( - "Invalid namespace member " + - v.name + - ": " + - v.type + - " not supported" - ); - } - } - }; - })(); -} - -/** - * idl_test is a promise_test wrapper that handles the fetching of the IDL, - * avoiding repetitive boilerplate. - * - * @param {String[]} srcs Spec name(s) for source idl files (fetched from - * /interfaces/{name}.idl). - * @param {String[]} deps Spec name(s) for dependency idl files (fetched - * from /interfaces/{name}.idl). Order is important - dependencies from - * each source will only be included if they're already know to be a - * dependency (i.e. have already been seen). - * @param {Function} setup_func Function for extra setup of the idl_array, such - * as adding objects. Do not call idl_array.test() in the setup; it is - * called by this function (idl_test). - */ -function idl_test(srcs, deps, idl_setup_func) { - return promise_test(function (t) { - var idl_array = new IdlArray(); - var setup_error = null; - const validationIgnored = [ - "constructor-member", - "dict-arg-default", - "require-exposed", - ]; - return Promise.all(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.internal_add_idls(astArray[i]); - } - for (var i = srcs.length; i < srcs.length + deps.length; i++) { - idl_array.internal_add_dependency_idls(astArray[i]); - } - }) - .then(function () { - if (idl_setup_func) { - return idl_setup_func(idl_array, t); - } - }) - .catch(function (e) { - setup_error = e || "IDL setup failed."; - }) - .then(function () { - var error = setup_error; - try { - idl_array.test(); // Test what we can. - } catch (e) { - // If testing fails hard here, the original setup error - // is more likely to be the real cause. - error = error || e; - } - if (error) { - throw error; - } - }); - }, "idl_test setup"); -} - -/** - * fetch_spec is a shorthand for a Promise that fetches the spec's content. - */ -function fetch_spec(spec) { - var url = "/interfaces/" + spec + ".idl"; - return fetch(url) - .then(function (r) { - if (!r.ok) { - 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/tests/wpt/resources/testharness.js b/tests/wpt/resources/testharness.js deleted file mode 100644 index 09a9e91be2..0000000000 --- a/tests/wpt/resources/testharness.js +++ /dev/null @@ -1,5472 +0,0 @@ -/*global self*/ -/*jshint latedef: nofunc*/ - -/* Documentation: https://web-platform-tests.org/writing-tests/testharness-api.html - * (../docs/_writing-tests/testharness-api.md) */ - -export default function (self) { - const location = self.location; - - (function (global_scope) { - // default timeout is 10 seconds, test can override if needed - var settings = { - output: true, - harness_timeout: { - normal: 10000, - long: 60000, - }, - test_timeout: null, - message_events: ["start", "test_state", "result", "completion"], - debug: false, - }; - - var xhtml_ns = "http://www.w3.org/1999/xhtml"; - - /* - * TestEnvironment is an abstraction for the environment in which the test - * harness is used. Each implementation of a test environment has to provide - * the following interface: - * - * interface TestEnvironment { - * // Invoked after the global 'tests' object has been created and it's - * // safe to call add_*_callback() to register event handlers. - * void on_tests_ready(); - * - * // Invoked after setup() has been called to notify the test environment - * // of changes to the test harness properties. - * void on_new_harness_properties(object properties); - * - * // Should return a new unique default test name. - * DOMString next_default_test_name(); - * - * // Should return the test harness timeout duration in milliseconds. - * float test_timeout(); - * }; - */ - - /* - * A test environment with a DOM. The global object is 'window'. By default - * test results are displayed in a table. Any parent windows receive - * callbacks or messages via postMessage() when test events occur. See - * apisample11.html and apisample12.html. - */ - function WindowTestEnvironment() { - this.name_counter = 0; - this.window_cache = null; - this.output_handler = null; - this.all_loaded = false; - var this_obj = this; - this.message_events = []; - this.dispatched_messages = []; - - this.message_functions = { - start: [ - add_start_callback, - remove_start_callback, - function (properties) { - this_obj._dispatch("start_callback", [properties], { - type: "start", - properties: properties, - }); - }, - ], - - test_state: [ - add_test_state_callback, - remove_test_state_callback, - function (test) { - this_obj._dispatch("test_state_callback", [test], { - type: "test_state", - test: test.structured_clone(), - }); - }, - ], - result: [ - add_result_callback, - remove_result_callback, - function (test) { - this_obj.output_handler.show_status(); - this_obj._dispatch("result_callback", [test], { - type: "result", - test: test.structured_clone(), - }); - }, - ], - completion: [ - add_completion_callback, - remove_completion_callback, - function (tests, harness_status, asserts) { - var cloned_tests = map(tests, function (test) { - return test.structured_clone(); - }); - this_obj._dispatch("completion_callback", [tests, harness_status], { - type: "complete", - tests: cloned_tests, - status: harness_status.structured_clone(), - asserts: asserts.map((assert) => assert.structured_clone()), - }); - }, - ], - }; - - on_event(window, "load", function () { - setTimeout(() => { - this_obj.all_loaded = true; - if (tests.all_done()) { - tests.complete(); - } - }, 0); - }); - - on_event(window, "message", function (event) { - if (event.data && event.data.type === "getmessages" && event.source) { - // A window can post "getmessages" to receive a duplicate of every - // message posted by this environment so far. This allows subscribers - // from fetch_tests_from_window to 'catch up' to the current state of - // this environment. - for (var i = 0; i < this_obj.dispatched_messages.length; ++i) { - event.source.postMessage(this_obj.dispatched_messages[i], "*"); - } - } - }); - } - - WindowTestEnvironment.prototype._dispatch = function ( - selector, - callback_args, - message_arg - ) { - this.dispatched_messages.push(message_arg); - this._forEach_windows(function (w, same_origin) { - if (same_origin) { - try { - var has_selector = selector in w; - } catch (e) { - // If document.domain was set at some point same_origin can be - // wrong and the above will fail. - has_selector = false; - } - if (has_selector) { - try { - w[selector].apply(undefined, callback_args); - } catch (e) {} - } - } - if (w !== self) { - w.postMessage(message_arg, "*"); - } - }); - }; - - WindowTestEnvironment.prototype._forEach_windows = function (callback) { - // Iterate over the windows [self ... top, opener]. The callback is passed - // two objects, the first one is the window object itself, the second one - // is a boolean indicating whether or not it's on the same origin as the - // current window. - var cache = this.window_cache; - if (!cache) { - cache = [[self, true]]; - var w = self; - var i = 0; - var so; - while (w != w.parent) { - w = w.parent; - so = is_same_origin(w); - cache.push([w, so]); - i++; - } - w = window.opener; - if (w) { - cache.push([w, is_same_origin(w)]); - } - this.window_cache = cache; - } - - forEach(cache, function (a) { - callback.apply(null, a); - }); - }; - - WindowTestEnvironment.prototype.on_tests_ready = function () { - var output = new Output(); - this.output_handler = output; - - var this_obj = this; - - add_start_callback(function (properties) { - this_obj.output_handler.init(properties); - }); - - add_test_state_callback(function (test) { - this_obj.output_handler.show_status(); - }); - - add_result_callback(function (test) { - this_obj.output_handler.show_status(); - }); - - add_completion_callback(function (tests, harness_status, asserts_run) { - this_obj.output_handler.show_results( - tests, - harness_status, - asserts_run - ); - }); - this.setup_messages(settings.message_events); - }; - - WindowTestEnvironment.prototype.setup_messages = function (new_events) { - var this_obj = this; - forEach(settings.message_events, function (x) { - var current_dispatch = this_obj.message_events.indexOf(x) !== -1; - var new_dispatch = new_events.indexOf(x) !== -1; - if (!current_dispatch && new_dispatch) { - this_obj.message_functions[x][0](this_obj.message_functions[x][2]); - } else if (current_dispatch && !new_dispatch) { - this_obj.message_functions[x][1](this_obj.message_functions[x][2]); - } - }); - this.message_events = new_events; - }; - - WindowTestEnvironment.prototype.next_default_test_name = function () { - var suffix = this.name_counter > 0 ? " " + this.name_counter : ""; - this.name_counter++; - return get_title() + suffix; - }; - - WindowTestEnvironment.prototype.on_new_harness_properties = function ( - properties - ) { - this.output_handler.setup(properties); - if (properties.hasOwnProperty("message_events")) { - this.setup_messages(properties.message_events); - } - }; - - WindowTestEnvironment.prototype.add_on_loaded_callback = function ( - callback - ) { - on_event(window, "load", callback); - }; - - WindowTestEnvironment.prototype.test_timeout = function () { - var metas = document.getElementsByTagName("meta"); - for (var i = 0; i < metas.length; i++) { - if (metas[i].name == "timeout") { - if (metas[i].content == "long") { - return settings.harness_timeout.long; - } - break; - } - } - return settings.harness_timeout.normal; - }; - - /* - * Base TestEnvironment implementation for a generic web worker. - * - * Workers accumulate test results. One or more clients can connect and - * retrieve results from a worker at any time. - * - * WorkerTestEnvironment supports communicating with a client via a - * MessagePort. The mechanism for determining the appropriate MessagePort - * for communicating with a client depends on the type of worker and is - * implemented by the various specializations of WorkerTestEnvironment - * below. - * - * A client document using testharness can use fetch_tests_from_worker() to - * retrieve results from a worker. See apisample16.html. - */ - function WorkerTestEnvironment() { - this.name_counter = 0; - this.all_loaded = true; - this.message_list = []; - this.message_ports = []; - } - - WorkerTestEnvironment.prototype._dispatch = function (message) { - this.message_list.push(message); - for (var i = 0; i < this.message_ports.length; ++i) { - this.message_ports[i].postMessage(message); - } - }; - - // The only requirement is that port has a postMessage() method. It doesn't - // have to be an instance of a MessagePort, and often isn't. - WorkerTestEnvironment.prototype._add_message_port = function (port) { - this.message_ports.push(port); - for (var i = 0; i < this.message_list.length; ++i) { - port.postMessage(this.message_list[i]); - } - }; - - WorkerTestEnvironment.prototype.next_default_test_name = function () { - var suffix = this.name_counter > 0 ? " " + this.name_counter : ""; - this.name_counter++; - return get_title() + suffix; - }; - - WorkerTestEnvironment.prototype.on_new_harness_properties = function () {}; - - WorkerTestEnvironment.prototype.on_tests_ready = function () { - var this_obj = this; - add_start_callback(function (properties) { - this_obj._dispatch({ - type: "start", - properties: properties, - }); - }); - add_test_state_callback(function (test) { - this_obj._dispatch({ - type: "test_state", - test: test.structured_clone(), - }); - }); - add_result_callback(function (test) { - this_obj._dispatch({ - type: "result", - test: test.structured_clone(), - }); - }); - add_completion_callback(function (tests, harness_status, asserts) { - this_obj._dispatch({ - type: "complete", - tests: map(tests, function (test) { - return test.structured_clone(); - }), - status: harness_status.structured_clone(), - asserts: asserts.map((assert) => assert.structured_clone()), - }); - }); - }; - - WorkerTestEnvironment.prototype.add_on_loaded_callback = function () {}; - - WorkerTestEnvironment.prototype.test_timeout = function () { - // Tests running in a worker don't have a default timeout. I.e. all - // worker tests behave as if settings.explicit_timeout is true. - return null; - }; - - /* - * Dedicated web workers. - * https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope - * - * This class is used as the test_environment when testharness is running - * inside a dedicated worker. - */ - function DedicatedWorkerTestEnvironment() { - WorkerTestEnvironment.call(this); - // self is an instance of DedicatedWorkerGlobalScope which exposes - // a postMessage() method for communicating via the message channel - // established when the worker is created. - this._add_message_port(self); - } - DedicatedWorkerTestEnvironment.prototype = Object.create( - WorkerTestEnvironment.prototype - ); - - DedicatedWorkerTestEnvironment.prototype.on_tests_ready = function () { - WorkerTestEnvironment.prototype.on_tests_ready.call(this); - // In the absence of an onload notification, we a require dedicated - // workers to explicitly signal when the tests are done. - tests.wait_for_finish = true; - }; - - /* - * Shared web workers. - * https://html.spec.whatwg.org/multipage/workers.html#sharedworkerglobalscope - * - * This class is used as the test_environment when testharness is running - * inside a shared web worker. - */ - function SharedWorkerTestEnvironment() { - WorkerTestEnvironment.call(this); - var this_obj = this; - // Shared workers receive message ports via the 'onconnect' event for - // each connection. - self.addEventListener( - "connect", - function (message_event) { - this_obj._add_message_port(message_event.source); - }, - false - ); - } - SharedWorkerTestEnvironment.prototype = Object.create( - WorkerTestEnvironment.prototype - ); - - SharedWorkerTestEnvironment.prototype.on_tests_ready = function () { - WorkerTestEnvironment.prototype.on_tests_ready.call(this); - // In the absence of an onload notification, we a require shared - // workers to explicitly signal when the tests are done. - tests.wait_for_finish = true; - }; - - /* - * Service workers. - * http://www.w3.org/TR/service-workers/ - * - * This class is used as the test_environment when testharness is running - * inside a service worker. - */ - function ServiceWorkerTestEnvironment() { - WorkerTestEnvironment.call(this); - this.all_loaded = false; - this.on_loaded_callback = null; - var this_obj = this; - self.addEventListener( - "message", - function (event) { - if (event.data && event.data.type && event.data.type === "connect") { - this_obj._add_message_port(event.source); - } - }, - false - ); - - // The oninstall event is received after the service worker script and - // all imported scripts have been fetched and executed. It's the - // equivalent of an onload event for a document. All tests should have - // been added by the time this event is received, thus it's not - // necessary to wait until the onactivate event. However, tests for - // installed service workers need another event which is equivalent to - // the onload event because oninstall is fired only on installation. The - // onmessage event is used for that purpose since tests using - // testharness.js should ask the result to its service worker by - // PostMessage. If the onmessage event is triggered on the service - // worker's context, that means the worker's script has been evaluated. - on_event(self, "install", on_all_loaded); - on_event(self, "message", on_all_loaded); - function on_all_loaded() { - if (this_obj.all_loaded) return; - this_obj.all_loaded = true; - if (this_obj.on_loaded_callback) { - this_obj.on_loaded_callback(); - } - } - } - - ServiceWorkerTestEnvironment.prototype = Object.create( - WorkerTestEnvironment.prototype - ); - - ServiceWorkerTestEnvironment.prototype.add_on_loaded_callback = function ( - callback - ) { - if (this.all_loaded) { - callback(); - } else { - this.on_loaded_callback = callback; - } - }; - - /* - * Shadow realms. - * https://github.com/tc39/proposal-shadowrealm - * - * This class is used as the test_environment when testharness is running - * inside a shadow realm. - */ - function ShadowRealmTestEnvironment() { - WorkerTestEnvironment.call(this); - this.all_loaded = false; - this.on_loaded_callback = null; - } - - ShadowRealmTestEnvironment.prototype = Object.create( - WorkerTestEnvironment.prototype - ); - - /** - * Signal to the test environment that the tests are ready and the on-loaded - * callback should be run. - * - * Shadow realms are not *really* a DOM context: they have no `onload` or similar - * event for us to use to set up the test environment; so, instead, this method - * is manually triggered from the incubating realm - * - * @param {Function} message_destination - a function that receives JSON-serializable - * data to send to the incubating realm, in the same format as used by RemoteContext - */ - ShadowRealmTestEnvironment.prototype.begin = function ( - message_destination - ) { - if (this.all_loaded) { - throw new Error( - "Tried to start a shadow realm test environment after it has already started" - ); - } - var fakeMessagePort = {}; - fakeMessagePort.postMessage = message_destination; - this._add_message_port(fakeMessagePort); - this.all_loaded = true; - if (this.on_loaded_callback) { - this.on_loaded_callback(); - } - }; - - ShadowRealmTestEnvironment.prototype.add_on_loaded_callback = function ( - callback - ) { - if (this.all_loaded) { - callback(); - } else { - this.on_loaded_callback = callback; - } - }; - - /* - * JavaScript shells. - * - * This class is used as the test_environment when testharness is running - * inside a JavaScript shell. - */ - function ShellTestEnvironment() { - this.name_counter = 0; - this.all_loaded = false; - this.on_loaded_callback = null; - Promise.resolve().then( - function () { - this.all_loaded = true; - if (this.on_loaded_callback) { - this.on_loaded_callback(); - } - }.bind(this) - ); - this.message_list = []; - this.message_ports = []; - } - - ShellTestEnvironment.prototype.next_default_test_name = function () { - var suffix = this.name_counter > 0 ? " " + this.name_counter : ""; - this.name_counter++; - return get_title() + suffix; - }; - - ShellTestEnvironment.prototype.on_new_harness_properties = function () {}; - - ShellTestEnvironment.prototype.on_tests_ready = function () {}; - - ShellTestEnvironment.prototype.add_on_loaded_callback = function ( - callback - ) { - if (this.all_loaded) { - callback(); - } else { - this.on_loaded_callback = callback; - } - }; - - ShellTestEnvironment.prototype.test_timeout = function () { - // Tests running in a shell don't have a default timeout, so behave as - // if settings.explicit_timeout is true. - return null; - }; - - function create_test_environment() { - if ("document" in global_scope) { - return new WindowTestEnvironment(); - } - if ( - "DedicatedWorkerGlobalScope" in global_scope && - global_scope instanceof DedicatedWorkerGlobalScope - ) { - return new DedicatedWorkerTestEnvironment(); - } - if ( - "SharedWorkerGlobalScope" in global_scope && - global_scope instanceof SharedWorkerGlobalScope - ) { - return new SharedWorkerTestEnvironment(); - } - if ( - "ServiceWorkerGlobalScope" in global_scope && - global_scope instanceof ServiceWorkerGlobalScope - ) { - return new ServiceWorkerTestEnvironment(); - } - if ( - "WorkerGlobalScope" in global_scope && - global_scope instanceof WorkerGlobalScope - ) { - return new DedicatedWorkerTestEnvironment(); - } - /* Shadow realm global objects are _ordinary_ objects (i.e. their prototype is - * Object) so we don't have a nice `instanceof` test to use; instead, we - * check if the there is a GLOBAL.isShadowRealm() property - * on the global object. that was set by the test harness when it - * created the ShadowRealm. - */ - if (global_scope.GLOBAL && global_scope.GLOBAL.isShadowRealm()) { - return new ShadowRealmTestEnvironment(); - } - - return new ShellTestEnvironment(); - } - - var test_environment = create_test_environment(); - - function is_shared_worker(worker) { - return "SharedWorker" in global_scope && worker instanceof SharedWorker; - } - - function is_service_worker(worker) { - // The worker object may be from another execution context, - // so do not use instanceof here. - return ( - "ServiceWorker" in global_scope && - Object.prototype.toString.call(worker) == "[object ServiceWorker]" - ); - } - - var seen_func_name = Object.create(null); - - function get_test_name(func, name) { - if (name) { - return name; - } - - if (func) { - var func_code = func.toString(); - - // Try and match with brackets, but fallback to matching without - var arrow = func_code.match(/^\(\)\s*=>\s*(?:{(.*)}\s*|(.*))$/); - - // Check for JS line separators - if (arrow !== null && !/[\u000A\u000D\u2028\u2029]/.test(func_code)) { - var trimmed = (arrow[1] !== undefined ? arrow[1] : arrow[2]).trim(); - // drop trailing ; if there's no earlier ones - trimmed = trimmed.replace(/^([^;]*)(;\s*)+$/, "$1"); - - if (trimmed) { - let name = trimmed; - if (seen_func_name[trimmed]) { - // This subtest name already exists, so add a suffix. - name += " " + seen_func_name[trimmed]; - } else { - seen_func_name[trimmed] = 0; - } - seen_func_name[trimmed] += 1; - return name; - } - } - } - - return test_environment.next_default_test_name(); - } - - /** - * @callback TestFunction - * @param {Test} test - The test currnetly being run. - * @param {Any[]} args - Additional args to pass to function. - * - */ - - /** - * Create a synchronous test - * - * @param {TestFunction} func - Test function. This is executed - * immediately. If it returns without error, the test status is - * set to ``PASS``. If it throws an :js:class:`AssertionError`, or - * any other exception, the test status is set to ``FAIL`` - * (typically from an `assert` function). - * @param {String} name - Test name. This must be unique in a - * given file and must be invariant between runs. - */ - function test(func, name, properties) { - if (tests.promise_setup_called) { - tests.status.status = tests.status.ERROR; - tests.status.message = "`test` invoked after `promise_setup`"; - tests.complete(); - } - var test_name = get_test_name(func, name); - var test_obj = new Test(test_name, properties); - var value = test_obj.step(func, test_obj, test_obj); - - if (value !== undefined) { - var msg = - 'Test named "' + - test_name + - '" passed a function to `test` that returned a value.'; - - try { - if (value && typeof value.then === "function") { - msg += - " Consider using `promise_test` instead when " + - "using Promises or async/await."; - } - } catch (err) {} - - tests.status.status = tests.status.ERROR; - tests.status.message = msg; - } - - if (test_obj.phase === test_obj.phases.STARTED) { - test_obj.done(); - } - } - - /** - * Create an asynchronous test - * - * @param {TestFunction|string} funcOrName - Initial step function - * to call immediately with the test name as an argument (if any), - * or name of the test. - * @param {String} name - Test name (if a test function was - * provided). This must be unique in a given file and must be - * invariant between runs. - * @returns {Test} An object representing the ongoing test. - */ - function async_test(func, name, properties) { - if (tests.promise_setup_called) { - tests.status.status = tests.status.ERROR; - tests.status.message = "`async_test` invoked after `promise_setup`"; - tests.complete(); - } - if (typeof func !== "function") { - properties = name; - name = func; - func = null; - } - var test_name = get_test_name(func, name); - var test_obj = new Test(test_name, properties); - if (func) { - var value = test_obj.step(func, test_obj, test_obj); - - // Test authors sometimes return values to async_test, expecting us - // to handle the value somehow. Make doing so a harness error to be - // clear this is invalid, and point authors to promise_test if it - // may be appropriate. - // - // Note that we only perform this check on the initial function - // passed to async_test, not on any later steps - we haven't seen a - // consistent problem with those (and it's harder to check). - if (value !== undefined) { - var msg = - 'Test named "' + - test_name + - '" passed a function to `async_test` that returned a value.'; - - try { - if (value && typeof value.then === "function") { - msg += - " Consider using `promise_test` instead when " + - "using Promises or async/await."; - } - } catch (err) {} - - tests.set_status(tests.status.ERROR, msg); - tests.complete(); - } - } - return test_obj; - } - - /** - * Create a promise test. - * - * Promise tests are tests which are represented by a promise - * object. If the promise is fulfilled the test passes, if it's - * rejected the test fails, otherwise the test passes. - * - * @param {TestFunction} func - Test function. This must return a - * promise. The test is automatically marked as complete once the - * promise settles. - * @param {String} name - Test name. This must be unique in a - * given file and must be invariant between runs. - */ - function promise_test(func, name, properties) { - if (typeof func !== "function") { - properties = name; - name = func; - func = null; - } - var test_name = get_test_name(func, name); - var test = new Test(test_name, properties); - test._is_promise_test = true; - - // If there is no promise tests queue make one. - if (!tests.promise_tests) { - tests.promise_tests = Promise.resolve(); - } - tests.promise_tests = tests.promise_tests.then(function () { - return new Promise(function (resolve) { - var promise = test.step(func, test, test); - - test.step(function () { - assert( - !!promise, - "promise_test", - null, - "test body must return a 'thenable' object (received ${value})", - { value: promise } - ); - assert( - typeof promise.then === "function", - "promise_test", - null, - "test body must return a 'thenable' object (received an object with no `then` method)", - null - ); - }); - - // Test authors may use the `step` method within a - // `promise_test` even though this reflects a mixture of - // asynchronous control flow paradigms. The "done" callback - // should be registered prior to the resolution of the - // user-provided Promise to avoid timeouts in cases where the - // Promise does not settle but a `step` function has thrown an - // error. - add_test_done_callback(test, resolve); - - Promise.resolve(promise) - .catch( - test.step_func(function (value) { - if (value instanceof AssertionError) { - throw value; - } - assert( - false, - "promise_test", - null, - "Unhandled rejection with value: ${value}", - { value: value } - ); - }) - ) - .then(function () { - test.done(); - }); - }); - }); - } - - /** - * Make a copy of a Promise in the current realm. - * - * @param {Promise} promise the given promise that may be from a different - * realm - * @returns {Promise} - * - * An arbitrary promise provided by the caller may have originated - * in another frame that have since navigated away, rendering the - * frame's document inactive. Such a promise cannot be used with - * `await` or Promise.resolve(), as microtasks associated with it - * may be prevented from being run. See `issue - * 5319`_ for a - * particular case. - * - * In functions we define here, there is an expectation from the caller - * that the promise is from the current realm, that can always be used with - * `await`, etc. We therefore create a new promise in this realm that - * inherit the value and status from the given promise. - */ - - function bring_promise_to_current_realm(promise) { - return new Promise(promise.then.bind(promise)); - } - - /** - * Assert that a Promise is rejected with the right ECMAScript exception. - * - * @param {Test} test - the `Test` to use for the assertion. - * @param {Function} constructor - The expected exception constructor. - * @param {Promise} promise - The promise that's expected to - * reject with the given exception. - * @param {string} [description] Error message to add to assert in case of - * failure. - */ - function promise_rejects_js(test, constructor, promise, description) { - return bring_promise_to_current_realm(promise) - .then(test.unreached_func("Should have rejected: " + description)) - .catch(function (e) { - assert_throws_js_impl( - constructor, - function () { - throw e; - }, - description, - "promise_rejects_js" - ); - }); - } - - /** - * Assert that a Promise is rejected with the right DOMException. - * - * 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 - * third argument should be the promise expected to reject, and a fourth, - * optional, argument is the assertion description. - * - * 2) If the DOMException is expected to come from some other global, the - * third argument should be the DOMException constructor from that global, - * the fourth argument the promise expected to reject, and the fifth, - * optional, argument the assertion description. - * - * @param {Test} test - the `Test` to use for the assertion. - * @param {number|string} type - See documentation for - * `assert_throws_dom <#assert_throws_dom>`_. - * @param {Function} promiseOrConstructor - Either the constructor - * for the expected exception (if the exception comes from another - * global), or the promise that's expected to reject (if the - * exception comes from the current global). - * @param {Function|string} descriptionOrPromise - Either the - * promise that's expected to reject (if the exception comes from - * another global), or the optional description of the condition - * being tested (if the exception comes from the current global). - * @param {string} [description] - Description of the condition - * being tested (if the exception comes from another global). - * - */ - function promise_rejects_dom( - test, - type, - promiseOrConstructor, - descriptionOrPromise, - maybeDescription - ) { - let constructor, promise, description; - if ( - typeof promiseOrConstructor === "function" && - promiseOrConstructor.name === "DOMException" - ) { - constructor = promiseOrConstructor; - promise = descriptionOrPromise; - description = maybeDescription; - } else { - constructor = self.DOMException; - promise = promiseOrConstructor; - description = descriptionOrPromise; - assert( - maybeDescription === undefined, - "Too many args passed to no-constructor version of promise_rejects_dom, or accidentally explicitly passed undefined" - ); - } - return bring_promise_to_current_realm(promise) - .then(test.unreached_func("Should have rejected: " + description)) - .catch(function (e) { - assert_throws_dom_impl( - type, - function () { - throw e; - }, - description, - "promise_rejects_dom", - constructor - ); - }); - } - - /** - * Assert that a Promise is rejected with the provided value. - * - * @param {Test} test - the `Test` to use for the assertion. - * @param {Any} exception - The expected value of the rejected promise. - * @param {Promise} promise - The promise that's expected to - * reject. - * @param {string} [description] Error message to add to assert in case of - * failure. - */ - function promise_rejects_exactly(test, exception, promise, description) { - return bring_promise_to_current_realm(promise) - .then(test.unreached_func("Should have rejected: " + description)) - .catch(function (e) { - assert_throws_exactly_impl( - exception, - function () { - throw e; - }, - description, - "promise_rejects_exactly" - ); - }); - } - - /** - * Allow DOM events to be handled using Promises. - * - * This can make it a lot easier to test a very specific series of events, - * including ensuring that unexpected events are not fired at any point. - * - * `EventWatcher` will assert if an event occurs while there is no `wait_for` - * created Promise waiting to be fulfilled, or if the event is of a different type - * to the type currently expected. This ensures that only the events that are - * expected occur, in the correct order, and with the correct timing. - * - * @constructor - * @param {Test} test - The `Test` to use for the assertion. - * @param {EventTarget} watchedNode - The target expected to receive the events. - * @param {string[]} eventTypes - List of events to watch for. - * @param {Promise} timeoutPromise - Promise that will cause the - * test to be set to `TIMEOUT` once fulfilled. - * - */ - function EventWatcher(test, watchedNode, eventTypes, timeoutPromise) { - if (typeof eventTypes == "string") { - eventTypes = [eventTypes]; - } - - var waitingFor = null; - - // This is null unless we are recording all events, in which case it - // will be an Array object. - var recordedEvents = null; - - var eventHandler = test.step_func(function (evt) { - assert_true( - !!waitingFor, - "Not expecting event, but got " + evt.type + " event" - ); - assert_equals( - evt.type, - waitingFor.types[0], - "Expected " + - waitingFor.types[0] + - " event, but got " + - evt.type + - " event instead" - ); - - if (Array.isArray(recordedEvents)) { - recordedEvents.push(evt); - } - - if (waitingFor.types.length > 1) { - // Pop first event from array - waitingFor.types.shift(); - return; - } - // We need to null out waitingFor before calling the resolve function - // since the Promise's resolve handlers may call wait_for() which will - // need to set waitingFor. - var resolveFunc = waitingFor.resolve; - waitingFor = null; - // Likewise, we should reset the state of recordedEvents. - var result = recordedEvents || evt; - recordedEvents = null; - resolveFunc(result); - }); - - for (var i = 0; i < eventTypes.length; i++) { - watchedNode.addEventListener(eventTypes[i], eventHandler, false); - } - - /** - * Returns a Promise that will resolve after the specified event or - * series of events has occurred. - * - * @param {Object} options An optional options object. If the 'record' property - * on this object has the value 'all', when the Promise - * returned by this function is resolved, *all* Event - * objects that were waited for will be returned as an - * array. - * - * @example - * const watcher = new EventWatcher(t, div, [ 'animationstart', - * 'animationiteration', - * 'animationend' ]); - * return watcher.wait_for([ 'animationstart', 'animationend' ], - * { record: 'all' }).then(evts => { - * assert_equals(evts[0].elapsedTime, 0.0); - * assert_equals(evts[1].elapsedTime, 2.0); - * }); - */ - this.wait_for = function (types, options) { - if (waitingFor) { - return Promise.reject("Already waiting for an event or events"); - } - if (typeof types == "string") { - types = [types]; - } - if (options && options.record && options.record === "all") { - recordedEvents = []; - } - return new Promise(function (resolve, reject) { - var timeout = test.step_func(function () { - // If the timeout fires after the events have been received - // or during a subsequent call to wait_for, ignore it. - if (!waitingFor || waitingFor.resolve !== resolve) return; - - // This should always fail, otherwise we should have - // resolved the promise. - assert_true( - waitingFor.types.length == 0, - "Timed out waiting for " + waitingFor.types.join(", ") - ); - var result = recordedEvents; - recordedEvents = null; - var resolveFunc = waitingFor.resolve; - waitingFor = null; - resolveFunc(result); - }); - - if (timeoutPromise) { - timeoutPromise().then(timeout); - } - - waitingFor = { - types: types, - resolve: resolve, - reject: reject, - }; - }); - }; - - /** - * Stop listening for events - */ - function stop_watching() { - for (var i = 0; i < eventTypes.length; i++) { - watchedNode.removeEventListener(eventTypes[i], eventHandler, false); - } - } - - test._add_cleanup(stop_watching); - - return this; - } - expose(EventWatcher, "EventWatcher"); - - /** - * @typedef {Object} SettingsObject - * @property {bool} single_test - Use the single-page-test - * mode. In this mode the Document represents a single - * `async_test`. Asserts may be used directly without requiring - * `Test.step` or similar wrappers, and any exceptions set the - * status of the test rather than the status of the harness. - * @property {bool} allow_uncaught_exception - don't treat an - * uncaught exception as an error; needed when e.g. testing the - * `window.onerror` handler. - * @property {boolean} explicit_done - Wait for a call to `done()` - * before declaring all tests complete (this is always true for - * single-page tests). - * @property hide_test_state - hide the test state output while - * the test is running; This is helpful when the output of the test state - * may interfere the test results. - * @property {bool} explicit_timeout - disable file timeout; only - * stop waiting for results when the `timeout()` function is - * called This should typically only be set for manual tests, or - * by a test runner that providees its own timeout mechanism. - * @property {number} timeout_multiplier - Multiplier to apply to - * per-test timeouts. This should only be set by a test runner. - * @property {Document} output_document - The document to which - * results should be logged. By default this is the current - * document but could be an ancestor document in some cases e.g. a - * SVG test loaded in an HTML wrapper - * - */ - - /** - * Configure the harness - * - * @param {Function|SettingsObject} funcOrProperties - Either a - * setup function to run, or a set of properties. If this is a - * function that function is run synchronously. Any exception in - * the function will set the overall harness status to `ERROR`. - * @param {SettingsObject} maybeProperties - An object containing - * the settings to use, if the first argument is a function. - * - */ - function setup(func_or_properties, maybe_properties) { - var func = null; - var properties = {}; - if (arguments.length === 2) { - func = func_or_properties; - properties = maybe_properties; - } else if (func_or_properties instanceof Function) { - func = func_or_properties; - } else { - properties = func_or_properties; - } - tests.setup(func, properties); - test_environment.on_new_harness_properties(properties); - } - - /** - * Configure the harness, waiting for a promise to resolve - * before running any `promise_test` tests. - * - * @param {Function} func - Function returning a promise that's - * run synchronously. Promise tests are not run until after this - * function has resolved. - * @param {SettingsObject} [properties] - An object containing - * the harness settings to use. - * - */ - function promise_setup(func, properties = {}) { - if (typeof func !== "function") { - tests.set_status( - tests.status.ERROR, - "promise_test invoked without a function" - ); - tests.complete(); - return; - } - tests.promise_setup_called = true; - - if (!tests.promise_tests) { - tests.promise_tests = Promise.resolve(); - } - - tests.promise_tests = tests.promise_tests - .then(function () { - var result; - - tests.setup(null, properties); - result = func(); - test_environment.on_new_harness_properties(properties); - - if (!result || typeof result.then !== "function") { - throw "Non-thenable returned by function passed to `promise_setup`"; - } - return result; - }) - .catch(function (e) { - tests.set_status(tests.status.ERROR, String(e), e && e.stack); - tests.complete(); - }); - } - - /** - * Mark test loading as complete. - * - * Typically this function is called implicitly on page load; it's - * only necessary for users to call this when either the - * ``explicit_done`` or ``single_page`` properties have been set - * via the :js:func:`setup` function. - * - * For single page tests this marks the test as complete and sets its status. - * For other tests, this marks test loading as complete, but doesn't affect ongoing tests. - */ - function done() { - if (tests.tests.length === 0) { - // `done` is invoked after handling uncaught exceptions, so if the - // harness status is already set, the corresponding message is more - // descriptive than the generic message defined here. - if (tests.status.status === null) { - tests.status.status = tests.status.ERROR; - tests.status.message = - "done() was called without first defining any tests"; - } - - tests.complete(); - return; - } - if (tests.file_is_test) { - // file is test files never have asynchronous cleanup logic, - // meaning the fully-synchronous `done` function can be used here. - tests.tests[0].done(); - } - tests.end_wait(); - } - - /** - * @deprecated generate a list of tests from a function and list of arguments - * - * This is deprecated because it runs all the tests outside of the test functions - * and as a result any test throwing an exception will result in no tests being - * run. In almost all cases, you should simply call test within the loop you would - * use to generate the parameter list array. - * - * @param {Function} func - The function that will be called for each generated tests. - * @param {Any[][]} args - An array of arrays. Each nested array - * has the structure `[testName, ...testArgs]`. For each of these nested arrays - * array, a test is generated with name `testName` and test function equivalent to - * `func(..testArgs)`. - */ - function generate_tests(func, args, properties) { - forEach(args, function (x, i) { - var name = x[0]; - test( - function () { - func.apply(this, x.slice(1)); - }, - name, - Array.isArray(properties) ? properties[i] : properties - ); - }); - } - - /** - * @deprecated - * - * Register a function as a DOM event listener to the - * given object for the event bubbling phase. - * - * @param {EventTarget} object - Event target - * @param {string} event - Event name - * @param {Function} callback - Event handler. - */ - function on_event(object, event, callback) { - object.addEventListener(event, callback, false); - } - - // Internal helper function to provide timeout-like functionality in - // environments where there is no setTimeout(). (No timeout ID or - // clearTimeout().) - function fake_set_timeout(callback, delay) { - var p = Promise.resolve(); - var start = Date.now(); - var end = start + delay; - function check() { - if (end - Date.now() > 0) { - p.then(check); - } else { - callback(); - } - } - p.then(check); - } - - /** - * Global version of :js:func:`Test.step_timeout` for use in single page tests. - * - * @param {Function} func - Function to run after the timeout - * @param {number} timeout - Time in ms to wait before running the - * test step. The actual wait time is ``timeout`` x - * ``timeout_multiplier``. - */ - function step_timeout(func, timeout) { - var outer_this = this; - var args = Array.prototype.slice.call(arguments, 2); - var local_set_timeout = - typeof global_scope.setTimeout === "undefined" - ? fake_set_timeout - : setTimeout; - return local_set_timeout(function () { - func.apply(outer_this, args); - }, timeout * tests.timeout_multiplier); - } - - expose(test, "test"); - expose(async_test, "async_test"); - expose(promise_test, "promise_test"); - expose(promise_rejects_js, "promise_rejects_js"); - expose(promise_rejects_dom, "promise_rejects_dom"); - expose(promise_rejects_exactly, "promise_rejects_exactly"); - expose(generate_tests, "generate_tests"); - expose(setup, "setup"); - expose(promise_setup, "promise_setup"); - expose(done, "done"); - expose(on_event, "on_event"); - expose(step_timeout, "step_timeout"); - - /* - * Return a string truncated to the given length, with ... added at the end - * if it was longer. - */ - function truncate(s, len) { - if (s.length > len) { - return s.substring(0, len - 3) + "..."; - } - return s; - } - - /* - * Return true if object is probably a Node object. - */ - function is_node(object) { - // I use duck-typing instead of instanceof, because - // instanceof doesn't work if the node is from another window (like an - // iframe's contentWindow): - // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295 - try { - var has_node_properties = - "nodeType" in object && - "nodeName" in object && - "nodeValue" in object && - "childNodes" in object; - } catch (e) { - // We're probably cross-origin, which means we aren't a node - return false; - } - - if (has_node_properties) { - try { - object.nodeType; - } catch (e) { - // The object is probably Node.prototype or another prototype - // object that inherits from it, and not a Node instance. - return false; - } - return true; - } - return false; - } - - var replacements = { - 0: "0", - 1: "x01", - 2: "x02", - 3: "x03", - 4: "x04", - 5: "x05", - 6: "x06", - 7: "x07", - 8: "b", - 9: "t", - 10: "n", - 11: "v", - 12: "f", - 13: "r", - 14: "x0e", - 15: "x0f", - 16: "x10", - 17: "x11", - 18: "x12", - 19: "x13", - 20: "x14", - 21: "x15", - 22: "x16", - 23: "x17", - 24: "x18", - 25: "x19", - 26: "x1a", - 27: "x1b", - 28: "x1c", - 29: "x1d", - 30: "x1e", - 31: "x1f", - "0xfffd": "ufffd", - "0xfffe": "ufffe", - "0xffff": "uffff", - }; - - /** - * Convert a value to a nice, human-readable string - * - * When many JavaScript Object values are coerced to a String, the - * resulting value will be ``"[object Object]"``. This obscures - * helpful information, making the coerced value unsuitable for - * use in assertion messages, test names, and debugging - * statements. `format_value` produces more distinctive string - * representations of many kinds of objects, including arrays and - * the more important DOM Node types. It also translates String - * values containing control characters to include human-readable - * representations. - * - * @example - * // "Document node with 2 children" - * format_value(document); - * @example - * // "\"foo\\uffffbar\"" - * format_value("foo\uffffbar"); - * @example - * // "[-0, Infinity]" - * format_value([-0, Infinity]); - * @param {Any} val - The value to convert to a string. - * @returns {string} - A string representation of ``val``, optimised for human readability. - */ - function format_value(val, seen) { - if (!seen) { - seen = []; - } - if (typeof val === "object" && val !== null) { - if (seen.indexOf(val) >= 0) { - return "[...]"; - } - seen.push(val); - } - if (Array.isArray(val)) { - let output = "["; - if (val.beginEllipsis !== undefined) { - output += "…, "; - } - output += val - .map(function (x) { - return format_value(x, seen); - }) - .join(", "); - if (val.endEllipsis !== undefined) { - output += ", …"; - } - return output + "]"; - } - - switch (typeof val) { - case "string": - val = val.replace(/\\/g, "\\\\"); - for (var p in replacements) { - var replace = "\\" + replacements[p]; - val = val.replace(RegExp(String.fromCharCode(p), "g"), replace); - } - return '"' + val.replace(/"/g, '\\"') + '"'; - case "boolean": - case "undefined": - return String(val); - case "number": - // In JavaScript, -0 === 0 and String(-0) == "0", so we have to - // special-case. - if (val === -0 && 1 / val === -Infinity) { - return "-0"; - } - return String(val); - case "object": - if (val === null) { - return "null"; - } - - // Special-case Node objects, since those come up a lot in my tests. I - // ignore namespaces. - if (is_node(val)) { - switch (val.nodeType) { - case Node.ELEMENT_NODE: - var ret = "<" + val.localName; - for (var i = 0; i < val.attributes.length; i++) { - ret += - " " + - val.attributes[i].name + - '="' + - val.attributes[i].value + - '"'; - } - ret += ">" + val.innerHTML + ""; - return "Element node " + truncate(ret, 60); - case Node.TEXT_NODE: - return 'Text node "' + truncate(val.data, 60) + '"'; - case Node.PROCESSING_INSTRUCTION_NODE: - return ( - "ProcessingInstruction node with target " + - format_value(truncate(val.target, 60)) + - " and data " + - format_value(truncate(val.data, 60)) - ); - case Node.COMMENT_NODE: - return "Comment node "; - case Node.DOCUMENT_NODE: - return ( - "Document node with " + - val.childNodes.length + - (val.childNodes.length == 1 ? " child" : " children") - ); - case Node.DOCUMENT_TYPE_NODE: - return "DocumentType node"; - case Node.DOCUMENT_FRAGMENT_NODE: - return ( - "DocumentFragment node with " + - val.childNodes.length + - (val.childNodes.length == 1 ? " child" : " children") - ); - default: - return "Node object of unknown type"; - } - } - - /* falls through */ - default: - try { - return typeof val + ' "' + truncate(String(val), 1000) + '"'; - } catch (e) { - return ( - "[stringifying object threw " + - String(e) + - " with type " + - String(typeof e) + - "]" - ); - } - } - } - expose(format_value, "format_value"); - - /* - * Assertions - */ - - function expose_assert(f, name) { - function assert_wrapper(...args) { - let status = Test.statuses.TIMEOUT; - let stack = null; - let new_assert_index = null; - try { - if (settings.debug) { - console.debug( - "ASSERT", - name, - tests.current_test && tests.current_test.name, - args - ); - } - if (tests.output) { - tests.set_assert(name, args); - // Remember the newly pushed assert's index, because `apply` - // below might push new asserts. - new_assert_index = tests.asserts_run.length - 1; - } - const rv = f.apply(undefined, args); - status = Test.statuses.PASS; - return rv; - } catch (e) { - status = Test.statuses.FAIL; - stack = e.stack ? e.stack : null; - throw e; - } finally { - if (tests.output && !stack) { - stack = get_stack(); - } - if (tests.output) { - tests.set_assert_status(new_assert_index, status, stack); - } - } - } - expose(assert_wrapper, name); - } - - /** - * Assert that ``actual`` is strictly true - * - * @param {Any} actual - Value that is asserted to be true - * @param {string} [description] - Description of the condition being tested - */ - function assert_true(actual, description) { - assert( - actual === true, - "assert_true", - description, - "expected true got ${actual}", - { actual: actual } - ); - } - expose_assert(assert_true, "assert_true"); - - /** - * Assert that ``actual`` is strictly false - * - * @param {Any} actual - Value that is asserted to be false - * @param {string} [description] - Description of the condition being tested - */ - function assert_false(actual, description) { - assert( - actual === false, - "assert_false", - description, - "expected false got ${actual}", - { actual: actual } - ); - } - expose_assert(assert_false, "assert_false"); - - function same_value(x, y) { - if (y !== y) { - //NaN case - return x !== x; - } - if (x === 0 && y === 0) { - //Distinguish +0 and -0 - return 1 / x === 1 / y; - } - return x === y; - } - - /** - * Assert that ``actual`` is the same value as ``expected``. - * - * For objects this compares by object identity; for primitives - * this distinguishes between 0 and -0, and has correct handling - * of NaN. - * - * @param {Any} actual - Test value. - * @param {Any} expected - Expected value. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_equals(actual, expected, description) { - /* - * Test if two primitives are equal or two objects - * are the same object - */ - if (typeof actual != typeof expected) { - assert( - false, - "assert_equals", - description, - "expected (" + - typeof expected + - ") ${expected} but got (" + - typeof actual + - ") ${actual}", - { expected: expected, actual: actual } - ); - return; - } - assert( - same_value(actual, expected), - "assert_equals", - description, - "expected ${expected} but got ${actual}", - { expected: expected, actual: actual } - ); - } - expose_assert(assert_equals, "assert_equals"); - - /** - * Assert that ``actual`` is not the same value as ``expected``. - * - * Comparison is as for :js:func:`assert_equals`. - * - * @param {Any} actual - Test value. - * @param {Any} expected - The value ``actual`` is expected to be different to. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_not_equals(actual, expected, description) { - assert( - !same_value(actual, expected), - "assert_not_equals", - description, - "got disallowed value ${actual}", - { actual: actual } - ); - } - expose_assert(assert_not_equals, "assert_not_equals"); - - /** - * Assert that ``expected`` is an array and ``actual`` is one of the members. - * This is implemented using ``indexOf``, so doesn't handle NaN or ±0 correctly. - * - * @param {Any} actual - Test value. - * @param {Array} expected - An array that ``actual`` is expected to - * be a member of. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_in_array(actual, expected, description) { - assert( - expected.indexOf(actual) != -1, - "assert_in_array", - description, - "value ${actual} not in array ${expected}", - { actual: actual, expected: expected } - ); - } - expose_assert(assert_in_array, "assert_in_array"); - - // This function was deprecated in July of 2015. - // See https://github.com/web-platform-tests/wpt/issues/2033 - /** - * @deprecated - * Recursively compare two objects for equality. - * - * See `Issue 2033 - * `_ for - * more information. - * - * @param {Object} actual - Test value. - * @param {Object} expected - Expected value. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_object_equals(actual, expected, description) { - assert( - typeof actual === "object" && actual !== null, - "assert_object_equals", - description, - "value is ${actual}, expected object", - { actual: actual } - ); - //This needs to be improved a great deal - function check_equal(actual, expected, stack) { - stack.push(actual); - - var p; - for (p in actual) { - assert( - expected.hasOwnProperty(p), - "assert_object_equals", - description, - "unexpected property ${p}", - { p: p } - ); - - if (typeof actual[p] === "object" && actual[p] !== null) { - if (stack.indexOf(actual[p]) === -1) { - check_equal(actual[p], expected[p], stack); - } - } else { - assert( - same_value(actual[p], expected[p]), - "assert_object_equals", - description, - "property ${p} expected ${expected} got ${actual}", - { p: p, expected: expected[p], actual: actual[p] } - ); - } - } - for (p in expected) { - assert( - actual.hasOwnProperty(p), - "assert_object_equals", - description, - "expected property ${p} missing", - { p: p } - ); - } - stack.pop(); - } - check_equal(actual, expected, []); - } - expose_assert(assert_object_equals, "assert_object_equals"); - - /** - * Assert that ``actual`` and ``expected`` are both arrays, and that the array properties of - * ``actual`` and ``expected`` are all the same value (as for :js:func:`assert_equals`). - * - * @param {Array} actual - Test array. - * @param {Array} expected - Array that is expected to contain the same values as ``actual``. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_array_equals(actual, expected, description) { - const max_array_length = 20; - function shorten_array(arr, offset = 0) { - // Make ", …" only show up when it would likely reduce the length, not accounting for - // fonts. - if (arr.length < max_array_length + 2) { - return arr; - } - // By default we want half the elements after the offset and half before - // But if that takes us past the end of the array, we have more before, and - // if it takes us before the start we have more after. - const length_after_offset = Math.floor(max_array_length / 2); - let upper_bound = Math.min(length_after_offset + offset, arr.length); - const lower_bound = Math.max(upper_bound - max_array_length, 0); - - if (lower_bound === 0) { - upper_bound = max_array_length; - } - - const output = arr.slice(lower_bound, upper_bound); - if (lower_bound > 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 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, - "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", - arrayExpected: shorten_array(expected, i), - arrayActual: shorten_array(actual, i), - } - ); - assert( - same_value(expected[i], actual[i]), - "assert_array_equals", - description, - "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(assert_array_equals, "assert_array_equals"); - - /** - * Assert that each array property in ``actual`` is a number within - * ± `epsilon` of the corresponding property in `expected`. - * - * @param {Array} actual - Array of test values. - * @param {Array} expected - Array of values expected to be close to the values in ``actual``. - * @param {number} epsilon - Magnitude of allowed difference - * between each value in ``actual`` and ``expected``. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_array_approx_equals( - actual, - expected, - epsilon, - description - ) { - /* - * Test if two primitive arrays are equal within +/- epsilon - */ - assert( - actual.length === expected.length, - "assert_array_approx_equals", - description, - "lengths differ, expected ${expected} got ${actual}", - { expected: expected.length, actual: actual.length } - ); - - for (var i = 0; i < actual.length; i++) { - assert( - actual.hasOwnProperty(i) === expected.hasOwnProperty(i), - "assert_array_approx_equals", - description, - "property ${i}, property expected to be ${expected} but was ${actual}", - { - i: i, - expected: expected.hasOwnProperty(i) ? "present" : "missing", - actual: actual.hasOwnProperty(i) ? "present" : "missing", - } - ); - assert( - typeof actual[i] === "number", - "assert_array_approx_equals", - description, - "property ${i}, expected a number but got a ${type_actual}", - { i: i, type_actual: typeof actual[i] } - ); - assert( - Math.abs(actual[i] - expected[i]) <= epsilon, - "assert_array_approx_equals", - description, - "property ${i}, expected ${expected} +/- ${epsilon}, expected ${expected} but got ${actual}", - { i: i, expected: expected[i], actual: actual[i], epsilon: epsilon } - ); - } - } - expose_assert(assert_array_approx_equals, "assert_array_approx_equals"); - - /** - * Assert that ``actual`` is within ± ``epsilon`` of ``expected``. - * - * @param {number} actual - Test value. - * @param {number} expected - Value number is expected to be close to. - * @param {number} epsilon - Magnitude of allowed difference between ``actual`` and ``expected``. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_approx_equals(actual, expected, epsilon, description) { - /* - * Test if two primitive numbers are equal within +/- epsilon - */ - assert( - typeof actual === "number", - "assert_approx_equals", - description, - "expected a number but got a ${type_actual}", - { type_actual: typeof actual } - ); - - // The epsilon math below does not place nice with NaN and Infinity - // But in this case Infinity = Infinity and NaN = NaN - if (isFinite(actual) || isFinite(expected)) { - assert( - Math.abs(actual - expected) <= epsilon, - "assert_approx_equals", - description, - "expected ${expected} +/- ${epsilon} but got ${actual}", - { expected: expected, actual: actual, epsilon: epsilon } - ); - } else { - assert_equals(actual, expected); - } - } - expose_assert(assert_approx_equals, "assert_approx_equals"); - - /** - * Assert that ``actual`` is a number less than ``expected``. - * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be less than. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_less_than(actual, expected, description) { - /* - * Test if a primitive number is less than another - */ - assert( - typeof actual === "number", - "assert_less_than", - description, - "expected a number but got a ${type_actual}", - { type_actual: typeof actual } - ); - - assert( - actual < expected, - "assert_less_than", - description, - "expected a number less than ${expected} but got ${actual}", - { expected: expected, actual: actual } - ); - } - expose_assert(assert_less_than, "assert_less_than"); - - /** - * Assert that ``actual`` is a number greater than ``expected``. - * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be greater than. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_greater_than(actual, expected, description) { - /* - * Test if a primitive number is greater than another - */ - assert( - typeof actual === "number", - "assert_greater_than", - description, - "expected a number but got a ${type_actual}", - { type_actual: typeof actual } - ); - - assert( - actual > expected, - "assert_greater_than", - description, - "expected a number greater than ${expected} but got ${actual}", - { expected: expected, actual: actual } - ); - } - expose_assert(assert_greater_than, "assert_greater_than"); - - /** - * Assert that ``actual`` is a number greater than ``lower`` and less - * than ``upper`` but not equal to either. - * - * @param {number} actual - Test value. - * @param {number} lower - Number that ``actual`` must be greater than. - * @param {number} upper - Number that ``actual`` must be less than. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_between_exclusive(actual, lower, upper, description) { - /* - * Test if a primitive number is between two others - */ - assert( - typeof actual === "number", - "assert_between_exclusive", - description, - "expected a number but got a ${type_actual}", - { type_actual: typeof actual } - ); - - assert( - actual > lower && actual < upper, - "assert_between_exclusive", - description, - "expected a number greater than ${lower} " + - "and less than ${upper} but got ${actual}", - { lower: lower, upper: upper, actual: actual } - ); - } - expose_assert(assert_between_exclusive, "assert_between_exclusive"); - - /** - * Assert that ``actual`` is a number less than or equal to ``expected``. - * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be less - * than or equal to. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_less_than_equal(actual, expected, description) { - /* - * Test if a primitive number is less than or equal to another - */ - assert( - typeof actual === "number", - "assert_less_than_equal", - description, - "expected a number but got a ${type_actual}", - { type_actual: typeof actual } - ); - - assert( - actual <= expected, - "assert_less_than_equal", - description, - "expected a number less than or equal to ${expected} but got ${actual}", - { expected: expected, actual: actual } - ); - } - expose_assert(assert_less_than_equal, "assert_less_than_equal"); - - /** - * Assert that ``actual`` is a number greater than or equal to ``expected``. - * - * @param {number} actual - Test value. - * @param {number} expected - Number that ``actual`` must be greater - * than or equal to. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_greater_than_equal(actual, expected, description) { - /* - * Test if a primitive number is greater than or equal to another - */ - assert( - typeof actual === "number", - "assert_greater_than_equal", - description, - "expected a number but got a ${type_actual}", - { type_actual: typeof actual } - ); - - assert( - actual >= expected, - "assert_greater_than_equal", - description, - "expected a number greater than or equal to ${expected} but got ${actual}", - { expected: expected, actual: actual } - ); - } - expose_assert(assert_greater_than_equal, "assert_greater_than_equal"); - - /** - * Assert that ``actual`` is a number greater than or equal to ``lower`` and less - * than or equal to ``upper``. - * - * @param {number} actual - Test value. - * @param {number} lower - Number that ``actual`` must be greater than or equal to. - * @param {number} upper - Number that ``actual`` must be less than or equal to. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_between_inclusive(actual, lower, upper, description) { - /* - * Test if a primitive number is between to two others or equal to either of them - */ - assert( - typeof actual === "number", - "assert_between_inclusive", - description, - "expected a number but got a ${type_actual}", - { type_actual: typeof actual } - ); - - assert( - actual >= lower && actual <= upper, - "assert_between_inclusive", - description, - "expected a number greater than or equal to ${lower} " + - "and less than or equal to ${upper} but got ${actual}", - { lower: lower, upper: upper, actual: actual } - ); - } - expose_assert(assert_between_inclusive, "assert_between_inclusive"); - - /** - * Assert that ``actual`` matches the RegExp ``expected``. - * - * @param {String} actual - Test string. - * @param {RegExp} expected - RegExp ``actual`` must match. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_regexp_match(actual, expected, description) { - /* - * Test if a string (actual) matches a regexp (expected) - */ - assert( - expected.test(actual), - "assert_regexp_match", - description, - "expected ${expected} but got ${actual}", - { expected: expected, actual: actual } - ); - } - expose_assert(assert_regexp_match, "assert_regexp_match"); - - /** - * Assert that the class string of ``object`` as returned in - * ``Object.prototype.toString`` is equal to ``class_name``. - * - * @param {Object} object - Object to stringify. - * @param {string} class_string - Expected class string for ``object``. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_class_string(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(assert_class_string, "assert_class_string"); - - /** - * Assert that ``object`` has an own property with name ``property_name``. - * - * @param {Object} object - Object that should have the given property. - * @param {string} property_name - Expected property name. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_own_property(object, property_name, description) { - assert( - object.hasOwnProperty(property_name), - "assert_own_property", - description, - "expected property ${p} missing", - { p: property_name } - ); - } - expose_assert(assert_own_property, "assert_own_property"); - - /** - * Assert that ``object`` does not have an own property with name ``property_name``. - * - * @param {Object} object - Object that should not have the given property. - * @param {string} property_name - Property name to test. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_not_own_property(object, property_name, description) { - assert( - !object.hasOwnProperty(property_name), - "assert_not_own_property", - description, - "unexpected property ${p} is found on object", - { p: property_name } - ); - } - expose_assert(assert_not_own_property, "assert_not_own_property"); - - function _assert_inherits(name) { - return function (object, property_name, description) { - assert( - (typeof object === "object" && object !== null) || - typeof object === "function" || - // Or has [[IsHTMLDDA]] slot - String(object) === "[object HTMLAllCollection]", - name, - description, - "provided value is not an object" - ); - - assert( - "hasOwnProperty" in object, - name, - description, - "provided value is an object but has no hasOwnProperty method" - ); - - assert( - !object.hasOwnProperty(property_name), - name, - description, - "property ${p} found on object expected in prototype chain", - { p: property_name } - ); - - assert( - property_name in object, - name, - description, - "property ${p} not found in prototype chain", - { p: property_name } - ); - }; - } - - /** - * Assert that ``object`` does not have an own property with name - * ``property_name``, but inherits one through the prototype chain. - * - * @param {Object} object - Object that should have the given property in its prototype chain. - * @param {string} property_name - Expected property name. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_inherits(object, property_name, description) { - return _assert_inherits("assert_inherits")( - object, - property_name, - description - ); - } - expose_assert(assert_inherits, "assert_inherits"); - - /** - * Alias for :js:func:`insert_inherits`. - * - * @param {Object} object - Object that should have the given property in its prototype chain. - * @param {string} property_name - Expected property name. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_idl_attribute(object, property_name, description) { - return _assert_inherits("assert_idl_attribute")( - object, - property_name, - description - ); - } - expose_assert(assert_idl_attribute, "assert_idl_attribute"); - - /** - * Assert that ``object`` has a property named ``property_name`` and that the property is readonly. - * - * Note: The implementation tries to update the named property, so - * any side effects of updating will be triggered. Users are - * encouraged to instead inspect the property descriptor of ``property_name`` on ``object``. - * - * @param {Object} object - Object that should have the given property in its prototype chain. - * @param {string} property_name - Expected property name. - * @param {string} [description] - Description of the condition being tested. - */ - function assert_readonly(object, property_name, description) { - var initial_value = object[property_name]; - try { - //Note that this can have side effects in the case where - //the property has PutForwards - object[property_name] = initial_value + "a"; //XXX use some other value here? - assert( - same_value(object[property_name], initial_value), - "assert_readonly", - description, - "changing property ${p} succeeded", - { p: property_name } - ); - } finally { - object[property_name] = initial_value; - } - } - expose_assert(assert_readonly, "assert_readonly"); - - /** - * Assert a JS Error with the expected constructor is thrown. - * - * @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_js(constructor, func, description) { - assert_throws_js_impl(constructor, func, description, "assert_throws_js"); - } - expose_assert(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, 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", - 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 } - ); - - // 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); - } - 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, - } - ); - } - } - - // TODO: Figure out how to document the overloads better. - // sphinx-js doesn't seem to handle @variation correctly, - // and only expects a single JSDoc entry per function. - /** - * Assert a DOMException with the expected type is thrown. - * - * There are two ways of calling assert_throws_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. - * - * @param {number|string} type - The expected exception name or - * code. See the `table of names and codes - * `_. 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``"). - * @param {Function} descriptionOrFunc - The function expected to - * throw (if the exception comes from another global), or the - * optional description of the condition being tested (if the - * exception comes from the current global). - * @param {string} [description] - Description of the condition - * being tested (if the exception comes from another global). - * - */ - 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 passed to no-constructor version of assert_throws_dom, or accidentally explicitly passed undefined" - ); - } - assert_throws_dom_impl( - type, - func, - description, - "assert_throws_dom", - constructor - ); - } - expose_assert(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; - } - - // 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", - INVALID_CHARACTER_ERR: "InvalidCharacterError", - NO_MODIFICATION_ALLOWED_ERR: "NoModificationAllowedError", - NOT_FOUND_ERR: "NotFoundError", - NOT_SUPPORTED_ERR: "NotSupportedError", - INUSE_ATTRIBUTE_ERR: "InUseAttributeError", - INVALID_STATE_ERR: "InvalidStateError", - SYNTAX_ERR: "SyntaxError", - INVALID_MODIFICATION_ERR: "InvalidModificationError", - NAMESPACE_ERR: "NamespaceError", - INVALID_ACCESS_ERR: "InvalidAccessError", - TYPE_MISMATCH_ERR: "TypeMismatchError", - SECURITY_ERR: "SecurityError", - NETWORK_ERR: "NetworkError", - ABORT_ERR: "AbortError", - URL_MISMATCH_ERR: "URLMismatchError", - QUOTA_EXCEEDED_ERR: "QuotaExceededError", - TIMEOUT_ERR: "TimeoutError", - INVALID_NODE_TYPE_ERR: "InvalidNodeTypeError", - DATA_CLONE_ERR: "DataCloneError", - }; - - var name_code_map = { - IndexSizeError: 1, - HierarchyRequestError: 3, - WrongDocumentError: 4, - InvalidCharacterError: 5, - NoModificationAllowedError: 7, - NotFoundError: 8, - NotSupportedError: 9, - InUseAttributeError: 10, - InvalidStateError: 11, - SyntaxError: 12, - InvalidModificationError: 13, - NamespaceError: 14, - InvalidAccessError: 15, - TypeMismatchError: 17, - SecurityError: 18, - NetworkError: 19, - AbortError: 20, - URLMismatchError: 21, - QuotaExceededError: 22, - TimeoutError: 23, - InvalidNodeTypeError: 24, - DataCloneError: 25, - - EncodingError: 0, - NotReadableError: 0, - UnknownError: 0, - ConstraintError: 0, - DataError: 0, - TransactionInactiveError: 0, - ReadOnlyError: 0, - VersionError: 0, - OperationError: 0, - NotAllowedError: 0, - OptOutError: 0, - }; - - 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 = {}; - 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 && - e.name !== e.name.toUpperCase() && - e.name !== "DOMException") - ) { - // New style exception: also test the name property. - required_props.name = name; - } - - for (var prop in required_props) { - assert( - prop in e && e[prop] == required_props[prop], - 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(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 } - ); - } - } - - /** - * Asserts if called. Used to ensure that a specific codepath is - * not taken e.g. that an error event isn't fired. - * - * @param {string} [description] - Description of the condition being tested. - */ - function assert_unreached(description) { - assert( - false, - "assert_unreached", - description, - "Reached unreachable code" - ); - } - expose_assert(assert_unreached, "assert_unreached"); - - /** - * @callback AssertFunc - * @param {Any} actual - * @param {Any} expected - * @param {Any[]} args - */ - - /** - * Asserts that ``actual`` matches at least one value of ``expected`` - * according to a comparison defined by ``assert_func``. - * - * Note that tests with multiple allowed pass conditions are bad - * practice unless the spec specifically allows multiple - * behaviours. Test authors should not use this method simply to - * hide UA bugs. - * - * @param {AssertFunc} assert_func - Function to compare actual - * and expected. It must throw when the comparison fails and - * return when the comparison passes. - * @param {Any} actual - Test value. - * @param {Array} expected_array - Array of possible expected values. - * @param {Any[]} args - Additional arguments to pass to ``assert_func``. - */ - function assert_any(assert_func, actual, expected_array, ...args) { - var errors = []; - var passed = false; - forEach(expected_array, function (expected) { - try { - assert_func.apply(this, [actual, expected].concat(args)); - passed = true; - } catch (e) { - errors.push(e.message); - } - }); - if (!passed) { - throw new AssertionError(errors.join("\n\n")); - } - } - // FIXME: assert_any cannot use expose_assert, because assert_wrapper does - // not support nested assert calls (e.g. to assert_func). We need to - // support bypassing assert_wrapper for the inner asserts here. - 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(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(assert_implements_optional, "assert_implements_optional"); - - /** - * @class - * - * A single subtest. A Test is not constructed directly but via the - * :js:func:`test`, :js:func:`async_test` or :js:func:`promise_test` functions. - * - * @param {string} name - This must be unique in a given file and must be - * invariant between runs. - * - */ - function Test(name, properties) { - if (tests.file_is_test && tests.tests.length) { - throw new Error("Tried to create a test with file_is_test"); - } - /** The test name. */ - this.name = name; - - this.phase = - tests.is_aborted || tests.phase === tests.phases.COMPLETE - ? this.phases.COMPLETE - : this.phases.INITIAL; - - /** The test status code.*/ - this.status = this.NOTRUN; - this.timeout_id = null; - this.index = null; - - this.properties = properties || {}; - this.timeout_length = settings.test_timeout; - if (this.timeout_length !== null) { - this.timeout_length *= tests.timeout_multiplier; - } - - /** A message indicating the reason for test failure. */ - this.message = null; - /** Stack trace in case of failure. */ - this.stack = null; - - this.steps = []; - this._is_promise_test = false; - - this.cleanup_callbacks = []; - this._user_defined_cleanup_count = 0; - this._done_callbacks = []; - - if (typeof AbortController === "function") { - this._abortController = new AbortController(); - } - - // Tests declared following harness completion are likely an indication - // of a programming error, but they cannot be reported - // deterministically. - if (tests.phase === tests.phases.COMPLETE) { - return; - } - - tests.push(this); - } - - /** - * Enum of possible test statuses. - * - * :values: - * - ``PASS`` - * - ``FAIL`` - * - ``TIMEOUT`` - * - ``NOTRUN`` - * - ``PRECONDITION_FAILED`` - */ - Test.statuses = { - PASS: 0, - FAIL: 1, - TIMEOUT: 2, - NOTRUN: 3, - PRECONDITION_FAILED: 4, - }; - - Test.prototype = merge({}, Test.statuses); - - Test.prototype.phases = { - INITIAL: 0, - STARTED: 1, - HAS_RESULT: 2, - CLEANING: 3, - COMPLETE: 4, - }; - - Test.prototype.status_formats = { - 0: "Pass", - 1: "Fail", - 2: "Timeout", - 3: "Not Run", - 4: "Optional Feature Unsupported", - }; - - Test.prototype.format_status = function () { - return this.status_formats[this.status]; - }; - - Test.prototype.structured_clone = function () { - if (!this._structured_clone) { - var msg = this.message; - msg = msg ? String(msg) : msg; - this._structured_clone = merge( - { - name: String(this.name), - properties: merge({}, this.properties), - phases: merge({}, this.phases), - }, - Test.statuses - ); - } - this._structured_clone.status = this.status; - this._structured_clone.message = this.message; - this._structured_clone.stack = this.stack; - this._structured_clone.index = this.index; - this._structured_clone.phase = this.phase; - return this._structured_clone; - }; - - /** - * Run a single step of an ongoing test. - * - * @param {string} func - Callback function to run as a step. If - * this throws an :js:func:`AssertionError`, or any other - * exception, the :js:class:`Test` status is set to ``FAIL``. - * @param {Object} [this_obj] - The object to use as the this - * value when calling ``func``. Defaults to the :js:class:`Test` object. - */ - Test.prototype.step = function (func, this_obj) { - if (this.phase > this.phases.STARTED) { - return; - } - - if (settings.debug && this.phase !== this.phases.STARTED) { - console.log("TEST START", this.name); - } - this.phase = this.phases.STARTED; - //If we don't get a result before the harness times out that will be a test timeout - this.set_status(this.TIMEOUT, "Test timed out"); - - tests.started = true; - tests.current_test = this; - tests.notify_test_state(this); - - if (this.timeout_id === null) { - this.set_timeout(); - } - - this.steps.push(func); - - if (arguments.length === 1) { - this_obj = this; - } - - if (settings.debug) { - console.debug("TEST STEP", this.name); - } - - try { - return func.apply(this_obj, Array.prototype.slice.call(arguments, 2)); - } catch (e) { - 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(status, message, stack); - this.phase = this.phases.HAS_RESULT; - this.done(); - } finally { - this.current_test = null; - } - }; - - /** - * Wrap a function so that it runs as a step of the current test. - * - * This allows creating a callback function that will run as a - * test step. - * - * @example - * let t = async_test("Example"); - * onload = t.step_func(e => { - * assert_equals(e.name, "load"); - * // Mark the test as complete. - * t.done(); - * }) - * - * @param {string} func - Function to run as a step. If this - * throws an :js:func:`AssertionError`, or any other exception, - * the :js:class:`Test` status is set to ``FAIL``. - * @param {Object} [this_obj] - The object to use as the this - * value when calling ``func``. Defaults to the :js:class:`Test` object. - */ - Test.prototype.step_func = function (func, this_obj) { - var test_this = this; - - if (arguments.length === 1) { - this_obj = test_this; - } - - return function () { - return test_this.step.apply( - test_this, - [func, this_obj].concat(Array.prototype.slice.call(arguments)) - ); - }; - }; - - /** - * Wrap a function so that it runs as a step of the current test, - * and automatically marks the test as complete if the function - * returns without error. - * - * @param {string} func - Function to run as a step. If this - * throws an :js:func:`AssertionError`, or any other exception, - * the :js:class:`Test` status is set to ``FAIL``. If it returns - * without error the status is set to ``PASS``. - * @param {Object} [this_obj] - The object to use as the this - * value when calling `func`. Defaults to the :js:class:`Test` object. - */ - Test.prototype.step_func_done = function (func, this_obj) { - var test_this = this; - - if (arguments.length === 1) { - this_obj = test_this; - } - - return function () { - if (func) { - test_this.step.apply( - test_this, - [func, this_obj].concat(Array.prototype.slice.call(arguments)) - ); - } - test_this.done(); - }; - }; - - /** - * Return a function that automatically sets the current test to - * ``FAIL`` if it's called. - * - * @param {string} [description] - Error message to add to assert - * in case of failure. - * - */ - Test.prototype.unreached_func = function (description) { - return this.step_func(function () { - assert_unreached(description); - }); - }; - - /** - * Run a function as a step of the test after a given timeout. - * - * This multiplies the timeout by the global timeout multiplier to - * account for the expected execution speed of the current test - * environment. For example ``test.step_timeout(f, 2000)`` with a - * timeout multiplier of 2 will wait for 4000ms before calling ``f``. - * - * In general it's encouraged to use :js:func:`Test.step_wait` or - * :js:func:`step_wait_func` in preference to this function where possible, - * as they provide better test performance. - * - * @param {Function} func - Function to run as a test - * step. - * @param {number} timeout - Time in ms to wait before running the - * test step. The actual wait time is ``timeout`` x - * ``timeout_multiplier``. - * - */ - Test.prototype.step_timeout = function (func, timeout) { - var test_this = this; - var args = Array.prototype.slice.call(arguments, 2); - var local_set_timeout = - typeof global_scope.setTimeout === "undefined" - ? fake_set_timeout - : setTimeout; - return local_set_timeout( - this.step_func(function () { - return func.apply(test_this, args); - }), - timeout * tests.timeout_multiplier - ); - }; - - /** - * Poll for a function to return true, and call a callback - * function once it does, or assert if a timeout is - * reached. This is preferred over a simple step_timeout - * whenever possible since it allows the timeout to be longer - * to reduce intermittents without compromising test execution - * speed when the condition is quickly met. - * - * @param {Function} cond A function taking no arguments and - * returning a boolean or a Promise. The callback is - * called when this function returns true, or the - * returned Promise is resolved with true. - * @param {Function} func A function taking no arguments to call once - * the condition is met. - * @param {string} [description] Error message to add to assert in case of - * failure. - * @param {number} timeout Timeout in ms. This is multiplied by the global - * timeout_multiplier - * @param {number} interval Polling interval in ms - * - */ - Test.prototype.step_wait_func = function ( - cond, - func, - description, - timeout = 3000, - interval = 100 - ) { - var timeout_full = timeout * tests.timeout_multiplier; - var remaining = Math.ceil(timeout_full / interval); - var test_this = this; - var local_set_timeout = - typeof global_scope.setTimeout === "undefined" - ? fake_set_timeout - : setTimeout; - - const step = test_this.step_func((result) => { - if (result) { - func(); - } else { - if (remaining === 0) { - assert( - false, - "step_wait_func", - description, - "Timed out waiting on condition" - ); - } - remaining--; - local_set_timeout(wait_for_inner, interval); - } - }); - - var wait_for_inner = test_this.step_func(() => { - Promise.resolve(cond()).then( - step, - test_this.unreached_func("step_wait_func") - ); - }); - - wait_for_inner(); - }; - - /** - * Poll for a function to return true, and invoke a callback - * followed by this.done() once it does, or assert if a timeout - * is reached. This is preferred over a simple step_timeout - * whenever possible since it allows the timeout to be longer - * to reduce intermittents without compromising test execution speed - * when the condition is quickly met. - * - * @example - * async_test(t => { - * const popup = window.open("resources/coop-coep.py?coop=same-origin&coep=&navigate=about:blank"); - * t.add_cleanup(() => popup.close()); - * assert_equals(window, popup.opener); - * - * popup.onload = t.step_func(() => { - * assert_true(popup.location.href.endsWith("&navigate=about:blank")); - * // Use step_wait_func_done as about:blank cannot message back. - * t.step_wait_func_done(() => popup.location.href === "about:blank"); - * }); - * }, "Navigating a popup to about:blank"); - * - * @param {Function} cond A function taking no arguments and - * returning a boolean or a Promise. The callback is - * called when this function returns true, or the - * returned Promise is resolved with true. - * @param {Function} func A function taking no arguments to call once - * the condition is met. - * @param {string} [description] Error message to add to assert in case of - * failure. - * @param {number} timeout Timeout in ms. This is multiplied by the global - * timeout_multiplier - * @param {number} interval Polling interval in ms - * - */ - Test.prototype.step_wait_func_done = function ( - cond, - func, - description, - timeout = 3000, - interval = 100 - ) { - this.step_wait_func( - cond, - () => { - if (func) { - func(); - } - this.done(); - }, - description, - timeout, - interval - ); - }; - - /** - * Poll for a function to return true, and resolve a promise - * once it does, or assert if a timeout is reached. This is - * preferred over a simple step_timeout whenever possible - * since it allows the timeout to be longer to reduce - * intermittents without compromising test execution speed - * when the condition is quickly met. - * - * @example - * promise_test(async t => { - * // … - * await t.step_wait(() => frame.contentDocument === null, "Frame navigated to a cross-origin document"); - * // … - * }, ""); - * - * @param {Function} cond A function taking no arguments and - * returning a boolean or a Promise. - * @param {string} [description] Error message to add to assert in case of - * failure. - * @param {number} timeout Timeout in ms. This is multiplied by the global - * timeout_multiplier - * @param {number} interval Polling interval in ms - * @returns {Promise} Promise resolved once cond is met. - * - */ - Test.prototype.step_wait = function ( - cond, - description, - timeout = 3000, - interval = 100 - ) { - return new Promise((resolve) => { - this.step_wait_func(cond, resolve, description, timeout, interval); - }); - }; - - /* - * Private method for registering cleanup functions. `testharness.js` - * internals should use this method instead of the public `add_cleanup` - * method in order to hide implementation details from the harness status - * message in the case errors. - */ - Test.prototype._add_cleanup = function (callback) { - this.cleanup_callbacks.push(callback); - }; - - /** - * Schedule a function to be run after the test result is known, regardless - * of passing or failing state. - * - * The behavior of this function will not - * influence the result of the test, but if an exception is thrown, the - * test harness will report an error. - * - * @param {Function} callback - The cleanup function to run. This - * is called with no arguments. - */ - Test.prototype.add_cleanup = function (callback) { - this._user_defined_cleanup_count += 1; - this._add_cleanup(callback); - }; - - Test.prototype.set_timeout = function () { - if (this.timeout_length !== null) { - var this_obj = this; - this.timeout_id = setTimeout(function () { - this_obj.timeout(); - }, this.timeout_length); - } - }; - - Test.prototype.set_status = function (status, message, stack) { - this.status = status; - this.message = message; - this.stack = stack ? stack : null; - }; - - /** - * Manually set the test status to ``TIMEOUT``. - */ - Test.prototype.timeout = function () { - this.timeout_id = null; - this.set_status(this.TIMEOUT, "Test timed out"); - this.phase = this.phases.HAS_RESULT; - this.done(); - }; - - /** - * Manually set the test status to ``TIMEOUT``. - * - * Alias for `Test.timeout <#Test.timeout>`_. - */ - Test.prototype.force_timeout = function () { - return this.timeout(); - }; - - /** - * Mark the test as complete. - * - * This sets the test status to ``PASS`` if no other status was - * already recorded. Any subsequent attempts to run additional - * test steps will be ignored. - * - * After setting the test status any test cleanup functions will - * be run. - */ - Test.prototype.done = function () { - if (this.phase >= this.phases.CLEANING) { - return; - } - - if (this.phase <= this.phases.STARTED) { - this.set_status(this.PASS, null); - } - - if (global_scope.clearTimeout) { - clearTimeout(this.timeout_id); - } - - if (settings.debug) { - console.log("TEST DONE", this.status, this.name); - } - - this.cleanup(); - }; - - function add_test_done_callback(test, callback) { - if (test.phase === test.phases.COMPLETE) { - callback(); - return; - } - - test._done_callbacks.push(callback); - } - - /* - * Invoke all specified cleanup functions. If one or more produce an error, - * the context is in an unpredictable state, so all further testing should - * be cancelled. - */ - Test.prototype.cleanup = function () { - var errors = []; - var bad_value_count = 0; - function on_error(e) { - errors.push(e); - // Abort tests immediately so that tests declared within subsequent - // cleanup functions are not run. - tests.abort(); - } - var this_obj = this; - var results = []; - - this.phase = this.phases.CLEANING; - - if (this._abortController) { - this._abortController.abort("Test cleanup"); - } - - forEach(this.cleanup_callbacks, function (cleanup_callback) { - var result; - - try { - result = cleanup_callback(); - } catch (e) { - on_error(e); - return; - } - - if (!is_valid_cleanup_result(this_obj, result)) { - bad_value_count += 1; - // Abort tests immediately so that tests declared - // within subsequent cleanup functions are not run. - tests.abort(); - } - - results.push(result); - }); - - if (!this._is_promise_test) { - cleanup_done(this_obj, errors, bad_value_count); - } else { - all_async( - results, - function (result, done) { - if (result && typeof result.then === "function") { - result.then(null, on_error).then(done); - } else { - done(); - } - }, - function () { - cleanup_done(this_obj, errors, bad_value_count); - } - ); - } - }; - - /* - * Determine if the return value of a cleanup function is valid for a given - * test. Any test may return the value `undefined`. Tests created with - * `promise_test` may alternatively return "thenable" object values. - */ - function is_valid_cleanup_result(test, result) { - if (result === undefined) { - return true; - } - - if (test._is_promise_test) { - return result && typeof result.then === "function"; - } - - return false; - } - - function cleanup_done(test, errors, bad_value_count) { - if (errors.length || bad_value_count) { - var total = test._user_defined_cleanup_count; - - tests.status.status = tests.status.ERROR; - tests.status.stack = null; - tests.status.message = - "Test named '" + - test.name + - "' specified " + - total + - " 'cleanup' function" + - (total > 1 ? "s" : ""); - - if (errors.length) { - tests.status.message += ", and " + errors.length + " failed"; - tests.status.stack = - typeof errors[0] === "object" && errors[0].hasOwnProperty("stack") - ? errors[0].stack - : null; - } - - if (bad_value_count) { - var type = test._is_promise_test ? "non-thenable" : "non-undefined"; - tests.status.message += - ", and " + bad_value_count + " returned a " + type + " value"; - } - - tests.status.message += "."; - } - - test.phase = test.phases.COMPLETE; - tests.result(test); - forEach(test._done_callbacks, function (callback) { - callback(); - }); - test._done_callbacks.length = 0; - } - - /** - * Gives an AbortSignal that will be aborted when the test finishes. - */ - Test.prototype.get_signal = function () { - if (!this._abortController) { - throw new Error("AbortController is not supported in this browser"); - } - return this._abortController.signal; - }; - - /** - * A RemoteTest object mirrors a Test object on a remote worker. The - * associated RemoteWorker updates the RemoteTest object in response to - * received events. In turn, the RemoteTest object replicates these events - * on the local document. This allows listeners (test result reporting - * etc..) to transparently handle local and remote events. - */ - function RemoteTest(clone) { - var this_obj = this; - Object.keys(clone).forEach(function (key) { - this_obj[key] = clone[key]; - }); - this.index = null; - this.phase = this.phases.INITIAL; - this.update_state_from(clone); - this._done_callbacks = []; - tests.push(this); - } - - RemoteTest.prototype.structured_clone = function () { - var clone = {}; - Object.keys(this).forEach( - function (key) { - var value = this[key]; - // `RemoteTest` instances are responsible for managing - // their own "done" callback functions, so those functions - // are not relevant in other execution contexts. Because of - // this (and because Function values cannot be serialized - // for cross-realm transmittance), the property should not - // be considered when cloning instances. - if (key === "_done_callbacks") { - return; - } - - if (typeof value === "object" && value !== null) { - clone[key] = merge({}, value); - } else { - clone[key] = value; - } - }.bind(this) - ); - clone.phases = merge({}, this.phases); - return clone; - }; - - /** - * `RemoteTest` instances are objects which represent tests running in - * another realm. They do not define "cleanup" functions (if necessary, - * such functions are defined on the associated `Test` instance within the - * external realm). However, `RemoteTests` may have "done" callbacks (e.g. - * as attached by the `Tests` instance responsible for tracking the overall - * test status in the parent realm). The `cleanup` method delegates to - * `done` in order to ensure that such callbacks are invoked following the - * completion of the `RemoteTest`. - */ - RemoteTest.prototype.cleanup = function () { - this.done(); - }; - RemoteTest.prototype.phases = Test.prototype.phases; - RemoteTest.prototype.update_state_from = function (clone) { - this.status = clone.status; - this.message = clone.message; - this.stack = clone.stack; - if (this.phase === this.phases.INITIAL) { - this.phase = this.phases.STARTED; - } - }; - RemoteTest.prototype.done = function () { - this.phase = this.phases.COMPLETE; - - forEach(this._done_callbacks, function (callback) { - callback(); - }); - }; - - RemoteTest.prototype.format_status = function () { - return Test.prototype.status_formats[this.status]; - }; - - /* - * A RemoteContext listens for test events from a remote test context, such - * as another window or a worker. These events are then used to construct - * and maintain RemoteTest objects that mirror the tests running in the - * remote context. - * - * An optional third parameter can be used as a predicate to filter incoming - * MessageEvents. - */ - function RemoteContext(remote, message_target, message_filter) { - this.running = true; - this.started = false; - this.tests = new Array(); - this.early_exception = null; - - var this_obj = this; - // If remote context is cross origin assigning to onerror is not - // possible, so silently catch those errors. - try { - remote.onerror = function (error) { - this_obj.remote_error(error); - }; - } catch (e) { - // Ignore. - } - - // Keeping a reference to the remote object and the message handler until - // remote_done() is seen prevents the remote object and its message channel - // from going away before all the messages are dispatched. - this.remote = remote; - this.message_target = message_target; - this.message_handler = function (message) { - var passesFilter = !message_filter || message_filter(message); - // The reference to the `running` property in the following - // condition is unnecessary because that value is only set to - // `false` after the `message_handler` function has been - // unsubscribed. - // TODO: Simplify the condition by removing the reference. - if ( - this_obj.running && - message.data && - passesFilter && - message.data.type in this_obj.message_handlers - ) { - this_obj.message_handlers[message.data.type].call( - this_obj, - message.data - ); - } - }; - - if (self.Promise) { - this.done = new Promise(function (resolve) { - this_obj.doneResolve = resolve; - }); - } - - this.message_target.addEventListener("message", this.message_handler); - } - - RemoteContext.prototype.remote_error = function (error) { - if (error.preventDefault) { - error.preventDefault(); - } - - // Defer interpretation of errors until the testing protocol has - // started and the remote test's `allow_uncaught_exception` property - // is available. - if (!this.started) { - this.early_exception = error; - } else if (!this.allow_uncaught_exception) { - this.report_uncaught(error); - } - }; - - RemoteContext.prototype.report_uncaught = function (error) { - var message = error.message || String(error); - var filename = error.filename ? " " + error.filename : ""; - // FIXME: Display remote error states separately from main document - // error state. - tests.set_status( - tests.status.ERROR, - "Error in remote" + filename + ": " + message, - error.stack - ); - }; - - RemoteContext.prototype.start = function (data) { - this.started = true; - this.allow_uncaught_exception = data.properties.allow_uncaught_exception; - - if (this.early_exception && !this.allow_uncaught_exception) { - this.report_uncaught(this.early_exception); - } - }; - - RemoteContext.prototype.test_state = function (data) { - var remote_test = this.tests[data.test.index]; - if (!remote_test) { - remote_test = new RemoteTest(data.test); - this.tests[data.test.index] = remote_test; - } - remote_test.update_state_from(data.test); - tests.notify_test_state(remote_test); - }; - - RemoteContext.prototype.test_done = function (data) { - var remote_test = this.tests[data.test.index]; - remote_test.update_state_from(data.test); - remote_test.done(); - tests.result(remote_test); - }; - - RemoteContext.prototype.remote_done = function (data) { - if ( - tests.status.status === null && - data.status.status !== data.status.OK - ) { - tests.set_status( - data.status.status, - data.status.message, - data.status.stack - ); - } - - for (let assert of data.asserts) { - var record = new AssertRecord(); - record.assert_name = assert.assert_name; - record.args = assert.args; - record.test = - assert.test != null ? this.tests[assert.test.index] : null; - record.status = assert.status; - record.stack = assert.stack; - tests.asserts_run.push(record); - } - - this.message_target.removeEventListener("message", this.message_handler); - this.running = false; - - // If remote context is cross origin assigning to onerror is not - // possible, so silently catch those errors. - try { - this.remote.onerror = null; - } catch (e) { - // Ignore. - } - - this.remote = null; - this.message_target = null; - if (this.doneResolve) { - this.doneResolve(); - } - - if (tests.all_done()) { - tests.complete(); - } - }; - - RemoteContext.prototype.message_handlers = { - start: RemoteContext.prototype.start, - test_state: RemoteContext.prototype.test_state, - result: RemoteContext.prototype.test_done, - complete: RemoteContext.prototype.remote_done, - }; - - /** - * @class - * Status of the overall harness - */ - function TestsStatus() { - /** The status code */ - this.status = null; - /** Message in case of failure */ - this.message = null; - /** Stack trace in case of an exception. */ - this.stack = null; - } - - /** - * Enum of possible harness statuses. - * - * :values: - * - ``OK`` - * - ``ERROR`` - * - ``TIMEOUT`` - * - ``PRECONDITION_FAILED`` - */ - TestsStatus.statuses = { - OK: 0, - ERROR: 1, - TIMEOUT: 2, - PRECONDITION_FAILED: 3, - }; - - TestsStatus.prototype = merge({}, TestsStatus.statuses); - - TestsStatus.prototype.formats = { - 0: "OK", - 1: "Error", - 2: "Timeout", - 3: "Optional Feature Unsupported", - }; - - TestsStatus.prototype.structured_clone = function () { - if (!this._structured_clone) { - var msg = this.message; - msg = msg ? String(msg) : msg; - this._structured_clone = merge( - { - status: this.status, - message: msg, - stack: this.stack, - }, - TestsStatus.statuses - ); - } - return this._structured_clone; - }; - - TestsStatus.prototype.format_status = function () { - return this.formats[this.status]; - }; - - /** - * @class - * Record of an assert that ran. - * - * @param {Test} test - The test which ran the assert. - * @param {string} assert_name - The function name of the assert. - * @param {Any} args - The arguments passed to the assert function. - */ - function AssertRecord(test, assert_name, args = []) { - /** Name of the assert that ran */ - this.assert_name = assert_name; - /** Test that ran the assert */ - this.test = test; - // Avoid keeping complex objects alive - /** Stringification of the arguments that were passed to the assert function */ - this.args = args.map((x) => format_value(x).replace(/\n/g, " ")); - /** Status of the assert */ - this.status = null; - } - - AssertRecord.prototype.structured_clone = function () { - return { - assert_name: this.assert_name, - test: this.test ? this.test.structured_clone() : null, - args: this.args, - status: this.status, - }; - }; - - function Tests() { - this.tests = []; - this.num_pending = 0; - - this.phases = { - INITIAL: 0, - SETUP: 1, - HAVE_TESTS: 2, - HAVE_RESULTS: 3, - COMPLETE: 4, - }; - this.phase = this.phases.INITIAL; - - this.properties = {}; - - this.wait_for_finish = false; - this.processing_callbacks = false; - - this.allow_uncaught_exception = false; - - this.file_is_test = false; - // This 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(); - this.timeout_id = null; - - this.start_callbacks = []; - this.test_state_callbacks = []; - this.test_done_callbacks = []; - this.all_done_callbacks = []; - - this.hide_test_state = false; - this.pending_remotes = []; - - this.current_test = null; - this.asserts_run = []; - - // Track whether output is enabled, and thus whether or not we should - // track asserts. - // - // On workers we don't get properties set from testharnessreport.js, so - // we don't know whether or not to track asserts. To avoid the - // resulting performance hit, we assume we are not meant to. This means - // that assert tracking does not function on workers. - this.output = settings.output && "document" in global_scope; - - this.status = new TestsStatus(); - - var this_obj = this; - - test_environment.add_on_loaded_callback(function () { - if (this_obj.all_done()) { - this_obj.complete(); - } - }); - - this.set_timeout(); - } - - Tests.prototype.setup = function (func, properties) { - if (this.phase >= this.phases.HAVE_RESULTS) { - return; - } - - if (this.phase < this.phases.SETUP) { - this.phase = this.phases.SETUP; - } - - this.properties = properties; - - for (var p in properties) { - if (properties.hasOwnProperty(p)) { - var value = properties[p]; - if (p == "allow_uncaught_exception") { - this.allow_uncaught_exception = value; - } else if (p == "explicit_done" && value) { - this.wait_for_finish = true; - } else if (p == "explicit_timeout" && value) { - this.timeout_length = null; - if (this.timeout_id) { - clearTimeout(this.timeout_id); - } - } else if (p == "single_test" && value) { - this.set_file_is_test(); - } else if (p == "timeout_multiplier") { - this.timeout_multiplier = value; - if (this.timeout_length) { - this.timeout_length *= this.timeout_multiplier; - } - } else if (p == "hide_test_state") { - this.hide_test_state = value; - } else if (p == "output") { - this.output = value; - } else if (p === "debug") { - settings.debug = value; - } - } - } - - if (func) { - try { - func(); - } catch (e) { - 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(); - }; - - Tests.prototype.set_file_is_test = function () { - if (this.tests.length > 0) { - throw new Error("Tried to set file as test after creating a test"); - } - this.wait_for_finish = true; - this.file_is_test = true; - // Create the test, which will add it to the list of tests - tests.current_test = async_test(); - }; - - Tests.prototype.set_status = function (status, message, stack) { - this.status.status = status; - this.status.message = message; - this.status.stack = stack ? stack : null; - }; - - Tests.prototype.set_timeout = function () { - if (global_scope.clearTimeout) { - var this_obj = this; - clearTimeout(this.timeout_id); - if (this.timeout_length !== null) { - this.timeout_id = setTimeout(function () { - this_obj.timeout(); - }, this.timeout_length); - } - } - }; - - Tests.prototype.timeout = function () { - var test_in_cleanup = null; - - if (this.status.status === null) { - forEach(this.tests, function (test) { - // No more than one test is expected to be in the - // "CLEANUP" phase at any time - if (test.phase === test.phases.CLEANING) { - test_in_cleanup = test; - } - - test.phase = test.phases.COMPLETE; - }); - - // Timeouts that occur while a test is in the "cleanup" phase - // indicate that some global state was not properly reverted. This - // invalidates the overall test execution, so the timeout should be - // reported as an error and cancel the execution of any remaining - // tests. - if (test_in_cleanup) { - this.status.status = this.status.ERROR; - this.status.message = - "Timeout while running cleanup for " + - 'test named "' + - test_in_cleanup.name + - '".'; - tests.status.stack = null; - } else { - this.status.status = this.status.TIMEOUT; - } - } - - this.complete(); - }; - - Tests.prototype.end_wait = function () { - this.wait_for_finish = false; - if (this.all_done()) { - this.complete(); - } - }; - - Tests.prototype.push = function (test) { - if (this.phase < this.phases.HAVE_TESTS) { - this.start(); - } - this.num_pending++; - test.index = this.tests.push(test); - this.notify_test_state(test); - }; - - Tests.prototype.notify_test_state = function (test) { - var this_obj = this; - forEach(this.test_state_callbacks, function (callback) { - callback(test, this_obj); - }); - }; - - Tests.prototype.all_done = function () { - return ( - (this.tests.length > 0 || this.pending_remotes.length > 0) && - test_environment.all_loaded && - (this.num_pending === 0 || this.is_aborted) && - !this.wait_for_finish && - !this.processing_callbacks && - !this.pending_remotes.some(function (w) { - return w.running; - }) - ); - }; - - Tests.prototype.start = function () { - this.phase = this.phases.HAVE_TESTS; - this.notify_start(); - }; - - Tests.prototype.notify_start = function () { - var this_obj = this; - forEach(this.start_callbacks, function (callback) { - callback(this_obj.properties); - }); - }; - - Tests.prototype.result = function (test) { - // If the harness has already transitioned beyond the `HAVE_RESULTS` - // phase, subsequent tests should not cause it to revert. - if (this.phase <= this.phases.HAVE_RESULTS) { - this.phase = this.phases.HAVE_RESULTS; - } - this.num_pending--; - this.notify_result(test); - }; - - Tests.prototype.notify_result = function (test) { - var this_obj = this; - this.processing_callbacks = true; - forEach(this.test_done_callbacks, function (callback) { - callback(test, this_obj); - }); - this.processing_callbacks = false; - if (this_obj.all_done()) { - this_obj.complete(); - } - }; - - Tests.prototype.complete = function () { - if (this.phase === this.phases.COMPLETE) { - return; - } - var this_obj = this; - var all_complete = function () { - this_obj.phase = this_obj.phases.COMPLETE; - this_obj.notify_complete(); - }; - var incomplete = filter(this.tests, function (test) { - return test.phase < test.phases.COMPLETE; - }); - - /** - * To preserve legacy behavior, overall test completion must be - * signaled synchronously. - */ - if (incomplete.length === 0) { - all_complete(); - return; - } - - all_async( - incomplete, - function (test, testDone) { - if (test.phase === test.phases.INITIAL) { - test.phase = test.phases.COMPLETE; - testDone(); - } else { - add_test_done_callback(test, testDone); - test.cleanup(); - } - }, - all_complete - ); - }; - - Tests.prototype.set_assert = function (assert_name, args) { - this.asserts_run.push( - new AssertRecord(this.current_test, assert_name, args) - ); - }; - - Tests.prototype.set_assert_status = function (index, status, stack) { - let assert_record = this.asserts_run[index]; - assert_record.status = status; - assert_record.stack = stack; - }; - - /** - * Update the harness status to reflect an unrecoverable harness error that - * should cancel all further testing. Update all previously-defined tests - * which have not yet started to indicate that they will not be executed. - */ - Tests.prototype.abort = function () { - this.status.status = this.status.ERROR; - this.is_aborted = true; - - forEach(this.tests, function (test) { - if (test.phase === test.phases.INITIAL) { - test.phase = test.phases.COMPLETE; - } - }); - }; - - /* - * Determine if any tests share the same `name` property. Return an array - * containing the names of any such duplicates. - */ - Tests.prototype.find_duplicates = function () { - var names = Object.create(null); - var duplicates = []; - - forEach(this.tests, function (test) { - if (test.name in names && duplicates.indexOf(test.name) === -1) { - duplicates.push(test.name); - } - names[test.name] = true; - }); - - return duplicates; - }; - - function code_unit_str(char) { - return "U+" + char.charCodeAt(0).toString(16); - } - - function sanitize_unpaired_surrogates(str) { - return str.replace( - /([\ud800-\udbff]+)(?![\udc00-\udfff])|(^|[^\ud800-\udbff])([\udc00-\udfff]+)/g, - function (_, low, prefix, high) { - var output = prefix || ""; // prefix may be undefined - var string = low || high; // only one of these alternates can match - for (var i = 0; i < string.length; i++) { - output += code_unit_str(string[i]); - } - return output; - } - ); - } - - function sanitize_all_unpaired_surrogates(tests) { - forEach(tests, function (test) { - var sanitized = sanitize_unpaired_surrogates(test.name); - - if (test.name !== sanitized) { - test.name = sanitized; - delete test._structured_clone; - } - }); - } - - Tests.prototype.notify_complete = function () { - var this_obj = this; - var duplicates; - - if (this.status.status === null) { - duplicates = this.find_duplicates(); - - // Some transports adhere to UTF-8's restriction on unpaired - // surrogates. Sanitize the titles so that the results can be - // consistently sent via all transports. - sanitize_all_unpaired_surrogates(this.tests); - - // Test names are presumed to be unique within test files--this - // allows consumers to use them for identification purposes. - // Duplicated names violate this expectation and should therefore - // be reported as an error. - if (duplicates.length) { - this.status.status = this.status.ERROR; - this.status.message = - duplicates.length + - " duplicate test name" + - (duplicates.length > 1 ? "s" : "") + - ': "' + - duplicates.join('", "') + - '"'; - } else { - this.status.status = this.status.OK; - } - } - - forEach(this.all_done_callbacks, function (callback) { - callback(this_obj.tests, this_obj.status, this_obj.asserts_run); - }); - }; - - /* - * Constructs a RemoteContext that tracks tests from a specific worker. - */ - Tests.prototype.create_remote_worker = function (worker) { - var message_port; - - if (is_service_worker(worker)) { - message_port = navigator.serviceWorker; - worker.postMessage({ type: "connect" }); - } else if (is_shared_worker(worker)) { - message_port = worker.port; - message_port.start(); - } else { - message_port = worker; - } - - return new RemoteContext(worker, message_port); - }; - - /* - * Constructs a RemoteContext that tracks tests from a specific window. - */ - Tests.prototype.create_remote_window = function (remote) { - remote.postMessage({ type: "getmessages" }, "*"); - return new RemoteContext(remote, window, function (msg) { - return msg.source === remote; - }); - }; - - Tests.prototype.fetch_tests_from_worker = function (worker) { - if (this.phase >= this.phases.COMPLETE) { - return; - } - - var remoteContext = this.create_remote_worker(worker); - this.pending_remotes.push(remoteContext); - return remoteContext.done; - }; - - /** - * Get test results from a worker and include them in the current test. - * - * @param {Worker|SharedWorker|ServiceWorker|MessagePort} port - - * Either a worker object or a port connected to a worker which is - * running tests.. - * @returns {Promise} - A promise that's resolved once all the remote tests are complete. - */ - function fetch_tests_from_worker(port) { - return tests.fetch_tests_from_worker(port); - } - expose(fetch_tests_from_worker, "fetch_tests_from_worker"); - - Tests.prototype.fetch_tests_from_window = function (remote) { - if (this.phase >= this.phases.COMPLETE) { - return; - } - - var remoteContext = this.create_remote_window(remote); - this.pending_remotes.push(remoteContext); - return remoteContext.done; - }; - - /** - * Aggregate tests from separate windows or iframes - * into the current document as if they were all part of the same test file. - * - * The document of the second window (or iframe) should include - * ``testharness.js``, but not ``testharnessreport.js``, and use - * :js:func:`test`, :js:func:`async_test`, and :js:func:`promise_test` in - * the usual manner. - * - * @param {Window} window - The window to fetch tests from. - */ - function fetch_tests_from_window(window) { - return tests.fetch_tests_from_window(window); - } - expose(fetch_tests_from_window, "fetch_tests_from_window"); - - /** - * Get test results from a shadow realm and include them in the current test. - * - * @param {ShadowRealm} realm - A shadow realm also running the test harness - * @returns {Promise} - A promise that's resolved once all the remote tests are complete. - */ - function fetch_tests_from_shadow_realm(realm) { - var chan = new MessageChannel(); - function receiveMessage(msg_json) { - chan.port1.postMessage(JSON.parse(msg_json)); - } - var done = tests.fetch_tests_from_worker(chan.port2); - realm.evaluate("begin_shadow_realm_tests")(receiveMessage); - chan.port2.start(); - return done; - } - expose(fetch_tests_from_shadow_realm, "fetch_tests_from_shadow_realm"); - - /** - * Begin running tests in this shadow realm test harness. - * - * To be called after all tests have been loaded; it is an error to call - * this more than once or in a non-Shadow Realm environment - * - * @param {Function} postMessage - A function to send test updates to the - * incubating realm-- accepts JSON-encoded messages in the format used by - * RemoteContext - */ - function begin_shadow_realm_tests(postMessage) { - if (!(test_environment instanceof ShadowRealmTestEnvironment)) { - throw new Error( - "begin_shadow_realm_tests called in non-Shadow Realm environment" - ); - } - - test_environment.begin(function (msg) { - postMessage(JSON.stringify(msg)); - }); - } - expose(begin_shadow_realm_tests, "begin_shadow_realm_tests"); - - /** - * Timeout the tests. - * - * This only has an effect when ``explicit_timeout`` has been set - * in :js:func:`setup`. In other cases any call is a no-op. - * - */ - function timeout() { - if (tests.timeout_length === null) { - tests.timeout(); - } - } - expose(timeout, "timeout"); - - /** - * Add a callback that's triggered when the first :js:class:`Test` is created. - * - * @param {Function} callback - Callback function. This is called - * without arguments. - */ - function add_start_callback(callback) { - tests.start_callbacks.push(callback); - } - - /** - * Add a callback that's triggered when a test state changes. - * - * @param {Function} callback - Callback function, called with the - * :js:class:`Test` as the only argument. - */ - function add_test_state_callback(callback) { - tests.test_state_callbacks.push(callback); - } - - /** - * Add a callback that's triggered when a test result is received. - * - * @param {Function} callback - Callback function, called with the - * :js:class:`Test` as the only argument. - */ - function add_result_callback(callback) { - tests.test_done_callbacks.push(callback); - } - - /** - * Add a callback that's triggered when all tests are complete. - * - * @param {Function} callback - Callback function, called with an - * array of :js:class:`Test` objects, a :js:class:`TestsStatus` - * object and an array of :js:class:`AssertRecord` objects. If the - * debug setting is ``false`` the final argument will be an empty - * array. - * - * For performance reasons asserts are only tracked when the debug - * setting is ``true``. In other cases the array of asserts will be - * empty. - */ - function add_completion_callback(callback) { - tests.all_done_callbacks.push(callback); - } - - expose(add_start_callback, "add_start_callback"); - expose(add_test_state_callback, "add_test_state_callback"); - expose(add_result_callback, "add_result_callback"); - expose(add_completion_callback, "add_completion_callback"); - - function remove(array, item) { - var index = array.indexOf(item); - if (index > -1) { - array.splice(index, 1); - } - } - - function remove_start_callback(callback) { - remove(tests.start_callbacks, callback); - } - - function remove_test_state_callback(callback) { - remove(tests.test_state_callbacks, callback); - } - - function remove_result_callback(callback) { - remove(tests.test_done_callbacks, callback); - } - - function remove_completion_callback(callback) { - remove(tests.all_done_callbacks, callback); - } - - /* - * Output listener - */ - - function Output() { - this.output_document = document; - this.output_node = null; - this.enabled = settings.output; - this.phase = this.INITIAL; - } - - Output.prototype.INITIAL = 0; - Output.prototype.STARTED = 1; - Output.prototype.HAVE_RESULTS = 2; - Output.prototype.COMPLETE = 3; - - Output.prototype.setup = function (properties) { - if (this.phase > this.INITIAL) { - return; - } - - //If output is disabled in testharnessreport.js the test shouldn't be - //able to override that - this.enabled = - this.enabled && - (properties.hasOwnProperty("output") - ? properties.output - : settings.output); - }; - - Output.prototype.init = function (properties) { - if (this.phase >= this.STARTED) { - return; - } - if (properties.output_document) { - this.output_document = properties.output_document; - } else { - this.output_document = document; - } - this.phase = this.STARTED; - }; - - Output.prototype.resolve_log = function () { - var output_document; - if (this.output_node) { - return; - } - if (typeof this.output_document === "function") { - output_document = this.output_document.apply(undefined); - } else { - output_document = this.output_document; - } - if (!output_document) { - return; - } - var node = output_document.getElementById("log"); - if (!node) { - if (output_document.readyState === "loading") { - return; - } - node = output_document.createElementNS( - "http://www.w3.org/1999/xhtml", - "div" - ); - node.id = "log"; - if (output_document.body) { - output_document.body.appendChild(node); - } else { - var root = output_document.documentElement; - var is_html = - root && - root.namespaceURI == "http://www.w3.org/1999/xhtml" && - root.localName == "html"; - var is_svg = - output_document.defaultView && - "SVGSVGElement" in output_document.defaultView && - root instanceof output_document.defaultView.SVGSVGElement; - if (is_svg) { - var foreignObject = output_document.createElementNS( - "http://www.w3.org/2000/svg", - "foreignObject" - ); - foreignObject.setAttribute("width", "100%"); - foreignObject.setAttribute("height", "100%"); - root.appendChild(foreignObject); - foreignObject.appendChild(node); - } else if (is_html) { - root - .appendChild( - output_document.createElementNS( - "http://www.w3.org/1999/xhtml", - "body" - ) - ) - .appendChild(node); - } else { - root.appendChild(node); - } - } - } - this.output_document = output_document; - this.output_node = node; - }; - - Output.prototype.show_status = function () { - if (this.phase < this.STARTED) { - this.init({}); - } - if (!this.enabled || this.phase === this.COMPLETE) { - return; - } - this.resolve_log(); - if (this.phase < this.HAVE_RESULTS) { - this.phase = this.HAVE_RESULTS; - } - var done_count = tests.tests.length - tests.num_pending; - if (this.output_node && !tests.hide_test_state) { - if ( - done_count < 100 || - (done_count < 1000 && done_count % 100 === 0) || - done_count % 1000 === 0 - ) { - this.output_node.textContent = - "Running, " + - done_count + - " complete, " + - tests.num_pending + - " remain"; - } - } - }; - - Output.prototype.show_results = function ( - tests, - harness_status, - asserts_run - ) { - if (this.phase >= this.COMPLETE) { - return; - } - if (!this.enabled) { - return; - } - if (!this.output_node) { - this.resolve_log(); - } - this.phase = this.COMPLETE; - - var log = this.output_node; - if (!log) { - return; - } - var output_document = this.output_document; - - while (log.lastChild) { - log.removeChild(log.lastChild); - } - - var stylesheet = output_document.createElementNS(xhtml_ns, "style"); - stylesheet.textContent = stylesheetContent; - var heads = output_document.getElementsByTagName("head"); - if (heads.length) { - heads[0].appendChild(stylesheet); - } - - var status_number = {}; - forEach(tests, function (test) { - var status = test.format_status(); - if (status_number.hasOwnProperty(status)) { - status_number[status] += 1; - } else { - status_number[status] = 1; - } - }); - - function status_class(status) { - return status.replace(/\s/g, "").toLowerCase(); - } - - var summary_template = [ - "section", - { id: "summary" }, - ["h2", {}, "Summary"], - function () { - var status = harness_status.format_status(); - var rv = [ - [ - "section", - {}, - [ - "p", - {}, - "Harness status: ", - ["span", { class: status_class(status) }, status], - ], - ["button", { id: "rerun" }, "Rerun"], - ], - ]; - - if (harness_status.status === harness_status.ERROR) { - rv[0].push(["pre", {}, harness_status.message]); - if (harness_status.stack) { - rv[0].push(["pre", {}, harness_status.stack]); - } - } - return rv; - }, - ["p", {}, "Found ${num_tests} tests"], - function () { - var rv = [["div", {}]]; - var i = 0; - while (Test.prototype.status_formats.hasOwnProperty(i)) { - if ( - status_number.hasOwnProperty(Test.prototype.status_formats[i]) - ) { - var status = Test.prototype.status_formats[i]; - rv[0].push([ - "div", - {}, - [ - "label", - {}, - ["input", { type: "checkbox", checked: "checked" }], - status_number[status] + " ", - ["span", { class: status_class(status) }, status], - ], - ]); - } - i++; - } - return rv; - }, - ]; - - log.appendChild( - render(summary_template, { num_tests: tests.length }, output_document) - ); - - output_document - .getElementById("rerun") - .addEventListener("click", function () { - let evt = new Event("__test_restart"); - let canceled = !window.dispatchEvent(evt); - if (!canceled) { - location.reload(); - } - }); - - forEach( - output_document.querySelectorAll("section#summary label"), - function (element) { - on_event(element, "click", function (e) { - if (output_document.getElementById("results") === null) { - e.preventDefault(); - return; - } - var result_class = element - .querySelector("span[class]") - .getAttribute("class"); - var style_element = output_document.querySelector( - "style#hide-" + result_class - ); - var input_element = element.querySelector("input"); - if (!style_element && !input_element.checked) { - style_element = output_document.createElementNS( - xhtml_ns, - "style" - ); - style_element.id = "hide-" + result_class; - style_element.textContent = - "table#results > tbody > tr.overall-" + - result_class + - "{display:none}"; - output_document.body.appendChild(style_element); - } else if (style_element && input_element.checked) { - style_element.parentNode.removeChild(style_element); - } - }); - } - ); - - function has_assertions() { - for (var i = 0; i < tests.length; i++) { - if (tests[i].properties.hasOwnProperty("assert")) { - return true; - } - } - return false; - } - - function get_assertion(test) { - if (test.properties.hasOwnProperty("assert")) { - if (Array.isArray(test.properties.assert)) { - return test.properties.assert.join(" "); - } - return test.properties.assert; - } - return ""; - } - - var asserts_run_by_test = new Map(); - asserts_run.forEach((assert) => { - if (!asserts_run_by_test.has(assert.test)) { - asserts_run_by_test.set(assert.test, []); - } - asserts_run_by_test.get(assert.test).push(assert); - }); - - function get_asserts_output(test) { - const asserts_output = render([ - "details", - {}, - ["summary", {}, "Asserts run"], - ["table", {}, ""], - ]); - - var asserts = asserts_run_by_test.get(test); - if (!asserts) { - asserts_output - .querySelector("summary") - .insertAdjacentText("afterend", "No asserts ran"); - return asserts_output; - } - - const table = asserts_output.querySelector("table"); - for (const assert of asserts) { - const status_class_name = status_class( - Test.prototype.status_formats[assert.status] - ); - var output_fn = "(" + assert.args.join(", ") + ")"; - if (assert.stack) { - output_fn += "\n"; - output_fn += assert.stack - .split("\n", 1)[0] - .replace(/@?\w+:\/\/[^ "\/]+(?::\d+)?/g, " "); - } - table.appendChild( - render([ - "tr", - { class: "overall-" + status_class_name }, - [ - "td", - { class: status_class_name }, - Test.prototype.status_formats[assert.status], - ], - [ - "td", - {}, - ["pre", {}, ["strong", {}, assert.assert_name], output_fn], - ], - ]) - ); - } - return asserts_output; - } - - var assertions = has_assertions(); - const section = render([ - "section", - {}, - ["h2", {}, "Details"], - [ - "table", - { id: "results", class: assertions ? "assertions" : "" }, - [ - "thead", - {}, - [ - "tr", - {}, - ["th", {}, "Result"], - ["th", {}, "Test Name"], - assertions ? ["th", {}, "Assertion"] : "", - ["th", {}, "Message"], - ], - ], - ["tbody", {}], - ], - ]); - - const tbody = section.querySelector("tbody"); - for (const test of tests) { - const status = test.format_status(); - const status_class_name = status_class(status); - tbody.appendChild( - render([ - "tr", - { class: "overall-" + status_class_name }, - ["td", { class: status_class_name }, status], - ["td", {}, test.name], - assertions ? ["td", {}, get_assertion(test)] : "", - ["td", {}, test.message ?? "", ["pre", {}, test.stack ?? ""]], - ]) - ); - if (!(test instanceof RemoteTest)) { - tbody.lastChild.lastChild.appendChild(get_asserts_output(test)); - } - } - log.appendChild(section); - }; - - /* - * Template code - * - * A template is just a JavaScript structure. An element is represented as: - * - * [tag_name, {attr_name:attr_value}, child1, child2] - * - * the children can either be strings (which act like text nodes), other templates or - * functions (see below) - * - * A text node is represented as - * - * ["{text}", value] - * - * String values have a simple substitution syntax; ${foo} represents a variable foo. - * - * It is possible to embed logic in templates by using a function in a place where a - * node would usually go. The function must either return part of a template or null. - * - * In cases where a set of nodes are required as output rather than a single node - * with children it is possible to just use a list - * [node1, node2, node3] - * - * Usage: - * - * render(template, substitutions) - take a template and an object mapping - * variable names to parameters and return either a DOM node or a list of DOM nodes - * - * substitute(template, substitutions) - take a template and variable mapping object, - * make the variable substitutions and return the substituted template - * - */ - - function is_single_node(template) { - return typeof template[0] === "string"; - } - - function substitute(template, substitutions) { - if (typeof template === "function") { - var replacement = template(substitutions); - if (!replacement) { - return null; - } - - return substitute(replacement, substitutions); - } - - if (is_single_node(template)) { - return substitute_single(template, substitutions); - } - - return filter( - map(template, function (x) { - return substitute(x, substitutions); - }), - function (x) { - return x !== null; - } - ); - } - - function substitute_single(template, substitutions) { - var substitution_re = /\$\{([^ }]*)\}/g; - - function do_substitution(input) { - var components = input.split(substitution_re); - var rv = []; - for (var i = 0; i < components.length; i += 2) { - rv.push(components[i]); - if (components[i + 1]) { - rv.push(String(substitutions[components[i + 1]])); - } - } - return rv; - } - - function substitute_attrs(attrs, rv) { - rv[1] = {}; - for (var name in template[1]) { - if (attrs.hasOwnProperty(name)) { - var new_name = do_substitution(name).join(""); - var new_value = do_substitution(attrs[name]).join(""); - rv[1][new_name] = new_value; - } - } - } - - function substitute_children(children, rv) { - for (var i = 0; i < children.length; i++) { - if (children[i] instanceof Object) { - var replacement = substitute(children[i], substitutions); - if (replacement !== null) { - if (is_single_node(replacement)) { - rv.push(replacement); - } else { - extend(rv, replacement); - } - } - } else { - extend(rv, do_substitution(String(children[i]))); - } - } - return rv; - } - - var rv = []; - rv.push(do_substitution(String(template[0])).join("")); - - if (template[0] === "{text}") { - substitute_children(template.slice(1), rv); - } else { - substitute_attrs(template[1], rv); - substitute_children(template.slice(2), rv); - } - - return rv; - } - - function make_dom_single(template, doc) { - var output_document = doc || document; - var element; - if (template[0] === "{text}") { - element = output_document.createTextNode(""); - for (var i = 1; i < template.length; i++) { - element.data += template[i]; - } - } else { - element = output_document.createElementNS(xhtml_ns, template[0]); - for (var name in template[1]) { - if (template[1].hasOwnProperty(name)) { - element.setAttribute(name, template[1][name]); - } - } - for (var i = 2; i < template.length; i++) { - if (template[i] instanceof Object) { - var sub_element = make_dom(template[i]); - element.appendChild(sub_element); - } else { - var text_node = output_document.createTextNode(template[i]); - element.appendChild(text_node); - } - } - } - - return element; - } - - function make_dom(template, substitutions, output_document) { - if (is_single_node(template)) { - return make_dom_single(template, output_document); - } - - return map(template, function (x) { - return make_dom_single(x, output_document); - }); - } - - function render(template, substitutions, output_document) { - return make_dom(substitute(template, substitutions), output_document); - } - - /* - * Utility functions - */ - function assert( - expected_true, - function_name, - description, - error, - substitutions - ) { - if (expected_true !== true) { - var msg = make_message( - function_name, - description, - error, - substitutions - ); - throw new AssertionError(msg); - } - } - - /** - * @class - * Exception type that represents a failing assert. - * - * @param {string} message - Error message. - */ - function AssertionError(message) { - if (typeof message == "string") { - message = sanitize_unpaired_surrogates(message); - } - this.message = message; - this.stack = get_stack(); - } - expose(AssertionError, "AssertionError"); - - AssertionError.prototype = Object.create(Error.prototype); - - const get_stack = function () { - var stack = new Error().stack; - - // 'Error.stack' is not supported in all browsers/versions - if (!stack) { - return "(Stack trace unavailable)"; - } - - var lines = stack.split("\n"); - - // Create a pattern to match stack frames originating within testharness.js. These include the - // script URL, followed by the line/col (e.g., '/resources/testharness.js:120:21'). - // Escape the URL per http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript - // in case it contains RegExp characters. - var script_url = get_script_url(); - var re_text = script_url - ? script_url.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") - : "\\btestharness.js"; - var re = new RegExp(re_text + ":\\d+:\\d+"); - - // Some browsers include a preamble that specifies the type of the error object. Skip this by - // advancing until we find the first stack frame originating from testharness.js. - var i = 0; - while (!re.test(lines[i]) && i < lines.length) { - i++; - } - - // Then skip the top frames originating from testharness.js to begin the stack at the test code. - while (re.test(lines[i]) && i < lines.length) { - i++; - } - - // Paranoid check that we didn't skip all frames. If so, return the original stack unmodified. - if (i >= lines.length) { - return stack; - } - - return lines.slice(i).join("\n"); - }; - - function 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) { - if (substitutions.hasOwnProperty(p)) { - substitutions[p] = format_value(substitutions[p]); - } - } - var node_form = substitute( - ["{text}", "${function_name}: ${description}" + error], - merge( - { - function_name: function_name, - description: description ? description + " " : "", - }, - substitutions - ) - ); - return node_form.slice(1).join(""); - } - - function filter(array, callable, thisObj) { - var rv = []; - for (var i = 0; i < array.length; i++) { - if (array.hasOwnProperty(i)) { - var pass = callable.call(thisObj, array[i], i, array); - if (pass) { - rv.push(array[i]); - } - } - } - return rv; - } - - function map(array, callable, thisObj) { - var rv = []; - rv.length = array.length; - for (var i = 0; i < array.length; i++) { - if (array.hasOwnProperty(i)) { - rv[i] = callable.call(thisObj, array[i], i, array); - } - } - return rv; - } - - function extend(array, items) { - Array.prototype.push.apply(array, items); - } - - function forEach(array, callback, thisObj) { - for (var i = 0; i < array.length; i++) { - if (array.hasOwnProperty(i)) { - callback.call(thisObj, array[i], i, array); - } - } - } - - /** - * Immediately invoke a "iteratee" function with a series of values in - * parallel and invoke a final "done" function when all of the "iteratee" - * invocations have signaled completion. - * - * If all callbacks complete synchronously (or if no callbacks are - * specified), the ``done_callback`` will be invoked synchronously. It is the - * responsibility of the caller to ensure asynchronicity in cases where - * that is desired. - * - * @param {array} value Zero or more values to use in the invocation of - * ``iter_callback`` - * @param {function} iter_callback A function that will be invoked - * once for each of the values min - * ``value``. Two arguments will - * be available in each - * invocation: the value from - * ``value`` and a function that - * must be invoked to signal - * completion - * @param {function} done_callback A function that will be invoked after - * all operations initiated by the - * ``iter_callback`` function have signaled - * completion - */ - function all_async(values, iter_callback, done_callback) { - var remaining = values.length; - - if (remaining === 0) { - done_callback(); - } - - forEach(values, function (element) { - var invoked = false; - var elDone = function () { - if (invoked) { - return; - } - - invoked = true; - remaining -= 1; - - if (remaining === 0) { - done_callback(); - } - }; - - iter_callback(element, elDone); - }); - } - - function merge(a, b) { - var rv = {}; - var p; - for (p in a) { - rv[p] = a[p]; - } - for (p in b) { - rv[p] = b[p]; - } - return rv; - } - - function expose(object, name) { - var components = name.split("."); - var target = global_scope; - for (var i = 0; i < components.length - 1; i++) { - if (!(components[i] in target)) { - target[components[i]] = {}; - } - target = target[components[i]]; - } - target[components[components.length - 1]] = object; - } - - function is_same_origin(w) { - try { - "random_prop" in w; - return true; - } catch (e) { - return false; - } - } - - /** Returns the 'src' URL of the first