diff --git a/JSTests/AGENTS.md b/JSTests/AGENTS.md new file mode 100644 index 0000000000000..99ee350f47d09 --- /dev/null +++ b/JSTests/AGENTS.md @@ -0,0 +1 @@ +@README.md diff --git a/JSTests/CLAUDE.md b/JSTests/CLAUDE.md new file mode 100644 index 0000000000000..99ee350f47d09 --- /dev/null +++ b/JSTests/CLAUDE.md @@ -0,0 +1 @@ +@README.md diff --git a/JSTests/README.md b/JSTests/README.md new file mode 100644 index 0000000000000..4a8bb4f572917 --- /dev/null +++ b/JSTests/README.md @@ -0,0 +1,23 @@ +# JSTests + +## Running Tests + +Tests are not always set up to run directly. Instead run them through `Tools/Scripts/run-javascriptcore-tests`, which runs everything, or `Tools/Scripts/run-jsc-stress-tests `, which runs a specific subset. For day-to-day development, the `JSTests/stress` and `JSTests/wasm.yaml` collections are sufficient for catching most bugs. + +Tests are run in a variety of different JSC configurations, for example with various JIT tiers disabled, concurrent compilation off, or the GC running continuously. These configurations show up as a suffix on the test name. For example, `stress/array-push.js.ftl-eager-no-cjit` corresponds to `JSTests/stress/array-push.js` with tier-up thresholds lowered and concurrent JIT off. + +`run-jsc-stress-tests` looks for a `jsc` in the release build directory unless `--debug` or `--jsc ` is passed. `run-javascriptcore-tests` builds one first unless `--no-build` or `--root ` is passed. + +`run-jsc-stress-tests` takes a collection (a directory or a `.yaml` file), not a single test file. To run a limited subset, pass `--filter `, which will pattern match on the test's name/configuration. e.g. `run-jsc-stress-tests JSTests/stress --filter array-push`. + +## Adding Tests + +Put tests that only target JS behavior in `JSTests/stress` and tests that involve wasm in `JSTests/wasm/stress`. + +New tests are *required* to adhere to the following rules: + +1. Tests must run in under 200ms in all configurations. This can be checked by passing `--report-execution-time` to `run-jsc-stress-tests`. +2. Use `testLoopCount` or `wasmTestLoopCount` to control how many iterations a test runs. The `jsc` CLI sets these based on the configuration of the test, so tests iterate enough to tier up where that matters and exit early where it doesn't. +3. Tests fail by crashing or throwing an uncaught exception, so assertions must throw rather than print. Add `//@ mustCrash!` or `//@ requireOptions("--exception=")` to the top of the test file if testing expected crashes/exceptions, respectively. +4. Don't print or log unless a test is about to fail. Test output goes straight to the terminal, so extra logging is noisy and disruptive. +5. Make sure the test actually reproduces the bug. Run the test against a build without the fix and make sure it fails. diff --git a/JSTests/microbenchmarks/array-prototype-sort-string-keys.js b/JSTests/microbenchmarks/array-prototype-sort-string-keys.js new file mode 100644 index 0000000000000..c97c89a6e7e9b --- /dev/null +++ b/JSTests/microbenchmarks/array-prototype-sort-string-keys.js @@ -0,0 +1,9 @@ +var keys = []; +for (var i = 0; i < 40; ++i) { + var name = ""; + for (var j = 0, x = i * 2654435761; j < 6; ++j, x = (x * 48271 + 12345) | 0) + name += String.fromCharCode(97 + ((x >>> 8) % 26)); + keys.push(name + (i % 3 ? "Id" : "Name")); +} +for (var i = 0; i < 5e4; ++i) + keys.slice().sort(); diff --git a/JSTests/microbenchmarks/function-bind-method.js b/JSTests/microbenchmarks/function-bind-method.js new file mode 100644 index 0000000000000..fe34c6a8ead18 --- /dev/null +++ b/JSTests/microbenchmarks/function-bind-method.js @@ -0,0 +1,20 @@ +class Component { + constructor(seed) { this.state = seed | 0; } + handleClick(e) { return this.state + e; } + onScroll(x) { return this.state - x; } + render() { + const onClick = this.handleClick.bind(this); + const onScr = this.onScroll.bind(this, 2); + return onClick(1) + onScr(); + } +} + +var shorthand = { + handle(e) { return e; }, +}; + +var component = new Component(3); +for (var i = 0; i < 1e5; ++i) { + component.render(); + shorthand.handle.bind(shorthand); +} diff --git a/JSTests/microbenchmarks/json-parse-short-string-ids.js b/JSTests/microbenchmarks/json-parse-short-string-ids.js new file mode 100644 index 0000000000000..4ec5ec52dd206 --- /dev/null +++ b/JSTests/microbenchmarks/json-parse-short-string-ids.js @@ -0,0 +1,33 @@ +//@ $skipModes << :lockdown if $buildType == "debug" + +let seed = 12345; +function next() { + seed = (seed + 0x9e3779b9) | 0; + let z = seed; + z = Math.imul(z ^ (z >>> 16), 0x85ebca6b); + z = Math.imul(z ^ (z >>> 13), 0xc2b2ae35); + return (z ^ (z >>> 16)) >>> 0; +} + +const colors = ["red", "blue", "green", "black", "white"]; +const sizes = ["S", "M", "L", "XL"]; +const items = []; +for (let i = 0; i < 2000; ++i) { + items.push({ + id: "item-" + (1000 + next() % 9000) + "-" + colors[next() % colors.length], + sku: "SKU-" + (10000 + next() % 90000) + "-" + sizes[next() % sizes.length], + owner: "user-" + (next() % 100000).toString(36), + parent: "ord-" + (next() % 1e6), + status: next() & 1 ? "active" : "archived", + }); +} +const json = JSON.stringify({ items }); + +// Each payload carries a distinct set of short ID-like string values, as successive API responses would. +const separators = "GHIJKLMNOPQRSTUVWXYZghijklmnopqrstuvwxyz"; +const payloads = []; +for (let i = 0; i < separators.length; ++i) + payloads.push(json.replaceAll("-", separators[i])); + +for (let i = 0; i < 240; ++i) + JSON.parse(payloads[i % payloads.length]); diff --git a/JSTests/microbenchmarks/json-stringify-class-instances.js b/JSTests/microbenchmarks/json-stringify-class-instances.js new file mode 100644 index 0000000000000..cfb958dab4a15 --- /dev/null +++ b/JSTests/microbenchmarks/json-stringify-class-instances.js @@ -0,0 +1,38 @@ +class Address { + constructor(i) { + this.street = "Street " + i; + this.city = "City"; + this.zip = 10000 + i; + } +} + +class User { + constructor(i) { + this.id = i; + this.name = "user" + i; + this.email = "user" + i + "@example.com"; + this.active = (i & 1) === 0; + this.address = new Address(i); + this.tags = ["a", "b", "c"]; + } + get displayName() { return this.name.toUpperCase(); } + greet() { return "hi " + this.name; } +} + +class Admin extends User { + constructor(i) { + super(i); + this.level = i % 5; + } +} + +let users = []; +for (let i = 0; i < 40; ++i) + users.push((i % 4) === 0 ? new Admin(i) : new User(i)); +let payload = { ok: true, count: users.length, data: users }; + +let result; +for (let i = 0; i < 2e4; ++i) + result = JSON.stringify(payload); +if (result.length !== JSON.stringify(JSON.parse(result)).length) + throw new Error("bad"); diff --git a/JSTests/microbenchmarks/locale-compare-with-locales.js b/JSTests/microbenchmarks/locale-compare-with-locales.js new file mode 100644 index 0000000000000..02b872f612c29 --- /dev/null +++ b/JSTests/microbenchmarks/locale-compare-with-locales.js @@ -0,0 +1,9 @@ +(function(a, b) { + var n = 200000; + var result = 0; + for (var i = 0; i < n; ++i) { + result += a.localeCompare(b, "en"); + } + if (result != n) + throw "Error: bad result: " + result; +})("yes", "no"); diff --git a/JSTests/microbenchmarks/regexp-buffer-boundary-anchor-end.js b/JSTests/microbenchmarks/regexp-buffer-boundary-anchor-end.js new file mode 100644 index 0000000000000..d61b9493722b2 --- /dev/null +++ b/JSTests/microbenchmarks/regexp-buffer-boundary-anchor-end.js @@ -0,0 +1,19 @@ +//@ requireOptions("--useRegExpBufferBoundaries=1") + +(function() { + var urls = []; + for (var i = 0; i < 1000; i++) + urls.push("https://cdn.example.com/assets/build/" + i + "/static/media/components/very/deeply/nested/directory/structure/image-" + i + (i % 7 == 0 ? ".png" : ".webp?width=1024&quality=80")); + + var re = /\.png\z/u; + var n = 400; + var result = 0; + for (var i = 0; i < n; i++) { + for (var j = 0; j < urls.length; j++) { + if (re.test(urls[j])) + result++; + } + } + if (result !== n * 143) + throw "Error: bad result: " + result; +})(); diff --git a/JSTests/microbenchmarks/regexp-buffer-boundary-anchor-start.js b/JSTests/microbenchmarks/regexp-buffer-boundary-anchor-start.js new file mode 100644 index 0000000000000..0491e98fc3b86 --- /dev/null +++ b/JSTests/microbenchmarks/regexp-buffer-boundary-anchor-start.js @@ -0,0 +1,20 @@ +//@ requireOptions("--useRegExpBufferBoundaries=1") + +(function() { + var source = ""; + for (var i = 0; i < 4000; i++) + source += "const value" + i + " = compute(" + i + "); // no shebang, no BOM\n"; + + var shebang = /\A#!.*\n/u; + var bom = /\A\uFEFF/u; + var n = 1000; + var result = 0; + for (var i = 0; i < n; i++) { + if (shebang.test(source)) + result++; + if (bom.test(source)) + result++; + } + if (result !== 0) + throw "Error: bad result: " + result; +})(); diff --git a/JSTests/microbenchmarks/regexp-u-flag-character-class-greedy.js b/JSTests/microbenchmarks/regexp-u-flag-character-class-greedy.js new file mode 100644 index 0000000000000..6b61eefd23094 --- /dev/null +++ b/JSTests/microbenchmarks/regexp-u-flag-character-class-greedy.js @@ -0,0 +1,27 @@ +// /u greedy character classes (\w, [a-z], \d) that hold no non-BMP or surrogate members, over UTF-16 text. +function splitmix32(seed) { + var a = seed; + return function () { + a |= 0; a = (a + 0x9e3779b9) | 0; + var t = a ^ (a >>> 16); t = Math.imul(t, 0x21f0aaad); + t = t ^ (t >>> 15); t = Math.imul(t, 0x735a2d97); + return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296; + }; +} +let rnd = splitmix32(42); +let words = []; +for (let i = 0; i < 60; ++i) { + let n = 300 + (rnd() * 400 | 0); + let w = ""; + for (let j = 0; j < n; ++j) + w += String.fromCharCode(0x61 + (rnd() * 26 | 0)); + words.push(w + (rnd() * 100000 | 0)); +} +let text = words.join(" あ "); + +let identifier = /^(?:\w+(?: あ )?)+$/u; +let word = /^(?:[a-z]+\d+(?: あ )?)+$/u; +for (let i = 0; i < 2000; ++i) { + if (!identifier.test(text) || !word.test(text)) + throw new Error("bad match"); +} diff --git a/JSTests/microbenchmarks/regexp-u-flag-class-astral-subject.js b/JSTests/microbenchmarks/regexp-u-flag-class-astral-subject.js new file mode 100644 index 0000000000000..f232eb87f488d --- /dev/null +++ b/JSTests/microbenchmarks/regexp-u-flag-class-astral-subject.js @@ -0,0 +1,11 @@ +// BMP-only /u character class scanning a subject dense in surrogate pairs. +let unit = "\u{1F600}a\u{1F601}bcd"; +let text = ""; +for (let i = 0; i < 4000; ++i) + text += unit + (i % 10); + +let re = /^(?:[\u{1F600}\u{1F601}]+[a-z0-9]+)+$/u; +for (let i = 0; i < 1500; ++i) { + if (!re.test(text)) + throw new Error("bad match"); +} diff --git a/JSTests/microbenchmarks/string-replace-regexp-trim-end.js b/JSTests/microbenchmarks/string-replace-regexp-trim-end.js new file mode 100644 index 0000000000000..ad932d9ab80fe --- /dev/null +++ b/JSTests/microbenchmarks/string-replace-regexp-trim-end.js @@ -0,0 +1,18 @@ +function test(str, re) +{ + return str.replace(re, ""); +} +noInline(test); + +const strs = []; +for (let k = 0; k < 16; k++) + strs.push("a".repeat(200 + k * 50) + " \t\n".repeat(k & 3)); + +const re = /\s+$/; + +let result; +for (let i = 0; i < 1000000; ++i) + result = test(strs[i & 15], re); + +if (test("hello ", re) !== "hello") + throw new Error("bad result: " + test("hello ", re)); diff --git a/JSTests/microbenchmarks/string-replace-regexp-trim-start.js b/JSTests/microbenchmarks/string-replace-regexp-trim-start.js new file mode 100644 index 0000000000000..e66076d893d61 --- /dev/null +++ b/JSTests/microbenchmarks/string-replace-regexp-trim-start.js @@ -0,0 +1,18 @@ +function test(str, re) +{ + return str.replace(re, ""); +} +noInline(test); + +const strs = []; +for (let k = 0; k < 16; k++) + strs.push(" \t\n".repeat(k & 3) + "a".repeat(200 + k * 50)); + +const re = /^\s+/; + +let result; +for (let i = 0; i < 1000000; ++i) + result = test(strs[i & 15], re); + +if (test(" hello", re) !== "hello") + throw new Error("bad result: " + test(" hello", re)); diff --git a/JSTests/microbenchmarks/string-split-non-atom-subject.js b/JSTests/microbenchmarks/string-split-non-atom-subject.js new file mode 100644 index 0000000000000..525b1d2461084 --- /dev/null +++ b/JSTests/microbenchmarks/string-split-non-atom-subject.js @@ -0,0 +1,21 @@ +// CSV-style row parsing where each subject string is built at runtime (not an atom), +// so String.prototype.split must not atomize the parts nor consult the split cache. + +const words = ["alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel"]; + +function parseRow(row) +{ + return row.split(","); +} +noInline(parseRow); + +let count = 0; +let seed = 1; +for (let i = 0; i < 3e5; ++i) { + seed = (seed * 1103515245 + 12345) >>> 0; + const row = words[seed & 7] + i + "," + words[(seed >>> 3) & 7] + (i + 1) + "," + words[(seed >>> 6) & 7] + (i + 2) + "," + words[(seed >>> 9) & 7] + (i + 3); + const parts = parseRow(row); + count += parts.length + parts[2].length; +} +if (count !== 4379333) + throw new Error("bad count: " + count); diff --git a/JSTests/microbenchmarks/string-substring-empty.js b/JSTests/microbenchmarks/string-substring-empty.js new file mode 100644 index 0000000000000..314a03ca7cb22 --- /dev/null +++ b/JSTests/microbenchmarks/string-substring-empty.js @@ -0,0 +1,8 @@ +function substring(string, start, end) +{ + return string.substring(start, end); +} +noInline(substring); + +for (var i = 0; i < 1e6; ++i) + substring("Cocoa", 3, 3); diff --git a/JSTests/microbenchmarks/string-substring-multi-chars.js b/JSTests/microbenchmarks/string-substring-multi-chars.js new file mode 100644 index 0000000000000..f718e80a89888 --- /dev/null +++ b/JSTests/microbenchmarks/string-substring-multi-chars.js @@ -0,0 +1,8 @@ +function substring(string, start, end) +{ + return string.substring(start, end); +} +noInline(substring); + +for (var i = 0; i < 1e6; ++i) + substring("Hello, World", 2, 9); diff --git a/JSTests/microbenchmarks/string-substring-no-end.js b/JSTests/microbenchmarks/string-substring-no-end.js new file mode 100644 index 0000000000000..a41e9d2a31b35 --- /dev/null +++ b/JSTests/microbenchmarks/string-substring-no-end.js @@ -0,0 +1,8 @@ +function substring(string, start) +{ + return string.substring(start); +} +noInline(substring); + +for (var i = 0; i < 1e6; ++i) + substring("Cocoa", 2); diff --git a/JSTests/microbenchmarks/string-substring-one-char.js b/JSTests/microbenchmarks/string-substring-one-char.js new file mode 100644 index 0000000000000..f77921552eddd --- /dev/null +++ b/JSTests/microbenchmarks/string-substring-one-char.js @@ -0,0 +1,8 @@ +function substring(string, start, end) +{ + return string.substring(start, end); +} +noInline(substring); + +for (var i = 0; i < 1e6; ++i) + substring("Cocoa", 2, 3); diff --git a/JSTests/microbenchmarks/string-substring-reversed.js b/JSTests/microbenchmarks/string-substring-reversed.js new file mode 100644 index 0000000000000..36384df147396 --- /dev/null +++ b/JSTests/microbenchmarks/string-substring-reversed.js @@ -0,0 +1,8 @@ +function substring(string, start, end) +{ + return string.substring(start, end); +} +noInline(substring); + +for (var i = 0; i < 1e6; ++i) + substring("Cocoa", 4, 1); diff --git a/JSTests/microbenchmarks/temporal-zoned-date-time-time-zone-cache.js b/JSTests/microbenchmarks/temporal-zoned-date-time-time-zone-cache.js new file mode 100644 index 0000000000000..fa5c35c2b77ec --- /dev/null +++ b/JSTests/microbenchmarks/temporal-zoned-date-time-time-zone-cache.js @@ -0,0 +1,19 @@ +//@ requireOptions("--useTemporal=1") + +// Guards the UCalendar TinyLRUCache in TimeZoneICUBridge.cpp (timeZoneCacheEntry, +// capacity 16). Cycling 12 named zones fits the cache; a smaller capacity evicts +// before reuse and pays ucal_open (~900ns) on every access. +const zones = [ + "America/Vancouver", "America/Denver", "America/Chicago", "America/New_York", + "America/Sao_Paulo", "Europe/London", "Europe/Paris", "Africa/Cairo", + "Asia/Kolkata", "Asia/Tokyo", "Australia/Sydney", "Pacific/Auckland", +]; +const instant = Temporal.Instant.fromEpochMilliseconds(1750000000000); // 2025-06-15T15:06:40Z +const zdts = zones.map(zone => instant.toZonedDateTimeISO(zone)); + +let acc = 0; +for (var i = 0; i < 2400000; ++i) + acc += zdts[i % zdts.length].offsetNanoseconds / 900e9; // ICU offset lookup per iteration + +if (acc !== 14000000) + throw "Bad result: " + acc; diff --git a/JSTests/modules/import-defer-async-cycle-sync-access.js b/JSTests/modules/import-defer-async-cycle-sync-access.js new file mode 100644 index 0000000000000..32c4a98bc00ba --- /dev/null +++ b/JSTests/modules/import-defer-async-cycle-sync-access.js @@ -0,0 +1,26 @@ +//@ requireOptions("--useImportDefer=1") +import { shouldBe, shouldThrow } from "./resources/assert.js"; +import { blocker, aStarted } from "./import-defer/async-cycle-setup.js"; + +// SCC {tla, member}: tla has TLA and is the cycle root, member is the non-root member. +// While tla is suspended on the blocker, member has already reached EVALUATED even though the +// cycle has not finished. The deferred module behind ns depends on member, so it still cannot be +// evaluated synchronously: ReadyForSyncExecution must consult IsModuleSCCEvaluated(member), which +// follows [[CycleRoot]], rather than member's own [[Status]]. + +const evaluations = globalThis.asyncCycleEvaluations; + +const pTLA = import("./import-defer/async-cycle-tla.js"); +await aStarted.promise; + +shouldBe(JSON.stringify(evaluations), JSON.stringify(["toucher", "member", "A-before-await"])); + +shouldThrow(() => globalThis.asyncCycleTouch(), "TypeError: Unable to synchronously evaluate deferred module"); +shouldBe(JSON.stringify(evaluations), JSON.stringify(["toucher", "member", "A-before-await"])); + +blocker.resolve(); +await pTLA; + +// The cycle root is EVALUATED now, so the same access succeeds and evaluates only the deferred module. +shouldBe(globalThis.asyncCycleTouch(), 1); +shouldBe(JSON.stringify(evaluations), JSON.stringify(["toucher", "member", "A-before-await", "A-after-await", "deferred"])); diff --git a/JSTests/modules/import-defer/async-cycle-deferred.js b/JSTests/modules/import-defer/async-cycle-deferred.js new file mode 100644 index 0000000000000..9437d66461799 --- /dev/null +++ b/JSTests/modules/import-defer/async-cycle-deferred.js @@ -0,0 +1,4 @@ +import "./async-cycle-member.js"; + +globalThis.asyncCycleEvaluations.push("deferred"); +export const value = 1; diff --git a/JSTests/modules/import-defer/async-cycle-member.js b/JSTests/modules/import-defer/async-cycle-member.js new file mode 100644 index 0000000000000..fec7ddffc3e1a --- /dev/null +++ b/JSTests/modules/import-defer/async-cycle-member.js @@ -0,0 +1,4 @@ +import "./async-cycle-tla.js"; +import "./async-cycle-toucher.js"; + +globalThis.asyncCycleEvaluations.push("member"); diff --git a/JSTests/modules/import-defer/async-cycle-setup.js b/JSTests/modules/import-defer/async-cycle-setup.js new file mode 100644 index 0000000000000..80aefbdfafe3a --- /dev/null +++ b/JSTests/modules/import-defer/async-cycle-setup.js @@ -0,0 +1,4 @@ +globalThis.asyncCycleEvaluations = []; + +export const blocker = Promise.withResolvers(); +export const aStarted = Promise.withResolvers(); diff --git a/JSTests/modules/import-defer/async-cycle-tla.js b/JSTests/modules/import-defer/async-cycle-tla.js new file mode 100644 index 0000000000000..4c35a46c5d95c --- /dev/null +++ b/JSTests/modules/import-defer/async-cycle-tla.js @@ -0,0 +1,7 @@ +import { blocker, aStarted } from "./async-cycle-setup.js"; +import "./async-cycle-member.js"; + +globalThis.asyncCycleEvaluations.push("A-before-await"); +aStarted.resolve(); +await blocker.promise; +globalThis.asyncCycleEvaluations.push("A-after-await"); diff --git a/JSTests/modules/import-defer/async-cycle-toucher.js b/JSTests/modules/import-defer/async-cycle-toucher.js new file mode 100644 index 0000000000000..35fbd515cc307 --- /dev/null +++ b/JSTests/modules/import-defer/async-cycle-toucher.js @@ -0,0 +1,7 @@ +import defer * as ns from "./async-cycle-deferred.js"; + +// This module is evaluated while the cycle it belongs to is still on the evaluation stack, so +// GatherAsynchronousTransitiveDependencies finds nothing to await and the deferred graph stays +// unevaluated. Handing the access out lets the test poke it at a chosen point. +globalThis.asyncCycleTouch = () => ns.value; +globalThis.asyncCycleEvaluations.push("toucher"); diff --git a/JSTests/modules/import-meta-syntax.js b/JSTests/modules/import-meta-syntax.js index c32b782c070fe..74f630330b971 100644 --- a/JSTests/modules/import-meta-syntax.js +++ b/JSTests/modules/import-meta-syntax.js @@ -10,7 +10,7 @@ shouldNotThrow(() => { shouldThrow(() => { checkModuleSyntax(`(import.cocoa)`); -}, `SyntaxError: Unexpected identifier 'cocoa'. "import." can only be followed with meta.:1`); +}, `SyntaxError: Unexpected identifier 'cocoa'. "import." can only be followed with meta or defer.:1`); shouldThrow(() => { checkModuleSyntax(`(import["Cocoa"])`); @@ -18,7 +18,7 @@ shouldThrow(() => { shouldThrow(() => { checkModuleSyntax(`import.cocoa`); -}, `SyntaxError: Unexpected identifier 'cocoa'. "import." can only be followed with meta.:1`); +}, `SyntaxError: Unexpected identifier 'cocoa'. "import." can only be followed with meta or defer.:1`); shouldThrow(() => { checkModuleSyntax(`import["Cocoa"]`); diff --git a/JSTests/stress/array-buffer-slice-larger-than-4gb.js b/JSTests/stress/array-buffer-slice-larger-than-4gb.js new file mode 100644 index 0000000000000..598d3d40df5ca --- /dev/null +++ b/JSTests/stress/array-buffer-slice-larger-than-4gb.js @@ -0,0 +1,29 @@ +//@ memoryHog! +//@ skip if $addressBits <= 32 +//@ runDefault + +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`bad value: expected ${expected} but got ${actual}`); +} + +const gib = 1024 * 1024 * 1024; +const size = 5 * gib; + +for (const ArrayBufferClass of [ArrayBuffer, SharedArrayBuffer]) { + const buffer = new ArrayBufferClass(size); + shouldBe(buffer.byteLength, size); + + new Uint8Array(buffer).set([1, 2, 3, 4], size - 4); + + // Slicing only a window at the end keeps the copy small while still forcing the byte length + // itself through the argument clamping. + shouldBe(buffer.slice(size - 4).byteLength, 4); + shouldBe([...new Uint8Array(buffer.slice(size - 4))].join(), "1,2,3,4"); + shouldBe(buffer.slice(-4).byteLength, 4); + shouldBe(buffer.slice(size - 4, size).byteLength, 4); + shouldBe(buffer.slice(size - 4, -1).byteLength, 3); + shouldBe(buffer.slice(4 * gib, 4 * gib + 8).byteLength, 8); + shouldBe(buffer.slice(size, size).byteLength, 0); + shouldBe(buffer.slice(size + 1).byteLength, 0); +} diff --git a/JSTests/stress/array-default-sort-radix-edge-cases.js b/JSTests/stress/array-default-sort-radix-edge-cases.js new file mode 100644 index 0000000000000..c9bee2d9322ca --- /dev/null +++ b/JSTests/stress/array-default-sort-radix-edge-cases.js @@ -0,0 +1,174 @@ +// Default (no-comparator) Array.prototype.sort must order elements like a comparator comparing +// String(a) vs String(b) in code-unit order. Comparing serialized results (not identity) keeps +// the legitimate instability of equal strings from causing false failures. + +function ser(arr) { + let parts = []; + for (let i = 0; i < arr.length; i++) + parts.push((i in arr) ? ("|" + String(arr[i])) : "|"); + return parts.join(""); +} + +function refCmp(a, b) { + let sa = String(a), sb = String(b); + return sa < sb ? -1 : (sa > sb ? 1 : 0); +} + +// slice() preserves holes and undefined, so each operation gets an independent copy +// of the same input (objects are shared by reference, which is fine here). +function check(arr, label) { + let a = arr.slice(); + let lenBefore = a.length; + let sorted = a.sort(); + if (sorted !== a) + throw new Error(label + ": sort() must return the same array"); + if (a.length !== lenBefore) + throw new Error(label + ": length changed " + lenBefore + " -> " + a.length); + let expected = arr.slice().sort(refCmp); + if (ser(a) !== ser(expected)) + throw new Error(label + ": order mismatch\n got: " + ser(a).slice(0, 200) + "\n exp: " + ser(expected).slice(0, 200)); + // toSorted must not mutate the receiver, and must order like refCmp's toSorted. + // (toSorted reads holes as undefined, unlike in-place sort which keeps trailing holes, + // so it is compared against refCmp's toSorted rather than against sort.) + let src = arr.slice(); + let copy = src.slice(); + let ts = src.toSorted(); + if (ser(src) !== ser(copy)) + throw new Error(label + ": toSorted mutated the receiver"); + let tsExpected = arr.slice().toSorted(refCmp); + if (ser(ts) !== ser(tsExpected)) + throw new Error(label + ": toSorted mismatch\n got: " + ser(ts).slice(0, 200) + "\n exp: " + ser(tsExpected).slice(0, 200)); +} + +// --- Boundary around the radix threshold (14) and the depth cap (32 levels: 32 Latin-1 chars / 16 UTF-16 code units). --- +for (let n = 0; n <= 40; n++) { + check(Array.from({ length: n }, (_, i) => (i * 2654435761) % 97), "numbers n=" + n); + check(Array.from({ length: n }, (_, i) => "s" + ((i * 40503) % 13)), "shortstr n=" + n); +} + +// --- All identical strings (everything funnels into one bucket, then terminal bucket 0). --- +for (let n of [1, 13, 14, 15, 100, 500]) { + check(Array.from({ length: n }, () => "same"), "identical-short n=" + n); + check(Array.from({ length: n }, () => "x".repeat(40)), "identical-deep n=" + n); // >32 chars -> depth cap + check(Array.from({ length: n }, () => "\u3042".repeat(20)), "identical-deep-16bit n=" + n); // >16 code units -> depth cap +} + +// --- Empty strings, and prefixes of varying length (terminal buckets at many depths). --- +check(["", "a", "ab", "abc", "a", "", "abcd", "ab", "abc", "abce", "abcz", "abca"], "prefix-ladder"); +(() => { + let a = []; + let words = ["", "a", "aa", "aaa", "aaaa", "ab", "aba", "abb", "b", "ba", "bb", "z", ""]; + for (let i = 0; i < 50; i++) a.push(words[(i * 7) % words.length]); + check(a, "prefix-ladder-big"); +})(); + +// --- Code-unit boundaries: NUL (byte 0), 0xFF/0x100 (high/low byte split), 0xFFFF. --- +check(["\u0000", "\u0000\u0000", "", "\u0001", "\u0000a", "a", "a\u0000"], "nul-bytes"); +(() => { + let a = []; + for (let i = 0; i < 60; i++) { + // alternate code units that differ only in low byte vs only in high byte + let cu = (i % 2) ? (0x0100 + (i % 5)) : (0x0001 + (i % 5) * 0x0100); + a.push(String.fromCharCode(cu) + String.fromCharCode(0x00FF + (i % 3))); + } + check(a, "high-low-byte-split"); +})(); +(() => { + let a = []; + for (let i = 0; i < 64; i++) + a.push(String.fromCharCode((i * 0x3B) & 0xFF, 0xFF - (i % 3), i % 2 ? 0x00 : 0xFF) + "x"); + a.push("\u0000", "\u00FF", "\u00FF\u00FF", "\u0000\u0000", ""); + check(a, "latin1-full-byte-range"); +})(); +check(["\uFFFF", "\uFFFE", "\u0100", "\u00FF", "\u0000", "\u0001", "\uFFFF\u0000", "\uFFFF"], "wide-code-units"); + +// --- Surrogate code units / non-BMP: default sort is code-unit order, not code-point order. --- +check(["\uD800", "\uDFFF", "\uD7FF", "\uE000", "\uD800\uDC00", "\uDBFF\uDFFF", "a", "\uFFFF"], "surrogates"); +(() => { + let a = []; + for (let i = 0; i < 50; i++) a.push(String.fromCodePoint(0x1F600 + (i % 7)) + String.fromCharCode(97 + (i % 4))); + check(a, "non-bmp"); +})(); + +// --- Numbers stringified: negatives, decimals, -0, Infinity, NaN. --- +check([10, 9, 100, 1, 20, 2, 3, 30, 0, -1, -10, -2, 11, 21, 101, 110, 12, 13, 14, 15, 16, 17, 18, 19, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33], "number-string-order"); +check([Infinity, -Infinity, NaN, 0, -0, 1.5, 1.25, 1.125, 1e21, 1e-7, 0.1, 0.2, 100, 99, 9, NaN, Infinity], "special-numbers"); + +// --- undefined and holes placement (must be: values, then undefined, then holes). --- +check([3, undefined, 1, undefined, 2], "undefined-mix"); +(() => { let a = [5, 4, 3, 2, 1]; a[10] = 0; a[20] = 9; check(a, "sparse-holes"); })(); +(() => { let a = new Array(40); for (let i = 0; i < 40; i += 2) a[i] = (i * 13) % 50; a[5] = undefined; a[7] = undefined; check(a, "holes-and-undefined-big"); })(); + +// --- Mixed types in one array (all funnel through ToString). --- +check([true, false, null, 1, "1", "true", {}, [1, 2], "a", 0, "0", "", "[object Object]"], "mixed-types"); +(() => { + let a = []; + for (let i = 0; i < 60; i++) { + let r = i % 6; + a.push(r === 0 ? i : r === 1 ? String(i) : r === 2 ? (i % 2 === 0) : r === 3 ? { x: i } : r === 4 ? [i] : null); + } + check(a, "mixed-types-big"); +})(); + +// --- Deep shared prefix beyond the depth cap (ordering must still be correct), 8-bit and 16-bit. --- +(() => { + let a = []; + let base = "Z".repeat(40); + for (let i = 0; i < 80; i++) a.push(base + ((i * 31) % 17) + "tail"); + check(a, "deep-prefix-ordering"); + let b = []; + let base16 = "\u3042".repeat(20); + for (let i = 0; i < 80; i++) b.push(base16 + ((i * 31) % 17) + "tail"); + check(b, "deep-prefix-ordering-16bit"); +})(); + +// --- A single 16-bit string switches the whole sort to the UTF-16 byte path. --- +(() => { + let a = []; + for (let i = 0; i < 80; i++) a.push("key" + ((i * 37) % 53)); + a.push("key\u0100", "key\u00FF", "ke\uFFFF"); + check(a, "one-16bit-among-8bit"); +})(); + +// --- Randomized differential across alphabets and sizes (deterministic seed). --- +let seed = 0x9e3779b9 >>> 0; +function rnd() { + seed ^= seed << 13; seed >>>= 0; + seed ^= seed >> 17; + seed ^= seed << 5; seed >>>= 0; + return seed >>> 0; +} +for (let trial = 0; trial < testLoopCount / 10; trial++) { + let n = rnd() % 400; + let mode = rnd() % 5; + let a = new Array(n); + for (let i = 0; i < n; i++) { + if (mode === 0) a[i] = rnd() % 1000000; // numbers, small distinct prefix + else if (mode === 1) { let len = rnd() % 8, s = ""; for (let j = 0; j < len; j++) s += String.fromCharCode(97 + rnd() % 4); a[i] = s; } // tiny alphabet, many collisions + else if (mode === 2) { let len = 1 + rnd() % 6, s = ""; for (let j = 0; j < len; j++) s += String.fromCharCode(32 + rnd() % 200); a[i] = s; } // wide alphabet, BMP + else if (mode === 3) { let len = rnd() % 5, s = ""; for (let j = 0; j < len; j++) s += String.fromCharCode(0xD800 + rnd() % 0x800); a[i] = s; } // raw surrogate code units + else { let r = rnd() % 4; a[i] = r === 0 ? (rnd() % 50) : r === 1 ? String.fromCharCode(97 + rnd() % 5) : r === 2 ? undefined : null; } // mixed incl undefined + } + check(a, "rand trial=" + trial + " mode=" + mode + " n=" + n); +} + +// --- Stability where the radix path guarantees it (shallow keys, n >= 14). --- +// Distinct objects whose ToString collides on short keys keep insertion order: equal-key +// runs are scattered stably and identical strings land in the terminal bucket in order. +function checkStability(n, distinctKeys, label) { + let a = []; + for (let i = 0; i < n; i++) { + let key = "k" + (rnd() % distinctKeys); + let o = { id: i, key }; + o.toString = function () { return this.key; }; + a.push(o); + } + let expected = a.slice().sort((x, y) => x.key < y.key ? -1 : x.key > y.key ? 1 : x.id - y.id); + let got = a.slice().sort(); + for (let i = 0; i < n; i++) { + if (got[i] !== expected[i]) + throw new Error("stability mismatch [" + label + "] at i=" + i); + } +} +for (let trial = 0; trial < testLoopCount / 20; trial++) + checkStability(14 + rnd() % 400, 1 + rnd() % 8, "stab trial=" + trial); diff --git a/JSTests/stress/array-sort-default-comparator-stability.js b/JSTests/stress/array-sort-default-comparator-stability.js new file mode 100644 index 0000000000000..59511a4000d32 --- /dev/null +++ b/JSTests/stress/array-sort-default-comparator-stability.js @@ -0,0 +1,29 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error("bad value: " + actual + ", expected: " + expected); +} + +function makeEntries(count, distinct, prefix) { + let entries = []; + for (let i = 0; i < count; ++i) { + let key = prefix + String.fromCharCode(0x41 + ((i * 7) % distinct)); + entries.push({ id: i, key, toString() { return this.key; } }); + } + return entries; +} + +function verifyStable(entries) { + let expected = entries.slice().sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : a.id - b.id); + let actual = entries.slice().sort(); + shouldBe(actual.length, expected.length); + for (let i = 0; i < actual.length; ++i) + shouldBe(actual[i].id, expected[i].id); +} + +for (let count = 20; count <= 40; ++count) { + for (let distinct of [1, 2, 3, 5]) + verifyStable(makeEntries(count, distinct, "")); +} + +for (let count of [40, 100]) + verifyStable(makeEntries(count, 3, "0123456789012345678901234567890123456789")); diff --git a/JSTests/stress/array-unshift-moved-cell-barrier.js b/JSTests/stress/array-unshift-moved-cell-barrier.js new file mode 100644 index 0000000000000..4f3b396832da1 --- /dev/null +++ b/JSTests/stress/array-unshift-moved-cell-barrier.js @@ -0,0 +1,71 @@ +//@ requireOptions("--useDollarVM=1", "--numberOfGCMarkers=4", "--minimumGCPauseMS=0", "--gcPauseScale=0", "--maximumMutatorUtilization=0.01", "--useSamplingProfiler=true", "--sampleInterval=1") + +// unshift on a Contiguous array moves the existing elements up to make room +// before publicLength is updated, so a moved cell is briefly outside the range +// a concurrent collector scans. Without a barrier on the array itself the +// collector can miss that cell and reclaim it while it is still reachable +// through the array. The inserted value is a double, so the store implies no +// barrier of its own. +// +// A cleared WeakRef whose only strong reference is the moved element means the +// collector lost track of a live cell. + +function unshiftOne(array) { + array.unshift(13.37); +} +noInline(unshiftOne); + +// Monomorphic Contiguous receivers with spare capacity for ArrayUnshift, so +// the single-element fast path is what gets compiled. +const warmObject = { warmup: 0 }; +for (let i = 0; i < testLoopCount; ++i) { + const array = [warmObject, 0]; + array.pop(); + unshiftOne(array); +} + +// Winning the race takes many attempts, but the configurations that lower +// testLoopCount are the ones where each attempt is most expensive, so scale +// both the victim pool and the round count with it. The pool is rounded down +// to a power of two so that the permutation below covers it exactly. +const victimCount = 1 << (28 - Math.clz32(testLoopCount)); +const mask = victimCount - 1; +const victims = new Array(victimCount); +const weak = new Array(victimCount); + +const rounds = Math.max(1, testLoopCount >> 9); + +for (let round = 0; round < rounds; ++round) { + for (let i = 0; i < victimCount; ++i) { + // Reachable only through the victim's element, so nothing else keeps + // it alive if the collector loses track of it. + const cell = new Array(100).fill(i + 0.25); + // Capacity for two elements while publicLength stays at one, which is + // what the fast path requires. + const victim = [cell, 0]; + victim.pop(); + victims[i] = victim; + weak[i] = new WeakRef(cell); + } + + // A deref'd WeakRef keeps its target alive until the next microtask + // checkpoint, so drop that protection before the collection. + releaseWeakRefs(); + $vm.gc(); // Pre-age the victims and clear newlyAllocated protection. + $vm.gcSweepAsynchronously(); + + // A full permutation decorrelates the mutator from the parallel markers' + // LIFO worklists, and the 1us sampling interval supplies the frequent + // preemption that exposes the window. + for (let i = 0; i < victimCount; ++i) + unshiftOne(victims[Math.imul(i, 0x9e3779b1) & mask]); + + // Let the collector finish this cycle. + for (let i = 0; i < 2000; ++i) + globalThis.safepointSink = { i }; + + for (let i = 0; i < victimCount; ++i) { + if (weak[i].deref() === undefined) + throw new Error('collector reclaimed a cell still referenced by victims[' + i + ']'); + } +} diff --git a/JSTests/stress/arrow-function-object-literal-shorthand-should-not-capture-arguments.js b/JSTests/stress/arrow-function-object-literal-shorthand-should-not-capture-arguments.js new file mode 100644 index 0000000000000..53391a56ed387 --- /dev/null +++ b/JSTests/stress/arrow-function-object-literal-shorthand-should-not-capture-arguments.js @@ -0,0 +1,65 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error("bad value: " + actual); +} + +// An object-literal shorthand property inside an arrow function must not force the +// enclosing function to capture its `arguments`. Otherwise the arguments passed to `make` +// are retained by the closures it returns. +function make(opts) { + const host = {}; + const worker = (r) => ({ r }); + return { worker, inner }; + function inner() { return host; } +} +noInline(make); + +const refs = []; +let cur = make({ previous: null }); +for (let i = 0; i < 128; i++) { + const opts = { previous: cur, payload: new Uint8Array(1024) }; + refs.push(new WeakRef(opts)); + cur = make(opts); +} + +// `{ eval }` and `{ arguments }` shorthands inside arrow functions still resolve to the +// enclosing function's bindings. +function shorthandEvalAndArguments(a, b, c) { + const f = () => ({ eval, arguments, len: arguments.length }); + return f(); +} +noInline(shorthandEvalAndArguments); +for (let i = 0; i < 100; i++) { + const result = shorthandEvalAndArguments(1, 2, 3); + shouldBe(result.eval, eval); + shouldBe(result.arguments[1], 2); + shouldBe(result.len, 3); +} + +// Direct eval next to a shorthand property inside an arrow function still sees the +// enclosing function's variables. +function shorthandWithDirectEval(code) { + const x = 42; + const f = (s) => ({ x, v: eval(s) }); + return f(code); +} +noInline(shorthandWithDirectEval); +for (let i = 0; i < 100; i++) { + const result = shorthandWithDirectEval("arguments[0].length"); + shouldBe(result.x, 42); + shouldBe(result.v, "arguments[0].length".length); +} + +// WeakRef targets are kept alive until the end of the current job, so check liveness +// from a later task. +setTimeout(() => { + gc(); + gc(); + let alive = 0; + for (const ref of refs) { + if (ref.deref()) + alive++; + } + if (alive > refs.length / 4) + throw new Error("Too many option objects are retained: " + alive + " / " + refs.length); +}, 0); diff --git a/JSTests/stress/bigint-add-sub-fixed-size.js b/JSTests/stress/bigint-add-sub-fixed-size.js new file mode 100644 index 0000000000000..2bf83022eed61 --- /dev/null +++ b/JSTests/stress/bigint-add-sub-fixed-size.js @@ -0,0 +1,86 @@ +// Equal-width BigInt addition and subtraction take kernels that are unrolled onto a static extent +// for narrow operands. They must agree with the size-agnostic path for every width and sign +// combination, including the carry-out and full-borrow-cascade cases. + +function shouldBe(actual, expected, message) { + if (actual !== expected) + throw new Error(`${message}: expected ${expected} but got ${actual}`); +} + +let seed = 0x9e3779b9n; +function nextDigit() { + seed = (seed * 6364136223846793005n + 1442695040888963407n) & 0xffffffffffffffffn; + return seed; +} + +function fromDigits(digits) { + let result = 0n; + for (let i = digits.length - 1; i >= 0; --i) + result = (result << 64n) | digits[i]; + return result; +} + +function randomOfLength(length) { + let digits = []; + for (let i = 0; i < length; ++i) + digits.push(nextDigit()); + digits[length - 1] |= 1n; // Keep the value exactly {length} digits wide. + return fromDigits(digits); +} + +// Addition and subtraction are checked against each other and against a shift-free reconstruction, +// so a wrong kernel cannot be masked by the path it is being compared with. +function check(a, b, label) { + let sum = a + b; + shouldBe(sum - b, a, `${label}: (a + b) - b`); + shouldBe(sum - a, b, `${label}: (a + b) - a`); + shouldBe(a - b, -(b - a), `${label}: a - b == -(b - a)`); + shouldBe(a + b, b + a, `${label}: commutative`); + shouldBe((-a) + (-b), -sum, `${label}: (-a) + (-b)`); + shouldBe((-a) - (-b), b - a, `${label}: (-a) - (-b)`); + shouldBe(a - (-b), sum, `${label}: a - (-b)`); + shouldBe((-a) + b, b - a, `${label}: (-a) + b`); + shouldBe(a - a, 0n, `${label}: a - a`); + // A + B == (A ^ B) + 2 * (A & B) for non-negative operands: no reliance on the add path. + if (a >= 0n && b >= 0n) + shouldBe(sum, (a ^ b) + 2n * (a & b), `${label}: xor/and identity`); +} + +// Widths 1..6 span the unrolled kernels (1..4) and the sizes just above them. +for (let length = 1; length <= 6; ++length) { + let allOnes = (1n << BigInt(64 * length)) - 1n; + let one = 1n; + + // Carry out of the top digit, and a borrow cascading through every digit. + check(allOnes, allOnes, `all ones + all ones, ${length} digits`); + check(allOnes, one, `all ones + 1, ${length} digits`); + check(1n << BigInt(64 * length - 1), 1n << BigInt(64 * length - 1), `top bits, ${length} digits`); + check(1n << BigInt(64 * (length - 1)), one, `borrow cascade, ${length} digits`); + check(allOnes - 1n, one, `no carry out, ${length} digits`); + check(0n, allOnes, `zero and all ones, ${length} digits`); + + for (let round = 0; round < 8; ++round) { + let a = randomOfLength(length); + let b = randomOfLength(length); + check(a, b, `random equal width ${length}, round ${round}`); + } + + // Mixed widths keep using the size-agnostic path, which must still be reachable. + for (let other = 1; other <= 6; ++other) { + let a = randomOfLength(length); + let b = randomOfLength(other); + check(a, b, `mixed widths ${length}/${other}`); + } +} + +// Repeated add/subtract cycles must return to the starting value. +{ + let value = randomOfLength(4); + let step = randomOfLength(4); + let running = value; + for (let i = 0; i < 1000; ++i) + running = running + step; + for (let i = 0; i < 1000; ++i) + running = running - step; + shouldBe(running, value, "add/subtract round trip"); +} diff --git a/JSTests/stress/bigint-square-comba.js b/JSTests/stress/bigint-square-comba.js new file mode 100644 index 0000000000000..22226f9293fb6 --- /dev/null +++ b/JSTests/stress/bigint-square-comba.js @@ -0,0 +1,101 @@ +// Squaring a BigInt by itself takes a dedicated Comba path that accumulates each off-diagonal +// product once at twice the weight. Its result must match the general multiply for every operand +// width, including the widths that have a fully unrolled kernel (1, 2, 4, 8 and 16 digits). + +function shouldBe(actual, expected, message) { + if (actual !== expected) + throw new Error(`${message}: expected ${expected} but got ${actual}`); +} + +// A distinct BigInt cell holding the same value, so that x * copy(x) cannot take the squaring path. +function copy(x) { + return BigInt(x.toString()); +} + +function checkSquare(x, label) { + let expected = x * copy(x); + shouldBe(x * x, expected, `${label}: x * x`); + shouldBe((-x) * (-x), expected, `${label}: (-x) * (-x)`); + shouldBe(x * (-x), -expected, `${label}: x * (-x)`); + shouldBe((-x) * x, -expected, `${label}: (-x) * x`); + // Independent of the multiply path: x^2 == x * (x - 1) + x. + shouldBe(expected, x * copy(x - 1n) + x, `${label}: x * (x - 1) + x`); + shouldBe(x ** 2n, expected, `${label}: x ** 2n`); +} + +let seed = 0x12345678n; +function nextDigit() { + // 64-bit LCG, so the test data is fixed but exercises every digit position. + seed = (seed * 6364136223846793005n + 1442695040888963407n) & 0xffffffffffffffffn; + return seed; +} + +function fromDigits(digits) { + let result = 0n; + for (let i = digits.length - 1; i >= 0; --i) + result = (result << 64n) | digits[i]; + return result; +} + +// Widths 1..17 digits cover both the unrolled kernels and the size-agnostic fallbacks, plus the +// boundaries just above each of them. +for (let length = 1; length <= 17; ++length) { + let allOnes = (1n << BigInt(64 * length)) - 1n; + checkSquare(allOnes, `all ones, ${length} digits`); + + checkSquare(1n << BigInt(64 * length - 1), `top bit only, ${length} digits`); + checkSquare((1n << BigInt(64 * (length - 1))) + 1n, `lowest and highest digit, ${length} digits`); + + // Small top digit: the product is one digit shorter than length * 2, so the caller has to trim. + let digits = []; + for (let i = 0; i < length; ++i) + digits.push(nextDigit()); + digits[length - 1] = 1n; + checkSquare(fromDigits(digits), `small top digit, ${length} digits`); + + // Zero digits in the middle must not disturb the carry chain. + digits = []; + for (let i = 0; i < length; ++i) + digits.push(i % 2 ? 0n : nextDigit() | 1n); + checkSquare(fromDigits(digits), `alternating zero digits, ${length} digits`); + + for (let round = 0; round < 4; ++round) { + digits = []; + for (let i = 0; i < length; ++i) + digits.push(nextDigit()); + digits[length - 1] |= 1n << 63n; + checkSquare(fromDigits(digits), `random, ${length} digits, round ${round}`); + } +} + +checkSquare(0n, "zero"); +checkSquare(1n, "one"); +checkSquare(0xffffffffffffffffn, "one all-ones digit"); + +// (a + b)^2 == a^2 + 2ab + b^2 ties the squaring path back to addition and the general multiply. +for (let length = 1; length <= 9; ++length) { + let a = fromDigits(Array.from({ length }, () => nextDigit())); + let b = fromDigits(Array.from({ length }, () => nextDigit())); + let sum = a + b; + shouldBe(sum * sum, a * a + 2n * (a * copy(b)) + b * b, `binomial, ${length} digits`); +} + +// Modular exponentiation squares the same cell repeatedly, which is the shape this path exists for. +{ + let base = 0xdeadbeefcafebabe0123456789abcdefn; + let modulus = (1n << 255n) - 19n; + let expected = 1n; + for (let i = 0; i < 64; ++i) + expected = (expected * copy(base)) % modulus; + let actual = 1n; + let running = base; + let exponent = 64n; + // Square-and-multiply, so every step squares one cell. + while (exponent > 0n) { + if (exponent & 1n) + actual = (actual * running) % modulus; + running = (running * running) % modulus; + exponent >>= 1n; + } + shouldBe(actual, expected, "modular exponentiation"); +} diff --git a/JSTests/stress/bound-function-strength-reduction-method.js b/JSTests/stress/bound-function-strength-reduction-method.js new file mode 100644 index 0000000000000..011c52f76fb14 --- /dev/null +++ b/JSTests/stress/bound-function-strength-reduction-method.js @@ -0,0 +1,78 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error('bad value: ' + actual); +} + +function shouldThrow(func, errorMessage) { + var errorThrown = false; + var error = null; + try { + func(); + } catch (e) { + errorThrown = true; + error = e; + } + if (!errorThrown) + throw new Error('not thrown'); + if (String(error) !== errorMessage) + throw new Error(`bad error: ${String(error)}`); +} + +// Class methods (strict, no "prototype" property) and object-literal shorthand methods +// use dedicated method structures. Function#bind on them should be strength-reduced +// to NewBoundFunction with lazily materialized .name/.length, and stay correct. +class Component { + constructor() { this.state = 40; } + handleClick(a, b) { return this.state + a + b; } +} + +var sloppyHolder = { + handle(a, b, c) { return this === sloppyHolder ? a + b + c : -1; }, +}; + +var component = new Component(); + +function bindClassMethod() { + return component.handleClick.bind(component, 1); +} +noInline(bindClassMethod); + +function bindShorthandMethod() { + return sloppyHolder.handle.bind(sloppyHolder); +} +noInline(bindShorthandMethod); + +for (var i = 0; i < testLoopCount; ++i) { + var f = bindClassMethod(); + shouldBe(f(1), 42); + shouldBe(f.name, "bound handleClick"); + shouldBe(f.length, 1); + + var g = bindShorthandMethod(); + shouldBe(g(1, 2, 3), 6); + shouldBe(g.name, "bound handle"); + shouldBe(g.length, 3); +} + +// Methods are not constructors, and neither are their bound versions. +shouldThrow(() => new (bindClassMethod())(), "TypeError: function is not a constructor (evaluating 'new (bindClassMethod())()')"); +shouldThrow(() => new (bindShorthandMethod())(), "TypeError: function is not a constructor (evaluating 'new (bindShorthandMethod())()')"); + +// Once .name/.length are reified (the structure transitions), bind must observe the modified values. +class Modified { + method(a, b) { return a; } +} +var modified = new Modified(); +Object.defineProperty(modified.method, "name", { value: "renamed" }); +Object.defineProperty(modified.method, "length", { value: 7 }); + +function bindModifiedMethod() { + return modified.method.bind(null, 0); +} +noInline(bindModifiedMethod); + +for (var i = 0; i < testLoopCount; ++i) { + var h = bindModifiedMethod(); + shouldBe(h.name, "bound renamed"); + shouldBe(h.length, 6); +} diff --git a/JSTests/stress/import-syntax.js b/JSTests/stress/import-syntax.js index 28fdb2fcf7180..60921ce3481f2 100644 --- a/JSTests/stress/import-syntax.js +++ b/JSTests/stress/import-syntax.js @@ -29,7 +29,7 @@ async function testSyntax(script, message) { testSyntaxError(`import)`, `SyntaxError: Unexpected token ')'. import call expects one or two arguments.`); testSyntaxError(`new import(`, `SyntaxError: Cannot use new with import.`); -testSyntaxError(`import.hello()`, `SyntaxError: Unexpected identifier 'hello'. "import." can only be followed with meta.`); +testSyntaxError(`import.hello()`, `SyntaxError: Unexpected identifier 'hello'. "import." can only be followed with meta or defer.`); testSyntaxError(`import[`, `SyntaxError: Unexpected token '['. import call expects one or two arguments.`); testSyntaxError(`import<`, `SyntaxError: Unexpected token '<'. import call expects one or two arguments.`); diff --git a/JSTests/stress/intl-datetimeformat-era-override-parts.js b/JSTests/stress/intl-datetimeformat-era-override-parts.js new file mode 100644 index 0000000000000..877d9c54ca2d3 --- /dev/null +++ b/JSTests/stress/intl-datetimeformat-era-override-parts.js @@ -0,0 +1,59 @@ +function expect(label, got, want) +{ + if (got !== want) + throw new Error(`${label}: expected ${JSON.stringify(want)}, got ${JSON.stringify(got)}`); +} + +// Locales chosen so era placement and trailing-space behaviour both vary. +const locales = ["en-US", "ja-JP", "zh-CN", "ar-EG", "he-IL", "de-DE", "fi-FI", "th-TH"]; + +const shapes = [ + { era: "short" }, + { era: "long" }, + { era: "narrow" }, + { era: "short", year: "numeric" }, + { era: "short", year: "numeric", month: "long", day: "numeric" }, + { era: "long", year: "2-digit", month: "2-digit", day: "2-digit" }, + { era: "short", year: "numeric", month: "numeric", day: "numeric", weekday: "long" }, + // Ends in dayPeriod for en-US, so ICU leaves no trailing space: the broken case. + { era: "short", year: "numeric", hour: "numeric", minute: "numeric" }, +]; + +// Dates before each calendar's era epoch, so the override fires. +const cases = [ + ["coptic", Date.UTC(100, 0, 1)], + ["coptic", Date.UTC(283, 7, 1)], + ["islamic-civil", Date.UTC(300, 0, 1)], + ["islamic-umalqura", Date.UTC(500, 0, 1)], + ["islamic-tbla", Date.UTC(300, 0, 1)], + // Controls: era present natively, and a calendar with no override at all. + ["coptic", Date.UTC(2020, 0, 1)], + ["gregory", Date.UTC(100, 0, 1)], +]; + +let checked = 0; +let sawOverride = false; +for (const locale of locales) { + for (const [calendar, epochMs] of cases) { + for (const shape of shapes) { + const format = new Intl.DateTimeFormat(locale, { calendar, timeZone: "UTC", ...shape }); + const formatted = format.format(epochMs); + const parts = format.formatToParts(epochMs); + const joined = parts.map(part => part.value).join(""); + expect(`${locale} ${calendar} ${JSON.stringify(shape)}`, joined, formatted); + + // A double-synthesized separator needs no separate check: it would make joined + // longer than format(), which the assertion above already catches. + for (const part of parts) + expect(`${locale} ${calendar} empty ${part.type} part`, part.value.length > 0, true); + + if (parts.some(part => part.type === "era") && format.resolvedOptions().calendar === calendar) + sawOverride = true; + ++checked; + } + } +} + +expect("cases checked", checked, locales.length * cases.length * shapes.length); +// Keeps the test from passing vacuously if era parts stop being produced. +expect("saw at least one era part", sawOverride, true); diff --git a/JSTests/stress/intl-datetimeformat.js b/JSTests/stress/intl-datetimeformat.js index 6bb394e393562..72c326f7f129d 100644 --- a/JSTests/stress/intl-datetimeformat.js +++ b/JSTests/stress/intl-datetimeformat.js @@ -8,6 +8,15 @@ function shouldBeOneOf(actual, expectedArray) { throw new Error('bad value: ' + actual + ' expected values: ' + expectedArray); } +const icuVersion = $vm.icuVersion(); +function shouldBeOneOfForICUVersion(minimumVersion, actual, expectedArray) { + if (icuVersion < minimumVersion) + return; + + if (!expectedArray.some((value) => value === actual)) + throw new Error('bad value: ' + actual + ' expected values: ' + expectedArray); +} + function shouldNotThrow(func) { func(); } @@ -305,7 +314,7 @@ shouldBe(Intl.DateTimeFormat('en-u-ca-ethiopic').resolvedOptions().calendar, 'et shouldBe(Intl.DateTimeFormat('ar-SA-u-ca-gregory').resolvedOptions().calendar, 'gregory'); shouldBe(Intl.DateTimeFormat('en-u-ca-hebrew').resolvedOptions().calendar, 'hebrew'); shouldBe(Intl.DateTimeFormat('en-u-ca-indian').resolvedOptions().calendar, 'indian'); -shouldBe(Intl.DateTimeFormat('en-u-ca-islamic').resolvedOptions().calendar, 'islamic'); +shouldBe(Intl.DateTimeFormat('en-u-ca-islamic').resolvedOptions().calendar, 'islamic-tbla'); shouldBe(Intl.DateTimeFormat('en-u-ca-islamicc').resolvedOptions().calendar, 'islamic-civil'); shouldBe(Intl.DateTimeFormat('en-u-ca-ISO8601').resolvedOptions().calendar, 'iso8601'); shouldBe(Intl.DateTimeFormat('en-u-ca-japanese').resolvedOptions().calendar, 'japanese'); @@ -315,7 +324,7 @@ shouldBe(Intl.DateTimeFormat('en-u-ca-ethiopic-amete-alem').resolvedOptions().ca shouldBe(Intl.DateTimeFormat('en-u-ca-islamic-umalqura').resolvedOptions().calendar, 'islamic-umalqura'); shouldBe(Intl.DateTimeFormat('en-u-ca-islamic-tbla').resolvedOptions().calendar, 'islamic-tbla'); shouldBe(Intl.DateTimeFormat('en-u-ca-islamic-civil').resolvedOptions().calendar, 'islamic-civil'); -shouldBe(Intl.DateTimeFormat('en-u-ca-islamic-rgsa').resolvedOptions().calendar, 'islamic-rgsa'); +shouldBe(Intl.DateTimeFormat('en-u-ca-islamic-rgsa').resolvedOptions().calendar, 'gregory'); // Calendar-sensitive format(). shouldBe(Intl.DateTimeFormat('en-u-ca-buddhist', { timeZone: 'America/Los_Angeles' }).format(1451099872641), '12/25/2558 BE'); @@ -337,7 +346,89 @@ shouldBeOneOf(Intl.DateTimeFormat('en-u-ca-ethiopic-amete-alem', { timeZone: 'Am shouldBe(Intl.DateTimeFormat('en-u-ca-islamic-umalqura', { timeZone: 'America/Los_Angeles' }).format(1451099872641), '3/14/1437 AH'); shouldBe(Intl.DateTimeFormat('en-u-ca-islamic-tbla', { timeZone: 'America/Los_Angeles' }).format(1451099872641), '3/14/1437 AH'); shouldBe(Intl.DateTimeFormat('en-u-ca-islamic-civil', { timeZone: 'America/Los_Angeles' }).format(1451099872641), '3/13/1437 AH'); -shouldBe(Intl.DateTimeFormat('en-u-ca-islamic-rgsa', { timeZone: 'America/Los_Angeles' }).format(1451099872641), '3/14/1437 AH'); +shouldBe(Intl.DateTimeFormat('en-u-ca-islamic-rgsa', { timeZone: 'America/Los_Angeles' }).format(1451099872641), '12/25/2015'); + +{ + const opts = { year: 'numeric', era: 'long', timeZone: 'UTC' }; + // islamic-civil/islamic-umalqura epoch is ISO 622-07-19T07:52:58.000Z; islamic-tbla's is one + // day earlier. A date in February 622 is before both real epochs but was previously missed by + // an overly early hardcoded threshold, so it incorrectly reported "Anno Hegirae". + shouldBeOneOf(Intl.DateTimeFormat('en-u-ca-islamic-civil', opts).format(Date.UTC(622, 1, 1)), ['Before Hijra 0', '0 Before Hijra']); + // ICU < 78 has no distinct long-form "Anno Hegirae" era string for islamic-civil in this locale and falls back to the short form ("AH"). + shouldBeOneOfForICUVersion(78, Intl.DateTimeFormat('en-u-ca-islamic-civil', opts).format(Date.UTC(650, 0, 1)), ['Anno Hegirae 29', '29 Anno Hegirae']); + shouldBeOneOf(Intl.DateTimeFormat('en-u-ca-islamic-tbla', opts).format(Date.UTC(622, 1, 1)), ['Before Hijra 0', '0 Before Hijra']); + shouldBeOneOf(Intl.DateTimeFormat('en-u-ca-islamic-umalqura', opts).format(Date.UTC(622, 1, 1)), ['Before Hijra 0', '0 Before Hijra']); + // coptic AM epoch is ISO 284-08-29T07:52:58.000Z. + shouldBeOneOf(Intl.DateTimeFormat('en-u-ca-coptic', opts).format(Date.UTC(200, 0, 1)), ['Anno Martyrum 85', '85 Anno Martyrum']); + // ICU < 78 has no distinct long-form "Anno Martyrum" era string for coptic in this locale and falls back to the generic "ERA1" placeholder. + shouldBeOneOfForICUVersion(78, Intl.DateTimeFormat('en-u-ca-coptic', opts).format(Date.UTC(400, 0, 1)), ['Anno Martyrum 116', '116 Anno Martyrum']); +} + +// The pre-Hijra/pre-AM era-text override must only apply when era was actually requested. +{ + const noEraOpts = { month: 'long', day: 'numeric', timeZone: 'UTC' }; + const civilNoEra = Intl.DateTimeFormat('en-u-ca-islamic-civil', noEraOpts).format(Date.UTC(622, 1, 1)); + if (civilNoEra.includes('Hijra')) + throw new Error(`islamic-civil pre-epoch format() with no era option must not include era text, got: ${civilNoEra}`); + const copticNoEra = Intl.DateTimeFormat('en-u-ca-coptic', noEraOpts).format(Date.UTC(200, 0, 1)); + if (copticNoEra.includes('Martyrum')) + throw new Error(`coptic pre-epoch format() with no era option must not include era text, got: ${copticNoEra}`); +} + +{ + const pre = Date.UTC(622, 1, 1); // islamic-civil pre-Hijra + const base = { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' }; + + // Width ignored: long/short/narrow all produce the same text. + for (const width of ['long', 'short', 'narrow']) { + const eraPart = Intl.DateTimeFormat('en-u-ca-islamic-civil', { ...base, era: width }).formatToParts(pre).find(p => p.type === 'era'); + shouldBe(eraPart && eraPart.value, 'Before Hijra'); + } + + // Locale ignored: ja gets the English text glued onto Japanese-formatted output. + const ja = Intl.DateTimeFormat('ja-u-ca-islamic-civil', { ...base, era: 'long' }).format(pre); + if (!ja.includes('Before Hijra')) + throw new Error(`islamic-civil pre-epoch ja locale expected (incorrect) "Before Hijra", got: ${ja}`); + + // Pattern position ignored: ja's real era position is first (see the post-epoch date, which + // ICU formats natively), but the override always appends last instead. + const postParts = Intl.DateTimeFormat('ja-u-ca-coptic', { ...base, era: 'long' }).formatToParts(Date.UTC(400, 0, 1)).map(p => p.type); + shouldBe(postParts[0], 'era'); + + // ICU4C-WORKAROUND: rdar://183226206 - below ICU 78, the coptic calendar emits a native + // era field even for pre-AM dates (ICU 78+ emits none there), so the override replaces + // that field in place instead of appending a new part. Only verifying current (78+) + // behavior here; not pinning down the old-ICU shape. + const preParts = Intl.DateTimeFormat('ja-u-ca-coptic', { ...base, era: 'long' }).formatToParts(Date.UTC(200, 0, 1)).map(p => p.type); + if (icuVersion >= 78) + shouldBe(preParts[preParts.length - 1], 'era'); +} + +// FIXME: rdar://182953351 (icu-issues/05) - formatRange/formatRangeToParts don't apply the +// pre-Hijra/pre-AM override at all, unlike format()/formatToParts. Wait for ICU to select the +// right era natively rather than threading this through more call sites; flip to `if (true)` +// once fixed. +if (false) { + const rangeOpts = { year: 'numeric', month: 'long', day: 'numeric', era: 'long', timeZone: 'UTC' }; + + const civilRange = Intl.DateTimeFormat('en-u-ca-islamic-civil', rangeOpts).formatRange(Date.UTC(622, 1, 1), Date.UTC(622, 1, 2)); + if (civilRange.includes('Anno Hegirae') || !civilRange.includes('Before Hijra')) + throw new Error(`islamic-civil pre-epoch formatRange must say "Before Hijra", got: ${civilRange}`); + + const civilPartsEra = Intl.DateTimeFormat('en-u-ca-islamic-civil', rangeOpts) + .formatRangeToParts(Date.UTC(622, 1, 1), Date.UTC(622, 1, 1)) + .filter(p => p.type === 'era'); + shouldBe(civilPartsEra.length, 1, "islamic-civil pre-epoch formatRangeToParts must include exactly one era part"); + if (civilPartsEra.length) + shouldBe(civilPartsEra[0].value, 'Before Hijra', "islamic-civil pre-epoch formatRangeToParts era value (currently shows raw ICU text)"); + + const copticPartsEra = Intl.DateTimeFormat('en-u-ca-coptic', rangeOpts) + .formatRangeToParts(Date.UTC(200, 0, 1), Date.UTC(200, 0, 1)) + .filter(p => p.type === 'era'); + shouldBe(copticPartsEra.length, 1, "coptic pre-AM formatRangeToParts must include exactly one era part (currently emits none)"); + if (copticPartsEra.length) + shouldBe(copticPartsEra[0].value, 'Anno Martyrum', "coptic pre-AM formatRangeToParts era value"); +} shouldBe(Intl.DateTimeFormat('en', { numberingSystem: 'gujr' }).resolvedOptions().numberingSystem, 'gujr'); shouldBe(Intl.DateTimeFormat('en-u-nu-bogus').resolvedOptions().locale, 'en'); diff --git a/JSTests/stress/intl-locale-info.js b/JSTests/stress/intl-locale-info.js index f03cc56161ff6..5619146bbf853 100644 --- a/JSTests/stress/intl-locale-info.js +++ b/JSTests/stress/intl-locale-info.js @@ -64,7 +64,7 @@ function shouldBeOneOf(actual, expectedArray) { { let locale = new Intl.Locale("ja") shouldBe(JSON.stringify(locale.getCalendars()), `["gregory","japanese"]`); - shouldBe(JSON.stringify(locale.getCollations()), `["unihan","emoji","eor"]`); + shouldBe(JSON.stringify(locale.getCollations()), `["emoji","eor","unihan"]`); shouldBe(locale.hourCycle, undefined); shouldBe(JSON.stringify(locale.getHourCycles()), `["h23"]`); shouldBe(JSON.stringify(locale.getNumberingSystems()), `["latn"]`); @@ -73,7 +73,7 @@ function shouldBeOneOf(actual, expectedArray) { { let locale = new Intl.Locale("ja-JP") shouldBe(JSON.stringify(locale.getCalendars()), `["gregory","japanese"]`); - shouldBe(JSON.stringify(locale.getCollations()), `["unihan","emoji","eor"]`); + shouldBe(JSON.stringify(locale.getCollations()), `["emoji","eor","unihan"]`); shouldBe(locale.hourCycle, undefined); shouldBe(JSON.stringify(locale.getHourCycles()), `["h23"]`); shouldBe(JSON.stringify(locale.getNumberingSystems()), `["latn"]`); @@ -100,7 +100,7 @@ function shouldBeOneOf(actual, expectedArray) { { let locale = new Intl.Locale("zh") shouldBe(JSON.stringify(locale.getCalendars()), `["gregory","chinese"]`); - shouldBeForICUVersion(74, JSON.stringify(locale.getCollations()), `["pinyin","stroke","unihan","zhuyin","emoji","eor"]`); + shouldBeForICUVersion(74, JSON.stringify(locale.getCollations()), `["emoji","eor","pinyin","stroke","unihan","zhuyin"]`); shouldBe(locale.hourCycle, undefined); shouldBeOneOf(JSON.stringify(locale.getHourCycles()), [ `["h23"]`, `["h12"]` ]); shouldBe(JSON.stringify(locale.getNumberingSystems()), `["latn"]`); @@ -109,7 +109,7 @@ function shouldBeOneOf(actual, expectedArray) { { let locale = new Intl.Locale("zh-TW") shouldBe(JSON.stringify(locale.getCalendars()), `["gregory","roc","chinese"]`); - shouldBeForICUVersion(74, JSON.stringify(locale.getCollations()), `["stroke","pinyin","unihan","zhuyin","emoji","eor"]`); + shouldBeForICUVersion(74, JSON.stringify(locale.getCollations()), `["emoji","eor","pinyin","stroke","unihan","zhuyin"]`); shouldBe(locale.hourCycle, undefined); shouldBe(JSON.stringify(locale.getHourCycles()), `["h12"]`); shouldBe(JSON.stringify(locale.getNumberingSystems()), `["latn"]`); @@ -118,7 +118,7 @@ function shouldBeOneOf(actual, expectedArray) { { let locale = new Intl.Locale("zh-HK") shouldBe(JSON.stringify(locale.getCalendars()), `["gregory","chinese"]`); - shouldBeForICUVersion(74, JSON.stringify(locale.getCollations()), `["stroke","pinyin","unihan","zhuyin","emoji","eor"]`); + shouldBeForICUVersion(74, JSON.stringify(locale.getCollations()), `["emoji","eor","pinyin","stroke","unihan","zhuyin"]`); shouldBe(locale.hourCycle, undefined); shouldBe(JSON.stringify(locale.getHourCycles()), `["h12"]`); shouldBe(JSON.stringify(locale.getNumberingSystems()), `["latn"]`); diff --git a/JSTests/stress/iterator-prototype-chunks.js b/JSTests/stress/iterator-prototype-chunks.js index 70e4b28e1402c..9ac2d228a90e9 100644 --- a/JSTests/stress/iterator-prototype-chunks.js +++ b/JSTests/stress/iterator-prototype-chunks.js @@ -144,14 +144,24 @@ function shouldThrow(fn, error, message) { { const invalidChunkSizes = [ undefined, + null, "test", + "1", + true, {}, + [1], + Symbol("symbol"), + NaN, + 0.5, + 1.5, + Infinity, + -Infinity, ]; const validIter = (function* gen() {})(); for (const invalidChunkSize of invalidChunkSizes) { shouldThrow(function () { Iterator.prototype.chunks.call(validIter, invalidChunkSize); - }, RangeError, "Iterator.prototype.chunks requires that first argument not be NaN."); + }, TypeError, "Iterator.prototype.chunks requires that first argument be an integral Number."); } } @@ -159,8 +169,9 @@ function shouldThrow(fn, error, message) { const invalidChunkSizes = [ -1, 0, + -0, 2 ** 32, - null, + 2 ** 53, ]; const validIter = (function* gen() {})(); for (const invalidChunkSize of invalidChunkSizes) { @@ -168,4 +179,57 @@ function shouldThrow(fn, error, message) { Iterator.prototype.chunks.call(validIter, invalidChunkSize); }, RangeError, "Iterator.prototype.chunks requires that first argument be between 1 and 2**32 - 1."); } + + validIter.chunks(1); + validIter.chunks(2 ** 32 - 1); +} + +{ + const validIter = (function* gen() {})(); + shouldThrow(function () { + validIter.chunks({ valueOf() { throw new Error("chunkSize must not be coerced"); } }); + }, TypeError, "Iterator.prototype.chunks requires that first argument be an integral Number."); +} + +{ + let closeCount = 0; + const closable = { + __proto__: Iterator.prototype, + get next() { + throw new Error("next must not be read before the arguments are validated"); + }, + return() { + ++closeCount; + return {}; + }, + }; + + shouldThrow(function () { + closable.chunks(); + }, TypeError, "Iterator.prototype.chunks requires that first argument be an integral Number."); + assert(closeCount === 1); + + shouldThrow(function () { + closable.chunks(0); + }, RangeError, "Iterator.prototype.chunks requires that first argument be between 1 and 2**32 - 1."); + assert(closeCount === 2); +} + +{ + let returnGets = 0; + const closable = { + __proto__: Iterator.prototype, + get next() { + throw new Error("next must not be read before the arguments are validated"); + }, + get return() { + ++returnGets; + throw new Error("this error must be masked by the validation error"); + }, + }; + + shouldThrow(function () { + closable.chunks(0); + }, RangeError, "Iterator.prototype.chunks requires that first argument be between 1 and 2**32 - 1."); + assert(returnGets === 1); } diff --git a/JSTests/stress/iterator-prototype-join.js b/JSTests/stress/iterator-prototype-join.js index 0a3b091e73fc2..7ee48ed186e67 100644 --- a/JSTests/stress/iterator-prototype-join.js +++ b/JSTests/stress/iterator-prototype-join.js @@ -48,22 +48,22 @@ sameValue([{ toString() { return ""; } }].values().join(), ""); sameValue([{ toString() { return "a"; } }].values().join(), "a"); sameValue([{ toString() { return "abc"; } }].values().join(), "abc"); -sameValue([1, undefined, 2, null, 3].values().join(), "1,2,3"); -sameValue([1, undefined, 2, null, 3].values().join(0), "10203"); -sameValue([1, undefined, 2, null, 3].values().join(null), "1null2null3"); -sameValue([1, undefined, 2, null, 3].values().join(undefined), "1,2,3"); +sameValue([1, undefined, 2, null, 3].values().join(), "1,,2,,3"); +sameValue([1, undefined, 2, null, 3].values().join(0), "1002003"); +sameValue([1, undefined, 2, null, 3].values().join(null), "1nullnull2nullnull3"); +sameValue([1, undefined, 2, null, 3].values().join(undefined), "1,,2,,3"); sameValue([1, undefined, 2, null, 3].values().join(""), "123"); -sameValue([1, undefined, 2, null, 3].values().join(","), "1,2,3"); -sameValue([1, undefined, 2, null, 3].values().join(", "), "1, 2, 3"); +sameValue([1, undefined, 2, null, 3].values().join(","), "1,,2,,3"); +sameValue([1, undefined, 2, null, 3].values().join(", "), "1, , 2, , 3"); -sameValue([1, undefined, 2, null, 3].values().join({ toString() { return 0; } }), "10203"); -sameValue([1, undefined, 2, null, 3].values().join({ toString() { return true; } }), "1true2true3"); -sameValue([1, undefined, 2, null, 3].values().join({ toString() { return false; } }), "1false2false3"); -sameValue([1, undefined, 2, null, 3].values().join({ toString() { return null; } }), "1null2null3"); -sameValue([1, undefined, 2, null, 3].values().join({ toString() { return undefined; } }), "1undefined2undefined3"); +sameValue([1, undefined, 2, null, 3].values().join({ toString() { return 0; } }), "1002003"); +sameValue([1, undefined, 2, null, 3].values().join({ toString() { return true; } }), "1truetrue2truetrue3"); +sameValue([1, undefined, 2, null, 3].values().join({ toString() { return false; } }), "1falsefalse2falsefalse3"); +sameValue([1, undefined, 2, null, 3].values().join({ toString() { return null; } }), "1nullnull2nullnull3"); +sameValue([1, undefined, 2, null, 3].values().join({ toString() { return undefined; } }), "1undefinedundefined2undefinedundefined3"); sameValue([1, undefined, 2, null, 3].values().join({ toString() { return ""; } }), "123"); -sameValue([1, undefined, 2, null, 3].values().join({ toString() { return ","; } }), "1,2,3"); -sameValue([1, undefined, 2, null, 3].values().join({ toString() { return ", "; } }), "1, 2, 3"); +sameValue([1, undefined, 2, null, 3].values().join({ toString() { return ","; } }), "1,,2,,3"); +sameValue([1, undefined, 2, null, 3].values().join({ toString() { return ", "; } }), "1, , 2, , 3"); { let nextGetCount = 0; @@ -96,7 +96,7 @@ sameValue([1, undefined, 2, null, 3].values().join({ toString() { return ", "; } yield null; yield undefined; } - sameValue(gen().join("|"), "1|2|3|4|5"); + sameValue(gen().join("|"), "||1|2|3|4|5||"); } { diff --git a/JSTests/stress/iterator-prototype-windows.js b/JSTests/stress/iterator-prototype-windows.js index 749e2846fe2ca..46338cbdc3e4f 100644 --- a/JSTests/stress/iterator-prototype-windows.js +++ b/JSTests/stress/iterator-prototype-windows.js @@ -363,14 +363,24 @@ function shouldThrow(fn, error, message) { { const invalidWindowSizes = [ undefined, + null, "test", + "1", + true, {}, + [1], + Symbol("symbol"), + NaN, + 0.5, + 1.5, + Infinity, + -Infinity, ]; const validIter = (function* gen() {})(); for (const invalidWindowSize of invalidWindowSizes) { shouldThrow(function () { Iterator.prototype.windows.call(validIter, invalidWindowSize); - }, RangeError, "Iterator.prototype.windows requires that first argument not be NaN."); + }, TypeError, "Iterator.prototype.windows requires that first argument be an integral Number."); } } @@ -378,8 +388,9 @@ function shouldThrow(fn, error, message) { const invalidWindowSizes = [ -1, 0, + -0, 2 ** 32, - null, + 2 ** 53, ]; const validIter = (function* gen() {})(); for (const invalidWindowSize of invalidWindowSizes) { @@ -387,9 +398,85 @@ function shouldThrow(fn, error, message) { Iterator.prototype.windows.call(validIter, invalidWindowSize); }, RangeError, "Iterator.prototype.windows requires that first argument be between 1 and 2**32 - 1."); } + + validIter.windows(1); + validIter.windows(2 ** 32 - 1); } -shouldThrow(function () { +{ + const validIter = (function* gen() {})(); + shouldThrow(function () { + validIter.windows({ valueOf() { throw new Error("windowSize must not be coerced"); } }); + }, TypeError, "Iterator.prototype.windows requires that first argument be an integral Number."); +} + +{ + const invalidUndersizedValues = [ + null, + "", + "invalid", + 0, + true, + false, + {}, + Symbol("symbol"), + ]; const validIter = (function* gen() {})(); - validIter.windows(1, "invalid"); -}, TypeError, "Iterator.prototype.windows requires that second argument be \"only-full\" or \"allow-partial\"."); + for (const invalidUndersized of invalidUndersizedValues) { + shouldThrow(function () { + validIter.windows(1, invalidUndersized); + }, TypeError, "Iterator.prototype.windows requires that second argument be \"only-full\" or \"allow-partial\"."); + } + + validIter.windows(1, undefined); + validIter.windows(1, "only-full"); + validIter.windows(1, "allow-partial"); +} + +{ + let closeCount = 0; + const closable = { + __proto__: Iterator.prototype, + get next() { + throw new Error("next must not be read before the arguments are validated"); + }, + return() { + ++closeCount; + return {}; + }, + }; + + shouldThrow(function () { + closable.windows(); + }, TypeError, "Iterator.prototype.windows requires that first argument be an integral Number."); + assert(closeCount === 1); + + shouldThrow(function () { + closable.windows(0); + }, RangeError, "Iterator.prototype.windows requires that first argument be between 1 and 2**32 - 1."); + assert(closeCount === 2); + + shouldThrow(function () { + closable.windows(1, "invalid"); + }, TypeError, "Iterator.prototype.windows requires that second argument be \"only-full\" or \"allow-partial\"."); + assert(closeCount === 3); +} + +{ + let returnGets = 0; + const closable = { + __proto__: Iterator.prototype, + get next() { + throw new Error("next must not be read before the arguments are validated"); + }, + get return() { + ++returnGets; + throw new Error("this error must be masked by the validation error"); + }, + }; + + shouldThrow(function () { + closable.windows(1, "invalid"); + }, TypeError, "Iterator.prototype.windows requires that second argument be \"only-full\" or \"allow-partial\"."); + assert(returnGets === 1); +} diff --git a/JSTests/stress/iterator-prototype.js b/JSTests/stress/iterator-prototype.js index 1967cd2f44041..1e75c9fbddccd 100644 --- a/JSTests/stress/iterator-prototype.js +++ b/JSTests/stress/iterator-prototype.js @@ -22,7 +22,10 @@ shouldBe(JSON.stringify(Object.getOwnPropertyNames(iteratorPrototype)), '[' + [ 'take', 'drop', 'flatMap', + 'chunks', + 'windows', 'includes', + 'join', ].map((val) => `"${val}"`).join(',') + ']'); shouldBe(Object.getOwnPropertySymbols(iteratorPrototype).length, 3); diff --git a/JSTests/stress/json-parse-short-value-then-key.js b/JSTests/stress/json-parse-short-value-then-key.js new file mode 100644 index 0000000000000..5499429be9244 --- /dev/null +++ b/JSTests/stress/json-parse-short-value-then-key.js @@ -0,0 +1,66 @@ +function shouldBe(actual, expected, msg) { + if (actual !== expected) + throw new Error("FAIL " + msg + ": got " + JSON.stringify(actual) + ", expected " + JSON.stringify(expected)); +} + +// A short string first seen as a value is cached without being atomized. The +// same string later used as a key must still produce a working property, and a +// subsequent value occurrence must still return the right string. +for (let i = 0; i < 3; ++i) { + let r = JSON.parse('["prop_a1", {"prop_a1": 1}, {"prop_a1": 2, "z": 3}, "prop_a1", {"prop_a1": 4}]'); + shouldBe(r[0], "prop_a1", "value before key"); + shouldBe(r[1].prop_a1, 1, "key after value"); + shouldBe(r[1][r[0]], 1, "computed lookup with parsed value"); + shouldBe(Object.keys(r[1])[0], "prop_a1", "Object.keys"); + shouldBe(r[2].prop_a1, 2, "key again (existing transition)"); + shouldBe(r[2].z, 3, "second key"); + shouldBe(r[3], "prop_a1", "value after key"); + shouldBe(r[4].prop_a1, 4, "key after value after key"); + + let o = {}; + o[r[3]] = 5; + shouldBe(o.prop_a1, 5, "parsed value used as a key elsewhere"); + shouldBe("prop_a1" in r[1], true, "in"); + + fullGC(); +} + +// Value evicted by a colliding value, then used as key. +{ + let r = JSON.parse('["kQx", "kRx", {"kQx": 1, "kRx": 2}, "kQx", "kRx"]'); + shouldBe(r[2].kQx, 1, "evicted value as key 1"); + shouldBe(r[2].kRx, 2, "evicted value as key 2"); + shouldBe(r[3], "kQx", "value after eviction 1"); + shouldBe(r[4], "kRx", "value after eviction 2"); +} + +// 16-bit source with Latin-1 and non-Latin-1 short values and keys. +{ + let r = JSON.parse('["v\\u00e9", "v\\u3042", {"v\\u00e9": 1, "v\\u3042": 2}, "v\\u00e9", "v\\u3042", "いk", {"いk": 3}]'); + shouldBe(r[0], "vé", "latin1 value"); + shouldBe(r[1], "vあ", "utf16 value"); + shouldBe(r[2]["vé"], 1, "latin1 key"); + shouldBe(r[2]["vあ"], 2, "utf16 key"); + shouldBe(r[3], "vé", "latin1 value again"); + shouldBe(r[4], "vあ", "utf16 value again"); + shouldBe(r[5], "いk", "utf16 source value"); + shouldBe(r[6]["いk"], 3, "utf16 source key"); + + let s = JSON.parse('["\\u3042", "\\u3042", {"\\u3042": 1}, "\\u3042"]'); + shouldBe(s[0], "あ", "single non-latin1 char value"); + shouldBe(s[1], "あ", "single non-latin1 char value again"); + shouldBe(s[2]["あ"], 1, "single non-latin1 char key"); + shouldBe(s[3], "あ", "single non-latin1 char value after key"); +} + +// Repeated short values across many parses share content correctly across GCs. +{ + let json = JSON.stringify(Array.from({ length: 200 }, (_, i) => "id-" + (i % 7))); + for (let i = 0; i < 200; ++i) { + let r = JSON.parse(json); + for (let j = 0; j < r.length; j += 37) + shouldBe(r[j], "id-" + (j % 7), "repeated parse " + i + "/" + j); + if (i % 50 == 0) + edenGC(); + } +} diff --git a/JSTests/stress/json-stringify-fast-path-custom-prototype-edge-cases.js b/JSTests/stress/json-stringify-fast-path-custom-prototype-edge-cases.js new file mode 100644 index 0000000000000..684db548813b9 --- /dev/null +++ b/JSTests/stress/json-stringify-fast-path-custom-prototype-edge-cases.js @@ -0,0 +1,481 @@ +//@ requireOptions("--useDollarVM=true") + +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error("bad value: " + actual + " expected: " + expected); +} + +function shouldThrow(func, errorMessage) { + let errorThrown = false; + try { + func(); + } catch (error) { + errorThrown = true; + if (String(error) !== errorMessage) + throw new Error("bad error: " + String(error)); + } + if (!errorThrown) + throw new Error("not thrown"); +} + +// Warm the fast path against an instance so its structure caches "no toJSON", then mutate. +function warm(value, expected) { + for (let i = 0; i < 20; ++i) + shouldBe(JSON.stringify(value), expected); +} + +// toJSON appears on the prototype through every route that can add a property. +{ + class C { constructor() { this.x = 1; } } + let c = new C; + warm(c, `{"x":1}`); + C.prototype.toJSON = () => "assign"; + shouldBe(JSON.stringify(c), `"assign"`); + delete C.prototype.toJSON; + warm(c, `{"x":1}`); + Object.defineProperty(C.prototype, "toJSON", { value: () => "define", configurable: true, enumerable: false, writable: true }); + shouldBe(JSON.stringify(c), `"define"`); + delete C.prototype.toJSON; + warm(c, `{"x":1}`); + Object.assign(C.prototype, { toJSON: () => "Object.assign" }); + shouldBe(JSON.stringify(c), `"Object.assign"`); + delete C.prototype.toJSON; + warm(c, `{"x":1}`); + Reflect.set(C.prototype, "toJSON", () => "Reflect.set"); + shouldBe(JSON.stringify(c), `"Reflect.set"`); + delete C.prototype.toJSON; + warm(c, `{"x":1}`); + C.prototype["to" + "JSON"] = () => "computed"; + shouldBe(JSON.stringify(c), `"computed"`); +} + +// toJSON replaced in place (same structure, different value). +{ + class C { constructor() { this.x = 1; } toJSON() { return "v1"; } } + let c = new C; + warm(c, `"v1"`); + C.prototype.toJSON = () => "v2"; + shouldBe(JSON.stringify(c), `"v2"`); + C.prototype.toJSON = undefined; + shouldBe(JSON.stringify(c), `{"x":1}`); + C.prototype.toJSON = null; + shouldBe(JSON.stringify(c), `{"x":1}`); + C.prototype.toJSON = 42; + shouldBe(JSON.stringify(c), `{"x":1}`); + C.prototype.toJSON = () => "v3"; + shouldBe(JSON.stringify(c), `"v3"`); +} + +// toJSON as accessor on the prototype: getter must run once per serialization, with the instance as receiver. +{ + let receivers = []; + class C { constructor() { this.x = 1; } } + Object.defineProperty(C.prototype, "toJSON", { get() { receivers.push(this); return function () { return this.x * 10; }; }, configurable: true }); + let a = new C, b = new C; + b.x = 2; + shouldBe(JSON.stringify([a, b, a]), `[10,20,10]`); + shouldBe(receivers.length, 3); + shouldBe(receivers[0], a); + shouldBe(receivers[1], b); + shouldBe(receivers[2], a); + + // Getter that mutates the holder mid-serialization. + class D { constructor() { this.p = 1; this.q = 2; } } + Object.defineProperty(D.prototype, "toJSON", { get() { delete this.q; this.r = 3; return undefined; }, configurable: true }); + shouldBe(JSON.stringify(new D), `{"p":1,"r":3}`); +} + +// Prototype swapped after warm-up. +{ + class C { constructor() { this.x = 1; } } + let c = new C; + warm(c, `{"x":1}`); + Object.setPrototypeOf(c, { toJSON() { return "swapped instance proto"; } }); + shouldBe(JSON.stringify(c), `"swapped instance proto"`); + + class D { constructor() { this.x = 1; } } + let d = new D; + warm(d, `{"x":1}`); + Object.setPrototypeOf(D.prototype, { toJSON() { return "swapped grandparent"; } }); + shouldBe(JSON.stringify(d), `"swapped grandparent"`); + Object.setPrototypeOf(D.prototype, Object.prototype); + shouldBe(JSON.stringify(d), `{"x":1}`); + Object.setPrototypeOf(D.prototype, null); + shouldBe(JSON.stringify(d), `{"x":1}`); +} + +// Deep chain: toJSON inserted at every level in turn. +{ + const depth = 12; + let protos = [Object.create(null)]; + for (let i = 1; i < depth; ++i) + protos.push(Object.create(protos[i - 1])); + let leaf = Object.create(protos[depth - 1]); + leaf.v = 1; + warm({ leaf }, `{"leaf":{"v":1}}`); + for (let i = 0; i < depth; ++i) { + protos[i].toJSON = () => "level" + i; + shouldBe(JSON.stringify({ leaf }), `{"leaf":"level${i}"}`); + delete protos[i].toJSON; + shouldBe(JSON.stringify({ leaf }), `{"leaf":{"v":1}}`); + } +} + +// Many distinct classes in one payload; toJSON added to one in the middle afterwards. +{ + let classes = []; + for (let i = 0; i < 30; ++i) + classes.push(eval(`(class K${i} { constructor() { this.i = ${i}; } })`)); + let payload = classes.map((K) => new K); + let expected = "[" + classes.map((_, i) => `{"i":${i}}`).join(",") + "]"; + warm(payload, expected); + classes[17].prototype.toJSON = function () { return -this.i; }; + shouldBe(JSON.stringify(payload), expected.replace(`{"i":17}`, `-17`)); +} + +// Same class, instances with divergent structures (property order / extra props / deletes). +{ + class C { constructor(flip) { if (flip) { this.b = 2; this.a = 1; } else { this.a = 1; this.b = 2; } } } + let x = new C(false), y = new C(true), z = new C(false); + z.c = 3; + delete z.a; + warm([x, y, z], `[{"a":1,"b":2},{"b":2,"a":1},{"b":2,"c":3}]`); + C.prototype.toJSON = function () { return Object.keys(this).join(""); }; + shouldBe(JSON.stringify([x, y, z]), `["ab","ba","bc"]`); +} + +// Dictionary-mode prototype and dictionary-mode instance. +{ + class C { constructor() { this.x = 1; } } + for (let i = 0; i < 100; ++i) + C.prototype["m" + i] = i; + for (let i = 0; i < 100; ++i) + delete C.prototype["m" + i]; + let c = new C; + warm(c, `{"x":1}`); + C.prototype.toJSON = () => "dict proto"; + shouldBe(JSON.stringify(c), `"dict proto"`); + delete C.prototype.toJSON; + warm(c, `{"x":1}`); + + $vm.toUncacheableDictionary(C.prototype); + warm(c, `{"x":1}`); + C.prototype.toJSON = () => "uncacheable dict proto"; + shouldBe(JSON.stringify(c), `"uncacheable dict proto"`); + delete C.prototype.toJSON; + shouldBe(JSON.stringify(c), `{"x":1}`); + + class D { constructor() { this.x = 1; } } + let d = new D; + for (let i = 0; i < 100; ++i) + d["k" + i] = i; + for (let i = 0; i < 100; ++i) + delete d["k" + i]; + warm({ d }, `{"d":{"x":1}}`); + D.prototype.toJSON = () => "dict instance"; + shouldBe(JSON.stringify({ d }), `{"d":"dict instance"}`); +} + +// Frozen / sealed / non-extensible prototypes and instances. +{ + class C { constructor() { this.x = 1; } } + Object.freeze(C.prototype); + let c = new C; + warm(c, `{"x":1}`); + shouldBe(JSON.stringify(Object.freeze(new C)), `{"x":1}`); + shouldBe(JSON.stringify(Object.seal(new C)), `{"x":1}`); + shouldBe(JSON.stringify(Object.preventExtensions(new C)), `{"x":1}`); + + class D { constructor() { this.x = 1; } } + Object.defineProperty(D.prototype, "toJSON", { value() { return "frozen toJSON"; }, writable: false, configurable: false }); + Object.freeze(D.prototype); + shouldBe(JSON.stringify(new D), `"frozen toJSON"`); +} + +// Objects in the chain whose property lookup is not side-effect-free must be observed. +{ + // Proxy as immediate prototype. + let traps = []; + let handler = { + get(t, k, r) { traps.push("get:" + String(k)); return Reflect.get(t, k, r); }, + has(t, k) { traps.push("has:" + String(k)); return Reflect.has(t, k); }, + getOwnPropertyDescriptor(t, k) { traps.push("gopd:" + String(k)); return Reflect.getOwnPropertyDescriptor(t, k); }, + ownKeys(t) { traps.push("ownKeys"); return Reflect.ownKeys(t); }, + getPrototypeOf(t) { traps.push("getPrototypeOf"); return Reflect.getPrototypeOf(t); }, + }; + let o = Object.create(new Proxy({}, handler)); + o.a = 1; + for (let i = 0; i < 5; ++i) { + traps.length = 0; + shouldBe(JSON.stringify({ o }), `{"o":{"a":1}}`); + shouldBe(traps.join(","), `get:toJSON`); + } + + // Proxy further up the chain, returning a toJSON. + let mid = Object.create(new Proxy({}, { get(t, k) { if (k === "toJSON") return () => "from proxy"; } })); + let leaf = Object.create(mid); + leaf.b = 2; + shouldBe(JSON.stringify({ leaf }), `{"leaf":"from proxy"}`); + + // Revoked proxy in the chain. + let { proxy, revoke } = Proxy.revocable({}, {}); + let r = Object.create(proxy); + r.c = 3; + shouldBe(JSON.stringify({ r }), `{"r":{"c":3}}`); + revoke(); + shouldThrow(() => JSON.stringify({ r }), "TypeError: Proxy has already been revoked. No more operations are allowed to be performed on it"); + + // $vm ImpureGetter (overridesGetOwnPropertySlot) in the chain, delegating to an object with toJSON. + let delegate = { toJSON() { return "impure"; } }; + let ig = Object.create($vm.createImpureGetter(delegate)); + ig.d = 4; + shouldBe(JSON.stringify({ ig }), `{"ig":"impure"}`); + let ig2 = Object.create($vm.createImpureGetter({})); + ig2.d = 4; + shouldBe(JSON.stringify({ ig2 }), `{"ig2":{"d":4}}`); +} + +// Built-in prototypes with lazily reified static property tables in the chain. +{ + let g = createGlobalObject(); + let { Object: GO, Date: GD, JSON: GJ } = g; + // Fresh realm so nothing has reified Date.prototype.toJSON yet. + let o = GO.create(GD.prototype); + o.x = 1; + shouldThrow(() => GJ.stringify({ o }), "TypeError: Type error"); + shouldThrow(() => GJ.stringify(o), "TypeError: Type error"); + let oo = GO.create(GO.create(GD.prototype)); + oo.y = 2; + shouldThrow(() => GJ.stringify({ oo }), "TypeError: Type error"); + + // A static-table prototype without toJSON is fine (RegExp.prototype has a static table but no toJSON). + let p = Object.create(RegExp.prototype); + p.z = 3; + warm({ p }, `{"p":{"z":3}}`); + RegExp.prototype.toJSON = () => "regexp proto"; + shouldBe(JSON.stringify({ p }), `{"p":"regexp proto"}`); + delete RegExp.prototype.toJSON; +} + +// Non-final ObjectType cells with custom prototypes must keep slow-path semantics. +{ + class C { constructor() { this.x = 1; } } + let bi = Object.setPrototypeOf(Object(1n), C.prototype); + shouldThrow(() => JSON.stringify({ bi }), "TypeError: JSON.stringify cannot serialize BigInt."); + C.prototype.toJSON = function () { return typeof this; }; + shouldBe(JSON.stringify({ bi }), `{"bi":"object"}`); + delete C.prototype.toJSON; + + let sym = Object.setPrototypeOf(Object(Symbol("s")), C.prototype); + shouldBe(JSON.stringify({ sym }), `{"sym":{}}`); + + let num = Object.setPrototypeOf(new Number(7), C.prototype); + shouldBe(JSON.stringify({ num }), `{"num":null}`); + let num2 = Object.setPrototypeOf(new Number(7), { valueOf() { return 8; } }); + shouldBe(JSON.stringify({ num2 }), `{"num2":8}`); + + let bool = Object.setPrototypeOf(new Boolean(false), C.prototype); + shouldBe(JSON.stringify({ bool }), `{"bool":false}`); + + let str = Object.setPrototypeOf(new String("s"), { toString() { return "custom toString"; } }); + shouldBe(JSON.stringify({ str }), `{"str":"custom toString"}`); + + let raw = JSON.rawJSON("123"); + shouldBe(Object.getPrototypeOf(raw), null); + shouldBe(JSON.stringify({ raw }), `{"raw":123}`); + + let err = new TypeError("m"); + shouldBe(JSON.stringify({ err }), `{"err":{}}`); + let re = /x/; + re.k = 1; + shouldBe(JSON.stringify({ re }), `{"re":{"k":1}}`); + let map = new Map; + map.k = 1; + shouldBe(JSON.stringify({ map }), `{"map":{"k":1}}`); + let date = new Date(0); + shouldBe(JSON.stringify({ date }), `{"date":"1970-01-01T00:00:00.000Z"}`); +} + +// Callable / arguments / namespace-like objects with custom or null prototypes. +{ + let f = Object.setPrototypeOf(function () { }, null); + f.x = 1; + shouldBe(JSON.stringify({ f }), `{}`); + shouldBe(JSON.stringify([f]), `[null]`); + let bound = Object.setPrototypeOf((function () { }).bind(), { toJSON() { return "bound"; } }); + shouldBe(JSON.stringify({ bound }), `{"bound":"bound"}`); + let args = (function () { return arguments; })(1, 2); + Object.setPrototypeOf(args, null); + shouldBe(JSON.stringify(args), `{"0":1,"1":2}`); +} + +// Own-property shapes on custom-prototype instances that the fast path must reject or handle. +{ + class C { constructor() { this.x = 1; } } + + let indexed = new C; + indexed[0] = "zero"; + shouldBe(JSON.stringify(indexed), `{"0":"zero","x":1}`); + + let withGetter = new C; + let calls = 0; + Object.defineProperty(withGetter, "g", { get() { calls++; return "got"; }, enumerable: true }); + shouldBe(JSON.stringify(withGetter), `{"x":1,"g":"got"}`); + shouldBe(calls, 1); + + let withSymbol = new C; + withSymbol[Symbol("s")] = 1; + withSymbol.y = 2; + shouldBe(JSON.stringify(withSymbol), `{"x":1,"y":2}`); + + let withNonEnumerable = new C; + Object.defineProperty(withNonEnumerable, "hidden", { value: 1, enumerable: false }); + withNonEnumerable.y = 2; + shouldBe(JSON.stringify(withNonEnumerable), `{"x":1,"y":2}`); + + let ownToJSON = new C; + ownToJSON.toJSON = () => "own enumerable"; + shouldBe(JSON.stringify({ ownToJSON }), `{"ownToJSON":"own enumerable"}`); + let ownHiddenToJSON = new C; + Object.defineProperty(ownHiddenToJSON, "toJSON", { value: () => "own non-enumerable", enumerable: false }); + shouldBe(JSON.stringify({ ownHiddenToJSON }), `{"ownHiddenToJSON":"own non-enumerable"}`); + let ownNonCallableToJSON = new C; + ownNonCallableToJSON.toJSON = "not callable"; + shouldBe(JSON.stringify(ownNonCallableToJSON), `{"x":1,"toJSON":"not callable"}`); + + let undefinedAndFunctionValues = new C; + undefinedAndFunctionValues.u = undefined; + undefinedAndFunctionValues.fn = function () { }; + undefinedAndFunctionValues.s = Symbol(); + undefinedAndFunctionValues.y = 2; + shouldBe(JSON.stringify(undefinedAndFunctionValues), `{"x":1,"y":2}`); + + let escapes = new C; + escapes["key\n"] = "v\"\\"; + shouldBe(JSON.stringify(escapes), `{"x":1,"key\\n":"v\\"\\\\\\u0001"}`); + + let sixteenBit = new C; + sixteenBit.name = "日本語"; + sixteenBit["キー"] = 1; + shouldBe(JSON.stringify(sixteenBit), `{"x":1,"name":"日本語","キー":1}`); + + class Big { constructor() { for (let i = 0; i < 200; ++i) this["p" + i] = i; } } + let big = new Big; + shouldBe(JSON.stringify(big), JSON.stringify(Object.assign({}, big))); + shouldBe(JSON.parse(JSON.stringify(big)).p199, 199); + + class P { #priv = 42; constructor() { this.pub = 1; } static has(o) { return #priv in o; } } + shouldBe(JSON.stringify(new P), `{"pub":1}`); + shouldBe(P.has(JSON.parse(JSON.stringify(new P))), false); +} + +// Enumerable data on the prototype never leaks; shadowing works. +{ + let proto = { inherited: "no", shadowed: "proto" }; + let o = Object.create(proto); + o.shadowed = "own"; + warm(o, `{"shadowed":"own"}`); + Object.defineProperty(proto, "accessor", { get() { throw new Error("prototype getter must not run"); }, enumerable: true }); + shouldBe(JSON.stringify(o), `{"shadowed":"own"}`); +} + +// Object.prototype.toJSON interacts with custom-prototype instances (chain ends at Object.prototype) but not null-prototype ones. +{ + class C { constructor() { this.x = 1; } } + let c = new C; + let n = Object.create(null); + n.x = 1; + let each = () => [c, n, {}].map((v) => JSON.stringify(v)).join("|"); + for (let i = 0; i < 20; ++i) + shouldBe(each(), `{"x":1}|{"x":1}|{}`); + Object.prototype.toJSON = function () { return "OP"; }; + shouldBe(each(), `"OP"|{"x":1}|"OP"`); + delete Object.prototype.toJSON; + for (let i = 0; i < 20; ++i) + shouldBe(each(), `{"x":1}|{"x":1}|{}`); + Object.defineProperty(Object.prototype, "toJSON", { get() { return () => "OP getter"; }, configurable: true }); + shouldBe(each(), `"OP getter"|{"x":1}|"OP getter"`); + delete Object.prototype.toJSON; + shouldBe(each(), `{"x":1}|{"x":1}|{}`); +} + +// Cross-realm instances and prototypes. +{ + let g = createGlobalObject(); + g.eval(`var R = class R { constructor() { this.r = 1; } }; var inst = new R; var plain = { p: 1 };`); + shouldBe(JSON.stringify({ a: g.inst, b: g.plain }), `{"a":{"r":1},"b":{"p":1}}`); + shouldBe(g.JSON.stringify({ a: g.inst, b: g.plain }), `{"a":{"r":1},"b":{"p":1}}`); + g.R.prototype.toJSON = () => "other realm"; + shouldBe(JSON.stringify({ a: g.inst }), `{"a":"other realm"}`); + delete g.R.prototype.toJSON; + g.Object.prototype.toJSON = () => "other realm OP"; + shouldBe(JSON.stringify({ a: g.inst, b: g.plain, c: {} }), `{"a":"other realm OP","b":"other realm OP","c":{}}`); + delete g.Object.prototype.toJSON; + + let mixed = Object.create(g.inst); + mixed.m = 1; + shouldBe(JSON.stringify(mixed), `{"m":1}`); + g.R.prototype.toJSON = () => "via other realm chain"; + shouldBe(JSON.stringify(mixed), `"via other realm chain"`); +} + +// toJSON receives the key and correct receiver on the fast->slow handoff; result feeds back into serialization. +{ + let seen = []; + class C { constructor(v) { this.v = v; } } + C.prototype.toJSON = function (key) { seen.push(key); return { wrapped: this.v, nested: new D(this.v) }; }; + class D { constructor(v) { this.d = v; } } + shouldBe(JSON.stringify({ a: new C(1), list: [new C(2)] }), `{"a":{"wrapped":1,"nested":{"d":1}},"list":[{"wrapped":2,"nested":{"d":2}}]}`); + shouldBe(seen.join(","), `a,0`); + D.prototype.toJSON = function (key) { seen.push("D:" + key); return this.d * 100; }; + shouldBe(JSON.stringify(new C(3)), `{"wrapped":3,"nested":300}`); + shouldBe(seen.join(","), `a,0,,D:nested`); +} + +// Interaction with gap / replacer arguments (replacer always takes the slow path; gap has its own fast path). +{ + class C { constructor() { this.x = 1; this.y = new D; } } + class D { constructor() { this.z = 2; } } + shouldBe(JSON.stringify(new C, null, 2), `{\n "x": 1,\n "y": {\n "z": 2\n }\n}`); + shouldBe(JSON.stringify(new C, null, "--"), `{\n--"x": 1,\n--"y": {\n----"z": 2\n--}\n}`); + shouldBe(JSON.stringify(new C, ["y", "z"]), `{"y":{"z":2}}`); + shouldBe(JSON.stringify(new C, (k, v) => (v instanceof D ? "replaced" : v)), `{"x":1,"y":"replaced"}`); + D.prototype.toJSON = () => "tj"; + shouldBe(JSON.stringify(new C, null, 1), `{\n "x": 1,\n "y": "tj"\n}`); +} + +// Large payload that overflows the static buffer mid-way through custom-prototype instances, then toJSON added. +{ + class Row { constructor(i) { this.id = i; this.label = "row-" + i + "-".repeat(50); } } + let rows = []; + for (let i = 0; i < 400; ++i) + rows.push(new Row(i)); + let s = JSON.stringify(rows); + shouldBe(s.length > 8192, true); + shouldBe(JSON.parse(s)[399].id, 399); + Row.prototype.toJSON = function () { return this.id; }; + shouldBe(JSON.stringify(rows), "[" + rows.map((r) => r.id).join(",") + "]"); +} + +// Cyclic structure through custom-prototype instances still throws. +{ + class N { constructor() { this.next = null; } } + let a = new N, b = new N; + a.next = b; + b.next = a; + shouldThrow(() => JSON.stringify(a), "TypeError: JSON.stringify cannot serialize cyclic structures."); +} + +// has-poly-proto structures (created by repeatedly instantiating a class defined inside a function). +{ + function make(v) { + class Poly { constructor() { this.v = v; } } + return new Poly; + } + let objs = []; + for (let i = 0; i < 50; ++i) + objs.push(make(i)); + shouldBe(JSON.stringify(objs), "[" + objs.map((o) => `{"v":${o.v}}`).join(",") + "]"); + Object.getPrototypeOf(objs[10]).toJSON = () => "poly10"; + shouldBe(JSON.stringify([objs[9], objs[10], objs[11]]), `[{"v":9},"poly10",{"v":11}]`); +} diff --git a/JSTests/stress/json-stringify-fast-path-custom-prototype.js b/JSTests/stress/json-stringify-fast-path-custom-prototype.js new file mode 100644 index 0000000000000..6db68cda1395d --- /dev/null +++ b/JSTests/stress/json-stringify-fast-path-custom-prototype.js @@ -0,0 +1,122 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error("bad value: " + actual + " expected: " + expected); +} + +function shouldThrow(func, errorMessage) { + let errorThrown = false; + try { + func(); + } catch (error) { + errorThrown = true; + if (String(error) !== errorMessage) + throw new Error("bad error: " + String(error)); + } + if (!errorThrown) + throw new Error("not thrown"); +} + +// These must run before anything reifies Date.prototype's static toJSON. +{ + Object.setPrototypeOf(Array.prototype, Date.prototype); + shouldThrow(() => JSON.stringify([]), "TypeError: Type error"); + shouldThrow(() => JSON.stringify({ a: [] }), "TypeError: Type error"); + Object.setPrototypeOf(Array.prototype, Object.prototype); + shouldBe(JSON.stringify([]), `[]`); +} + +{ + class X { constructor() { this.a = 1; } } + Object.setPrototypeOf(X.prototype, Date.prototype); + shouldThrow(() => JSON.stringify(new X), "TypeError: Type error"); + shouldThrow(() => JSON.stringify({ v: new X }), "TypeError: Type error"); + let o = { b: 2 }; + Object.setPrototypeOf(o, Date.prototype); + shouldThrow(() => JSON.stringify({ o }), "TypeError: Type error"); +} + +class A { constructor() { this.x = 1; } method() { } get accessor() { return 0; } } +class AA extends A { constructor() { super(); this.y = 2; } } +for (let i = 0; i < 10; ++i) { + shouldBe(JSON.stringify(new A), `{"x":1}`); + shouldBe(JSON.stringify({ a: new A, b: [new AA] }), `{"a":{"x":1},"b":[{"x":1,"y":2}]}`); + shouldBe(JSON.stringify(new AA, null, 1), `{\n "x": 1,\n "y": 2\n}`); +} + +{ + class C { constructor() { this.x = 1; } } + let c = new C; + for (let i = 0; i < 10; ++i) + shouldBe(JSON.stringify(c), `{"x":1}`); + C.prototype.toJSON = function () { return 42; }; + shouldBe(JSON.stringify(c), `42`); + shouldBe(JSON.stringify({ c }), `{"c":42}`); + delete C.prototype.toJSON; + shouldBe(JSON.stringify(c), `{"x":1}`); + Object.defineProperty(C.prototype, "toJSON", { value() { return "non-enumerable"; }, enumerable: false, configurable: true }); + shouldBe(JSON.stringify(c), `"non-enumerable"`); +} + +{ + class D0 { } + class D1 extends D0 { constructor() { super(); this.y = 2; } } + let d = new D1; + for (let i = 0; i < 10; ++i) + shouldBe(JSON.stringify(d), `{"y":2}`); + D0.prototype.toJSON = () => "D0"; + shouldBe(JSON.stringify(d), `"D0"`); +} + +{ + let count = 0; + class E { constructor() { this.z = 3; } } + Object.defineProperty(E.prototype, "toJSON", { get() { count++; return undefined; }, configurable: true }); + for (let i = 0; i < 10; ++i) + shouldBe(JSON.stringify(new E), `{"z":3}`); + shouldBe(count, 10); +} + +{ + let n = Object.create(null); + n.k = 1; + for (let i = 0; i < 10; ++i) + shouldBe(JSON.stringify({ n }), `{"n":{"k":1}}`); +} + +{ + let proto = { inherited: 1 }; + let o = Object.create(proto); + o.own = 2; + for (let i = 0; i < 10; ++i) + shouldBe(JSON.stringify(o), `{"own":2}`); + proto.toJSON = () => "P"; + shouldBe(JSON.stringify(o), `"P"`); +} + +{ + let log = []; + let p = new Proxy({}, { get(t, k, r) { log.push(String(k)); return Reflect.get(t, k, r); } }); + let q = Object.create(p); + q.a = 1; + shouldBe(JSON.stringify(q), `{"a":1}`); + shouldBe(log.includes("toJSON"), true); +} + +{ + class G { constructor() { this.b = Object(1n); } } + shouldThrow(() => JSON.stringify(new G), "TypeError: JSON.stringify cannot serialize BigInt."); + shouldBe(JSON.stringify({ s: Object.setPrototypeOf(new String("s"), A.prototype) }), `{"s":"[object String]"}`); + shouldBe(JSON.stringify({ b: Object.setPrototypeOf(new Boolean(true), A.prototype) }), `{"b":true}`); +} + +{ + class F { constructor() { this.w = 4; } } + let f = new F; + for (let i = 0; i < 10; ++i) + shouldBe(JSON.stringify(f), `{"w":4}`); + Object.prototype.toJSON = function () { return "OP"; }; + shouldBe(JSON.stringify(f), `"OP"`); + shouldBe(JSON.stringify({}), `"OP"`); + delete Object.prototype.toJSON; + shouldBe(JSON.stringify(f), `{"w":4}`); +} diff --git a/JSTests/stress/modules-syntax-error.js b/JSTests/stress/modules-syntax-error.js index 920f3183aab7e..5525628db791f 100644 --- a/JSTests/stress/modules-syntax-error.js +++ b/JSTests/stress/modules-syntax-error.js @@ -180,10 +180,6 @@ checkModuleSyntaxError(String.raw` import { hello, binding as `, `SyntaxError: Unexpected end of script:3`); -checkModuleSyntaxError(String.raw` -import defer * as ns from "mod" -`, `SyntaxError: Unexpected token '*'. Expected 'from' before imported module name.:2`); - // --------------- export ------------------- checkModuleSyntaxError(String.raw` diff --git a/JSTests/stress/regexp-and-arg-same.js b/JSTests/stress/regexp-and-arg-same.js new file mode 100644 index 0000000000000..95588686de97c --- /dev/null +++ b/JSTests/stress/regexp-and-arg-same.js @@ -0,0 +1,8 @@ +function f() { + const re = /a/y; + return re.test(re); +} +noInline(f); + +for (let i = 0; i < testLoopCount; ++i) + f(); diff --git a/JSTests/stress/regexp-boundary-assertions.js b/JSTests/stress/regexp-boundary-assertions.js new file mode 100644 index 0000000000000..21770241de257 --- /dev/null +++ b/JSTests/stress/regexp-boundary-assertions.js @@ -0,0 +1,39 @@ +//@ requireOptions("--useRegExpBufferBoundaries=1") + +function assertEqual(a, b) { + if (a !== b) + throw new Error(`Assertion ${a} === ${b} failed`); +} + +const re1 = /\Afoo\z/u; +assertEqual(re1.test("foo"), true); +assertEqual(re1.test("foo\nbar"), false); + +const re2 = /\Afoo\z/um; +assertEqual(re2.test("foo"), true); +assertEqual(re2.test("foo\nbar"), false); + +// mixing buffer boundaries and anchors +const re3 = /\Afoo|^bar$|baz\z/um; +assertEqual(re3.test("foo"), true); +assertEqual(re3.test("foo\n"), true); +assertEqual(re3.test("\nfoo"), false); +assertEqual(re3.test("bar"), true); +assertEqual(re3.test("bar\n"), true); +assertEqual(re3.test("\nbar"), true); +assertEqual(re3.test("baz"), true); +assertEqual(re3.test("baz\n"), false); +assertEqual(re3.test("\nbaz"), true); + +// matching at line terminator sequence at end of buffer +const re4 = /end\Z/u; +assertEqual(re4.test("The end"), true); +assertEqual(re4.test("The end\n"), true); +assertEqual(re4.test("The end\r\n"), true); +assertEqual(re4.test("The end\u2028"), true); +assertEqual(re4.test("The end\n...or is it?"), false); + +const re5 = /\Aa/; +assertEqual(re5.test("Aa"), true); +assertEqual(re5.test("bAab"), true); +assertEqual(re5.test("a"), false); diff --git a/JSTests/stress/regexp-buffer-boundaries-anchoring.js b/JSTests/stress/regexp-buffer-boundaries-anchoring.js new file mode 100644 index 0000000000000..83e382598b42c --- /dev/null +++ b/JSTests/stress/regexp-buffer-boundaries-anchoring.js @@ -0,0 +1,109 @@ +//@ requireOptions("--useRegExpBufferBoundaries=1") + +// \A and \z participate in the same anchoring optimizations as non-multiline ^ and $ +// (once-through alternatives and end-anchored fixed-size matching). Check that these +// patterns still produce exactly the same results as their lookaround equivalents at +// every start position, for every flag combination. + +function shouldBe(actual, expected, message) +{ + if (actual !== expected) + throw new Error(message + ": expected " + expected + " but got " + actual); +} + +const inputs = [ + "", "a", "b", "c", "ab", "ba", "abc", "cba", "aab", "aaaa", "aaaaaaaaaaaab", + "foo", "xfoo", "foox", "barfoo", "foobar", "afoob", "bcd", "abcd", "xbcd", "bcdx", + "\n", "\r\n", "a\n", "\na", "a\nb", "b\na", "ab\nfoo", "foo\nbar", "foo\r\n", "\nabc", + "abc\n", "b\nfoo\nfoo", "foo\nfoo\n", "a\r\n\n", "a
b", "GET /", "PUT", +]; + +const boundaryToLookaround = new Map([ + ["\\A", "(?")), JSON.stringify(input.replace(referenceRE, "<$&>")), "replace /" + source + "/" + flags + " on " + JSON.stringify(input)); + } +} + +const sources = [ + // \A anchored alternatives (once-through) mixed with looping ones. + "\\A", + "\\Afoo", + "\\Afoo|bar", + "bar|\\Afoo", + "\\Afoo|foo", + "\\Aa|\\Ab|c", + "\\A(?:foo|bar)", + "\\A(?:a+|b)*c", + "\\A(a)(b)?", + "(?:\\Aa|b)+", + "(?:x|\\Ay)z", + "\\Aa*b", + "\\A[ab]{2,}", + "(\\A)?x", + "(\\Aa|b)\\1", + "(?\\Aa)b|\\kc", + "(?=\\Aa)a", + "(?!\\Aa)[ab]", + "(?<=\\Aa)b", + "(? x === undefined ? null : x), expected, context); + if (result !== null) + shouldBe(result.index, expectedIndex, context + " (index)"); +} + +let re; + +re = /a|^x|aa/g; +re.lastIndex = 2; +check(re, "xxcaa", ["a"], 3); +shouldBe(re.lastIndex, 4, "/a|^x|aa/g lastIndex after match"); +re = /a|^x|aa/y; +re.lastIndex = 1; +check(re, "caa", ["a"], 1); +re = /a|^x|aa/y; +re.lastIndex = 0; +check(re, "caa", null); + +check(/a|^b|c|^d|ab|^e/, "xab", ["a"], 1); +check(/a|aa|^x/, "caa", ["a"], 1); +check(/ab|a|^x|abc/, "cabc", ["ab"], 1); +check(/aaaa|^x|a/, "ca", ["a"], 1); +check(/a|^x|aa|(?:^y|b)/, "caa", ["a"], 1); +check(/b|^x|(?:^y|a)|aa/, "caa", ["a"], 1); +check(/(?:a|^x|aa)/, "caa", ["a"], 1); + +check(/(a)|^(b)|(a)a/, "caa", ["a", "a", null, null], 1); +check(/(?a)|^x|(?a)a/, "caa", ["a", "a", null], 1); +shouldBe("caab".replace(/a|^x|(a+)b/, "[$&,$1]"), "c[a,]ab", "replace with capture"); + +check(/a|(?:^x)|aa/, "caa", ["a"], 1); +check(/a|(^)x|aa/, "caa", ["a", null], 1); +check(/a|(?:^x)?b|ab/, "cab", ["a"], 1); +check(/a|(?=^)x|aa/, "caa", ["a"], 1); +check(/a|(?<=^)x|aa/, "caa", ["a"], 1); +check(/a|(?<=^c)a|aa|^x/, "caa", ["a"], 1); +check(/(?<=c)a|^x|aa/, "caa", ["a"], 1); +check(/(? m[0]), ["a", "a"], "matchAll"); +shouldBe("caa xaa".replace(/a|^x|aa/g, "_"), "c__ x__", "replace /g"); diff --git a/JSTests/stress/regexp-once-through-advance-tries-loop-alternatives-first.js b/JSTests/stress/regexp-once-through-advance-tries-loop-alternatives-first.js new file mode 100644 index 0000000000000..ace930ee09d5e --- /dev/null +++ b/JSTests/stress/regexp-once-through-advance-tries-loop-alternatives-first.js @@ -0,0 +1,26 @@ +function shouldBe(actual, expected, context) { + if (JSON.stringify(actual) !== JSON.stringify(expected)) + throw new Error("bad value: " + JSON.stringify(actual) + " expected: " + JSON.stringify(expected) + " for " + context); +} + +function check(re, string, expected) { + const result = re.exec(string); + shouldBe(result === null ? null : Array.from(result), expected, re + " against " + JSON.stringify(string)); +} + +check(/a|^x|aa/, "caa", ["a"]); +check(/(?:^|[^g])x6|ar?/, "gax6", ["ax6"]); +check(/(^|[^g])hz+|w/, "uwhz", ["whz", "w"]); +check(/(?:^-|^\+)?d|dd/, "xdd", ["d"]); +shouldBe("caa xaa".replace(/a|^x|aa/g, "_"), "c__ x__", "replace /a|^x|aa/g"); + +check(/a(? x === undefined ? null : x), expected, context); + if (result !== null) + shouldBe(result.index, expectedIndex, context + " (index)"); +} + +let re; + +check(/^a/, "ba", null); +check(/^a/, "", null); +check(/^a|^b/, "xab", null); +check(/^a|^b/, "b", ["b"], 0); +check(/^a|^b/, "", null); +check(/^(a)|^b/, "xb", null); +check(/(?:^a)+|^b/, "xab", null); +check(/(^a|^c)|^b/, "xab", null); +check(/(?=^)a|^b/, "xa", null); +check(/(?=^a)\w|^b/, "xa", null); + +check(/^A|^B/i, "xab", null); +check(/^.a|^b/s, "x\nab", null); +check(/^a|^b/u, "\u{1F600}a", null); +check(/^a|^b/m, "x\nb", ["b"], 2); + +re = /^a|^b/g; +re.lastIndex = 1; +check(re, "aab", null); +shouldBe(re.lastIndex, 0, "lastIndex reset after failing from lastIndex 1"); +re = /^a|^b/y; +re.lastIndex = 1; +check(re, "xab", null); +shouldBe("abab".match(/^a|^b/g), ["a"], "match /g"); +shouldBe("bab".replace(/^b|^a/g, "_"), "_ab", "replace /g"); +shouldBe("aXbX".split(/^a|^b/), ["", "XbX"], "split"); + +for (let i = 0; i < testLoopCount; ++i) { + shouldBe(/^a/.test("b".repeat(i & 0xff)), false, "/^a/ long input"); + shouldBe(/^a|^b(? { + for (let i = 0; i < 3; ++i) + shouldBe("ö".localeCompare("z", "xx"), new Intl.Collator("xx").compare("ö", "z"), "en-US"); + shouldBe("ö".localeCompare("z", "xx") < 0, true, "en-US ordering"); + + $vm.setUserPreferredLanguages(["sv-SE"]); + + setTimeout(() => { + shouldBe(new Intl.Collator("xx").compare("ö", "z") > 0, true, "sv-SE ordering via fresh collator"); + for (let i = 0; i < 3; ++i) + shouldBe("ö".localeCompare("z", "xx"), new Intl.Collator("xx").compare("ö", "z"), "sv-SE"); + }, 0); +}, 0); diff --git a/JSTests/stress/string-locale-compare-locales-cache.js b/JSTests/stress/string-locale-compare-locales-cache.js new file mode 100644 index 0000000000000..b86631b3115ae --- /dev/null +++ b/JSTests/stress/string-locale-compare-locales-cache.js @@ -0,0 +1,50 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`bad value: ${actual}, expected: ${expected}`); +} + +function shouldThrow(func, errorType) { + let error; + try { + func(); + } catch (e) { + error = e; + } + if (!(error instanceof errorType)) + throw new Error(`bad error: ${String(error)}`); +} + +const words = ["a", "A", "ä", "Z", "z", "ss", "ß", "ch", "cz", "資料", "10", "9", "co-op", "coop", ""]; +const locales = ["en", "de", "sv", "ja", "es", "tr", "de-u-co-phonebk", "en-US", "und"]; + +// Repeated calls with the same string locale must keep matching a fresh Intl.Collator. +for (const locale of locales) { + const collator = new Intl.Collator(locale); + for (let i = 0; i < 3; ++i) { + for (const x of words) { + for (const y of words) + shouldBe(x.localeCompare(y, locale), collator.compare(x, y)); + } + } +} + +// Alternating locales. +for (let i = 0; i < 100; ++i) { + const locale = locales[i % locales.length]; + shouldBe("ä".localeCompare("z", locale), new Intl.Collator(locale).compare("ä", "z")); +} + +// An invalid locale must throw on every call. +for (let i = 0; i < 3; ++i) + shouldThrow(() => "a".localeCompare("b", "xx-fake-INVALID!"), RangeError); +shouldBe("a".localeCompare("b", "en"), -1); + +// Non-string locales and explicit options take the general path. +shouldBe("a".localeCompare("b", ["en"]), -1); +shouldBe("a".localeCompare("b", undefined), -1); +shouldBe("a".localeCompare("A", "en", { sensitivity: "base" }), 0); +shouldBe("a".localeCompare("A", "en") !== 0, true); + +// Rope locale string. +const prefix = "e"; +shouldBe("a".localeCompare("b", prefix + "n"), -1); diff --git a/JSTests/stress/string-substring-jit-constant-indices.js b/JSTests/stress/string-substring-jit-constant-indices.js new file mode 100644 index 0000000000000..9d83ebd6a0ce4 --- /dev/null +++ b/JSTests/stress/string-substring-jit-constant-indices.js @@ -0,0 +1,60 @@ +function shouldBe(actual, expected) { + if (!Object.is(actual, expected)) + throw new Error(`Bad value: ${actual}!`); +} + +function head(string) +{ + return string.substring(0, 5); +} +noInline(head); + +function reversed(string) +{ + return string.substring(5, 1); +} +noInline(reversed); + +function negative(string) +{ + return string.substring(-3, 2); +} +noInline(negative); + +function huge(string) +{ + return string.substring(3, 1000); +} +noInline(huge); + +function tail(string) +{ + return string.substring(4); +} +noInline(tail); + +for (var i = 0; i < testLoopCount; ++i) { + shouldBe(head(""), ""); + shouldBe(reversed(""), ""); + shouldBe(negative(""), ""); + shouldBe(huge(""), ""); + shouldBe(tail(""), ""); + + shouldBe(head("AB"), "AB"); + shouldBe(reversed("AB"), "B"); + shouldBe(negative("AB"), "AB"); + shouldBe(huge("AB"), ""); + shouldBe(tail("AB"), ""); + + shouldBe(head("ABCDE"), "ABCDE"); + shouldBe(reversed("ABCDE"), "BCDE"); + shouldBe(negative("ABCDE"), "AB"); + shouldBe(huge("ABCDE"), "DE"); + shouldBe(tail("ABCDE"), "E"); + + shouldBe(head("ABCDEFGHIJ"), "ABCDE"); + shouldBe(reversed("ABCDEFGHIJ"), "BCDE"); + shouldBe(negative("ABCDEFGHIJ"), "AB"); + shouldBe(huge("ABCDEFGHIJ"), "DEFGHIJ"); + shouldBe(tail("ABCDEFGHIJ"), "EFGHIJ"); +} diff --git a/JSTests/stress/string-substring-jit.js b/JSTests/stress/string-substring-jit.js new file mode 100644 index 0000000000000..3c10021127edf --- /dev/null +++ b/JSTests/stress/string-substring-jit.js @@ -0,0 +1,61 @@ +function shouldBe(actual, expected) { + if (!Object.is(actual, expected)) + throw new Error(`Bad value: ${actual}!`); +} + +function referenceSubstring(string, start, end) +{ + var length = string.length; + start = Math.min(Math.max(start, 0), length); + end = end === undefined ? length : Math.min(Math.max(end, 0), length); + if (start > end) { + var swap = start; + start = end; + end = swap; + } + var result = ""; + for (var i = start; i < end; ++i) + result += string[i]; + return result; +} + +function substring(string, start, end) +{ + return string.substring(start, end); +} +noInline(substring); + +function substringNoEnd(string, start) +{ + return string.substring(start); +} +noInline(substringNoEnd); + +function makeRope(tail) +{ + var result = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + result += "0123456789"; + result += tail; + return result; +} +noInline(makeRope); + +var strings = ["", "A", "ABCDE", "ABCDEFGHIJKLMN", "\u3042\u3044\u3046\u3048\u304A", "\uD842\uDFB7\u91CE\u5BB6"]; +var indices = [-100, -5, -1, 0, 1, 2, 3, 5, 13, 14, 100]; + +for (var i = 0; i < testLoopCount; ++i) { + for (var string of strings) { + for (var start of indices) { + shouldBe(substringNoEnd(string, start), referenceSubstring(string, start, undefined)); + for (var end of indices) + shouldBe(substring(string, start, end), referenceSubstring(string, start, end)); + } + } + + shouldBe(substring(makeRope("XY"), 26, 30), "0123"); + shouldBe(substring(makeRope("XY"), 30, 26), "0123"); + shouldBe(substring(makeRope("XY"), 36, 37), "X"); + shouldBe(substring(makeRope("XY"), 36, 36), ""); + shouldBe(substring(makeRope("XY"), -1, 100), "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789XY"); + shouldBe(substringNoEnd(makeRope("XY"), 34), "89XY"); +} diff --git a/JSTests/stress/strong-handle-gc.js b/JSTests/stress/strong-handle-gc.js new file mode 100644 index 0000000000000..92e9f3335305e --- /dev/null +++ b/JSTests/stress/strong-handle-gc.js @@ -0,0 +1,39 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error("bad value: expected " + expected + " but got " + actual); +} + +// An unhandled rejected promise is pinned only by a Vector> in +// VM (m_aboutToBeNotifiedRejectedPromises) until the rejection tracker fires, so +// with Strong<> marking broken every WeakRef below would go empty. The count spans +// multiple StrongBlocks. +const promiseCount = 2200; +const weakRefs = []; + +for (let i = 0; i < promiseCount; ++i) + weakRefs.push(new WeakRef(Promise.reject(new Error("strong-handle-gc " + i)))); + +// Churn the stack so conservative scanning cannot keep the promises alive via +// stale pointers left in registers or on the native stack. +function churn(depth) { + if (depth <= 0) + return 0; + let sum = 0; + const objects = []; + for (let i = 0; i < 1000; ++i) + objects.push({ a: i, b: i * 2, c: [i, i + 1] }); + for (let i = 0; i < objects.length; ++i) + sum += objects[i].a + objects[i].b; + return sum + churn(depth - 1); +} + +for (let round = 0; round < 3; ++round) { + churn(4); + // Drop the "kept alive until end of turn" list, so this observes only the + // Strong<> root. + $.clearKeptObjects(); + $vm.gc(); +} + +for (let i = 0; i < promiseCount; ++i) + shouldBe(weakRefs[i].deref() !== undefined, true); diff --git a/JSTests/stress/temporal-calendar-canonical-set.js b/JSTests/stress/temporal-calendar-canonical-set.js index f6639c40d199f..de8e71fc11ef7 100644 --- a/JSTests/stress/temporal-calendar-canonical-set.js +++ b/JSTests/stress/temporal-calendar-canonical-set.js @@ -1,4 +1,4 @@ -//@ requireOptions("--useTemporal=1", "--useIntlEraMonthcode=1") +//@ requireOptions("--useTemporal=1") // Regression for proposal-intl-era-monthcode Phase 0. diff --git a/JSTests/stress/temporal-calendar-fields-monthcode-typeerror.js b/JSTests/stress/temporal-calendar-fields-monthcode-typeerror.js new file mode 100644 index 0000000000000..eba0881a3306a --- /dev/null +++ b/JSTests/stress/temporal-calendar-fields-monthcode-typeerror.js @@ -0,0 +1,38 @@ +//@ requireOptions("--useTemporal=1") + +function shouldThrow(fn, type, message) { + let err; + try { fn(); } catch (e) { err = e; } + if (!(err instanceof type)) + throw new Error(`Expected ${type.name} but got ${err}`); + if (message !== undefined && err.message !== message) + throw new Error(`Expected message "${message}" but got "${err.message}"`); +} + +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`); +} + +const typeErrorMessage = "monthCode must be a string"; + +for (const badMonthCode of [42, true]) { + shouldThrow(() => Temporal.PlainDate.from({ year: 2020, month: 1, day: 1, monthCode: badMonthCode }), TypeError, typeErrorMessage); + shouldThrow(() => Temporal.PlainDateTime.from({ year: 2020, month: 1, day: 1, monthCode: badMonthCode }), TypeError, typeErrorMessage); + shouldThrow(() => Temporal.PlainYearMonth.from({ year: 2020, month: 1, monthCode: badMonthCode }), TypeError, typeErrorMessage); + shouldThrow(() => Temporal.PlainMonthDay.from({ day: 1, monthCode: badMonthCode }), TypeError, typeErrorMessage); + + shouldThrow(() => Temporal.PlainDate.from("2020-01-01").with({ monthCode: badMonthCode }), TypeError, typeErrorMessage); + shouldThrow(() => Temporal.PlainDateTime.from("2020-01-01T00:00").with({ monthCode: badMonthCode }), TypeError, typeErrorMessage); + shouldThrow(() => Temporal.PlainYearMonth.from("2020-01").with({ monthCode: badMonthCode }), TypeError, typeErrorMessage); + shouldThrow(() => Temporal.PlainMonthDay.from({ day: 1, monthCode: "M01" }).with({ monthCode: badMonthCode }), TypeError, typeErrorMessage); +} + +// Sanity: grammar-invalid (but string) monthCode still RangeErrors, not TypeErrors. +// "M99" is grammatically valid (any 2-digit M-code parses) but out of ISO's 1-12 range, +// so use "M00" (bare, no "L") — the grammar explicitly rejects it (only "M00L" is valid). +shouldThrow(() => Temporal.PlainDate.from({ year: 2020, day: 1, monthCode: "M00" }), RangeError, "Invalid monthCode"); + +// Sanity: a valid monthCode still works normally. +shouldBe(Temporal.PlainDate.from({ year: 2020, day: 1, monthCode: "M01" }).toString(), "2020-01-01"); +shouldBe(Temporal.PlainDate.from("2020-01-01").with({ monthCode: "M02" }).toString(), "2020-02-01"); diff --git a/JSTests/stress/temporal-calendar-icu-bridge-non-iso.js b/JSTests/stress/temporal-calendar-icu-bridge-non-iso.js index f1c4ceb545fbd..3d68de63343ba 100644 --- a/JSTests/stress/temporal-calendar-icu-bridge-non-iso.js +++ b/JSTests/stress/temporal-calendar-icu-bridge-non-iso.js @@ -440,3 +440,19 @@ for (const [calendar, startFields, endFields, expectedMonths] of [ shouldBe(reverse.toString(), `-P${expectedMonths}M`, `${calendar} wide reverse month difference`); shouldBe(start.until(end, {largestUnit:"years"}).toString(), "P500000Y", `${calendar} wide year difference`); } + +// Extreme .add({years}) that fits int32 but drives ucal_add(UCAL_EXTENDED_YEAR) far enough that +// epochMs can exceed int64_t's range; must throw RangeError, not crash or produce a wrong date. +for (const calendar of ["chinese", "dangi", "hebrew"]) { + const start = calendar === "hebrew" + ? Temporal.PlainDate.from({year:5760, monthCode:"M01", day:1, calendar}) + : Temporal.PlainDate.from({year:2000, monthCode:"M01", day:1, calendar}); + shouldThrow(() => start.add({years: 2147483637}), RangeError, `${calendar} add extreme years (just under INT32_MAX)`); + shouldThrow(() => start.add({years: 2147483647}), RangeError, `${calendar} add extreme years (exactly INT32_MAX)`); + shouldThrow(() => start.subtract({years: 2147483637}), RangeError, `${calendar} subtract extreme years`); +} + +// Hebrew has no extreme-year ISO-fallback (unlike chinese/dangi), so a huge year reaches +// construction directly. +shouldThrow(() => Temporal.PlainDate.from({year: 2147483637, monthCode: "M01", day: 1, calendar: "hebrew"}), RangeError, "hebrew extreme positive year construction"); +shouldThrow(() => Temporal.PlainDate.from({year: -2147483637, monthCode: "M01", day: 1, calendar: "hebrew"}), RangeError, "hebrew extreme negative year construction"); diff --git a/JSTests/stress/temporal-calendar-merge-fields-with.js b/JSTests/stress/temporal-calendar-merge-fields-with.js new file mode 100644 index 0000000000000..9938c64b1e1a1 --- /dev/null +++ b/JSTests/stress/temporal-calendar-merge-fields-with.js @@ -0,0 +1,50 @@ +//@ requireOptions("--useTemporal=1") + +function shouldThrow(fn, type, message) { + let err; + try { fn(); } catch (e) { err = e; } + if (!(err instanceof type)) + throw new Error(`Expected ${type.name} but got ${err}`); + if (message !== undefined && err.message !== message) + throw new Error(`Expected message "${message}" but got "${err.message}"`); +} + +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`); +} + +// --- PlainDate.prototype.with --- + +shouldBe(Temporal.PlainDate.from("2020-06-15").with({ day: 20 }).toString(), "2020-06-20"); +shouldBe(Temporal.PlainDate.from("2020-06-15").with({ month: 3 }).toString(), "2020-03-15"); +shouldBe(Temporal.PlainDate.from("2020-06-15").with({ year: 2021 }).toString(), "2021-06-15"); + +shouldBe(Temporal.PlainDate.from({ year: 2020, month: 5, day: 1, calendar: "hebrew" }).with({ day: 10 }).toString(), "-001741-12-28[u-ca=hebrew]"); +shouldBe(Temporal.PlainDate.from({ year: 2020, month: 5, day: 1, calendar: "hebrew" }).with({ year: 2021 }).toString(), "-001739-01-07[u-ca=hebrew]"); + +// Chinese (lunisolar): year changes with month/monthCode absent from the partial — the +// case the removed lunisolarYearChange special case targeted (month must fall back via +// monthCode, not a raw ordinal, since leap-month insertion can shift the mapping). +shouldBe(Temporal.PlainDate.from({ year: 2020, month: 5, day: 1, calendar: "chinese" }).with({ year: 2023 }).toString(), "2023-05-19[u-ca=chinese]"); +shouldBe(Temporal.PlainDate.from({ year: 2020, month: 5, day: 1, calendar: "chinese" }).with({ day: 10 }).toString(), "2020-06-01[u-ca=chinese]"); + +shouldBe(Temporal.PlainDate.from({ era: "reiwa", eraYear: 3, month: 5, day: 1, calendar: "japanese" }).with({ month: 8 }).toString(), "2021-08-01[u-ca=japanese]"); + +// --- PlainYearMonth.prototype.with --- + +shouldBe(Temporal.PlainYearMonth.from("2020-06").with({ month: 3 }).toString(), "2020-03"); +shouldBe(Temporal.PlainYearMonth.from({ year: 2020, month: 5, calendar: "hebrew" }).with({ month: 2 }).toString(), "-001741-09-21[u-ca=hebrew]"); +shouldBe(Temporal.PlainYearMonth.from({ year: 2020, month: 5, calendar: "chinese" }).with({ year: 2023 }).toString(), "2023-05-19[u-ca=chinese]"); + +// --- PlainMonthDay.prototype.with --- + +shouldBe(Temporal.PlainMonthDay.from({ month: 6, day: 15 }).with({ day: 20 }).toString(), "06-20"); +shouldBe(Temporal.PlainMonthDay.from({ month: 6, day: 15 }).with({ month: 3 }).toString(), "03-15"); +shouldBe(Temporal.PlainMonthDay.from({ monthCode: "M05", day: 1, calendar: "hebrew" }).with({ day: 10 }).toString(), "1972-01-26[u-ca=hebrew]"); +shouldBe(Temporal.PlainMonthDay.from({ monthCode: "M05", day: 1, calendar: "hebrew" }).with({ month: 2, year: 2020 }).toString(), "1972-10-09[u-ca=hebrew]"); + +// Non-ISO month given without year (or era+eraYear): now deferred to +// nonISOResolveFields's own "year property must be present" check instead of a +// PlainMonthDay.prototype.with-specific message — same TypeError kind either way. +shouldThrow(() => Temporal.PlainMonthDay.from({ monthCode: "M05", day: 1, calendar: "hebrew" }).with({ month: 2 }), TypeError, "year property must be present"); diff --git a/JSTests/stress/temporal-construct-newtarget-prototype-order.js b/JSTests/stress/temporal-construct-newtarget-prototype-order.js new file mode 100644 index 0000000000000..6a06a8c3f7581 --- /dev/null +++ b/JSTests/stress/temporal-construct-newtarget-prototype-order.js @@ -0,0 +1,68 @@ +//@ requireOptions("--useTemporal=1") + +function shouldThrowRangeErrorNotPrototypeRead(name, C, args) { + const newTarget = new Proxy(function () { }, { + get(target, key, receiver) { + if (key === "prototype") + throw new EvalError("read newTarget.prototype before validating arguments"); + return Reflect.get(target, key, receiver); + }, + }); + + let error = null; + try { + Reflect.construct(C, args, newTarget); + } catch (e) { + error = e; + } + + if (!error) + throw new Error(`${name}: expected a RangeError, got no exception`); + if (error instanceof EvalError) + throw new Error(`${name}: ${error.message}`); + if (!(error instanceof RangeError)) + throw new Error(`${name}: expected a RangeError, got ${error}`); +} + +// Each case picks arguments that fail the AO's own validity check — the one that lives inside +// CreateTemporalX rather than in the constructor body — so the ordering is what is under test. +shouldThrowRangeErrorNotPrototypeRead("Temporal.Duration", Temporal.Duration, [1, -1]); // IsValidDuration: mixed signs +shouldThrowRangeErrorNotPrototypeRead("Temporal.Duration", Temporal.Duration, [Infinity]); +shouldThrowRangeErrorNotPrototypeRead("Temporal.Instant", Temporal.Instant, [10n ** 30n]); // IsValidEpochNanoseconds +shouldThrowRangeErrorNotPrototypeRead("Temporal.PlainDate", Temporal.PlainDate, [2020, 13, 1]); // IsValidISODate +shouldThrowRangeErrorNotPrototypeRead("Temporal.PlainDate", Temporal.PlainDate, [300000, 1, 1]); // ISODateWithinLimits +shouldThrowRangeErrorNotPrototypeRead("Temporal.PlainDateTime", Temporal.PlainDateTime, [2020, 13, 1]); +shouldThrowRangeErrorNotPrototypeRead("Temporal.PlainDateTime", Temporal.PlainDateTime, [300000, 1, 1]); +shouldThrowRangeErrorNotPrototypeRead("Temporal.PlainTime", Temporal.PlainTime, [25]); // IsValidTime +shouldThrowRangeErrorNotPrototypeRead("Temporal.PlainYearMonth", Temporal.PlainYearMonth, [2020, 13]); +shouldThrowRangeErrorNotPrototypeRead("Temporal.PlainYearMonth", Temporal.PlainYearMonth, [300000, 1]); +shouldThrowRangeErrorNotPrototypeRead("Temporal.PlainMonthDay", Temporal.PlainMonthDay, [13, 1]); +shouldThrowRangeErrorNotPrototypeRead("Temporal.ZonedDateTime", Temporal.ZonedDateTime, [10n ** 30n, "UTC"]); + +// The tz/calendar checks in the ZonedDateTime constructor body precede Step 11 too. +shouldThrowRangeErrorNotPrototypeRead("Temporal.ZonedDateTime", Temporal.ZonedDateTime, [0n, "Nope/Nope"]); +shouldThrowRangeErrorNotPrototypeRead("Temporal.ZonedDateTime", Temporal.ZonedDateTime, [0n, "UTC", "nope"]); + +// Subclassing must still work: with valid arguments the derived prototype is honoured. +class MyDuration extends Temporal.Duration { } +const derived = new MyDuration(1, 2); +if (!(derived instanceof MyDuration) || !(derived instanceof Temporal.Duration)) + throw new Error("subclass prototype chain broken"); +if (derived.years !== 1 || derived.months !== 2) + throw new Error("subclass slots not initialized"); + +class MyPlainDate extends Temporal.PlainDate { } +const derivedDate = new MyPlainDate(2020, 1, 1); +if (!(derivedDate instanceof MyPlainDate) || derivedDate.toString() !== "2020-01-01") + throw new Error("PlainDate subclass broken"); + +// Reflect.construct with a distinct newTarget uses that newTarget's prototype. +const alt = { prototype: { tag: "alt" } }; +const viaReflect = Reflect.construct(Temporal.Duration, [3], Object.assign(function () { }, alt)); +if (Object.getPrototypeOf(viaReflect) !== alt.prototype) + throw new Error("Reflect.construct did not use newTarget.prototype"); +// `years` is an accessor on Temporal.Duration.prototype, which is deliberately NOT in this +// object's prototype chain, so read the slot through the getter rather than as a property. +const yearsGetter = Object.getOwnPropertyDescriptor(Temporal.Duration.prototype, "years").get; +if (yearsGetter.call(viaReflect) !== 3) + throw new Error("Reflect.construct did not initialize slots"); diff --git a/JSTests/stress/temporal-era-aliases.js b/JSTests/stress/temporal-era-aliases.js new file mode 100644 index 0000000000000..db60037d1d4b8 --- /dev/null +++ b/JSTests/stress/temporal-era-aliases.js @@ -0,0 +1,41 @@ +//@ requireOptions("--useTemporal=1") + +function shouldBe(actual, expected, label) { + if (actual !== expected) + throw new Error(`${label}: expected ${expected}, got ${actual}`); +} + +function shouldThrow(fn, label) { + let threw = false; + try { fn(); } + catch (e) { threw = e instanceof RangeError; } + shouldBe(threw, true, label); +} + +// gregory/japanese: ad -> ce, bc -> bce (case-insensitive). +{ + const ad = Temporal.PlainDate.from({ calendar: "gregory", era: "ad", eraYear: 2024, year: 2024, month: 1, day: 1 }); + shouldBe(ad.era, "ce", "gregory ad -> ce"); + shouldBe(ad.eraYear, 2024, "gregory ad eraYear"); + const bc = Temporal.PlainDate.from({ calendar: "gregory", era: "bc", eraYear: 44, year: -43, month: 3, day: 15 }); + shouldBe(bc.era, "bce", "gregory bc -> bce"); + shouldBe(bc.eraYear, 44, "gregory bc eraYear"); + const jad = Temporal.PlainDate.from({ calendar: "japanese", era: "ad", eraYear: 1, month: 1, day: 1 }); + shouldBe(jad.era, "ce", "japanese ad -> ce"); + const upper = Temporal.PlainDate.from({ calendar: "gregory", era: "AD", eraYear: 1, year: 1, month: 1, day: 1 }); + shouldBe(upper.era, "ce", "AD (upper) -> ce"); +} + +// Non-alias calendars still reject "ad"/"bc". +for (const cal of ["ethiopic", "buddhist", "hebrew", "islamic-civil", "persian", "roc"]) { + shouldThrow(() => Temporal.PlainDate.from({ calendar: cal, era: "ad", eraYear: 1, month: 1, day: 1 }), `${cal} rejects ad`); + shouldThrow(() => Temporal.PlainDate.from({ calendar: cal, era: "bc", eraYear: 1, month: 1, day: 1 }), `${cal} rejects bc`); +} + +// Canonical form still works. +{ + const ce = Temporal.PlainDate.from({ calendar: "gregory", era: "ce", eraYear: 2024, year: 2024, month: 1, day: 1 }); + shouldBe(ce.era, "ce", "gregory ce -> ce"); + const bce = Temporal.PlainDate.from({ calendar: "gregory", era: "bce", eraYear: 44, year: -43, month: 3, day: 15 }); + shouldBe(bce.era, "bce", "gregory bce -> bce"); +} diff --git a/JSTests/stress/temporal-era-boundaries.js b/JSTests/stress/temporal-era-boundaries.js new file mode 100644 index 0000000000000..9de92fc358333 --- /dev/null +++ b/JSTests/stress/temporal-era-boundaries.js @@ -0,0 +1,58 @@ +//@ requireOptions("--useTemporal=1") + +function shouldBe(actual, expected, label) { + if (actual !== expected) + throw new Error(`${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); +} + +function assertPD(d, year, month, day, era, eraYear, label) { + shouldBe(d.year, year, `${label} year`); + shouldBe(d.month, month, `${label} month`); + shouldBe(d.day, day, `${label} day`); + shouldBe(d.era, era, `${label} era`); + shouldBe(d.eraYear, eraYear, `${label} eraYear`); +} + +// gregory / roc / islamic-civil / ethiopic: non-positive eraYear remaps to opposite era. +{ + const d = Temporal.PlainDate.from({ calendar: "gregory", era: "ce", eraYear: 0, monthCode: "M01", day: 1 }); + assertPD(d, 0, 1, 1, "bce", 1, "gregory ce 0 -> bce 1"); + const d2 = Temporal.PlainDate.from({ calendar: "gregory", era: "bce", eraYear: -1, monthCode: "M01", day: 1 }); + assertPD(d2, 2, 1, 1, "ce", 2, "gregory bce -1 -> ce 2"); + const d3 = Temporal.PlainDate.from({ calendar: "roc", era: "roc", eraYear: 0, monthCode: "M01", day: 1 }); + assertPD(d3, 0, 1, 1, "broc", 1, "roc roc 0 -> broc 1"); + const d4 = Temporal.PlainDate.from({ calendar: "islamic-civil", era: "ah", eraYear: 0, monthCode: "M01", day: 1 }); + assertPD(d4, 0, 1, 1, "bh", 1, "islamic-civil ah 0 -> bh 1"); + const d5 = Temporal.PlainDate.from({ calendar: "ethiopic", era: "am", eraYear: 0, monthCode: "M01", day: 1 }); + assertPD(d5, 0, 1, 1, "aa", 5500, "ethiopic am 0 -> aa 5500"); + const d6 = Temporal.PlainDate.from({ calendar: "ethiopic", era: "aa", eraYear: 0, monthCode: "M01", day: 1 }); + assertPD(d6, -5500, 1, 1, "aa", 0, "ethiopic aa 0 (not remapped)"); +} + +// Single-era calendars: negative eraYear NOT remapped. +for (const [cal, era] of [["buddhist","be"], ["coptic","am"], ["ethioaa","aa"], ["hebrew","am"], ["indian","shaka"], ["persian","ap"]]) { + for (const y of [-1, 0, 1]) { + const d = Temporal.PlainDate.from({ calendar: cal, era, eraYear: y, monthCode: "M01", day: 1 }); + shouldBe(d.era, era, `${cal} ${era} ${y} era`); + shouldBe(d.eraYear, y, `${cal} ${era} ${y} eraYear`); + } +} + +// Japanese dated era boundaries + pre-Gregorian meiji fallback. +{ + const d = Temporal.PlainDate.from({ calendar: "japanese", era: "reiwa", eraYear: 1, monthCode: "M04", day: 30 }); + assertPD(d, 2019, 4, 30, "heisei", 31, "reiwa 1 before start -> heisei 31"); + const d2 = Temporal.PlainDate.from({ calendar: "japanese", era: "heisei", eraYear: 31, monthCode: "M05", day: 1 }); + assertPD(d2, 2019, 5, 1, "reiwa", 1, "heisei 31 on reiwa start -> reiwa 1"); + const d3 = Temporal.PlainDate.from({ calendar: "japanese", era: "meiji", eraYear: 5, monthCode: "M12", day: 31 }); + assertPD(d3, 1872, 12, 31, "ce", 1872, "meiji 5 (pre-1873) -> ce 1872"); + const d4 = Temporal.PlainDate.from({ calendar: "japanese", era: "ce", eraYear: 1873, monthCode: "M01", day: 1 }); + assertPD(d4, 1873, 1, 1, "meiji", 6, "ce 1873 -> meiji 6"); +} + +// Case-insensitive alias canonicalizes then remaps. +{ + const d = Temporal.PlainDate.from({ calendar: "gregory", era: "AD", eraYear: 0, monthCode: "M01", day: 1 }); + assertPD(d, 0, 1, 1, "bce", 1, "AD 0 -> bce 1"); +} + diff --git a/JSTests/stress/temporal-extreme-proleptic.js b/JSTests/stress/temporal-extreme-proleptic.js new file mode 100644 index 0000000000000..67dfdf1cf8cf0 --- /dev/null +++ b/JSTests/stress/temporal-extreme-proleptic.js @@ -0,0 +1,39 @@ +//@ requireOptions("--useTemporal=1") + +function shouldBe(actual, expected, label) { + if (actual !== expected) + throw new Error(`${label}: expected ${expected}, got ${actual}`); +} + +// Buddhist: BE = ISO + 543, both directions across the representable range. +{ + // Minimum ISO year -271821 → BE -271278. + const min = Temporal.PlainDate.from({ calendar: "buddhist", year: -271278, era: "be", eraYear: -271278, month: 4, monthCode: "M04", day: 19 }); + shouldBe(min.year, -271278, "buddhist min year"); + shouldBe(min.eraYear, -271278, "buddhist min eraYear"); + shouldBe(min.era, "be", "buddhist min era"); + // Maximum ISO year 275760 → BE 276303. + const max = Temporal.PlainDate.from({ calendar: "buddhist", year: 276303, era: "be", eraYear: 276303, month: 9, monthCode: "M09", day: 13 }); + shouldBe(max.year, 276303, "buddhist max year"); + shouldBe(max.eraYear, 276303, "buddhist max eraYear"); +} + +// ROC: ISO 1912 = ROC 1; era boundary at ISO 1912. +{ + const min = Temporal.PlainDate.from({ calendar: "roc", year: -273732, era: "broc", eraYear: 273733, month: 4, monthCode: "M04", day: 19 }); + shouldBe(min.year, -273732, "roc min year"); + shouldBe(min.eraYear, 273733, "roc min eraYear"); + shouldBe(min.era, "broc", "roc min era"); + const max = Temporal.PlainDate.from({ calendar: "roc", year: 273849, era: "roc", eraYear: 273849, month: 9, monthCode: "M09", day: 13 }); + shouldBe(max.year, 273849, "roc max year"); + shouldBe(max.eraYear, 273849, "roc max eraYear"); + shouldBe(max.era, "roc", "roc max era"); +} + +// Japanese: pre-meiji reports ce/bce; ISO year direct. +{ + const bceExtreme = Temporal.PlainDate.from({ calendar: "japanese", year: -271821, era: "bce", eraYear: 271822, month: 4, monthCode: "M04", day: 19 }); + shouldBe(bceExtreme.year, -271821, "japanese bce extreme year"); + shouldBe(bceExtreme.eraYear, 271822, "japanese bce extreme eraYear"); + shouldBe(bceExtreme.era, "bce", "japanese bce extreme era"); +} diff --git a/JSTests/stress/temporal-hebrew-year0-kislev.js b/JSTests/stress/temporal-hebrew-year0-kislev.js new file mode 100644 index 0000000000000..cb5924ef7d24d --- /dev/null +++ b/JSTests/stress/temporal-hebrew-year0-kislev.js @@ -0,0 +1,106 @@ +//@ requireOptions("--useTemporal=1") + +function shouldBe(actual, expected, label) { + if (actual !== expected) + throw new Error(`${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); +} + +const kislev30 = Temporal.PlainDate.from({calendar: "hebrew", year: 0, monthCode: "M03", day: 30}); +shouldBe(kislev30.day, 30, "y0 Kislev D30 .day"); +shouldBe(kislev30.monthCode, "M03", "y0 Kislev D30 .monthCode"); +shouldBe(kislev30.year, 0, "y0 Kislev D30 .year"); + +// day <= daysInMonth is a Temporal invariant. Pre-fix: daysInMonth=29. +shouldBe(kislev30.daysInMonth, 30, "y0 Kislev D30 .daysInMonth (pre-fix: 29)"); +if (!(kislev30.day <= kislev30.daysInMonth)) + throw new Error("Invariant day <= daysInMonth violated"); + +// y0 is Regular leap (384 days) per icu4x, not Deficient leap (383). +const tishri1 = Temporal.PlainDate.from({calendar: "hebrew", year: 0, monthCode: "M01", day: 1}); +shouldBe(tishri1.daysInYear, 384, "y0 .daysInYear (pre-fix: 383)"); +shouldBe(tishri1.inLeapYear, true, "y0 .inLeapYear"); +shouldBe(tishri1.monthsInYear, 13, "y0 .monthsInYear"); + +// PlainDateTime and PlainYearMonth share the accessor implementations. +const pdt = Temporal.PlainDateTime.from({calendar: "hebrew", year: 0, monthCode: "M03", day: 30}); +shouldBe(pdt.daysInMonth, 30, "PDT y0 Kislev D30 .daysInMonth"); +shouldBe(pdt.daysInYear, 384, "PDT y0 .daysInYear"); + +const pym = Temporal.PlainYearMonth.from({calendar: "hebrew", year: 0, monthCode: "M03"}); +shouldBe(pym.daysInMonth, 30, "PYM y0 Kislev .daysInMonth"); +shouldBe(pym.daysInYear, 384, "PYM y0 .daysInYear"); + +// .add({months:1}) from Kislev D30 must land on Tevet (M04), not Shevat (M05). +// Pre-fix: origMonthCode snapshot read ICU's "M04" (Tevet slot) and ucal_add advanced from +// Tevet to Shevat, dropping a month. +const plusOne = kislev30.add({months: 1}); +shouldBe(plusOne.monthCode, "M04", "y0 Kislev D30 + 1mo .monthCode (pre-fix: M05)"); +shouldBe(plusOne.year, 0, "y0 Kislev D30 + 1mo .year stays 0"); +// Tevet has 29 days per both ICU and icu4x; day 30 constrains to 29. +shouldBe(plusOne.day, 29, "y0 Kislev D30 + 1mo .day (Tevet has 29)"); + +// .add({years:1}) from Kislev D30 preserves monthCode M03 in the target year. +const plusOneYear = kislev30.add({years: 1}); +shouldBe(plusOneYear.year, 1, "y0 Kislev D30 + 1y .year"); +shouldBe(plusOneYear.monthCode, "M03", "y0 Kislev D30 + 1y .monthCode (pre-fix: M04)"); + +// .subtract({months:1}) from Kislev D30 = Cheshvan (M02). +const minusOne = kislev30.subtract({months: 1}); +shouldBe(minusOne.monthCode, "M02", "y0 Kislev D30 - 1mo .monthCode"); + +// Sanity: y0 M03 D29 (non-fabricated Kislev) is unaffected — normal ICU path. +const kislev29 = Temporal.PlainDate.from({calendar: "hebrew", year: 0, monthCode: "M03", day: 29}); +shouldBe(kislev29.day, 29, "y0 Kislev D29 .day"); +shouldBe(kislev29.monthCode, "M03", "y0 Kislev D29 .monthCode"); +shouldBe(kislev29.add({months: 1}).monthCode, "M04", "y0 Kislev D29 + 1mo .monthCode"); +shouldBe(kislev29.add({months: 1}).day, 29, "y0 Kislev D29 + 1mo .day"); + +// Sanity: post-y0 (year 1) Hebrew years are unaffected by the workaround. +const y1kislev = Temporal.PlainDate.from({calendar: "hebrew", year: 1, monthCode: "M03", day: 1}); +shouldBe(y1kislev.daysInYear >= 353 && y1kislev.daysInYear <= 385, true, "y1 daysInYear in valid range"); + +// Ordinal-month and monthCode input must agree on whether y0 Kislev day 30 exists: the maxDay +// override is keyed on calendar position (year 0, Kislev), not on which field selected the month. +{ + const viaMonthCode = Temporal.PlainDate.from({calendar: "hebrew", year: 0, monthCode: "M03", day: 30}); + const viaOrdinal = Temporal.PlainDate.from({calendar: "hebrew", year: 0, month: 3, day: 30}, {overflow: "constrain"}); + shouldBe(viaOrdinal.day, viaMonthCode.day, "y0 ordinal month=3 day=30 must agree with monthCode M03 day=30"); + + let threw = false; + try { + Temporal.PlainDate.from({calendar: "hebrew", year: 0, month: 3, day: 30}, {overflow: "reject"}); + } catch (e) { + threw = e instanceof RangeError; + } + shouldBe(threw, false, "y0 ordinal month=3 day=30 overflow:reject must not throw"); +} + +// FIXME: rdar://182958553 (icu-issues/01) - the Kislev D30 fabrication only relabels the single +// Tevet D1 slot, so it doesn't shift the rest of the year. Wait for ICU's classification to +// match icu4x rather than growing more relabeling logic; flip to `if (true)` once fixed. +if (false) { + // The last day of the year must have dayOfYear === daysInYear. + { + const elul29 = Temporal.PlainDate.from({calendar: "hebrew", year: 0, monthCode: "M12", day: 29}); + shouldBe(elul29.daysInYear, elul29.dayOfYear, "y0 last day (Elul 29): dayOfYear must equal daysInYear (currently 383 vs 384)"); + } + + // "Tevet D1" is currently unreachable: monthCode input collapses it to Kislev D30, and + // arithmetic (+1 day from Kislev D30) skips straight to Tevet D2. + { + const requestedTevet1 = Temporal.PlainDate.from({calendar: "hebrew", year: 0, monthCode: "M04", day: 1}); + shouldBe(requestedTevet1.monthCode, "M04", "y0 requested Tevet D1 .monthCode (currently collapses to M03)"); + shouldBe(requestedTevet1.day, 1, "y0 requested Tevet D1 .day (currently collapses to 30)"); + + const dayAfterKislev30 = kislev30.add({days: 1}); + shouldBe(dayAfterKislev30.monthCode, "M04", "y0 Kislev D30 + 1 day .monthCode"); + shouldBe(dayAfterKislev30.day, 1, "y0 Kislev D30 + 1 day .day (currently skips to 2)"); + } + + // PlainYearMonth and PlainDate must agree on Tevet's daysInMonth in y0. + { + const pymTevet = Temporal.PlainYearMonth.from({calendar: "hebrew", year: 0, monthCode: "M04"}); + const pdTevet = Temporal.PlainDate.from({calendar: "hebrew", year: 0, monthCode: "M04", day: 15}); + shouldBe(pymTevet.daysInMonth, pdTevet.daysInMonth, "y0 Tevet .daysInMonth must agree between PlainYearMonth (currently 30) and PlainDate (29)"); + } +} diff --git a/JSTests/stress/temporal-isodatetofields-call-sites.js b/JSTests/stress/temporal-isodatetofields-call-sites.js new file mode 100644 index 0000000000000..b1e85f2135f9d --- /dev/null +++ b/JSTests/stress/temporal-isodatetofields-call-sites.js @@ -0,0 +1,110 @@ +//@ requireOptions("--useTemporal=1") + +function shouldBe(actual, expected, msg) { + if (String(actual) !== String(expected)) + throw new Error(`${msg}: expected ${JSON.stringify(String(expected))} but got ${JSON.stringify(String(actual))}`); +} + +function shouldThrow(fn, type, msg) { + let err; + try { fn(); } catch (e) { err = e; } + if (!(err instanceof type)) + throw new Error(`${msg}: expected ${type.name} but got ${err}`); +} + +const ym = (cal) => Temporal.PlainYearMonth.from({ year: 2021, month: 5, calendar: cal }); + +// --- AddDurationToYearMonth: ISODateToFields(~year-month~) at steps 9 and 13 --- +// The receiver's era/eraYear are NOT part of ISODateToFields' output; only monthCode and year are, +// so an era-bearing calendar must round-trip through the arithmetic year alone. +shouldBe(ym("iso8601").add({ months: 1 }), "2021-06", "iso add P1M"); +shouldBe(ym("iso8601").add({ months: 13 }), "2022-06", "iso add P13M"); +shouldBe(ym("iso8601").subtract({ months: 1 }), "2021-04", "iso sub P1M"); + +shouldBe(ym("japanese").add({ months: 1 }).toString(), "2021-06-01[u-ca=japanese]", "japanese add P1M"); +shouldBe(ym("japanese").add({ months: 13 }).toString(), "2022-06-01[u-ca=japanese]", "japanese add P13M"); +shouldBe(ym("japanese").add({ years: 1 }).toString(), "2022-05-01[u-ca=japanese]", "japanese add P1Y"); +shouldBe(ym("japanese").subtract({ months: 1 }).toString(), "2021-04-01[u-ca=japanese]", "japanese sub P1M"); +shouldBe(ym("gregory").add({ months: 1 }).toString(), "2021-06-01[u-ca=gregory]", "gregory add P1M"); +shouldBe(ym("roc").add({ months: 1 }).toString(), "3932-06-01[u-ca=roc]", "roc add P1M"); +shouldBe(ym("roc").add({ years: 1 }).toString(), "3933-05-01[u-ca=roc]", "roc add P1Y"); + +// Lunisolar: leap-month insertion means the ordinal month cannot be carried across years, so +// ISODateToFields must hand CalendarYearMonthFromFields a monthCode. +shouldBe(ym("hebrew").add({ months: 1 }).toString(), "-001739-02-06[u-ca=hebrew]", "hebrew add P1M"); +shouldBe(ym("hebrew").add({ months: 13 }).toString(), "-001738-01-25[u-ca=hebrew]", "hebrew add P13M"); +shouldBe(ym("hebrew").subtract({ months: 1 }).toString(), "-001740-12-09[u-ca=hebrew]", "hebrew sub P1M"); +shouldBe(ym("chinese").add({ months: 1 }).toString(), "2021-07-10[u-ca=chinese]", "chinese add P1M"); +shouldBe(ym("chinese").add({ months: 13 }).toString(), "2022-06-29[u-ca=chinese]", "chinese add P13M"); +shouldBe(ym("chinese").subtract({ months: 1 }).toString(), "2021-05-12[u-ca=chinese]", "chinese sub P1M"); + +// An era-bearing receiver built FROM era+eraYear must add identically to one built from year. +shouldBe(Temporal.PlainYearMonth.from({ era: "reiwa", eraYear: 3, month: 5, calendar: "japanese" }) + .add({ months: 1 }).toString(), "2021-06-01[u-ca=japanese]", "japanese add P1M from era"); + +// ISO takes the pure isoDateAdd overload so the extreme boundary years do not get ICU-clamped. +shouldBe(Temporal.PlainYearMonth.from("+275760-08").add({ months: 1 }), "+275760-09", "add to max boundary"); +shouldThrow(() => Temporal.PlainYearMonth.from("+275760-09").add({ months: 1 }), RangeError, "add past max"); +shouldThrow(() => Temporal.PlainYearMonth.from("-271821-05").subtract({ months: 1 }), RangeError, "sub past min"); + +for (const [cal, year, isoPrefix] of [["buddhist", 2021, "1478"], ["roc", -433, "1478"], ["japanese", 1478, "1478"]]) { + const base = Temporal.PlainYearMonth.from({ year, month: 5, calendar: cal }); + shouldBe(base.toString(), `${isoPrefix}-05-01[u-ca=${cal}]`, `${cal} base at ISO ${isoPrefix}`); + shouldBe(base.add({ months: 0 }).month, 5, `${cal}: add P0M must be identity`); + shouldBe(base.add({ months: 1 }).month, 6, `${cal}: add P1M`); + shouldBe(base.subtract({ months: 1 }).month, 4, `${cal}: sub P1M`); +} +// Outside the affected ISO-year range the same calendars are correct, which pins the boundary. +shouldBe(Temporal.PlainYearMonth.from({ year: 2200, month: 5, calendar: "buddhist" }) + .add({ months: 0 }).toString(), "1657-05-01[u-ca=buddhist]", "buddhist add P0M above range"); +shouldBe(Temporal.PlainYearMonth.from({ year: 543, month: 5, calendar: "buddhist" }) + .add({ months: 0 }).toString(), "0000-05-01[u-ca=buddhist]", "buddhist add P0M below range"); +// gregory at the same ISO year is unaffected, isolating the proleptic-Gregorian era path. +shouldBe(Temporal.PlainYearMonth.from({ year: 1478, month: 5, calendar: "gregory" }) + .add({ months: 0 }).month, 5, "gregory add P0M at ISO 1478"); + +// --- PlainYearMonth.toPlainDate: ISODateToFields(~year-month~) then CalendarMergeFields(«day») --- +shouldBe(ym("iso8601").toPlainDate({ day: 15 }), "2021-05-15", "iso toPlainDate day=15"); +shouldBe(ym("iso8601").toPlainDate({ day: 31 }), "2021-05-31", "iso toPlainDate day=31"); +shouldBe(ym("japanese").toPlainDate({ day: 1 }).toString(), "2021-05-01[u-ca=japanese]", "japanese toPlainDate day=1"); +shouldBe(ym("japanese").toPlainDate({ day: 31 }).toString(), "2021-05-31[u-ca=japanese]", "japanese toPlainDate day=31"); +shouldBe(ym("hebrew").toPlainDate({ day: 1 }).toString(), "-001739-01-07[u-ca=hebrew]", "hebrew toPlainDate day=1"); +shouldBe(ym("hebrew").toPlainDate({ day: 31 }).toString(), "-001739-02-05[u-ca=hebrew]", "hebrew toPlainDate day=31"); +shouldBe(ym("chinese").toPlainDate({ day: 15 }).toString(), "2021-06-24[u-ca=chinese]", "chinese toPlainDate day=15"); +shouldBe(ym("chinese").toPlainDate({ day: 31 }).toString(), "2021-07-09[u-ca=chinese]", "chinese toPlainDate day=31"); + +// --- ToTemporalYearMonth step 12 / PlainDate.toPlainYearMonth --- +for (const [cal, expected] of [["japanese", "2021-05-01[u-ca=japanese]"], ["hebrew", "2021-05-12[u-ca=hebrew]"], + ["chinese", "2021-05-12[u-ca=chinese]"]]) { + shouldBe(Temporal.PlainYearMonth.from(`2021-05-17[u-ca=${cal}]`).toString(), expected, `${cal} YM.from string`); + shouldBe(Temporal.PlainDate.from(`2021-05-17[u-ca=${cal}]`).toPlainYearMonth().toString(), expected, `${cal} PD.toPlainYearMonth`); +} +shouldBe(Temporal.PlainYearMonth.from("2021-05-17"), "2021-05", "iso YM.from string"); +// The reference day is canonical (1 for ISO), which is why step 14 forces ~constrain~. +shouldBe(Temporal.PlainYearMonth.from("2020-05-23[u-ca=chinese]").toString(), "2020-05-23[u-ca=chinese]", "chinese YM.from leap-month year"); +shouldBe(Temporal.PlainYearMonth.from("2022-03-05[u-ca=hebrew]").toString(), "2022-03-04[u-ca=hebrew]", "hebrew YM.from leap-month year"); + +// --- ToTemporalMonthDay step 13 / PlainDate.toPlainMonthDay --- +shouldBe(Temporal.PlainMonthDay.from("2021-05-17"), "05-17", "iso MD.from string"); +shouldBe(Temporal.PlainMonthDay.from("2024-02-29"), "02-29", "iso MD.from Feb 29"); +for (const [cal, expected] of [["japanese", "1972-05-17[u-ca=japanese]"], ["hebrew", "1972-05-19[u-ca=hebrew]"], + ["chinese", "1972-05-18[u-ca=chinese]"]]) { + shouldBe(Temporal.PlainMonthDay.from(`2021-05-17[u-ca=${cal}]`).toString(), expected, `${cal} MD.from string`); + shouldBe(Temporal.PlainDate.from(`2021-05-17[u-ca=${cal}]`).toPlainMonthDay().toString(), expected, `${cal} PD.toPlainMonthDay`); +} + +// --- getter/resolution agreement --- +for (const cal of ["iso8601", "gregory", "hebrew", "chinese", "dangi", "islamic-civil", + "islamic-tbla", "islamic-umalqura", "japanese", "buddhist", "roc", "coptic", "ethiopic", + "ethioaa", "persian", "indian"]) { + for (const iso of ["2020-05-01", "2020-06-21", "1978-02-28", "2024-02-29", "2023-06-18"]) { + const d = Temporal.PlainDate.from(iso).withCalendar(cal); + const tag = `${cal} ${iso}`; + shouldBe(d.with({ day: d.day }).equals(d), true, `${tag}: with({day: d.day}) is identity`); + shouldBe(d.with({ year: d.year }).equals(d), true, `${tag}: with({year: d.year}) is identity`); + shouldBe(d.with({ monthCode: d.monthCode }).equals(d), true, `${tag}: with({monthCode}) is identity`); + shouldBe(d.with({ month: d.month }).equals(d), true, `${tag}: with({month: d.month}) is identity`); + if (d.era !== undefined) + shouldBe(d.with({ era: d.era, eraYear: d.eraYear }).equals(d), true, `${tag}: with({era, eraYear}) is identity`); + } +} diff --git a/JSTests/stress/temporal-lunisolar-and-wrappers.js b/JSTests/stress/temporal-lunisolar-and-wrappers.js new file mode 100644 index 0000000000000..c82f7e6ed8fae --- /dev/null +++ b/JSTests/stress/temporal-lunisolar-and-wrappers.js @@ -0,0 +1,92 @@ +//@ requireOptions("--useTemporal=1") + +function shouldBe(actual, expected, label) { + if (actual !== expected) + throw new Error(`${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); +} + +const icuVersion = $vm.icuVersion(); + +// dayOfYear returns calendar-native day (chinese year starts different day than ISO). +{ + const chinese1969 = Temporal.PlainDate.from({ year: 1969, month: 1, day: 1, calendar: "chinese" }); + shouldBe(chinese1969.dayOfYear, 1, "chinese year 1969 M01 D01 -> dayOfYear=1"); + const pdt = Temporal.PlainDateTime.from({ year: 1969, month: 1, day: 1, hour: 12, calendar: "chinese" }); + shouldBe(pdt.dayOfYear, 1, "PDT chinese year 1969 M01 D01 -> dayOfYear=1"); + const zdt = Temporal.ZonedDateTime.from({ year: 1969, month: 1, day: 1, hour: 12, timeZone: "UTC", calendar: "chinese" }); + shouldBe(zdt.dayOfYear, 1, "ZDT chinese year 1969 M01 D01 -> dayOfYear=1"); +} + +// hebrew M13 and non-M05L leap month codes are invalid. +for (const cal of ["hebrew"]) { + for (const mc of ["M13", "M01L", "M03L", "M06L", "M12L"]) { + let threw = false; + try { Temporal.PlainDate.from({ year: 5779, monthCode: mc, day: 1, calendar: cal }); } + catch (e) { threw = e instanceof RangeError; } + shouldBe(threw, true, `${cal} ${mc} -> RangeError`); + } + // M05L (Adar I) is valid in hebrew leap years. + const validAdarI = Temporal.PlainDate.from({ year: 5779, monthCode: "M05L", day: 1, calendar: "hebrew" }); + shouldBe(validAdarI.monthCode, "M05L", "hebrew year 5779 M05L is valid"); +} + +// lunisolar leap-month year addition constrains monthCode to base month. +{ + // Chinese 1938 M07L D30. Chinese 1939 has no M07L, so +1y constrains to M07 D29 (M07 has 29 days in 1939). + const start = Temporal.PlainDate.from({ year: 1938, monthCode: "M07L", day: 30, calendar: "chinese" }); + const plusOne = start.add(new Temporal.Duration(1)); + shouldBe(plusOne.monthCode, "M07", "chinese M07L +1y constrains to M07 in non-leap year"); + shouldBe(plusOne.day, 29, "chinese M07 D30 constrains to D29 (M07 chinese 1939 = 29 days)"); +} + +// lunisolar month/monthCode conflict check. +{ + // In chinese 2020 (has M04L), monthCode M05 is not at ordinal 12. Conflict. + let threw = false; + try { Temporal.PlainDate.from({ calendar: "chinese", year: 2020, monthCode: "M05", month: 12, day: 1 }); } + catch (e) { threw = e instanceof RangeError; } + shouldBe(threw, true, "chinese M05/month=12 conflict -> RangeError"); + // Correct ordinal works. ICU < 78 places 2020's leap month after M06 instead of M04 + // (verified wrong against icu4x's china_data.rs, which gives M04L; rdar://182753821), + // making M05 ordinal 5 there instead of 6, so the call below throws — skip entirely + // on affected ICU versions rather than just the assertion. + if (icuVersion >= 78) { + const ok = Temporal.PlainDate.from({ calendar: "chinese", year: 2020, monthCode: "M05", month: 6, day: 1 }); + shouldBe(ok.monthCode, "M05", "chinese M05/month=6 accepted"); + } +} + +// PMD leap-month with year-from-options-bag falls back to reference year. +{ + // Chinese year 1651 has M01L (ICU4C). PMD should carry the reference year (1972), not 1651. + const pd = Temporal.PlainDate.from({ calendar: "chinese", year: 1651, monthCode: "M01L", day: 29 }); + if (pd.monthCode === "M01L" && pd.day === 29) { + const pmd = Temporal.PlainMonthDay.from({ calendar: "chinese", year: 1651, monthCode: "M01L", day: 29 }); + const pmdYear = Number(pmd.toString().split("-")[0]); + shouldBe(pmdYear, 1972, "PMD chinese M01L D29 uses reference year 1972"); + } +} + +// with() on hebrew year that doesn't have same leap distribution — monthCode preserved, +// month adjusts to new year's ordinal. +{ + const start = new Temporal.PlainDate(2024, 8, 8, "hebrew"); // hebrew 5784 + const changed = start.with({ year: 5783 }); + shouldBe(changed.year, 5783, "hebrew with year=5783"); + shouldBe(changed.monthCode, "M11", "hebrew monthCode M11 preserved"); + // The ordinal adjusts to match the new year's leap-month layout. + shouldBe(typeof changed.month, "number", "month is numeric"); +} + +// REGRESSION: at extreme chinese/dangi years (beyond the ±10000 astronomical-reliability +// threshold), construction clamps to a representable ISO date without throwing, but reading +// any field back off the constructed object throws instead of returning the clamped values. +for (const cal of ["chinese", "dangi"]) { + for (const year of [100000, -100000]) { + const pd = Temporal.PlainDate.from({ calendar: cal, year, month: 1, day: 1 }); + shouldBe(typeof pd.year, "number", `${cal} year=${year} .year must not throw`); + shouldBe(typeof pd.month, "number", `${cal} year=${year} .month must not throw`); + shouldBe(typeof pd.day, "number", `${cal} year=${year} .day must not throw`); + shouldBe(typeof pd.monthCode, "string", `${cal} year=${year} .monthCode must not throw`); + } +} diff --git a/JSTests/stress/temporal-nonISO-arithmetic.js b/JSTests/stress/temporal-nonISO-arithmetic.js new file mode 100644 index 0000000000000..6b4514bcf88ae --- /dev/null +++ b/JSTests/stress/temporal-nonISO-arithmetic.js @@ -0,0 +1,61 @@ +//@ requireOptions("--useTemporal=1") + +function shouldBe(actual, expected, label) { + if (actual !== expected) + throw new Error(`${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); +} + +function assertPD(d, year, month, day, era, eraYear, label) { + shouldBe(d.year, year, `${label} year`); + shouldBe(d.month, month, `${label} month`); + shouldBe(d.day, day, `${label} day`); + shouldBe(d.era, era, `${label} era`); + shouldBe(d.eraYear, eraYear, `${label} eraYear`); +} + +// Non-Gregorian-structured calendars: add-year preserves calendar month/day (not ISO month/day). +{ + // Coptic M02 always has 30 days; adding 1 year to M02 D29 stays D29. + const d = Temporal.PlainDate.from({ year: 1747, monthCode: "M02", day: 29, calendar: "coptic" }); + assertPD(d.add(new Temporal.Duration(1)), 1748, 2, 29, "am", 1748, "coptic add 1y preserves M02 D29"); + // Ethiopic same shape (13 months). + const e = Temporal.PlainDate.from({ year: 2016, monthCode: "M13", day: 5, calendar: "ethiopic" }); + assertPD(e.add(new Temporal.Duration(1)), 2017, 13, 5, "am", 2017, "ethiopic add 1y preserves M13 D5"); +} + +// Islamic-civil add years/months preserves calendar frame. +{ + const d = Temporal.PlainDate.from({ year: 1443, monthCode: "M02", day: 29, calendar: "islamic-civil" }); + const added = d.add(new Temporal.Duration(0, 1)); + shouldBe(added.year, 1443, "islamic-civil add 1 month year"); + shouldBe(added.month, 3, "islamic-civil add 1 month month"); +} + +// Persian: 30-day month (Mordad) + 1 year stays 30-day. +{ + const d = Temporal.PlainDate.from({ year: 1400, monthCode: "M05", day: 30, calendar: "persian" }); + assertPD(d.add(new Temporal.Duration(1)), 1401, 5, 30, "ap", 1401, "persian add 1y preserves M05 D30"); +} + +// Gregorian-structured calendars (buddhist/roc/japanese) treat calendar year proleptically. +{ + // Buddhist BE 2125 M10 D04 = ISO 1582-10-04 (proleptic Gregorian, not Julian). + const d = Temporal.PlainDate.from({ year: 2125, monthCode: "M10", day: 4, calendar: "buddhist" }); + assertPD(d.add(new Temporal.Duration(0, 0, 0, 3)), 2125, 10, 7, "be", 2125, "buddhist proleptic add 3 days"); + // ROC year=-329 (BROC 330) = ISO 1582; same +3 days = day 7. + const r = Temporal.PlainDate.from({ year: -329, monthCode: "M10", day: 4, calendar: "roc" }); + assertPD(r.add(new Temporal.Duration(0, 0, 0, 3)), -329, 10, 7, "broc", 330, "roc proleptic add 3 days"); + // Japanese year=1582 = ISO 1582. + const j = Temporal.PlainDate.from({ year: 1582, monthCode: "M10", day: 4, calendar: "japanese" }); + assertPD(j.add(new Temporal.Duration(0, 0, 0, 3)), 1582, 10, 7, "ce", 1582, "japanese proleptic add 3 days"); +} + +// until in non-Gregorian-structured calendar produces calendar-frame duration. +{ + const a = Temporal.PlainDate.from({ year: 1747, monthCode: "M02", day: 1, calendar: "coptic" }); + const b = Temporal.PlainDate.from({ year: 1748, monthCode: "M02", day: 1, calendar: "coptic" }); + const dur = a.until(b, { largestUnit: "years" }); + shouldBe(dur.years, 1, "coptic until: years"); + shouldBe(dur.months, 0, "coptic until: months"); + shouldBe(dur.days, 0, "coptic until: days"); +} diff --git a/JSTests/stress/temporal-nonISO-with.js b/JSTests/stress/temporal-nonISO-with.js new file mode 100644 index 0000000000000..122b632ed9d29 --- /dev/null +++ b/JSTests/stress/temporal-nonISO-with.js @@ -0,0 +1,46 @@ +//@ requireOptions("--useTemporal=1") + +function shouldBe(actual, expected, label) { + if (actual !== expected) + throw new Error(`${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); +} + +// Buddhist: batched fields.year must be BE year (not Gregorian), else with() double-shifts. +{ + const d = Temporal.PlainDate.from({ year: 2543, monthCode: "M01", day: 1, calendar: "buddhist" }); + shouldBe(d.year, 2543, "buddhist year"); + const withDay = d.with({ day: 15 }); + shouldBe(withDay.year, 2543, "buddhist with day preserves year"); + shouldBe(withDay.eraYear, 2543, "buddhist with day preserves eraYear"); + shouldBe(withDay.day, 15, "buddhist with day"); +} + +// ROC: with should preserve calendar year. +{ + const d = Temporal.PlainDate.from({ year: 113, monthCode: "M06", day: 15, calendar: "roc" }); + shouldBe(d.year, 113, "roc year"); + const withMonth = d.with({ month: 1 }); + shouldBe(withMonth.year, 113, "roc with month preserves year"); + shouldBe(withMonth.month, 1, "roc with month"); +} + +// Coptic: with a different day preserves M13 (13-month calendar). +{ + const d = Temporal.PlainDate.from({ year: 1741, monthCode: "M13", day: 3, calendar: "coptic" }); + shouldBe(d.month, 13, "coptic M13"); + const withDay = d.with({ day: 5 }); + shouldBe(withDay.year, 1741, "coptic with day preserves year"); + shouldBe(withDay.month, 13, "coptic with day preserves M13"); + shouldBe(withDay.day, 5, "coptic with day"); +} + +// Islamic-civil: with year advances era correctly. +{ + const d = Temporal.PlainDate.from({ year: 1445, monthCode: "M06", day: 15, calendar: "islamic-civil" }); + const withYear = d.with({ year: 1446 }); + shouldBe(withYear.year, 1446, "islamic-civil with year"); + shouldBe(withYear.month, 6, "islamic-civil with year preserves month"); + shouldBe(withYear.day, 15, "islamic-civil with year preserves day"); + shouldBe(withYear.era, "ah", "islamic-civil era"); + shouldBe(withYear.eraYear, 1446, "islamic-civil eraYear"); +} diff --git a/JSTests/stress/temporal-nonisoresolvefields-monthcode-validation.js b/JSTests/stress/temporal-nonisoresolvefields-monthcode-validation.js new file mode 100644 index 0000000000000..a7471d86e220f --- /dev/null +++ b/JSTests/stress/temporal-nonisoresolvefields-monthcode-validation.js @@ -0,0 +1,52 @@ +//@ requireOptions("--useTemporal=1") + +function shouldThrow(fn, ErrCtor, label) { + let threw; + try { fn(); } catch (e) { threw = e.constructor; } + if (threw !== ErrCtor) + throw new Error(`${label}: expected ${ErrCtor.name}, got ${threw ? threw.name : "no throw"}`); +} + +// Hebrew: only M05L is valid. +for (const mc of ["M01L", "M02L", "M03L", "M04L", "M06L", "M07L", "M08L", "M09L", "M10L", "M11L", "M12L"]) { + shouldThrow(() => Temporal.PlainDate.from({ calendar: "hebrew", year: 5784, monthCode: mc, day: 1 }), + RangeError, `hebrew PD ${mc}`); + shouldThrow(() => Temporal.PlainYearMonth.from({ calendar: "hebrew", year: 5784, monthCode: mc }), + RangeError, `hebrew PYM ${mc}`); + shouldThrow(() => Temporal.PlainMonthDay.from({ calendar: "hebrew", monthCode: mc, day: 1 }), + RangeError, `hebrew PMD ${mc}`); +} + +// Chinese/Dangi: M01L..M12L only, no M13. +for (const cal of ["chinese", "dangi"]) { + shouldThrow(() => Temporal.PlainDate.from({ calendar: cal, year: 2024, monthCode: "M13", day: 1 }), + RangeError, `${cal} M13`); + shouldThrow(() => Temporal.PlainDate.from({ calendar: cal, year: 2024, monthCode: "M13L", day: 1 }), + RangeError, `${cal} M13L`); +} + +// Solar calendars: no leap monthCodes. +for (const cal of ["gregory", "buddhist", "indian", "japanese", "persian", "roc"]) { + shouldThrow(() => Temporal.PlainDate.from({ calendar: cal, year: 2024, monthCode: "M05L", day: 1 }), + RangeError, `${cal} M05L`); +} + +// Coptic/Ethiopic/Ethioaa: M13 valid; MnnL and M14+ invalid. +for (const cal of ["coptic", "ethiopic", "ethioaa"]) { + Temporal.PlainDate.from({ calendar: cal, year: 1740, monthCode: "M13", day: 1 }); + shouldThrow(() => Temporal.PlainDate.from({ calendar: cal, year: 1740, monthCode: "M14", day: 1 }), + RangeError, `${cal} M14`); + shouldThrow(() => Temporal.PlainDate.from({ calendar: cal, year: 1740, monthCode: "M05L", day: 1 }), + RangeError, `${cal} M05L`); + shouldThrow(() => Temporal.PlainDate.from({ calendar: cal, year: 1740, monthCode: "M13L", day: 1 }), + RangeError, `${cal} M13L`); +} + +// Chinese 2020 (no M02L, ~skip-backward~ → M02, ordinal 2). +Temporal.PlainDate.from({ calendar: "chinese", year: 2020, month: 2, monthCode: "M02L", day: 1 }); +shouldThrow(() => Temporal.PlainDate.from({ calendar: "chinese", year: 2020, month: 3, monthCode: "M02L", day: 1 }), + RangeError, "chinese 2020 M02L constrain->M02 vs m=3"); +// Hebrew 5783 (non-leap, ~skip-forward~ → M06, ordinal 6). +Temporal.PlainDate.from({ calendar: "hebrew", year: 5783, month: 6, monthCode: "M05L", day: 1 }); +shouldThrow(() => Temporal.PlainDate.from({ calendar: "hebrew", year: 5783, month: 5, monthCode: "M05L", day: 1 }), + RangeError, "hebrew 5783 M05L constrain->M06 vs m=5"); diff --git a/JSTests/stress/temporal-plaindate.js b/JSTests/stress/temporal-plaindate.js index b409a48463ebe..83a8fb6260bc1 100644 --- a/JSTests/stress/temporal-plaindate.js +++ b/JSTests/stress/temporal-plaindate.js @@ -139,15 +139,10 @@ let failures = [ "2007-01-09T03:24:30+01:00[Hey/Hello", "2007-01-09T03:24:30+01:00[]", "2007-01-09T03:24:30+01:00[Hey/]", - "2007-01-09T03:24:30+01:00[..]", - "2007-01-09T03:24:30+01:00[.]", - "2007-01-09T03:24:30+01:00[./.]", - "2007-01-09T03:24:30+01:00[../..]", "2007-01-09T03:24:30+01:00[-Hey/Hello]", "2007-01-09T03:24:30+01:00[-]", "2007-01-09T03:24:30+01:00[-/_]", "2007-01-09T03:24:30+01:00[_/-]", - "2007-01-09T03:24:30+01:00[CocoaCappuccinoMatcha]", "2007-01-09T03:24:30+10:20:30.0123456789", "2007-01-09 03:24:30+01:00[Etc/GMT\u221201]", "2007-01-09 03:24:30+01:00[+02:00:00.0123456789]", @@ -437,3 +432,16 @@ shouldBe(Temporal.PlainDate.prototype.toPlainYearMonth.length, 0); shouldBe(date.toPlainMonthDay().toString(), '02-28'); shouldBe(date.toPlainYearMonth().toString(), '2020-02'); } + +// proposal-temporal a8f6b0d3 ("Editorial: Align time zone name syntax with IXDTF") dropped +// TimeZoneIANANameComponent's 14-character limit and its exclusion of "." and "..", so these +// annotations are valid now. Do not move them back to `failures`. +for (let text of [ + "2007-01-09T03:24:30+01:00[..]", + "2007-01-09T03:24:30+01:00[.]", + "2007-01-09T03:24:30+01:00[./.]", + "2007-01-09T03:24:30+01:00[../..]", + "2007-01-09T03:24:30+01:00[CocoaCappuccinoMatcha]", +]) { + Temporal.PlainDate.from(text); +} diff --git a/JSTests/stress/temporal-plaindatetime-from-getter-order.js b/JSTests/stress/temporal-plaindatetime-from-getter-order.js new file mode 100644 index 0000000000000..594d19c771439 --- /dev/null +++ b/JSTests/stress/temporal-plaindatetime-from-getter-order.js @@ -0,0 +1,72 @@ +//@ requireOptions("--useTemporal=1") + +function shouldThrow(fn, type, message) { + let err; + try { fn(); } catch (e) { err = e; } + if (!(err instanceof type)) + throw new Error(`Expected ${type.name} but got ${err}`); + if (message !== undefined && err.message !== message) + throw new Error(`Expected message "${message}" but got "${err.message}"`); +} + +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`); +} + +function trackedBag(reads, props) { + let obj = {}; + for (const key of Object.keys(props)) { + Object.defineProperty(obj, key, { + get() { reads.push(key); return props[key]; }, + enumerable: true, + }); + } + return obj; +} + +// Missing `day` (alphabetically first): every other defined property must still be read. +{ + let reads = []; + shouldThrow(() => { + Temporal.PlainDateTime.from(trackedBag(reads, { + hour: 5, month: 3, monthCode: undefined, nanosecond: 1, second: 2, year: 2020, + })); + }, TypeError, "day property must be present"); + shouldBe(JSON.stringify(reads), JSON.stringify(["hour", "month", "monthCode", "nanosecond", "second", "year"])); +} + +// Missing both `month` and `monthCode`. +{ + let reads = []; + shouldThrow(() => { + Temporal.PlainDateTime.from(trackedBag(reads, { + day: 5, hour: 1, nanosecond: 1, second: 1, year: 2020, + })); + }, TypeError, "month or monthCode property must be present"); + shouldBe(JSON.stringify(reads), JSON.stringify(["day", "hour", "nanosecond", "second", "year"])); +} + +// Missing `year` (alphabetically last): everything before it must still be read. +{ + let reads = []; + shouldThrow(() => { + Temporal.PlainDateTime.from(trackedBag(reads, { + day: 5, hour: 1, month: 3, nanosecond: 1, second: 1, + })); + }, TypeError, "year property must be present"); + shouldBe(JSON.stringify(reads), JSON.stringify(["day", "hour", "month", "nanosecond", "second"])); +} + +// Sanity: a fully-populated bag still reads every property, in alphabetical order, and succeeds. +{ + let reads = []; + let pdt = Temporal.PlainDateTime.from(trackedBag(reads, { + day: 15, hour: 1, microsecond: 0, millisecond: 0, minute: 0, month: 6, + monthCode: undefined, nanosecond: 0, second: 0, year: 2020, + })); + shouldBe(JSON.stringify(reads), JSON.stringify([ + "day", "hour", "microsecond", "millisecond", "minute", "month", "monthCode", "nanosecond", "second", "year", + ])); + shouldBe(pdt.toString(), "2020-06-15T01:00:00"); +} diff --git a/JSTests/stress/temporal-plaindatetime.js b/JSTests/stress/temporal-plaindatetime.js index 50b764a75a3ff..c8b377dabb0db 100644 --- a/JSTests/stress/temporal-plaindatetime.js +++ b/JSTests/stress/temporal-plaindatetime.js @@ -163,15 +163,10 @@ const badStrings = [ "2007-01-09T03:24:30+01:00[Hey/Hello", "2007-01-09T03:24:30+01:00[]", "2007-01-09T03:24:30+01:00[Hey/]", - "2007-01-09T03:24:30+01:00[..]", - "2007-01-09T03:24:30+01:00[.]", - "2007-01-09T03:24:30+01:00[./.]", - "2007-01-09T03:24:30+01:00[../..]", "2007-01-09T03:24:30+01:00[-Hey/Hello]", "2007-01-09T03:24:30+01:00[-]", "2007-01-09T03:24:30+01:00[-/_]", "2007-01-09T03:24:30+01:00[_/-]", - "2007-01-09T03:24:30+01:00[CocoaCappuccinoMatcha]", "2007-01-09T03:24:30+10:20:30.0123456789", "2007-01-09 03:24:30+01:00[Etc/GMT\u221201]", "2007-01-09 03:24:30+01:00[+02:00:00.0123456789]", @@ -302,3 +297,16 @@ shouldThrow(() => { pdt.round({}); }, RangeError); shouldThrow(() => { pdt.round({ smallestUnit: 'bogus' }); }, RangeError); shouldThrow(() => { pdt.round({ smallestUnit: 'minute', roundingIncrement: 24 }); }, RangeError); shouldThrow(() => { pdt.round({ smallestUnit: 'minute', roundingMode: 'bogus' }); }, RangeError); + +// proposal-temporal a8f6b0d3 ("Editorial: Align time zone name syntax with IXDTF") dropped +// TimeZoneIANANameComponent's 14-character limit and its exclusion of "." and "..", so these +// annotations are valid now. Do not move them back to `failures`. +for (let text of [ + "2007-01-09T03:24:30+01:00[..]", + "2007-01-09T03:24:30+01:00[.]", + "2007-01-09T03:24:30+01:00[./.]", + "2007-01-09T03:24:30+01:00[../..]", + "2007-01-09T03:24:30+01:00[CocoaCappuccinoMatcha]", +]) { + Temporal.PlainDateTime.from(text); +} diff --git a/JSTests/stress/temporal-plaintime.js b/JSTests/stress/temporal-plaintime.js index 670fb65745604..86750196ba572 100644 --- a/JSTests/stress/temporal-plaintime.js +++ b/JSTests/stress/temporal-plaintime.js @@ -233,15 +233,10 @@ let failures = [ "1995-12-07T03:24:30+01:00[Hey/Hello", "1995-12-07T03:24:30+01:00[]", "1995-12-07T03:24:30+01:00[Hey/]", - "1995-12-07T03:24:30+01:00[..]", - "1995-12-07T03:24:30+01:00[.]", - "1995-12-07T03:24:30+01:00[./.]", - "1995-12-07T03:24:30+01:00[../..]", "1995-12-07T03:24:30+01:00[-Hey/Hello]", "1995-12-07T03:24:30+01:00[-]", "1995-12-07T03:24:30+01:00[-/_]", "1995-12-07T03:24:30+01:00[_/-]", - "1995-12-07T03:24:30+01:00[CocoaCappuccinoMatcha]", "1995-12-07T03:24:30+10:20:30.0123456789", "1995-12-07 03:24:30+01:00[Etc/GMT\u221201]", "1995-12-07 03:24:30+01:00[+02:00:00.0123456789]", @@ -370,3 +365,16 @@ shouldThrow(() => { shouldBe(String(time.since(Temporal.PlainTime.from('19:39:09.068346205'))), `PT34M11.903051894S`); shouldBe(String(time.since(Temporal.PlainTime.from('22:39:09.068346205'))), `-PT2H25M48.096948106S`); } + +// proposal-temporal a8f6b0d3 ("Editorial: Align time zone name syntax with IXDTF") dropped +// TimeZoneIANANameComponent's 14-character limit and its exclusion of "." and "..", so these +// annotations are valid now. Do not move them back to `failures`. +for (let text of [ + "1995-12-07T03:24:30+01:00[..]", + "1995-12-07T03:24:30+01:00[.]", + "1995-12-07T03:24:30+01:00[./.]", + "1995-12-07T03:24:30+01:00[../..]", + "1995-12-07T03:24:30+01:00[CocoaCappuccinoMatcha]", +]) { + Temporal.PlainTime.from(text); +} diff --git a/JSTests/stress/temporal-resolvefields-error-ordering.js b/JSTests/stress/temporal-resolvefields-error-ordering.js new file mode 100644 index 0000000000000..3efad0d7e10f5 --- /dev/null +++ b/JSTests/stress/temporal-resolvefields-error-ordering.js @@ -0,0 +1,51 @@ +//@ requireOptions("--useTemporal=1") + +function shouldThrow(fn, ErrCtor, label) { + let threw; + try { fn(); } catch (e) { threw = e.constructor; } + if (threw !== ErrCtor) + throw new Error(`${label}: expected ${ErrCtor.name}, got ${threw ? threw.name : "no throw"}`); +} + +// PlainDate.from: TypeError before RangeError. +{ + shouldThrow(() => Temporal.PlainDate.from({ calendar: "gregory", monthCode: "M05", month: 6, day: 1 }), + TypeError, "PD gregory missing year -> TypeError before conflict"); + shouldThrow(() => Temporal.PlainDate.from({ calendar: "gregory", year: 2020, day: 32 }), + TypeError, "PD gregory missing month -> TypeError"); + shouldThrow(() => Temporal.PlainDate.from({ calendar: "gregory", year: 2020, monthCode: "M05", month: 6 }), + TypeError, "PD gregory missing day -> TypeError"); + // era without eraYear -> TypeError. + shouldThrow(() => Temporal.PlainDate.from({ calendar: "gregory", era: "ce", monthCode: "M05", month: 6, day: 1 }), + TypeError, "PD gregory era without eraYear -> TypeError"); + // Range check still fires after all types valid. + shouldThrow(() => Temporal.PlainDate.from({ calendar: "gregory", year: 2020, monthCode: "M05", month: 6, day: 1 }), + RangeError, "PD gregory month/monthCode conflict -> RangeError"); +} + +// PlainYearMonth.from: TypeError before RangeError. +{ + shouldThrow(() => Temporal.PlainYearMonth.from({ calendar: "gregory", monthCode: "M05", month: 6 }), + TypeError, "PYM gregory missing year -> TypeError"); + shouldThrow(() => Temporal.PlainYearMonth.from({ calendar: "gregory", year: 2020 }), + TypeError, "PYM gregory missing month -> TypeError"); + shouldThrow(() => Temporal.PlainYearMonth.from({ calendar: "gregory", year: 2020, monthCode: "M05", month: 6 }), + RangeError, "PYM gregory month/monthCode conflict -> RangeError"); +} + +// PlainMonthDay.from: month+monthCode requires year for calendar-year disambiguation. +{ + shouldThrow(() => Temporal.PlainMonthDay.from({ calendar: "gregory", monthCode: "M04", month: 5, day: 1 }), + TypeError, "PMD gregory month+monthCode without year -> TypeError"); + shouldThrow(() => Temporal.PlainMonthDay.from({ calendar: "gregory", year: 2020, day: 15 }), + TypeError, "PMD gregory missing month/monthCode -> TypeError"); +} + +// ZonedDateTime.with: era without eraYear -> TypeError. +{ + const z = Temporal.ZonedDateTime.from({ calendar: "gregory", timeZone: "UTC", year: 2020, month: 5, day: 15, hour: 12 }); + shouldThrow(() => z.with({ era: "ce", monthCode: "M05", month: 6 }), + TypeError, "ZDT.with era without eraYear -> TypeError"); + shouldThrow(() => z.with({ monthCode: "M05", month: 6 }), + RangeError, "ZDT.with month/monthCode conflict -> RangeError"); +} diff --git a/JSTests/stress/temporal-timezone.js b/JSTests/stress/temporal-timezone.js index e2125c1ae8a5d..a2ff33952eb26 100644 --- a/JSTests/stress/temporal-timezone.js +++ b/JSTests/stress/temporal-timezone.js @@ -48,3 +48,104 @@ shouldThrow(() => { Temporal.TimeZone.from("UTC"); }, TypeError); shouldBe(typeof tzId, "string"); shouldBe(tzId.length > 0, true); } + +// ParseTemporalTimeZoneString Step 3 allows the annotation on any of 6 productions, not just the +// two datetime ones. https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaltimezonestring +{ + let cases = [ + ["2024-12[Europe/Berlin]", "Europe/Berlin"], // TemporalYearMonthString + bracket + ["--12-25[Europe/Berlin]", "Europe/Berlin"], // TemporalMonthDayString + bracket + ["12:00[Europe/Berlin]", "Europe/Berlin"], // TemporalTimeString + bracket + ]; + for (let [tzLike, expected] of cases) { + shouldBe(Temporal.Now.zonedDateTimeISO(tzLike).timeZoneId, expected); + shouldBe(Temporal.ZonedDateTime.from("2024-06-15T12:00[UTC]").withTimeZone(tzLike).timeZoneId, expected); + shouldBe(Temporal.Instant.fromEpochNanoseconds(0n).toZonedDateTimeISO(tzLike).timeZoneId, expected); + } + + // The annotation is what makes those acceptable, not the production. + shouldThrow(() => Temporal.Now.zonedDateTimeISO("2024-12"), RangeError); + shouldThrow(() => Temporal.Now.zonedDateTimeISO("--12-25"), RangeError); + shouldThrow(() => Temporal.Now.zonedDateTimeISO("12:00"), RangeError); +} + +// Step 7 delegates to ParseTimeZoneIdentifier, which only accepts UTCOffset[~SubMinutePrecision]. +{ + shouldThrow(() => Temporal.Now.zonedDateTimeISO("+01:00:30"), RangeError); + shouldThrow(() => Temporal.Now.zonedDateTimeISO("2024-01-01T00:00[+01:00:30]"), RangeError); +} + +// The 4th caller of ToTemporalTimeZoneIdentifier; the other three are covered above. +{ + let instant = Temporal.Instant.fromEpochNanoseconds(0n); + for (let tzLike of ["2024-12[Europe/Berlin]", "--12-25[Europe/Berlin]", "12:00[Europe/Berlin]"]) + shouldBe(instant.toString({ timeZone: tzLike }), "1970-01-01T01:00:00+01:00"); + + // toLocaleString goes through Intl.DateTimeFormat instead, which rejects annotated strings. + shouldThrow(() => instant.toLocaleString("en", { timeZone: "12:00[Europe/Berlin]" }), RangeError); +} + +// An offset with no annotation matches TemporalDateTimeString[~Zoned]; [+Zoned] requires one. +{ + shouldBe(Temporal.Now.zonedDateTimeISO("2024-01-01T00:00+01:00").timeZoneId, "+01:00"); + shouldBe(Temporal.Now.zonedDateTimeISO("2024-01-01T00:00Z").timeZoneId, "UTC"); +} + +// Every Temporal.Now entry point resolves its timeZone argument through the same AO. +{ + shouldBe(Temporal.Now.plainDateISO("2024-12[Europe/Berlin]") instanceof Temporal.PlainDate, true); + shouldBe(Temporal.Now.plainDateTimeISO("12:00[Europe/Berlin]") instanceof Temporal.PlainDateTime, true); + shouldBe(Temporal.Now.plainTimeISO("--12-25[Europe/Berlin]") instanceof Temporal.PlainTime, true); +} + +// Step 2 commits to the ParseTimeZoneIdentifier reading. "T12+01" is both a TimeZoneIANAName and a +// TemporalTimeString with an offset, so falling through to Step 3 would give "+01:00" rather than rejecting an unavailable named zone. +{ + let instant = Temporal.Instant.fromEpochNanoseconds(0n); + shouldThrow(() => instant.toString({ timeZone: "T12+01" }), RangeError); + shouldThrow(() => Temporal.Now.zonedDateTimeISO("T12+01"), RangeError); + shouldThrow(() => new Temporal.ZonedDateTime(0n, "T12+01"), RangeError); + + // "Z" is an unavailable TimeZoneIANAName; only Step 6 gives it its UTC meaning. + shouldThrow(() => new Temporal.ZonedDateTime(0n, "Z"), RangeError); + shouldThrow(() => instant.toString({ timeZone: "Z" }), RangeError); + shouldBe(Temporal.Now.zonedDateTimeISO("2024-01-01T00:00Z").timeZoneId, "UTC"); +} + +// ParseTimeZoneIdentifier Steps 5-8: offset identifiers are minute-granular and sign-normalized. +{ + let cases = [ + ["+01:00", "+01:00"], + ["+0530", "+05:30"], // UTCOffset accepts the colon-less form + ["-05:00", "-05:00"], + ["-00:00", "+00:00"], // negative zero formats as "+00:00" + ["+23:59", "+23:59"], + ["-12:00", "-12:00"], + ]; + for (let [tz, expected] of cases) { + let zdt = new Temporal.ZonedDateTime(0n, tz); + shouldBe(zdt.timeZoneId, expected); + shouldBe(zdt.offset, expected); + } + shouldThrow(() => new Temporal.ZonedDateTime(0n, "+24:00"), RangeError); + + // The constructor takes ParseTimeZoneIdentifier, so datetime strings are not identifiers here. + shouldThrow(() => new Temporal.ZonedDateTime(0n, "2024-01-01T00:00+01:00"), RangeError); + shouldThrow(() => new Temporal.ZonedDateTime(0n, "2024-01-01T00:00[Europe/Berlin]"), RangeError); + shouldBe(Temporal.Now.zonedDateTimeISO("2024-01-01T00:00[Europe/Berlin]").timeZoneId, "Europe/Berlin"); +} + +// Step 3 accepts a syntactically valid name without checking availability; the constructor's +// Step 6.b rejects it. "." and "./." are grammatical, "Hey/" and "_/-" are not. +{ + for (let tz of ["Foo/Bar", ".", "./.", "Hey/", "/Hey", "_/-", ""]) + shouldThrow(() => new Temporal.ZonedDateTime(0n, tz), RangeError); + + // Every TimeZoneIANANameComponent must be non-empty and start with a TZLeadingChar, not just the first. + for (let tz of ["a/", "a/-", "a/-b", "a//b"]) + shouldThrow(() => Temporal.PlainDate.from(`2007-01-09T03:24:30+01:00[${tz}]`), RangeError); + + // Case-normalization happens in Step 6.c, via GetAvailableNamedTimeZoneIdentifier. + shouldBe(new Temporal.ZonedDateTime(0n, "europe/berlin").timeZoneId, "Europe/Berlin"); + shouldBe(new Temporal.ZonedDateTime(0n, "UTC").timeZoneId, "UTC"); +} diff --git a/JSTests/stress/temporal-zdt-dst-gap-epoch-limits.js b/JSTests/stress/temporal-zdt-dst-gap-epoch-limits.js new file mode 100644 index 0000000000000..87ec3c7d38bc5 --- /dev/null +++ b/JSTests/stress/temporal-zdt-dst-gap-epoch-limits.js @@ -0,0 +1,60 @@ +//@ requireOptions("--useTemporal=1") + +function shouldThrow(fn, type, msg) { + let err; + try { fn(); } catch (e) { err = e; } + if (!(err instanceof type)) + throw new Error(`${msg}: expected ${type.name} but got ${err}`); +} + +function shouldBe(actual, expected, msg) { + if (String(actual) !== String(expected)) + throw new Error(`${msg}: expected ${JSON.stringify(String(expected))} but got ${JSON.stringify(String(actual))}`); +} + +// Sydney springs forward 02:00 -> 03:00 on the first Sunday in October, so 02:30 does not exist. +// +275760-10-05 is such a Sunday and is past the maximum representable epoch, so resolving the gap +// must be rejected rather than silently produce an out-of-range instant. +shouldThrow(() => Temporal.ZonedDateTime.from("+275760-10-05T02:30[Australia/Sydney]"), + RangeError, "gap shift past max epoch"); + +// Every disambiguation that resolves a gap goes through the same re-entrant conversion. +for (const disambiguation of ["compatible", "earlier", "later"]) { + shouldThrow(() => Temporal.ZonedDateTime.from("+275760-10-05T02:30[Australia/Sydney]", { disambiguation }), + RangeError, `gap shift past max epoch (${disambiguation})`); +} + +// ~reject~ throws for being a gap at all, before any range question arises. +shouldThrow(() => Temporal.ZonedDateTime.from("+275760-10-05T02:30[Australia/Sydney]", { disambiguation: "reject" }), + RangeError, "gap with disambiguation reject"); + +// The same wall clock reached through the other entry point that funnels into +// GetEpochNanosecondsFor must reject too. +shouldThrow(() => Temporal.PlainDateTime.from("+275760-10-05T02:30").toZonedDateTime("Australia/Sydney"), + RangeError, "PlainDateTime.toZonedDateTime into an out-of-range gap"); + +// A fold below the minimum epoch — Sydney falls back in April, so this covers +// getPossibleEpochNanosecondsFor's candidate range check, not the gap branch. (Sydney's gap is in +// October, which is inside the range at this year, so min-side gap coverage is not included here.) +shouldThrow(() => Temporal.ZonedDateTime.from("-271821-04-11T02:30[Australia/Sydney]"), + RangeError, "fold below min epoch"); + +// --- Regression guards: in-range gaps and folds must still resolve exactly as before. --- + +shouldBe(Temporal.ZonedDateTime.from("2023-10-01T02:30[Australia/Sydney]").toString(), + "2023-10-01T03:30:00+11:00[Australia/Sydney]", "in-range Sydney gap, compatible"); +shouldBe(Temporal.ZonedDateTime.from("2023-10-01T02:30[Australia/Sydney]", { disambiguation: "earlier" }).toString(), + "2023-10-01T01:30:00+10:00[Australia/Sydney]", "in-range Sydney gap, earlier"); +shouldBe(Temporal.ZonedDateTime.from("2023-10-01T02:30[Australia/Sydney]", { disambiguation: "later" }).toString(), + "2023-10-01T03:30:00+11:00[Australia/Sydney]", "in-range Sydney gap, later"); +shouldBe(Temporal.ZonedDateTime.from("2023-09-03T00:30[America/Santiago]").toString(), + "2023-09-03T01:30:00-03:00[America/Santiago]", "in-range Santiago gap"); + +shouldBe(Temporal.ZonedDateTime.from("2023-04-02T02:30[Australia/Sydney]").toString(), + "2023-04-02T02:30:00+11:00[Australia/Sydney]", "in-range Sydney fold, compatible"); +shouldBe(Temporal.ZonedDateTime.from("2023-04-02T02:30[Australia/Sydney]", { disambiguation: "later" }).toString(), + "2023-04-02T02:30:00+10:00[Australia/Sydney]", "in-range Sydney fold, later"); + +// The boundary stays reachable — the largest representable Sydney local time. +shouldBe(Temporal.ZonedDateTime.from("+275760-09-13T03:30[Australia/Sydney]").toString(), + "+275760-09-13T03:30:00+10:00[Australia/Sydney]", "max in-range Sydney local time"); diff --git a/JSTests/stress/trim-regexp-dfg.js b/JSTests/stress/trim-regexp-dfg.js new file mode 100644 index 0000000000000..fe16d85339fde --- /dev/null +++ b/JSTests/stress/trim-regexp-dfg.js @@ -0,0 +1,53 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error('bad value: ' + actual); +} + +const trimStart = /^\s+/; +const trimEnd = /\s+$/; + +function testStart(string) { + return string.replace(trimStart, ""); +} +noInline(testStart); + +function testEnd(string) { + return string.replace(trimEnd, ""); +} +noInline(testEnd); + +function testOther(string) { + return string.replace(/Hello/, ""); +} +noInline(testOther); + +for (let i = 0; i < testLoopCount; ++i) { + shouldBe(testStart(" \t\n Hello"), "Hello"); + shouldBe(RegExp.input, " \t\n Hello"); + shouldBe(RegExp.leftContext, ""); + shouldBe(RegExp.lastMatch, " \t\n "); + shouldBe(RegExp.rightContext, "Hello"); + + shouldBe(testEnd("Hello \t\n "), "Hello"); + shouldBe(RegExp.input, "Hello \t\n "); + shouldBe(RegExp.leftContext, "Hello"); + shouldBe(RegExp.lastMatch, " \t\n "); + shouldBe(RegExp.rightContext, ""); + + shouldBe(testStart(" "), ""); + shouldBe(RegExp.input, " "); + shouldBe(RegExp.lastMatch, " "); + + shouldBe(testEnd(" "), ""); + shouldBe(RegExp.input, " "); + shouldBe(RegExp.lastMatch, " "); + + shouldBe(testOther("errorHelloerror"), "errorerror"); + shouldBe(testStart("Hello "), "Hello "); + shouldBe(testEnd(" Hello"), " Hello"); + shouldBe(testStart(""), ""); + shouldBe(testEnd(""), ""); + shouldBe(RegExp.input, "errorHelloerror"); + shouldBe(RegExp.leftContext, "error"); + shouldBe(RegExp.rightContext, "error"); +} diff --git a/JSTests/stress/typedarray-canonical-numeric-index-string-past-max-array-index.js b/JSTests/stress/typedarray-canonical-numeric-index-string-past-max-array-index.js new file mode 100644 index 0000000000000..0b60be55785d9 --- /dev/null +++ b/JSTests/stress/typedarray-canonical-numeric-index-string-past-max-array-index.js @@ -0,0 +1,74 @@ +// A canonical numeric index string on a typed array never reaches ordinary property lookup, whatever +// number it denotes. https://tc39.es/ecma262/#sec-canonicalnumericindexstring +// +// Keys past MAX_ARRAY_INDEX are recognized as integer indices, so they must behave like any other +// out-of-bounds index on a short array rather than becoming ordinary properties. + +function shouldBe(actual, expected, what) { + if (actual !== expected) + throw new Error(`bad value for ${what}: expected ${expected} but got ${actual}`); +} + +function shouldThrowTypeError(f, what) { + try { + f(); + } catch (e) { + if (e instanceof TypeError) + return; + throw new Error(`${what}: expected a TypeError but got ${e}`); + } + throw new Error(`${what}: expected a TypeError but nothing was thrown`); +} + +const array = new Uint8Array(4); +const prototype = Object.getPrototypeOf(array); + +// String(n) is by construction the canonical spelling of n, so each of these is a +// CanonicalNumericIndexString. They span both sides of every internal cutoff: MAX_ARRAY_INDEX, 2^32, +// 2^53, and 2^64, plus the values that are canonical but are not integer indices at all. +const keys = [ + 2 ** 32 - 1, // 0xFFFFFFFF, excluded by isIndex() + 2 ** 32, + 2 ** 53 - 1, // the largest integer index the spec allows + 2 ** 53, + 2 ** 60, + 2 ** 64, // past uint64_t, so no index is recoverable + 1e21, // spelled in exponential form + -1, + 1.5, + 2 ** 32 + 0.5, // canonical, past MAX_ARRAY_INDEX, and not an integer + NaN, + Infinity, + -Infinity, +].map(String).concat(["-0"]); // String(-0) is "0", so -0 has to be spelled out. + +for (const key of keys) { + // An element that does not exist must not be found on the prototype either. + prototype[key] = "fromPrototype"; + + shouldBe(array[key], undefined, `array["${key}"]`); + shouldBe(key in array, false, `"${key}" in array`); + shouldBe(array.hasOwnProperty(key), false, `hasOwnProperty("${key}")`); + shouldBe(Object.getOwnPropertyDescriptor(array, key), undefined, `getOwnPropertyDescriptor("${key}")`); + + // The store is ignored, but the right hand side is still coerced first. + let coerced = false; + array[key] = { valueOf() { coerced = true; return 1; } }; + shouldBe(coerced, true, `array["${key}"] = ... coerces the right hand side`); + shouldBe(array.hasOwnProperty(key), false, `"${key}" was not added as a property`); + + // Deleting an index that does not exist succeeds vacuously. + shouldBe(delete array[key], true, `delete array["${key}"]`); + + shouldThrowTypeError(() => Object.defineProperty(array, key, { value: 1 }), `defineProperty("${key}")`); + + delete prototype[key]; +} + +// Numeric-looking strings that are not canonical are ordinary properties, and stay that way. +for (const key of ["042", "1e3", " 1", "0x10", "4294967296 ", "+4294967296"]) { + array[key] = "ordinary"; + shouldBe(array.hasOwnProperty(key), true, `"${key}" is an ordinary property`); + shouldBe(array[key], "ordinary", `array["${key}"]`); + shouldBe(delete array[key], true, `delete array["${key}"]`); +} diff --git a/JSTests/stress/typedarray-index-past-max-array-index.js b/JSTests/stress/typedarray-index-past-max-array-index.js new file mode 100644 index 0000000000000..dee0ad5ebb529 --- /dev/null +++ b/JSTests/stress/typedarray-index-past-max-array-index.js @@ -0,0 +1,73 @@ +//@ memoryHog! +//@ skip if $addressBits <= 32 +//@ runDefault + +// Indexed access on a typed array longer than MAX_ARRAY_INDEX elements. +// +// MAX_ARRAY_BUFFER_SIZE is 2^34, so a Uint8Array can be longer than MAX_ARRAY_INDEX (0xFFFFFFFE), +// the ceiling on a property key expressed as an index. Every element below the view's length is a +// valid integer index per IsValidIntegerIndex, so [] must reach it. Such a key arrives as a string, +// where parseIndex() cannot hold it, so it is recognized by isCanonicalNumericIndexString() instead. + +function shouldBe(actual, expected, what) { + if (actual !== expected) + throw new Error(`bad value for ${what}: expected ${expected} but got ${actual}`); +} + +// 4GiB + 2 bytes: the smallest view with indices on both sides of 0xFFFFFFFF and above 2^32, so one +// allocation covers every case. +let array; +try { + array = new Uint8Array(4294967298); +} catch (e) { + // A port that cannot spare 4GiB has nothing to test here. + if (!(e instanceof RangeError)) + throw e; +} + +if (array !== undefined) { + // fill() and subarray() take the size_t path, so they establish what a byte holds independently of []. + const trueValueAt = index => array.subarray(index, index + 1)[0]; + + for (const index of [ + 4294967294, // 0xFFFFFFFE, the last index a property key can express: the control. + 4294967295, // 0xFFFFFFFF: fits a uint32 but is excluded by isIndex(). + 4294967296, // 2^32: does not fit a uint32 at all. + array.length - 1, + ]) { + array.fill(7, index, index + 1); + shouldBe(trueValueAt(index), 7, `fill() at ${index}`); + + shouldBe(array[index], 7, `array[${index}]`); + shouldBe(array.at(index), 7, `array.at(${index})`); + + array[index] = 99; + shouldBe(trueValueAt(index), 99, `array[${index}] = 99`); + shouldBe(array[index], 99, `array[${index}] after assignment`); + + shouldBe(index in array, true, `${index} in array`); + shouldBe(array.hasOwnProperty(String(index)), true, `hasOwnProperty(${index})`); + shouldBe(Reflect.has(array, String(index)), true, `Reflect.has(array, "${index}")`); + + const descriptor = Object.getOwnPropertyDescriptor(array, String(index)); + shouldBe(descriptor !== undefined, true, `getOwnPropertyDescriptor(${index}) exists`); + shouldBe(descriptor.value, 99, `getOwnPropertyDescriptor(${index}).value`); + shouldBe(descriptor.writable, true, `getOwnPropertyDescriptor(${index}).writable`); + shouldBe(descriptor.enumerable, true, `getOwnPropertyDescriptor(${index}).enumerable`); + shouldBe(descriptor.configurable, true, `getOwnPropertyDescriptor(${index}).configurable`); + + // An integer index that exists cannot be deleted. + shouldBe(delete array[index], false, `delete array[${index}]`); + shouldBe(trueValueAt(index), 99, `array[${index}] survives delete`); + + Object.defineProperty(array, String(index), { value: 123 }); + shouldBe(trueValueAt(index), 123, `defineProperty at ${index}`); + + // Reading through the prototype must not shadow an element that exists. + Object.getPrototypeOf(array)[String(index)] = "fromProto"; + shouldBe(array[index], 123, `array[${index}] is not shadowed by the prototype`); + delete Object.getPrototypeOf(array)[String(index)]; + + array.fill(0, index, index + 1); + } +} diff --git a/JSTests/stress/typedarray-put-out-of-range-canonical-numeric-index.js b/JSTests/stress/typedarray-put-out-of-range-canonical-numeric-index.js new file mode 100644 index 0000000000000..e05997581bcf5 --- /dev/null +++ b/JSTests/stress/typedarray-put-out-of-range-canonical-numeric-index.js @@ -0,0 +1,41 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`bad value: expected ${expected}, got ${actual}`); +} + +// A canonical numeric index string outside the valid integer index range must be a no-op store, not a +// store at the index narrowed to the width of size_t. The low 32 bits of each key below are within the +// array's length, so a narrowing store would land on an element. +const keys = ["4294967295", "4294967296", "4294967299", "8589934592", "1e+21"]; + +for (const constructor of [Uint8Array, Int32Array, Float64Array, BigInt64Array]) { + const zero = constructor === BigInt64Array ? 0n : 0; + const value = constructor === BigInt64Array ? 42n : 42; + + for (const key of keys) { + const array = new constructor(8); + let coerced = 0; + array[key] = { valueOf() { ++coerced; return value; } }; + + shouldBe(coerced, 1); // TypedArraySetElement coerces the RHS before validating the index. + shouldBe(array[key], undefined); + shouldBe(Object.getOwnPropertyDescriptor(array, key), undefined); + shouldBe(Object.prototype.hasOwnProperty.call(array, key), false); + for (let i = 0; i < array.length; ++i) + shouldBe(array[i], zero); + } + + // The same keys through defineProperty, which must throw rather than store. + for (const key of keys) { + const array = new constructor(8); + let threw = false; + try { + Object.defineProperty(array, key, { value }); + } catch (error) { + threw = error instanceof TypeError; + } + shouldBe(threw, true); + for (let i = 0; i < array.length; ++i) + shouldBe(array[i], zero); + } +} diff --git a/JSTests/test262/config.yaml b/JSTests/test262/config.yaml index e2819df2feaf6..44d2e607ba813 100644 --- a/JSTests/test262/config.yaml +++ b/JSTests/test262/config.yaml @@ -4,7 +4,6 @@ flags: SharedArrayBuffer: useSharedArrayBuffer Atomics: useSharedArrayBuffer Temporal: useTemporal - Intl.Era-monthcode: useIntlEraMonthcode ShadowRealm: useShadowRealm json-parse-with-source: useJSONSourceTextAccess iterator-sequencing: useIteratorSequencing @@ -17,7 +16,6 @@ skip: - FinalizationRegistry.prototype.cleanupSome - decorators - source-phase-imports - - Intl.Era-monthcode - await-dictionary - import-bytes - immutable-arraybuffer @@ -25,9 +23,6 @@ skip: paths: # Depends on the nonextensible-applies-to-private proposal which JSC has not implemented yet. - test/language/import/import-defer/evaluation-triggers/ignore-private-name-access.js - # Incorrect tests, see https://github.com/tc39/test262/issues/4980 - - test/language/import/import-defer/evaluation-triggers/ignore-super-property-set-exported.js - - test/language/import/import-defer/evaluation-triggers/ignore-super-property-set-not-exported.js - test/staging/Intl402 - test/staging/JSON files: diff --git a/JSTests/test262/expectations-linux.yaml b/JSTests/test262/expectations-linux.yaml index f77383ea3c3e7..3ce3fd0ef29ff 100644 --- a/JSTests/test262/expectations-linux.yaml +++ b/JSTests/test262/expectations-linux.yaml @@ -1,238 +1,256 @@ +# Expected test262 failures. Generated by Tools/Scripts/test262-runner --save. +# +# Each entry maps a test file to the modes it is expected to fail in ("default", +# "strict mode", "module" or "raw"), and each mode to the exit code jsc is +# expected to terminate with: +# +# 3 The test ran to completion and failed: an assertion in the test or +# harness failed, or the test threw an uncaught exception. This is +# jsc's EXIT_EXCEPTION. +# 128 + N jsc was killed by signal N, i.e. it crashed. Common values are 134 +# (SIGABRT, which includes assertion failures in debug builds), 138 +# (SIGBUS) and 139 (SIGSEGV). +# 1 jsc could not run the test at all, or the runner timed out waiting +# for it. +# +# A failing test counts as expected only if it fails with the exit code recorded +# here, so a test that starts crashing where it previously failed an assertion +# is still reported as a new failure. --- test/annexB/language/function-code/block-decl-func-skip-arguments.js: - default: 'Test262Error: Expected SameValue(«"function arguments() {}"», «"[object Arguments]"») to be true' + default: 3 test/built-ins/Function/internals/Construct/derived-return-val-realm.js: - default: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' - strict mode: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/built-ins/Function/internals/Construct/derived-this-uninitialized-realm.js: - default: 'Test262Error: Expected a ReferenceError but got a different error constructor with the same name' - strict mode: 'Test262Error: Expected a ReferenceError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/built-ins/Function/prototype/arguments/prop-desc.js: - default: 'Test262Error: Function.prototype.arguments property getter/setter are the same function Expected SameValue(«function arguments() {' - strict mode: 'Test262Error: Function.prototype.arguments property getter/setter are the same function Expected SameValue(«function arguments() {' + default: 3 + strict mode: 3 test/built-ins/Function/prototype/caller-arguments/accessor-properties.js: - default: 'Test262Error: Function.prototype.arguments and Function.prototype.caller accessor functions should match (%ThrowTypeError%) Expected SameValue(«function caller() {' - strict mode: 'Test262Error: Function.prototype.arguments and Function.prototype.caller accessor functions should match (%ThrowTypeError%) Expected SameValue(«function caller() {' + default: 3 + strict mode: 3 test/built-ins/Function/prototype/caller/prop-desc.js: - default: 'Test262Error: Caller property getter/setter are the same function Expected SameValue(«function caller() {' - strict mode: 'Test262Error: Caller property getter/setter are the same function Expected SameValue(«function caller() {' + default: 3 + strict mode: 3 test/built-ins/Function/prototype/toString/built-in-function-object.js: - default: 'Test262Error: Conforms to NativeFunction Syntax: "function $*() {\n [native code]\n}" (%RegExp%.$*)' - strict mode: 'Test262Error: Conforms to NativeFunction Syntax: "function $*() {\n [native code]\n}" (%RegExp%.$*)' + default: 3 + strict mode: 3 test/built-ins/Proxy/apply/arguments-realm.js: - default: 'Test262Error: Expected SameValue(«function Array() {' - strict mode: 'Test262Error: Expected SameValue(«function Array() {' + default: 3 + strict mode: 3 test/built-ins/Proxy/apply/null-handler-realm.js: - default: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' - strict mode: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/built-ins/Proxy/apply/trap-is-not-callable-realm.js: - default: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' - strict mode: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/built-ins/Proxy/construct/arguments-realm.js: - default: 'Test262Error: Expected SameValue(«function Array() {' - strict mode: 'Test262Error: Expected SameValue(«function Array() {' + default: 3 + strict mode: 3 test/built-ins/Proxy/construct/trap-is-not-callable-realm.js: - default: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' - strict mode: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-offset.js: - default: 'Test262Error: Actual [20, 30, 40, 60] and expected [20, 20, 20, 60] should have the same contents. (Testing with Float64Array and makePassthrough.)' - strict mode: 'Test262Error: Actual [20, 30, 40, 60] and expected [20, 20, 20, 60] should have the same contents. (Testing with Float64Array and makePassthrough.)' + default: 3 + strict mode: 3 test/built-ins/TypedArrayConstructors/ctors/object-arg/iterated-array-changed-by-tonumber.js: - default: 'Test262Error: Expected SameValue(«NaN», «2») to be true (Testing with Float64Array and makePassthrough.)' - strict mode: 'Test262Error: Expected SameValue(«NaN», «2») to be true (Testing with Float64Array and makePassthrough.)' + default: 3 + strict mode: 3 test/built-ins/Uint8Array/prototype/setFromBase64/trailing-garbage-empty.js: - default: 'SyntaxError: Uint8Array.prototype.setFromBase64 requires a valid base64 string' - strict mode: 'SyntaxError: Uint8Array.prototype.setFromBase64 requires a valid base64 string' + default: 3 + strict mode: 3 test/built-ins/Uint8Array/prototype/setFromBase64/trailing-garbage.js: - default: 'SyntaxError: Uint8Array.prototype.setFromBase64 requires a valid base64 string' - strict mode: 'SyntaxError: Uint8Array.prototype.setFromBase64 requires a valid base64 string' + default: 3 + strict mode: 3 test/intl402/Temporal/PlainDate/prototype/monthCode/chinese-calendar-dates.js: - default: 'Test262Error: constructing PlainDate from month number: monthCode result: Expected SameValue(«"M07"», «"M06L"») to be true' - strict mode: 'Test262Error: constructing PlainDate from month number: monthCode result: Expected SameValue(«"M07"», «"M06L"») to be true' + default: 3 + strict mode: 3 test/intl402/Temporal/PlainDateTime/prototype/monthCode/chinese-calendar-dates.js: - default: 'Test262Error: constructing PlainDateTime from month number: monthCode result: Expected SameValue(«"M07"», «"M06L"») to be true' - strict mode: 'Test262Error: constructing PlainDateTime from month number: monthCode result: Expected SameValue(«"M07"», «"M06L"») to be true' + default: 3 + strict mode: 3 test/intl402/Temporal/PlainMonthDay/prototype/monthCode/chinese-calendar-dates.js: - default: 'Test262Error: md: monthCode result: Expected SameValue(«"M07"», «"M06L"») to be true' - strict mode: 'Test262Error: md: monthCode result: Expected SameValue(«"M07"», «"M06L"») to be true' + default: 3 + strict mode: 3 test/intl402/Temporal/PlainYearMonth/prototype/monthCode/chinese-calendar-dates.js: - default: 'Test262Error: constructing PlainYearMonth from month number: monthCode result: Expected SameValue(«"M07"», «"M06L"») to be true' - strict mode: 'Test262Error: constructing PlainYearMonth from month number: monthCode result: Expected SameValue(«"M07"», «"M06L"») to be true' + default: 3 + strict mode: 3 test/intl402/Temporal/ZonedDateTime/prototype/monthCode/chinese-calendar-dates.js: - default: 'Test262Error: constructing ZonedDateTime from month number: monthCode result: Expected SameValue(«"M07"», «"M06L"») to be true' - strict mode: 'Test262Error: constructing ZonedDateTime from month number: monthCode result: Expected SameValue(«"M07"», «"M06L"») to be true' + default: 3 + strict mode: 3 test/language/destructuring/binding/keyed-destructuring-property-reference-target-evaluation-order-with-bindings.js: - default: 'Test262Error: Actual [binding::source, binding::sourceKey, sourceKey, get source, binding::defaultValue, binding::varTarget] and expected [binding::source, binding::sourceKey, sourceKey, binding::varTarget, get source, binding::defaultValue] should have the same contents. ' + default: 3 test/language/eval-code/direct/arrow-fn-body-cntns-arguments-func-decl-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-body-cntns-arguments-func-decl-arrow-func-declare-arguments-assign.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-body-cntns-arguments-lex-bind-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-body-cntns-arguments-lex-bind-arrow-func-declare-arguments-assign.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-body-cntns-arguments-var-bind-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-body-cntns-arguments-var-bind-arrow-func-declare-arguments-assign.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-no-pre-existing-arguments-bindings-are-present-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-no-pre-existing-arguments-bindings-are-present-arrow-func-declare-arguments-assign.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/expressions/assignment/fn-name-lhs-cover.js: - default: 'Test262Error: name descriptor value should be ; name value should be ' - strict mode: 'Test262Error: name descriptor value should be ; name value should be ' + default: 3 + strict mode: 3 test/language/expressions/call/tco-non-eval-function-dynamic.js: - default: 'RangeError: Maximum call stack size exceeded.' + default: 3 test/language/expressions/call/tco-non-eval-function.js: - default: 'RangeError: Maximum call stack size exceeded.' + default: 3 test/language/expressions/call/tco-non-eval-global.js: - default: 'RangeError: Maximum call stack size exceeded.' + default: 3 test/language/expressions/call/tco-non-eval-with.js: - default: 'RangeError: Maximum call stack size exceeded.' + default: 3 test/language/expressions/delete/super-property-uninitialized-this.js: - default: 'Test262Error: Expected a ReferenceError but got a Test262Error' - strict mode: 'Test262Error: Expected a ReferenceError but got a Test262Error' + default: 3 + strict mode: 3 test/language/expressions/dynamic-import/import-attributes/2nd-param-with-type-text.js: - default: 'Test262:AsyncTestFailure:TypeError: Import attribute type "text" is not valid' - strict mode: 'Test262:AsyncTestFailure:TypeError: Import attribute type "text" is not valid' + default: 3 + strict mode: 3 test/language/expressions/new/non-ctor-err-realm.js: - default: 'Test262Error: production including Arguments Expected a TypeError but got a different error constructor with the same name' - strict mode: 'Test262Error: production including Arguments Expected a TypeError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/language/expressions/object/computed-property-name-topropertykey-before-value-evaluation.js: - default: 'Test262Error: Expected SameValue(«"bad"», «"ok"») to be true' - strict mode: 'Test262Error: Expected SameValue(«"bad"», «"ok"») to be true' + default: 3 + strict mode: 3 test/language/expressions/super/prop-expr-uninitialized-this-putvalue-compound-assign.js: - default: 'Test262Error: Expected a ReferenceError but got a Test262Error' - strict mode: 'Test262Error: Expected a ReferenceError but got a Test262Error' + default: 3 + strict mode: 3 test/language/expressions/super/prop-expr-uninitialized-this-putvalue-increment.js: - default: 'Test262Error: Expected a ReferenceError but got a Test262Error' - strict mode: 'Test262Error: Expected a ReferenceError but got a Test262Error' + default: 3 + strict mode: 3 test/language/expressions/super/prop-expr-uninitialized-this-putvalue.js: - default: 'Test262Error: Expected a ReferenceError but got a Test262Error' - strict mode: 'Test262Error: Expected a ReferenceError but got a Test262Error' + default: 3 + strict mode: 3 test/language/expressions/yield/star-iterable.js: - default: 'Test262Error: First result `done` flag Expected SameValue(«false», «undefined») to be true' - strict mode: 'Test262Error: First result `done` flag Expected SameValue(«false», «undefined») to be true' + default: 3 + strict mode: 3 test/language/expressions/yield/star-rhs-iter-nrml-res-done-no-value.js: - default: 'Test262Error: access count (first iteration) Expected SameValue(«1», «0») to be true' - strict mode: 'Test262Error: access count (first iteration) Expected SameValue(«1», «0») to be true' + default: 3 + strict mode: 3 test/language/expressions/yield/star-rhs-iter-rtrn-res-done-no-value.js: - default: 'Test262Error: access count (second iteration) Expected SameValue(«1», «0») to be true' - strict mode: 'Test262Error: access count (second iteration) Expected SameValue(«1», «0») to be true' + default: 3 + strict mode: 3 test/language/expressions/yield/star-rhs-iter-thrw-res-done-no-value.js: - default: 'Test262Error: access count (second iteration) Expected SameValue(«1», «0») to be true' - strict mode: 'Test262Error: access count (second iteration) Expected SameValue(«1», «0») to be true' + default: 3 + strict mode: 3 test/language/identifier-resolution/assign-to-global-undefined.js: - strict mode: Expected uncaught exception with name 'ReferenceError' but none was thrown + strict mode: 3 test/language/import/import-attributes/text-empty.js: - module: 'TypeError: Import attribute type "text" is not valid' + module: 3 test/language/import/import-attributes/text-javascript.js: - module: 'TypeError: Import attribute type "text" is not valid' + module: 3 test/language/import/import-attributes/text-self.js: - module: 'TypeError: Import attribute type "text" is not valid' + module: 3 test/language/import/import-attributes/text-string.js: - module: 'TypeError: Import attribute type "text" is not valid' + module: 3 test/language/import/import-attributes/text-via-namespace.js: - module: 'TypeError: Import attribute type "text" is not valid' + module: 3 test/language/statements/class/elements/private-class-field-on-nonextensible-objects.js: - strict mode: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' + strict mode: 3 test/language/statements/class/subclass/private-class-field-on-nonextensible-return-override.js: - strict mode: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' + strict mode: 3 test/language/statements/for-await-of/head-lhs-async.js: - default: "SyntaxError: Unexpected identifier 'of'" - strict mode: "SyntaxError: Unexpected identifier 'of'" + default: 3 + strict mode: 3 test/language/statements/for-in/head-lhs-let.js: - default: "SyntaxError: Cannot use the keyword 'in' as a lexical variable name." + default: 3 test/language/statements/for-in/identifier-let-allowed-as-lefthandside-expression-not-strict.js: - default: "SyntaxError: Cannot use the keyword 'in' as a lexical variable name." + default: 3 test/language/statements/for/head-lhs-let.js: - default: "SyntaxError: Unexpected token ';'. Expected a parameter pattern or a ')' in parameter list." + default: 3 test/language/statements/with/get-binding-value-call-with-proxy-env.js: - default: 'Test262Error: Actual [has:Object, get:Symbol(Symbol.unscopables), get:Object] and expected [has:Object, get:Symbol(Symbol.unscopables), has:Object, get:Object] should have the same contents. ' + default: 3 test/language/statements/with/get-binding-value-idref-with-proxy-env.js: - default: 'Test262Error: Actual [has:Object, get:Symbol(Symbol.unscopables), get:Object] and expected [has:Object, get:Symbol(Symbol.unscopables), has:Object, get:Object] should have the same contents. ' + default: 3 test/language/statements/with/get-mutable-binding-binding-deleted-in-get-unscopables.js: - default: "ReferenceError: Can't find variable: binding" + default: 3 test/language/statements/with/set-mutable-binding-idref-compound-assign-with-proxy-env.js: - default: 'Test262Error: Actual [has:p, get:Symbol(Symbol.unscopables), get:p, has:p, set:p, getOwnPropertyDescriptor:p, defineProperty:p] and expected [has:p, get:Symbol(Symbol.unscopables), has:p, get:p, has:p, set:p, getOwnPropertyDescriptor:p, defineProperty:p] should have the same contents. ' + default: 3 test/staging/sm/ArrayBuffer/slice-species.js: - default: 'Test262Error: Expected SameValue(«function ArrayBuffer() {' - strict mode: 'Test262Error: Expected SameValue(«function ArrayBuffer() {' + default: 3 + strict mode: 3 test/staging/sm/Date/two-digit-years.js: - default: 'Test262Error: Expected SameValue(«NaN», «957164400000») to be true' - strict mode: 'Test262Error: Expected SameValue(«NaN», «957164400000») to be true' + default: 3 + strict mode: 3 test/staging/sm/Function/arguments-parameter-shadowing.js: - default: 'Test262Error: Expected SameValue(«true», «false») to be true' + default: 3 test/staging/sm/Function/function-name-assignment.js: - default: 'Test262Error: Expected SameValue(«"inParen"», «""») to be true' + default: 3 test/staging/sm/Function/function-toString-builtin-name.js: - default: 'Test262Error: Incorrect match for undefined Expected SameValue(«"fn"», «undefined») to be true' - strict mode: 'Test262Error: Incorrect match for undefined Expected SameValue(«"fn"», «undefined») to be true' + default: 3 + strict mode: 3 test/staging/sm/PrivateName/modify-non-extensible.js: - default: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' + default: 3 test/staging/sm/Proxy/revoked-get-function-realm-typeerror.js: - default: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' - strict mode: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' + default: 3 + strict mode: 3 test/staging/sm/RegExp/replace-sticky-lastIndex.js: - default: 'Test262Error: Expected SameValue(«"b"», «"a"») to be true' - strict mode: 'Test262Error: Expected SameValue(«"b"», «"a"») to be true' + default: 3 + strict mode: 3 test/staging/sm/RegExp/replace-sticky.js: - default: 'Test262Error: Expected SameValue(«"ABCDEabcdeabcdefghij"», «"abcdeABCDEabcdefghij"») to be true' - strict mode: 'Test262Error: Expected SameValue(«"ABCDEabcdeabcdefghij"», «"abcdeABCDEabcdefghij"») to be true' + default: 3 + strict mode: 3 test/staging/sm/RegExp/unicode-braced.js: - default: 'SyntaxError: Invalid regular expression: regular expression too large' - strict mode: 'SyntaxError: Invalid regular expression: regular expression too large' + default: 3 + strict mode: 3 test/staging/sm/RegExp/unicode-class-braced.js: - default: 'SyntaxError: Invalid regular expression: regular expression too large' - strict mode: 'SyntaxError: Invalid regular expression: regular expression too large' + default: 3 + strict mode: 3 test/staging/sm/TypedArray/slice-memcpy.js: - default: 'Test262Error: Actual [1, 2, 1, 2, 3, 4] and expected [1, 2, 1, 2, 1, 2] should have the same contents. ' - strict mode: 'Test262Error: Actual [1, 2, 1, 2, 3, 4] and expected [1, 2, 1, 2, 1, 2] should have the same contents. ' + default: 3 + strict mode: 3 test/staging/sm/class/superPropOrdering.js: - default: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' - strict mode: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' + default: 3 + strict mode: 3 test/staging/sm/eval/redeclared-arguments-in-param-expression-eval.js: - default: 'Test262Error: Expected SameValue(«true», «false») to be true' + default: 3 test/staging/sm/expressions/exponentiation-unparenthesised-unary.js: - default: 'Test262Error: AsyncFunction:await a ** 0 Expected a SyntaxError to be thrown but no exception was thrown at all' - strict mode: 'Test262Error: AsyncFunction:await a ** 0 Expected a SyntaxError to be thrown but no exception was thrown at all' + default: 3 + strict mode: 3 test/staging/sm/expressions/object-literal-computed-property-evaluation.js: - default: 'Test262Error: Expected SameValue(«undefined», «"abc"») to be true' - strict mode: 'Test262Error: Expected SameValue(«undefined», «"abc"») to be true' + default: 3 + strict mode: 3 test/staging/sm/expressions/short-circuit-compound-assignment-anon-fns.js: - default: 'Test262Error: Expected SameValue(«"a"», «""») to be true' - strict mode: 'Test262Error: Expected SameValue(«"a"», «""») to be true' + default: 3 + strict mode: 3 test/staging/sm/extensions/function-caller-skips-eval-frames.js: - default: 'Test262Error: Expected SameValue(«null», «function nest() { return eval("innermost();"); }») to be true' + default: 3 test/staging/sm/extensions/new-cross-compartment.js: - default: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' - strict mode: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/staging/sm/fields/await-identifier-module-2.js: - module: 'Test262: This statement should not be evaluated.' + module: 3 test/staging/sm/generators/delegating-yield-1.js: - default: 'Test262Error: Expected [Object {value: 1}, Object {value: 34, done: true}] to be structurally equal to [Object {value: 1, done: false}, Object {value: 34, done: true}]. ' - strict mode: 'Test262Error: Expected [Object {value: 1}, Object {value: 34, done: true}] to be structurally equal to [Object {value: 1, done: false}, Object {value: 34, done: true}]. ' + default: 3 + strict mode: 3 test/staging/sm/generators/delegating-yield-3.js: - default: 'Test262Error: Expected SameValue(«true», «undefined») to be true' - strict mode: 'Test262Error: Expected SameValue(«true», «undefined») to be true' + default: 3 + strict mode: 3 test/staging/sm/generators/delegating-yield-5.js: - default: 'Test262Error: Expected [Object {value: 1}, Object {value: 34, done: true}] to be structurally equal to [Object {value: 1, done: false}, Object {value: 34, done: true}]. ' - strict mode: 'Test262Error: Expected [Object {value: 1}, Object {value: 34, done: true}] to be structurally equal to [Object {value: 1, done: false}, Object {value: 34, done: true}]. ' + default: 3 + strict mode: 3 test/staging/sm/generators/delegating-yield-6.js: - default: 'Test262Error: Expected SameValue(«"indvndvndvndvndvndv"», «"indndndndndndv"») to be true' - strict mode: 'Test262Error: Expected SameValue(«"indvndvndvndvndvndv"», «"indndndndndndv"») to be true' + default: 3 + strict mode: 3 test/staging/sm/generators/delegating-yield-7.js: - default: 'Test262Error: Expected [Object {value: 1}, Object {value: 34, done: true}] to be structurally equal to [Object {value: 1, done: false}, Object {value: 34, done: true}]. ' - strict mode: 'Test262Error: Expected [Object {value: 1}, Object {value: 34, done: true}] to be structurally equal to [Object {value: 1, done: false}, Object {value: 34, done: true}]. ' + default: 3 + strict mode: 3 test/staging/sm/lexical-environment/block-scoped-functions-annex-b-label.js: - default: "TypeError: f1 is not a function. (In 'f1()', 'f1' is undefined)" + default: 3 test/staging/sm/lexical-environment/block-scoped-functions-deprecated-redecl.js: - default: 'Test262Error: Expected SameValue(«3», «4») to be true' + default: 3 test/staging/sm/lexical-environment/for-loop.js: - default: 'Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all' - strict mode: 'Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all' + default: 3 + strict mode: 3 test/staging/sm/misc/future-reserved-words.js: - default: 'Test262Error: implements: function argument retroactively strict Expected a SyntaxError to be thrown but no exception was thrown at all' + default: 3 test/staging/sm/module/await-restricted-nested.js: - module: 'Test262: This statement should not be evaluated.' + module: 3 diff --git a/JSTests/test262/expectations.yaml b/JSTests/test262/expectations.yaml index 3a53a8145943f..e06eabf3ddd43 100644 --- a/JSTests/test262/expectations.yaml +++ b/JSTests/test262/expectations.yaml @@ -1,223 +1,247 @@ +# Expected test262 failures. Generated by Tools/Scripts/test262-runner --save. +# +# Each entry maps a test file to the modes it is expected to fail in ("default", +# "strict mode", "module" or "raw"), and each mode to the exit code jsc is +# expected to terminate with: +# +# 3 The test ran to completion and failed: an assertion in the test or +# harness failed, or the test threw an uncaught exception. This is +# jsc's EXIT_EXCEPTION. +# 128 + N jsc was killed by signal N, i.e. it crashed. Common values are 134 +# (SIGABRT, which includes assertion failures in debug builds), 138 +# (SIGBUS) and 139 (SIGSEGV). +# 1 jsc could not run the test at all, or the runner timed out waiting +# for it. +# +# A failing test counts as expected only if it fails with the exit code recorded +# here, so a test that starts crashing where it previously failed an assertion +# is still reported as a new failure. --- test/annexB/language/function-code/block-decl-func-skip-arguments.js: - default: 'Test262Error: Expected SameValue(«"function arguments() {}"», «"[object Arguments]"») to be true' + default: 3 test/built-ins/Function/internals/Construct/derived-return-val-realm.js: - default: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' - strict mode: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/built-ins/Function/internals/Construct/derived-this-uninitialized-realm.js: - default: 'Test262Error: Expected a ReferenceError but got a different error constructor with the same name' - strict mode: 'Test262Error: Expected a ReferenceError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/built-ins/Function/prototype/arguments/prop-desc.js: - default: 'Test262Error: Function.prototype.arguments property getter/setter are the same function Expected SameValue(«function arguments() {' - strict mode: 'Test262Error: Function.prototype.arguments property getter/setter are the same function Expected SameValue(«function arguments() {' + default: 3 + strict mode: 3 test/built-ins/Function/prototype/caller-arguments/accessor-properties.js: - default: 'Test262Error: Function.prototype.arguments and Function.prototype.caller accessor functions should match (%ThrowTypeError%) Expected SameValue(«function caller() {' - strict mode: 'Test262Error: Function.prototype.arguments and Function.prototype.caller accessor functions should match (%ThrowTypeError%) Expected SameValue(«function caller() {' + default: 3 + strict mode: 3 test/built-ins/Function/prototype/caller/prop-desc.js: - default: 'Test262Error: Caller property getter/setter are the same function Expected SameValue(«function caller() {' - strict mode: 'Test262Error: Caller property getter/setter are the same function Expected SameValue(«function caller() {' + default: 3 + strict mode: 3 test/built-ins/Function/prototype/toString/built-in-function-object.js: - default: 'Test262Error: Conforms to NativeFunction Syntax: "function $*() {\n [native code]\n}" (%RegExp%.$*)' - strict mode: 'Test262Error: Conforms to NativeFunction Syntax: "function $*() {\n [native code]\n}" (%RegExp%.$*)' + default: 3 + strict mode: 3 test/built-ins/Proxy/apply/arguments-realm.js: - default: 'Test262Error: Expected SameValue(«function Array() {' - strict mode: 'Test262Error: Expected SameValue(«function Array() {' + default: 3 + strict mode: 3 test/built-ins/Proxy/apply/null-handler-realm.js: - default: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' - strict mode: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/built-ins/Proxy/apply/trap-is-not-callable-realm.js: - default: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' - strict mode: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/built-ins/Proxy/construct/arguments-realm.js: - default: 'Test262Error: Expected SameValue(«function Array() {' - strict mode: 'Test262Error: Expected SameValue(«function Array() {' + default: 3 + strict mode: 3 test/built-ins/Proxy/construct/trap-is-not-callable-realm.js: - default: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' - strict mode: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-offset.js: - default: 'Test262Error: Actual [20, 30, 40, 60] and expected [20, 20, 20, 60] should have the same contents. (Testing with Float64Array and makePassthrough.)' - strict mode: 'Test262Error: Actual [20, 30, 40, 60] and expected [20, 20, 20, 60] should have the same contents. (Testing with Float64Array and makePassthrough.)' + default: 3 + strict mode: 3 test/built-ins/TypedArrayConstructors/ctors/object-arg/iterated-array-changed-by-tonumber.js: - default: 'Test262Error: Expected SameValue(«NaN», «2») to be true (Testing with Float64Array and makePassthrough.)' - strict mode: 'Test262Error: Expected SameValue(«NaN», «2») to be true (Testing with Float64Array and makePassthrough.)' + default: 3 + strict mode: 3 test/built-ins/Uint8Array/prototype/setFromBase64/trailing-garbage-empty.js: - default: 'SyntaxError: Uint8Array.prototype.setFromBase64 requires a valid base64 string' - strict mode: 'SyntaxError: Uint8Array.prototype.setFromBase64 requires a valid base64 string' + default: 3 + strict mode: 3 test/built-ins/Uint8Array/prototype/setFromBase64/trailing-garbage.js: - default: 'SyntaxError: Uint8Array.prototype.setFromBase64 requires a valid base64 string' - strict mode: 'SyntaxError: Uint8Array.prototype.setFromBase64 requires a valid base64 string' + default: 3 + strict mode: 3 +test/intl402/Locale/prototype/getHourCycles/region-priority.js: + default: 3 + strict mode: 3 +test/intl402/Locale/prototype/getHourCycles/subdivision-region.js: + default: 3 + strict mode: 3 test/language/destructuring/binding/keyed-destructuring-property-reference-target-evaluation-order-with-bindings.js: - default: 'Test262Error: Actual [binding::source, binding::sourceKey, sourceKey, get source, binding::defaultValue, binding::varTarget] and expected [binding::source, binding::sourceKey, sourceKey, binding::varTarget, get source, binding::defaultValue] should have the same contents. ' + default: 3 test/language/eval-code/direct/arrow-fn-body-cntns-arguments-func-decl-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-body-cntns-arguments-func-decl-arrow-func-declare-arguments-assign.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-body-cntns-arguments-lex-bind-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-body-cntns-arguments-lex-bind-arrow-func-declare-arguments-assign.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-body-cntns-arguments-var-bind-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-body-cntns-arguments-var-bind-arrow-func-declare-arguments-assign.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-no-pre-existing-arguments-bindings-are-present-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/eval-code/direct/arrow-fn-no-pre-existing-arguments-bindings-are-present-arrow-func-declare-arguments-assign.js: - default: 'Test262Error: globalThis.arguments unchanged Expected SameValue(«"param"», «undefined») to be true' + default: 3 test/language/expressions/assignment/fn-name-lhs-cover.js: - default: 'Test262Error: name descriptor value should be ; name value should be ' - strict mode: 'Test262Error: name descriptor value should be ; name value should be ' + default: 3 + strict mode: 3 test/language/expressions/call/tco-non-eval-function-dynamic.js: - default: 'RangeError: Maximum call stack size exceeded.' + default: 3 test/language/expressions/call/tco-non-eval-function.js: - default: 'RangeError: Maximum call stack size exceeded.' + default: 3 test/language/expressions/call/tco-non-eval-global.js: - default: 'RangeError: Maximum call stack size exceeded.' + default: 3 test/language/expressions/call/tco-non-eval-with.js: - default: 'RangeError: Maximum call stack size exceeded.' + default: 3 test/language/expressions/delete/super-property-uninitialized-this.js: - default: 'Test262Error: Expected a ReferenceError but got a Test262Error' - strict mode: 'Test262Error: Expected a ReferenceError but got a Test262Error' + default: 3 + strict mode: 3 test/language/expressions/dynamic-import/import-attributes/2nd-param-with-type-text.js: - default: 'Test262:AsyncTestFailure:TypeError: Import attribute type "text" is not valid' - strict mode: 'Test262:AsyncTestFailure:TypeError: Import attribute type "text" is not valid' + default: 3 + strict mode: 3 test/language/expressions/new/non-ctor-err-realm.js: - default: 'Test262Error: production including Arguments Expected a TypeError but got a different error constructor with the same name' - strict mode: 'Test262Error: production including Arguments Expected a TypeError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/language/expressions/object/computed-property-name-topropertykey-before-value-evaluation.js: - default: 'Test262Error: Expected SameValue(«"bad"», «"ok"») to be true' - strict mode: 'Test262Error: Expected SameValue(«"bad"», «"ok"») to be true' + default: 3 + strict mode: 3 test/language/expressions/super/prop-expr-uninitialized-this-putvalue-compound-assign.js: - default: 'Test262Error: Expected a ReferenceError but got a Test262Error' - strict mode: 'Test262Error: Expected a ReferenceError but got a Test262Error' + default: 3 + strict mode: 3 test/language/expressions/super/prop-expr-uninitialized-this-putvalue-increment.js: - default: 'Test262Error: Expected a ReferenceError but got a Test262Error' - strict mode: 'Test262Error: Expected a ReferenceError but got a Test262Error' + default: 3 + strict mode: 3 test/language/expressions/super/prop-expr-uninitialized-this-putvalue.js: - default: 'Test262Error: Expected a ReferenceError but got a Test262Error' - strict mode: 'Test262Error: Expected a ReferenceError but got a Test262Error' + default: 3 + strict mode: 3 test/language/expressions/yield/star-iterable.js: - default: 'Test262Error: First result `done` flag Expected SameValue(«false», «undefined») to be true' - strict mode: 'Test262Error: First result `done` flag Expected SameValue(«false», «undefined») to be true' + default: 3 + strict mode: 3 test/language/expressions/yield/star-rhs-iter-nrml-res-done-no-value.js: - default: 'Test262Error: access count (first iteration) Expected SameValue(«1», «0») to be true' - strict mode: 'Test262Error: access count (first iteration) Expected SameValue(«1», «0») to be true' + default: 3 + strict mode: 3 test/language/expressions/yield/star-rhs-iter-rtrn-res-done-no-value.js: - default: 'Test262Error: access count (second iteration) Expected SameValue(«1», «0») to be true' - strict mode: 'Test262Error: access count (second iteration) Expected SameValue(«1», «0») to be true' + default: 3 + strict mode: 3 test/language/expressions/yield/star-rhs-iter-thrw-res-done-no-value.js: - default: 'Test262Error: access count (second iteration) Expected SameValue(«1», «0») to be true' - strict mode: 'Test262Error: access count (second iteration) Expected SameValue(«1», «0») to be true' + default: 3 + strict mode: 3 test/language/identifier-resolution/assign-to-global-undefined.js: - strict mode: Expected uncaught exception with name 'ReferenceError' but none was thrown + strict mode: 3 test/language/import/import-attributes/text-empty.js: - module: 'TypeError: Import attribute type "text" is not valid' + module: 3 test/language/import/import-attributes/text-javascript.js: - module: 'TypeError: Import attribute type "text" is not valid' + module: 3 test/language/import/import-attributes/text-self.js: - module: 'TypeError: Import attribute type "text" is not valid' + module: 3 test/language/import/import-attributes/text-string.js: - module: 'TypeError: Import attribute type "text" is not valid' + module: 3 test/language/import/import-attributes/text-via-namespace.js: - module: 'TypeError: Import attribute type "text" is not valid' + module: 3 test/language/statements/class/elements/private-class-field-on-nonextensible-objects.js: - strict mode: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' + strict mode: 3 test/language/statements/class/subclass/private-class-field-on-nonextensible-return-override.js: - strict mode: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' + strict mode: 3 test/language/statements/for-await-of/head-lhs-async.js: - default: "SyntaxError: Unexpected identifier 'of'" - strict mode: "SyntaxError: Unexpected identifier 'of'" + default: 3 + strict mode: 3 test/language/statements/for-in/head-lhs-let.js: - default: "SyntaxError: Cannot use the keyword 'in' as a lexical variable name." + default: 3 test/language/statements/for-in/identifier-let-allowed-as-lefthandside-expression-not-strict.js: - default: "SyntaxError: Cannot use the keyword 'in' as a lexical variable name." + default: 3 test/language/statements/for/head-lhs-let.js: - default: "SyntaxError: Unexpected token ';'. Expected a parameter pattern or a ')' in parameter list." + default: 3 test/language/statements/with/get-binding-value-call-with-proxy-env.js: - default: 'Test262Error: Actual [has:Object, get:Symbol(Symbol.unscopables), get:Object] and expected [has:Object, get:Symbol(Symbol.unscopables), has:Object, get:Object] should have the same contents. ' + default: 3 test/language/statements/with/get-binding-value-idref-with-proxy-env.js: - default: 'Test262Error: Actual [has:Object, get:Symbol(Symbol.unscopables), get:Object] and expected [has:Object, get:Symbol(Symbol.unscopables), has:Object, get:Object] should have the same contents. ' + default: 3 test/language/statements/with/get-mutable-binding-binding-deleted-in-get-unscopables.js: - default: "ReferenceError: Can't find variable: binding" + default: 3 test/language/statements/with/set-mutable-binding-idref-compound-assign-with-proxy-env.js: - default: 'Test262Error: Actual [has:p, get:Symbol(Symbol.unscopables), get:p, has:p, set:p, getOwnPropertyDescriptor:p, defineProperty:p] and expected [has:p, get:Symbol(Symbol.unscopables), has:p, get:p, has:p, set:p, getOwnPropertyDescriptor:p, defineProperty:p] should have the same contents. ' + default: 3 test/staging/sm/ArrayBuffer/slice-species.js: - default: 'Test262Error: Expected SameValue(«function ArrayBuffer() {' - strict mode: 'Test262Error: Expected SameValue(«function ArrayBuffer() {' + default: 3 + strict mode: 3 test/staging/sm/Date/two-digit-years.js: - default: 'Test262Error: Expected SameValue(«NaN», «957164400000») to be true' - strict mode: 'Test262Error: Expected SameValue(«NaN», «957164400000») to be true' + default: 3 + strict mode: 3 test/staging/sm/Function/arguments-parameter-shadowing.js: - default: 'Test262Error: Expected SameValue(«true», «false») to be true' + default: 3 test/staging/sm/Function/function-name-assignment.js: - default: 'Test262Error: Expected SameValue(«"inParen"», «""») to be true' + default: 3 test/staging/sm/Function/function-toString-builtin-name.js: - default: 'Test262Error: Incorrect match for undefined Expected SameValue(«"fn"», «undefined») to be true' - strict mode: 'Test262Error: Incorrect match for undefined Expected SameValue(«"fn"», «undefined») to be true' + default: 3 + strict mode: 3 test/staging/sm/PrivateName/modify-non-extensible.js: - default: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' + default: 3 test/staging/sm/Proxy/revoked-get-function-realm-typeerror.js: - default: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' - strict mode: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' + default: 3 + strict mode: 3 test/staging/sm/RegExp/replace-sticky-lastIndex.js: - default: 'Test262Error: Expected SameValue(«"b"», «"a"») to be true' - strict mode: 'Test262Error: Expected SameValue(«"b"», «"a"») to be true' + default: 3 + strict mode: 3 test/staging/sm/RegExp/replace-sticky.js: - default: 'Test262Error: Expected SameValue(«"ABCDEabcdeabcdefghij"», «"abcdeABCDEabcdefghij"») to be true' - strict mode: 'Test262Error: Expected SameValue(«"ABCDEabcdeabcdefghij"», «"abcdeABCDEabcdefghij"») to be true' + default: 3 + strict mode: 3 test/staging/sm/RegExp/unicode-braced.js: - default: 'SyntaxError: Invalid regular expression: regular expression too large' - strict mode: 'SyntaxError: Invalid regular expression: regular expression too large' + default: 3 + strict mode: 3 test/staging/sm/RegExp/unicode-class-braced.js: - default: 'SyntaxError: Invalid regular expression: regular expression too large' - strict mode: 'SyntaxError: Invalid regular expression: regular expression too large' + default: 3 + strict mode: 3 test/staging/sm/TypedArray/slice-memcpy.js: - default: 'Test262Error: Actual [1, 2, 1, 2, 3, 4] and expected [1, 2, 1, 2, 1, 2] should have the same contents. ' - strict mode: 'Test262Error: Actual [1, 2, 1, 2, 3, 4] and expected [1, 2, 1, 2, 1, 2] should have the same contents. ' + default: 3 + strict mode: 3 test/staging/sm/class/superPropOrdering.js: - default: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' - strict mode: 'Test262Error: Expected a TypeError to be thrown but no exception was thrown at all' + default: 3 + strict mode: 3 test/staging/sm/eval/redeclared-arguments-in-param-expression-eval.js: - default: 'Test262Error: Expected SameValue(«true», «false») to be true' + default: 3 test/staging/sm/expressions/exponentiation-unparenthesised-unary.js: - default: 'Test262Error: AsyncFunction:await a ** 0 Expected a SyntaxError to be thrown but no exception was thrown at all' - strict mode: 'Test262Error: AsyncFunction:await a ** 0 Expected a SyntaxError to be thrown but no exception was thrown at all' + default: 3 + strict mode: 3 test/staging/sm/expressions/object-literal-computed-property-evaluation.js: - default: 'Test262Error: Expected SameValue(«undefined», «"abc"») to be true' - strict mode: 'Test262Error: Expected SameValue(«undefined», «"abc"») to be true' + default: 3 + strict mode: 3 test/staging/sm/expressions/short-circuit-compound-assignment-anon-fns.js: - default: 'Test262Error: Expected SameValue(«"a"», «""») to be true' - strict mode: 'Test262Error: Expected SameValue(«"a"», «""») to be true' + default: 3 + strict mode: 3 test/staging/sm/extensions/function-caller-skips-eval-frames.js: - default: 'Test262Error: Expected SameValue(«null», «function nest() { return eval("innermost();"); }») to be true' + default: 3 test/staging/sm/extensions/new-cross-compartment.js: - default: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' - strict mode: 'Test262Error: Expected a TypeError but got a different error constructor with the same name' + default: 3 + strict mode: 3 test/staging/sm/fields/await-identifier-module-2.js: - module: 'Test262: This statement should not be evaluated.' + module: 3 test/staging/sm/generators/delegating-yield-1.js: - default: 'Test262Error: Expected [Object {value: 1}, Object {value: 34, done: true}] to be structurally equal to [Object {value: 1, done: false}, Object {value: 34, done: true}]. ' - strict mode: 'Test262Error: Expected [Object {value: 1}, Object {value: 34, done: true}] to be structurally equal to [Object {value: 1, done: false}, Object {value: 34, done: true}]. ' + default: 3 + strict mode: 3 test/staging/sm/generators/delegating-yield-3.js: - default: 'Test262Error: Expected SameValue(«true», «undefined») to be true' - strict mode: 'Test262Error: Expected SameValue(«true», «undefined») to be true' + default: 3 + strict mode: 3 test/staging/sm/generators/delegating-yield-5.js: - default: 'Test262Error: Expected [Object {value: 1}, Object {value: 34, done: true}] to be structurally equal to [Object {value: 1, done: false}, Object {value: 34, done: true}]. ' - strict mode: 'Test262Error: Expected [Object {value: 1}, Object {value: 34, done: true}] to be structurally equal to [Object {value: 1, done: false}, Object {value: 34, done: true}]. ' + default: 3 + strict mode: 3 test/staging/sm/generators/delegating-yield-6.js: - default: 'Test262Error: Expected SameValue(«"indvndvndvndvndvndv"», «"indndndndndndv"») to be true' - strict mode: 'Test262Error: Expected SameValue(«"indvndvndvndvndvndv"», «"indndndndndndv"») to be true' + default: 3 + strict mode: 3 test/staging/sm/generators/delegating-yield-7.js: - default: 'Test262Error: Expected [Object {value: 1}, Object {value: 34, done: true}] to be structurally equal to [Object {value: 1, done: false}, Object {value: 34, done: true}]. ' - strict mode: 'Test262Error: Expected [Object {value: 1}, Object {value: 34, done: true}] to be structurally equal to [Object {value: 1, done: false}, Object {value: 34, done: true}]. ' + default: 3 + strict mode: 3 test/staging/sm/lexical-environment/block-scoped-functions-annex-b-label.js: - default: "TypeError: f1 is not a function. (In 'f1()', 'f1' is undefined)" + default: 3 test/staging/sm/lexical-environment/block-scoped-functions-deprecated-redecl.js: - default: 'Test262Error: Expected SameValue(«3», «4») to be true' + default: 3 test/staging/sm/lexical-environment/for-loop.js: - default: 'Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all' - strict mode: 'Test262Error: Expected a SyntaxError to be thrown but no exception was thrown at all' + default: 3 + strict mode: 3 test/staging/sm/misc/future-reserved-words.js: - default: 'Test262Error: implements: function argument retroactively strict Expected a SyntaxError to be thrown but no exception was thrown at all' + default: 3 test/staging/sm/module/await-restricted-nested.js: - module: 'Test262: This statement should not be evaluated.' + module: 3 diff --git a/JSTests/test262/harness/assert.js b/JSTests/test262/harness/assert.js index b4827d2f5ac09..55a0e2a32f2e6 100644 --- a/JSTests/test262/harness/assert.js +++ b/JSTests/test262/harness/assert.js @@ -5,6 +5,7 @@ description: | Collection of assertion functions used throughout test262 defines: - assert + - compareArray - formatIdentityFreeValue - formatSimpleValue - isNegativeZero diff --git a/JSTests/test262/harness/compareArray.js b/JSTests/test262/harness/compareArray.js index dfeef3fe6bee7..fa5eae5f4c7c6 100644 --- a/JSTests/test262/harness/compareArray.js +++ b/JSTests/test262/harness/compareArray.js @@ -3,5 +3,5 @@ /*--- description: | Deprecated now that compareArray is defined in assert.js. -defines: [compareArray] +allow_unused: true ---*/ diff --git a/JSTests/test262/latest-changes-summary.txt b/JSTests/test262/latest-changes-summary.txt index 82c174c3bb9d3..8635df7d42c87 100644 --- a/JSTests/test262/latest-changes-summary.txt +++ b/JSTests/test262/latest-changes-summary.txt @@ -1,132 +1,243 @@ -M harness/asyncHelpers.js -A harness/testIntlNumberFormat.js -M test/built-ins/AsyncIteratorPrototype/Symbol.asyncDispose/invokes-return.js -D test/built-ins/Atomics/pause/non-integral-iterationnumber-throws.js -M test/built-ins/Atomics/pause/returns-undefined.js -M test/built-ins/Iterator/prototype/Symbol.dispose/invokes-return.js -A test/built-ins/Promise/allKeyed/capability-executor-not-callable.js -A test/built-ins/Promise/allKeyed/capability-resolve-throws-reject.js -A test/built-ins/Promise/allKeyed/ctx-ctor-constructed.js -A test/built-ins/Promise/allKeyed/ctx-ctor-throws.js -A test/built-ins/Promise/allKeyed/get-value-not-called-for-non-enumerable.js -A test/built-ins/Promise/allKeyed/get-value-throws-reject.js -A test/built-ins/Promise/allKeyed/getownproperty-not-enumerable.js -A test/built-ins/Promise/allKeyed/getownproperty-returns-undefined.js -A test/built-ins/Promise/allKeyed/getownproperty-throws.js -A test/built-ins/Promise/allKeyed/invoke-resolve-custom.js -A test/built-ins/Promise/allKeyed/invoke-resolve-error-reject.js -A test/built-ins/Promise/allKeyed/invoke-resolve-get-error-reject.js -A test/built-ins/Promise/allKeyed/invoke-resolve-get-once.js -A test/built-ins/Promise/allKeyed/invoke-resolve-return.js -A test/built-ins/Promise/allKeyed/invoke-then-error-reject.js -A test/built-ins/Promise/allKeyed/invoke-then-get-error-reject.js -A test/built-ins/Promise/allKeyed/invoke-then-not-callable-reject.js -A test/built-ins/Promise/allKeyed/non-enumerable-properties-only.js -A test/built-ins/Promise/allKeyed/ownkeys-throws.js -A test/built-ins/Promise/allKeyed/reject-first.js -A test/built-ins/Promise/allKeyed/reject-last.js -A test/built-ins/Promise/allKeyed/reject-second.js -A test/built-ins/Promise/allKeyed/resolve-before-loop-exit.js -A test/built-ins/Promise/allKeyed/resolve-element-function-properties.js -A test/built-ins/Promise/allKeyed/resolve-from-same-thenable.js -A test/built-ins/Promise/allKeyed/resolve-missing-reject-with-typeerror.js -A test/built-ins/Promise/allKeyed/result-property-descriptors.js -A test/built-ins/Promise/allSettledKeyed/capability-executor-not-callable.js -A test/built-ins/Promise/allSettledKeyed/capability-resolve-throws-reject.js -A test/built-ins/Promise/allSettledKeyed/ctx-ctor-constructed.js -A test/built-ins/Promise/allSettledKeyed/ctx-ctor-throws.js -A test/built-ins/Promise/allSettledKeyed/element-function-properties.js -A test/built-ins/Promise/allSettledKeyed/get-value-not-called-for-non-enumerable.js -A test/built-ins/Promise/allSettledKeyed/get-value-throws-reject.js -A test/built-ins/Promise/allSettledKeyed/getownproperty-not-enumerable.js -A test/built-ins/Promise/allSettledKeyed/getownproperty-returns-undefined.js -A test/built-ins/Promise/allSettledKeyed/getownproperty-throws.js -A test/built-ins/Promise/allSettledKeyed/invoke-resolve-custom.js -A test/built-ins/Promise/allSettledKeyed/invoke-resolve-error-reject.js -A test/built-ins/Promise/allSettledKeyed/invoke-resolve-get-error-reject.js -A test/built-ins/Promise/allSettledKeyed/invoke-resolve-get-once.js -A test/built-ins/Promise/allSettledKeyed/invoke-resolve-return.js -A test/built-ins/Promise/allSettledKeyed/invoke-then-error-reject.js -A test/built-ins/Promise/allSettledKeyed/invoke-then-get-error-reject.js -A test/built-ins/Promise/allSettledKeyed/invoke-then-not-callable-reject.js -A test/built-ins/Promise/allSettledKeyed/non-enumerable-properties-only.js -A test/built-ins/Promise/allSettledKeyed/ownkeys-throws.js -A test/built-ins/Promise/allSettledKeyed/reject-from-same-thenable.js -A test/built-ins/Promise/allSettledKeyed/resolve-before-loop-exit.js -A test/built-ins/Promise/allSettledKeyed/resolve-from-same-thenable.js -A test/built-ins/Promise/allSettledKeyed/resolve-missing-reject-with-typeerror.js -A test/built-ins/Promise/allSettledKeyed/result-property-descriptors.js -M test/intl402/Locale/constructor-apply-options-canonicalizes-twice.js -M test/intl402/Locale/likely-subtags-grandfathered.js -M test/intl402/Locale/prototype/calendar/canonicalize.js -M test/intl402/NumberFormat/prototype/format/unit-ja-JP.js -M test/intl402/NumberFormat/prototype/format/unit-zh-TW.js -M test/intl402/NumberFormat/prototype/formatToParts/unit-ja-JP.js -M test/intl402/NumberFormat/prototype/formatToParts/unit-zh-TW.js -M test/intl402/Temporal/PlainMonthDay/prototype/toLocaleString/basic.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-arrow-assignment-expression-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-arrow-assignment-expression-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-arrow-assignment-expression-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-arrow-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-arrow-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-arrow-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-await-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-await-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-await-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-return-await-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-return-await-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-arrow-function-return-await-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-await-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-await-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-await-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-return-await-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-return-await-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-function-return-await-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-gen-await-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-gen-await-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-async-gen-await-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-block-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-block-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-block-labeled-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-block-labeled-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-block-labeled-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-block-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-do-while-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-do-while-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-do-while-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-else-braceless-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-else-braceless-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-else-braceless-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-else-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-else-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-else-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-function-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-function-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-function-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-function-return-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-function-return-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-function-return-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-if-braceless-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-if-braceless-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-if-braceless-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-if-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-if-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-if-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-while-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-while-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-while-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-with-expression-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-with-expression-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-with-expression-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-with-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-with-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/nested-with-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/top-level-import-defer-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/top-level-import-source-no-new-call-expression-prop-access.js -A test/language/expressions/dynamic-import/syntax/invalid/top-level-no-new-call-expression-prop-access.js -D test/staging/sm/Error/constructor-proto.js -D test/staging/sm/Error/prototype-properties.js -D test/staging/sm/Error/prototype.js \ No newline at end of file +M harness/assert.js +M harness/compareArray.js +A test/built-ins/Array/prototype/Symbol.unscopables/at.js +A test/built-ins/Iterator/prototype/chunks/argument-effect-order.js +A test/built-ins/Iterator/prototype/chunks/argument-validation-failure-close-throws.js +A test/built-ins/Iterator/prototype/chunks/argument-validation-failure-closes-underlying.js +A test/built-ins/Iterator/prototype/chunks/callable.js +A test/built-ins/Iterator/prototype/chunks/chunkSize-no-coercion.js +A test/built-ins/Iterator/prototype/chunks/chunkSize-not-a-number.js +A test/built-ins/Iterator/prototype/chunks/chunkSize-out-of-range.js +A test/built-ins/Iterator/prototype/chunks/chunks-evenly-divisible.js +A test/built-ins/Iterator/prototype/chunks/chunks-last-chunk-partial.js +A test/built-ins/Iterator/prototype/chunks/chunks-size-1.js +A test/built-ins/Iterator/prototype/chunks/chunks-size-larger-than-iterator.js +A test/built-ins/Iterator/prototype/chunks/exhaustion-does-not-call-return.js +A test/built-ins/Iterator/prototype/chunks/get-next-method-only-once.js +A test/built-ins/Iterator/prototype/chunks/get-next-method-throws.js +A test/built-ins/Iterator/prototype/chunks/get-return-method-throws.js +A test/built-ins/Iterator/prototype/chunks/is-function.js +A test/built-ins/Iterator/prototype/chunks/iterator-already-exhausted.js +A test/built-ins/Iterator/prototype/chunks/iterator-return-method-throws.js +A test/built-ins/Iterator/prototype/chunks/length.js +A test/built-ins/Iterator/prototype/chunks/name.js +A test/built-ins/Iterator/prototype/chunks/next-method-returns-non-object.js +A test/built-ins/Iterator/prototype/chunks/next-method-returns-throwing-done.js +A test/built-ins/Iterator/prototype/chunks/next-method-returns-throwing-value-done.js +A test/built-ins/Iterator/prototype/chunks/next-method-returns-throwing-value.js +A test/built-ins/Iterator/prototype/chunks/next-method-throws.js +A test/built-ins/Iterator/prototype/chunks/non-constructible.js +A test/built-ins/Iterator/prototype/chunks/prop-desc.js +A test/built-ins/Iterator/prototype/chunks/proto.js +A test/built-ins/Iterator/prototype/chunks/result-is-iterator.js +A test/built-ins/Iterator/prototype/chunks/return-is-forwarded-to-underlying-iterator.js +A test/built-ins/Iterator/prototype/chunks/return-is-not-forwarded-after-exhaustion.js +A test/built-ins/Iterator/prototype/chunks/this-non-callable-next.js +A test/built-ins/Iterator/prototype/chunks/this-non-object.js +A test/built-ins/Iterator/prototype/chunks/this-plain-iterator.js +A test/built-ins/Iterator/prototype/chunks/throws-typeerror-when-generator-is-running.js +A test/built-ins/Iterator/prototype/chunks/underlying-iterator-advanced-in-parallel.js +A test/built-ins/Iterator/prototype/chunks/underlying-iterator-closed-in-parallel.js +A test/built-ins/Iterator/prototype/chunks/yields-distinct-arrays.js +M test/built-ins/Iterator/prototype/drop/argument-effect-order.js +M test/built-ins/Iterator/prototype/drop/argument-validation-failure-closes-underlying.js +M test/built-ins/Iterator/prototype/drop/limit-rangeerror.js +A test/built-ins/Iterator/prototype/includes/argument-effect-order.js +A test/built-ins/Iterator/prototype/includes/argument-validation-failure-closes-underlying.js +A test/built-ins/Iterator/prototype/includes/basic-match-and-miss.js +A test/built-ins/Iterator/prototype/includes/callable.js +A test/built-ins/Iterator/prototype/includes/closes-on-match.js +A test/built-ins/Iterator/prototype/includes/exhaustion-does-not-call-return.js +A test/built-ins/Iterator/prototype/includes/get-next-method-only-once.js +A test/built-ins/Iterator/prototype/includes/get-next-method-throws.js +A test/built-ins/Iterator/prototype/includes/get-return-method-throws.js +A test/built-ins/Iterator/prototype/includes/infinite-iterator.js +A test/built-ins/Iterator/prototype/includes/is-function.js +A test/built-ins/Iterator/prototype/includes/iterator-already-exhausted.js +A test/built-ins/Iterator/prototype/includes/iterator-has-no-return.js +A test/built-ins/Iterator/prototype/includes/iterator-return-method-throws.js +A test/built-ins/Iterator/prototype/includes/length.js +A test/built-ins/Iterator/prototype/includes/name.js +A test/built-ins/Iterator/prototype/includes/next-method-returns-non-object.js +A test/built-ins/Iterator/prototype/includes/next-method-returns-throwing-done.js +A test/built-ins/Iterator/prototype/includes/next-method-returns-throwing-value-done.js +A test/built-ins/Iterator/prototype/includes/next-method-returns-throwing-value.js +A test/built-ins/Iterator/prototype/includes/next-method-throws.js +A test/built-ins/Iterator/prototype/includes/non-constructible.js +A test/built-ins/Iterator/prototype/includes/object-identity.js +A test/built-ins/Iterator/prototype/includes/prop-desc.js +A test/built-ins/Iterator/prototype/includes/proto.js +A test/built-ins/Iterator/prototype/includes/result-is-boolean.js +A test/built-ins/Iterator/prototype/includes/samevaluezero-nan.js +A test/built-ins/Iterator/prototype/includes/samevaluezero-zeroes.js +A test/built-ins/Iterator/prototype/includes/skipped-elements-default.js +A test/built-ins/Iterator/prototype/includes/skipped-elements-max-safe-integer.js +A test/built-ins/Iterator/prototype/includes/skipped-elements-nan-typeerror.js +A test/built-ins/Iterator/prototype/includes/skipped-elements-negative-infinity-rangeerror.js +A test/built-ins/Iterator/prototype/includes/skipped-elements-negative-integral-rangeerror.js +A test/built-ins/Iterator/prototype/includes/skipped-elements-no-coercion.js +A test/built-ins/Iterator/prototype/includes/skipped-elements-non-integral-typeerror.js +A test/built-ins/Iterator/prototype/includes/skipped-elements-not-a-number.js +A test/built-ins/Iterator/prototype/includes/skipped-elements-positive-infinity.js +A test/built-ins/Iterator/prototype/includes/skipped-elements-positive-integral.js +A test/built-ins/Iterator/prototype/includes/skipped-elements-too-large-rangeerror.js +A test/built-ins/Iterator/prototype/includes/skipped-elements-zero-and-negative-zero.js +A test/built-ins/Iterator/prototype/includes/symbol-identity.js +A test/built-ins/Iterator/prototype/includes/this-non-callable-next.js +A test/built-ins/Iterator/prototype/includes/this-non-object.js +A test/built-ins/Iterator/prototype/includes/this-plain-iterator.js +A test/built-ins/Iterator/prototype/join/closes-on-contents-coercion-exception.js +A test/built-ins/Iterator/prototype/join/closes-on-separator-coercion-exception.js +A test/built-ins/Iterator/prototype/join/contents-nullish.js +A test/built-ins/Iterator/prototype/join/contents-tostring.js +A test/built-ins/Iterator/prototype/join/descriptor.js +A test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-error.js +A test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-exhaustion.js +A test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-protocol-violation.js +A test/built-ins/Iterator/prototype/join/does-not-close-on-next-getter-error.js +A test/built-ins/Iterator/prototype/join/length.js +A test/built-ins/Iterator/prototype/join/name.js +A test/built-ins/Iterator/prototype/join/next-lookup-after-separator-tostring.js +A test/built-ins/Iterator/prototype/join/not-a-constructor.js +A test/built-ins/Iterator/prototype/join/receiver-not-object.js +A test/built-ins/Iterator/prototype/join/results-empty-separator.js +A test/built-ins/Iterator/prototype/join/results-no-separator.js +A test/built-ins/Iterator/prototype/join/results-nonempty-separator.js +A test/built-ins/Iterator/prototype/join/separator-tostring.js +M test/built-ins/Iterator/prototype/take/argument-effect-order.js +M test/built-ins/Iterator/prototype/take/argument-validation-failure-closes-underlying.js +M test/built-ins/Iterator/prototype/take/limit-rangeerror.js +A test/built-ins/Iterator/prototype/windows/argument-effect-order.js +A test/built-ins/Iterator/prototype/windows/argument-validation-failure-close-throws.js +A test/built-ins/Iterator/prototype/windows/argument-validation-failure-closes-underlying.js +A test/built-ins/Iterator/prototype/windows/callable.js +A test/built-ins/Iterator/prototype/windows/exhaustion-does-not-call-return.js +A test/built-ins/Iterator/prototype/windows/get-next-method-only-once.js +A test/built-ins/Iterator/prototype/windows/get-next-method-throws.js +A test/built-ins/Iterator/prototype/windows/get-return-method-throws.js +A test/built-ins/Iterator/prototype/windows/is-function.js +A test/built-ins/Iterator/prototype/windows/iterator-already-exhausted.js +A test/built-ins/Iterator/prototype/windows/iterator-return-method-throws.js +A test/built-ins/Iterator/prototype/windows/length.js +A test/built-ins/Iterator/prototype/windows/name.js +A test/built-ins/Iterator/prototype/windows/next-method-returns-non-object.js +A test/built-ins/Iterator/prototype/windows/next-method-returns-throwing-done.js +A test/built-ins/Iterator/prototype/windows/next-method-returns-throwing-value-done.js +A test/built-ins/Iterator/prototype/windows/next-method-returns-throwing-value.js +A test/built-ins/Iterator/prototype/windows/next-method-throws.js +A test/built-ins/Iterator/prototype/windows/non-constructible.js +A test/built-ins/Iterator/prototype/windows/prop-desc.js +A test/built-ins/Iterator/prototype/windows/proto.js +A test/built-ins/Iterator/prototype/windows/result-is-iterator.js +A test/built-ins/Iterator/prototype/windows/return-is-forwarded-to-underlying-iterator.js +A test/built-ins/Iterator/prototype/windows/return-is-not-forwarded-after-exhaustion.js +A test/built-ins/Iterator/prototype/windows/this-non-callable-next.js +A test/built-ins/Iterator/prototype/windows/this-non-object.js +A test/built-ins/Iterator/prototype/windows/this-plain-iterator.js +A test/built-ins/Iterator/prototype/windows/throws-typeerror-when-generator-is-running.js +A test/built-ins/Iterator/prototype/windows/underlying-iterator-advanced-in-parallel.js +A test/built-ins/Iterator/prototype/windows/underlying-iterator-closed-in-parallel.js +A test/built-ins/Iterator/prototype/windows/undersized-default.js +A test/built-ins/Iterator/prototype/windows/undersized-invalid.js +A test/built-ins/Iterator/prototype/windows/windowSize-no-coercion.js +A test/built-ins/Iterator/prototype/windows/windowSize-not-a-number.js +A test/built-ins/Iterator/prototype/windows/windowSize-out-of-range.js +A test/built-ins/Iterator/prototype/windows/windows-allow-partial.js +A test/built-ins/Iterator/prototype/windows/windows-basic.js +A test/built-ins/Iterator/prototype/windows/windows-size-1.js +A test/built-ins/Iterator/prototype/windows/windows-size-3.js +A test/built-ins/Iterator/prototype/windows/yields-distinct-arrays.js +M test/built-ins/Object/freeze/15.2.3.9-1-1.js +M test/built-ins/Object/freeze/15.2.3.9-1-2.js +M test/built-ins/Object/freeze/15.2.3.9-1-3.js +M test/built-ins/Object/freeze/15.2.3.9-1-4.js +M test/built-ins/Object/freeze/15.2.3.9-1.js +M test/built-ins/Object/isExtensible/15.2.3.13-1-1.js +M test/built-ins/Object/isExtensible/15.2.3.13-1-2.js +M test/built-ins/Object/isExtensible/15.2.3.13-1-3.js +M test/built-ins/Object/isExtensible/15.2.3.13-1-4.js +M test/built-ins/Object/isExtensible/15.2.3.13-1.js +M test/built-ins/Object/isFrozen/15.2.3.12-1-1.js +M test/built-ins/Object/isFrozen/15.2.3.12-1-2.js +M test/built-ins/Object/isFrozen/15.2.3.12-1-3.js +M test/built-ins/Object/isFrozen/15.2.3.12-1-4.js +M test/built-ins/Object/isFrozen/15.2.3.12-1.js +M test/built-ins/Object/isSealed/15.2.3.11-1.js +M test/built-ins/Object/keys/15.2.3.14-1-1.js +M test/built-ins/Object/keys/15.2.3.14-1-2.js +M test/built-ins/Object/keys/15.2.3.14-1-3.js +M test/built-ins/Object/seal/seal-boolean-literal.js +M test/built-ins/Object/seal/seal-infinity.js +M test/built-ins/Object/seal/seal-nan.js +M test/built-ins/Object/seal/seal-null.js +M test/built-ins/Object/seal/seal-symbol.js +M test/built-ins/Object/seal/seal-undefined.js +M test/built-ins/Promise/allSettledKeyed/result-property-descriptors.js +R100 test/built-ins/Temporal/Duration/prototype/round/relativeTo-ignores-incorrect-properties.js test/built-ins/Temporal/Duration/prototype/round/relativeto-ignores-incorrect-properties.js +R100 test/built-ins/Temporal/Duration/prototype/round/relativeTo-required-properties.js test/built-ins/Temporal/Duration/prototype/round/relativeto-required-properties.js +R100 test/built-ins/Temporal/Duration/prototype/total/relativeTo-must-have-required-properties.js test/built-ins/Temporal/Duration/prototype/total/relativeto-must-have-required-properties.js +M test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-offset.js +M test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/BigInt/index-prop-desc.js +M test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/index-prop-desc.js +M test/built-ins/TypedArrayConstructors/internals/Set/BigInt/null-tobigint.js +M test/built-ins/TypedArrayConstructors/internals/Set/BigInt/number-tobigint.js +M test/built-ins/TypedArrayConstructors/internals/Set/BigInt/string-nan-tobigint.js +M test/built-ins/TypedArrayConstructors/internals/Set/BigInt/symbol-tobigint.js +M test/built-ins/TypedArrayConstructors/internals/Set/BigInt/tonumber-value-throws.js +M test/built-ins/TypedArrayConstructors/internals/Set/BigInt/undefined-tobigint.js +M test/built-ins/TypedArrayConstructors/internals/Set/bigint-tonumber.js +M test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-throws.js +A test/intl402/Locale/prototype/getCalendars/likely-subtags-region.js +A test/intl402/Locale/prototype/getCalendars/region-override.js +A test/intl402/Locale/prototype/getCalendars/region-priority.js +A test/intl402/Locale/prototype/getCalendars/subdivision-region.js +A test/intl402/Locale/prototype/getCollations/collation-keyword.js +A test/intl402/Locale/prototype/getCollations/output-array-sorted.js +M test/intl402/Locale/prototype/getCollations/output-array-values.js +M test/intl402/Locale/prototype/getCollations/output-array.js +A test/intl402/Locale/prototype/getCollations/und-language.js +A test/intl402/Locale/prototype/getHourCycles/language-priority.js +A test/intl402/Locale/prototype/getHourCycles/likely-subtags-region.js +A test/intl402/Locale/prototype/getHourCycles/region-override.js +A test/intl402/Locale/prototype/getHourCycles/region-priority.js +A test/intl402/Locale/prototype/getHourCycles/subdivision-region.js +A test/intl402/Locale/prototype/getWeekInfo/likely-subtags-region.js +A test/intl402/Locale/prototype/getWeekInfo/region-override.js +A test/intl402/Locale/prototype/getWeekInfo/region-priority.js +A test/intl402/Locale/prototype/getWeekInfo/subdivision-region.js +R069 test/language/expressions/assignment/dstr/array-rest-elision-invalid.js test/language/expressions/assignment/dstr/obj-rest-before-comma-invalid.js +M test/language/expressions/assignment/dstr/obj-rest-not-last-element-invalid.js +A test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-a_FIXTURE.js +A test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-b_FIXTURE.js +A test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-c_FIXTURE.js +A test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-main_FIXTURE.js +A test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-x_FIXTURE.js +A test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle.js +A test/language/import/import-defer/deferred-namespace-object/json-module.js +A test/language/import/import-defer/deferred-namespace-object/json-module_FIXTURE.json +A test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/a-tla_FIXTURE.js +A test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/b_FIXTURE.js +A test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/c_FIXTURE.js +A test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/d_FIXTURE.js +A test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/main.js +A test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/middle_FIXTURE.js +A test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/resolve-blocker_FIXTURE.js +A test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/setup_FIXTURE.js +A test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-imported-module.js +A test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-imported-module_FIXTURE.js +A test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-module.js +A test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-imported-module.js +A test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-imported-module_FIXTURE.js +A test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-module.js +R078 test/language/statements/for-in/dstr/array-rest-elision-invalid.js test/language/statements/for-in/dstr/obj-rest-before-comma-invalid.js +M test/language/statements/for-in/dstr/obj-rest-not-last-element-invalid.js +A test/language/statements/for-in/return-from-catch.js +A test/language/statements/for-in/return-from-finally.js +A test/language/statements/for-in/return-from-try.js +A test/language/statements/for-in/return.js +R078 test/language/statements/for-of/dstr/array-rest-elision-invalid.js test/language/statements/for-of/dstr/obj-rest-before-comma-invalid.js +M test/language/statements/for-of/dstr/obj-rest-not-last-element-invalid.js +A test/language/statements/using/initializer-disposed-at-end-of-imported-module.js +A test/language/statements/using/initializer-disposed-at-end-of-imported-module_FIXTURE.js +A test/language/statements/using/initializer-disposed-at-end-of-module.js +A test/staging/source-phase-imports/module-source-prototype-chain.js \ No newline at end of file diff --git a/JSTests/test262/test/built-ins/Array/prototype/Symbol.unscopables/at.js b/JSTests/test262/test/built-ins/Array/prototype/Symbol.unscopables/at.js new file mode 100644 index 0000000000000..f2ffb35b814cb --- /dev/null +++ b/JSTests/test262/test/built-ins/Array/prototype/Symbol.unscopables/at.js @@ -0,0 +1,25 @@ +// Copyright (C) 2026 Ojus Chugh. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-array.prototype-@@unscopables +description: > + Array.prototype[Symbol.unscopables].at is true +info: | + 22.1.3.32 Array.prototype [ @@unscopables ] + + ... + 2. Perform ! CreateDataPropertyOrThrow(unscopableList, "at", true). + ... + +includes: [propertyHelper.js] +features: [Symbol.unscopables, Array.prototype.at] +---*/ + +var unscopables = Array.prototype[Symbol.unscopables]; + +assert.sameValue(unscopables.at, true, '`at` property value'); +verifyProperty(unscopables, "at", { + writable: true, + enumerable: true, + configurable: true +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/argument-effect-order.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/argument-effect-order.js new file mode 100644 index 0000000000000..ec53bf4d6ee1d --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/argument-effect-order.js @@ -0,0 +1,60 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Arguments and this value are validated in the correct order +info: | + Iterator.prototype.chunks ( chunkSize ) + + 1. Let O be the this value. + 2. If O is not an Object, throw a TypeError exception. + 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + 4. If chunkSize is not a Number, throw a TypeError ... IteratorClose. + 5. If chunkSize is not an integral Number, throw a TypeError ... IteratorClose. + 6. If chunkSize is not in the inclusive interval from 1𝔽 to 𝔽(2^32 - 1), then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + 7. Set iterated to ? GetIteratorDirect(O). + +includes: [compareArray.js] +features: [iterator-chunking] +---*/ +let effects = []; + +// TypeError for non-object this before chunkSize is examined +assert.throws(TypeError, function () { + Iterator.prototype.chunks.call(null, 0); +}); + +// RangeError for invalid chunkSize before next is accessed +assert.throws(RangeError, function () { + Iterator.prototype.chunks.call( + { + get next() { + effects.push('get next'); + return function () { + return { done: true, value: undefined }; + }; + } + }, + 0 + ); +}); + +assert.compareArray(effects, []); + +// With valid args, next getter IS accessed (GetIteratorDirect runs) +Iterator.prototype.chunks.call( + { + get next() { + effects.push('get next'); + return function () { + return { done: true, value: undefined }; + }; + } + }, + 1 +); + +assert.compareArray(effects, ['get next']); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/argument-validation-failure-close-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/argument-validation-failure-close-throws.js new file mode 100644 index 0000000000000..4fb167dd9a650 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/argument-validation-failure-close-throws.js @@ -0,0 +1,36 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Original validation error is preserved when closing the underlying iterator + throws +info: | + Iterator.prototype.chunks ( chunkSize ) + + 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + 4. If chunkSize is not a Number, throw a TypeError exception ... IteratorClose(iterated, error). + 5. If chunkSize is not an integral Number, throw a TypeError exception ... IteratorClose(iterated, error). + 6. If chunkSize is not in the inclusive interval from 1𝔽 to 𝔽(2^32 - 1), then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + +features: [iterator-chunking] +---*/ + +let returnGets = 0; +let closable = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error('next should not be read'); + }, + get return() { + ++returnGets; + throw new Test262Error('return getter error should be masked'); + }, +}; + +assert.throws(RangeError, function () { + closable.chunks(0); +}); +assert.sameValue(returnGets, 1, 'return getter is still consulted'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/argument-validation-failure-closes-underlying.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/argument-validation-failure-closes-underlying.js new file mode 100644 index 0000000000000..c9261f6317b4e --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/argument-validation-failure-closes-underlying.js @@ -0,0 +1,53 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Underlying iterator is closed when chunkSize validation fails +info: | + Iterator.prototype.chunks ( chunkSize ) + + 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + 4. If chunkSize is not a Number, throw a TypeError exception ... IteratorClose(iterated, error). + 5. If chunkSize is not an integral Number, throw a TypeError exception ... IteratorClose(iterated, error). + 6. If chunkSize is not in the inclusive interval from 1𝔽 to 𝔽(2^32 - 1), then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + +features: [iterator-chunking] +---*/ + +let closed = false; +let closable = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error('next should not be read'); + }, + return() { + closed = true; + return {}; + }, +}; + +assert.throws(TypeError, function () { + closable.chunks(); +}); +assert.sameValue(closed, true, 'iterator closed when chunkSize is undefined'); + +closed = false; +assert.throws(RangeError, function () { + closable.chunks(0); +}); +assert.sameValue(closed, true, 'iterator closed when chunkSize is 0'); + +closed = false; +assert.throws(TypeError, function () { + closable.chunks(NaN); +}); +assert.sameValue(closed, true, 'iterator closed when chunkSize is NaN'); + +closed = false; +assert.throws(TypeError, function () { + closable.chunks('1'); +}); +assert.sameValue(closed, true, 'iterator closed when chunkSize is a string'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/callable.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/callable.js new file mode 100644 index 0000000000000..6da9af3fc88d8 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/callable.js @@ -0,0 +1,13 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Iterator.prototype.chunks is callable +features: [iterator-chunking, generators] +---*/ +function* g() {} +Iterator.prototype.chunks.call(g(), 1); + +let iter = g(); +iter.chunks(1); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunkSize-no-coercion.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunkSize-no-coercion.js new file mode 100644 index 0000000000000..7b4c1c6787e91 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunkSize-no-coercion.js @@ -0,0 +1,38 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Iterator.prototype.chunks does not coerce chunkSize using ToNumber; the + argument must already be a Number. Unlike take/drop, valueOf and toString + are never called. +info: | + Iterator.prototype.chunks ( chunkSize ) + + 4. If chunkSize is not a Number, throw a TypeError exception. + +features: [iterator-chunking, generators] +---*/ +let iterator = (function* () {})(); + +let valueOfCalled = false; +assert.throws(TypeError, () => { + iterator.chunks({ + valueOf() { + valueOfCalled = true; + return 2; + } + }); +}); +assert.sameValue(valueOfCalled, false, 'valueOf must not be called'); + +let toStringCalled = false; +assert.throws(TypeError, () => { + iterator.chunks({ + toString() { + toStringCalled = true; + return '2'; + } + }); +}); +assert.sameValue(toStringCalled, false, 'toString must not be called'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunkSize-not-a-number.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunkSize-not-a-number.js new file mode 100644 index 0000000000000..3ce6125979abb --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunkSize-not-a-number.js @@ -0,0 +1,68 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Iterator.prototype.chunks throws TypeError when chunkSize is not an integral + Number +info: | + Iterator.prototype.chunks ( chunkSize ) + + 4. If chunkSize is not a Number, throw a TypeError exception. + 5. If chunkSize is not an integral Number, throw a TypeError exception. + +features: [iterator-chunking, generators] +---*/ +let iterator = (function* () {})(); + +assert.throws(TypeError, () => { + iterator.chunks(); +}); + +assert.throws(TypeError, () => { + iterator.chunks(undefined); +}); + +assert.throws(TypeError, () => { + iterator.chunks('1'); +}); + +assert.throws(TypeError, () => { + iterator.chunks(true); +}); + +assert.throws(TypeError, () => { + iterator.chunks(null); +}); + +assert.throws(TypeError, () => { + iterator.chunks({}); +}); + +assert.throws(TypeError, () => { + iterator.chunks(Symbol()); +}); + +assert.throws(TypeError, () => { + iterator.chunks([2]); +}); + +assert.throws(TypeError, () => { + iterator.chunks(NaN); +}); + +assert.throws(TypeError, () => { + iterator.chunks(0.5); +}); + +assert.throws(TypeError, () => { + iterator.chunks(1.5); +}); + +assert.throws(TypeError, () => { + iterator.chunks(Infinity); +}); + +assert.throws(TypeError, () => { + iterator.chunks(-Infinity); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunkSize-out-of-range.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunkSize-out-of-range.js new file mode 100644 index 0000000000000..56458fd941958 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunkSize-out-of-range.js @@ -0,0 +1,41 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Iterator.prototype.chunks throws RangeError when chunkSize is an integral + Number outside the valid range [1, 2^32 - 1] +info: | + Iterator.prototype.chunks ( chunkSize ) + + 6. If chunkSize is not in the inclusive interval from 1𝔽 to 𝔽(2^32 - 1), then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + +features: [iterator-chunking, generators, exponentiation] +---*/ +let iterator = (function* () {})(); + +assert.throws(RangeError, () => { + iterator.chunks(0); +}); + +assert.throws(RangeError, () => { + iterator.chunks(-0); +}); + +assert.throws(RangeError, () => { + iterator.chunks(-1); +}); + +assert.throws(RangeError, () => { + iterator.chunks(2 ** 32); +}); + +assert.throws(RangeError, () => { + iterator.chunks(2 ** 53); +}); + +// Boundary: valid values do not throw +iterator.chunks(1); +iterator.chunks(2 ** 32 - 1); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunks-evenly-divisible.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunks-evenly-divisible.js new file mode 100644 index 0000000000000..0efcc15f643b8 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunks-evenly-divisible.js @@ -0,0 +1,33 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + All chunks are full-sized when the iterator length is evenly divisible by + chunkSize +info: | + Iterator.prototype.chunks ( chunkSize ) + + 6.a.iv. If the number of elements in buffer is ℝ(chunkSize), then + 6.a.iv.a. Let completion be Completion(Yield(CreateArrayFromList(buffer))). + +features: [iterator-chunking, generators] +includes: [compareArray.js] +---*/ +function* g(n) { + for (let i = 0; i < n; ++i) { + yield i; + } +} + +let chunks = Array.from(g(4).chunks(2)); + +assert.sameValue(chunks.length, 2); +assert.compareArray(chunks[0], [0, 1]); +assert.compareArray(chunks[1], [2, 3]); + +chunks = Array.from(g(6).chunks(3)); + +assert.sameValue(chunks.length, 2); +assert.compareArray(chunks[0], [0, 1, 2]); +assert.compareArray(chunks[1], [3, 4, 5]); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunks-last-chunk-partial.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunks-last-chunk-partial.js new file mode 100644 index 0000000000000..d2afefb2127f9 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunks-last-chunk-partial.js @@ -0,0 +1,32 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Last chunk may be smaller than chunkSize when the iterator is not evenly + divisible +info: | + Iterator.prototype.chunks ( chunkSize ) + + 6.a.i. Let value be ? IteratorStepValue(iterated). + 6.a.ii. If value is ~done~, then + 6.a.ii.a. If buffer is not empty, then + 6.a.ii.a.i. Perform Completion(Yield(CreateArrayFromList(buffer))). + +features: [iterator-chunking, generators] +includes: [compareArray.js] +---*/ +function* g() { + yield 0; + yield 1; + yield 2; + yield 3; + yield 4; +} + +let chunks = Array.from(g().chunks(2)); + +assert.sameValue(chunks.length, 3); +assert.compareArray(chunks[0], [0, 1]); +assert.compareArray(chunks[1], [2, 3]); +assert.compareArray(chunks[2], [4]); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunks-size-1.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunks-size-1.js new file mode 100644 index 0000000000000..820c411670c5c --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunks-size-1.js @@ -0,0 +1,28 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + When chunkSize is 1, each element is yielded as a single-element array +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, generators] +includes: [compareArray.js] +---*/ +function* g() { + yield 0; + yield 1; + yield 2; + yield 3; + yield 4; +} + +let chunks = Array.from(g().chunks(1)); + +assert.sameValue(chunks.length, 5); +assert.compareArray(chunks[0], [0]); +assert.compareArray(chunks[1], [1]); +assert.compareArray(chunks[2], [2]); +assert.compareArray(chunks[3], [3]); +assert.compareArray(chunks[4], [4]); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunks-size-larger-than-iterator.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunks-size-larger-than-iterator.js new file mode 100644 index 0000000000000..d935cc6ce9765 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/chunks-size-larger-than-iterator.js @@ -0,0 +1,31 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + When chunkSize is larger than the number of elements, a single partial chunk + is yielded +info: | + Iterator.prototype.chunks ( chunkSize ) + + 6.a.i. Let value be ? IteratorStepValue(iterated). + 6.a.ii. If value is ~done~, then + 6.a.ii.a. If buffer is not empty, then + 6.a.ii.a.i. Perform Completion(Yield(CreateArrayFromList(buffer))). + +features: [iterator-chunking, generators] +includes: [compareArray.js] +---*/ +function* g() { + yield 0; + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; +} + +let chunks = Array.from(g().chunks(100)); + +assert.sameValue(chunks.length, 1); +assert.compareArray(chunks[0], [0, 1, 2, 3, 4, 5]); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/exhaustion-does-not-call-return.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/exhaustion-does-not-call-return.js new file mode 100644 index 0000000000000..551e6e983195e --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/exhaustion-does-not-call-return.js @@ -0,0 +1,32 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Underlying iterator return is not called when result iterator is exhausted +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, generators] +---*/ +function* g() { + yield 0; + yield 1; + yield 2; +} + +class TestIterator extends Iterator { + get next() { + let n = g(); + return function () { + return n.next(); + }; + } + return() { + throw new Test262Error(); + } +} + +let iterator = new TestIterator().chunks(2); +iterator.next(); +iterator.next(); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/get-next-method-only-once.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/get-next-method-only-once.js new file mode 100644 index 0000000000000..b5cb6e6c6584d --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/get-next-method-only-once.js @@ -0,0 +1,40 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Gets the next method from the underlying iterator only once +info: | + Iterator.prototype.chunks ( chunkSize ) + + 5. Set iterated to ? GetIteratorDirect(O). + +features: [iterator-chunking, generators] +---*/ +let nextGets = 0; +let nextCalls = 0; + +class CountingIterator extends Iterator { + get next() { + ++nextGets; + let iter = (function* () { + for (let i = 1; i < 5; ++i) { + yield i; + } + })(); + return function () { + ++nextCalls; + return iter.next(); + }; + } +} + +let iterator = new CountingIterator(); + +assert.sameValue(nextGets, 0); +assert.sameValue(nextCalls, 0); + +for (const value of iterator.chunks(2)); + +assert.sameValue(nextGets, 1); +assert.sameValue(nextCalls, 5); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/get-next-method-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/get-next-method-throws.js new file mode 100644 index 0000000000000..373b91d98f53d --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/get-next-method-throws.js @@ -0,0 +1,27 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Throws when getting the next method from the underlying iterator throws +info: | + Iterator.prototype.chunks ( chunkSize ) + + 5. Set iterated to ? GetIteratorDirect(O). + +features: [iterator-chunking, class] +---*/ +class ThrowingIterator extends Iterator { + get next() { + throw new Test262Error(); + } + get return() { + throw new TypeError; + } +} + +let iter = new ThrowingIterator(); + +assert.throws(Test262Error, function () { + iter.chunks(1); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/get-return-method-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/get-return-method-throws.js new file mode 100644 index 0000000000000..69a825e32ec70 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/get-return-method-throws.js @@ -0,0 +1,29 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Underlying iterator return is a throwing getter +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, class] +---*/ +class TestIterator extends Iterator { + next() { + return { + done: false, + value: 1, + }; + } + get return() { + throw new Test262Error(); + } +} + +let iterator = new TestIterator().chunks(1); +iterator.next(); + +assert.throws(Test262Error, function () { + iterator.return(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/is-function.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/is-function.js new file mode 100644 index 0000000000000..9db493612313c --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/is-function.js @@ -0,0 +1,10 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Iterator.prototype.chunks is a built-in function +features: [iterator-chunking] +---*/ + +assert.sameValue(typeof Iterator.prototype.chunks, 'function'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/iterator-already-exhausted.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/iterator-already-exhausted.js new file mode 100644 index 0000000000000..6a46e179eb350 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/iterator-already-exhausted.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Iterator.prototype.chunks yields no chunks when the iterator is already + exhausted +info: | + Iterator.prototype.chunks ( chunkSize ) + + 6.a.i. Let value be ? IteratorStepValue(iterated). + 6.a.ii. If value is ~done~, then + 6.a.ii.a. If buffer is not empty, then + ... + 6.a.ii.b. Return ReturnCompletion(undefined). + +features: [iterator-chunking, generators] +---*/ +function* g() {} + +let chunks = Array.from(g().chunks(2)); +assert.sameValue(chunks.length, 0); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/iterator-return-method-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/iterator-return-method-throws.js new file mode 100644 index 0000000000000..7d016074b2252 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/iterator-return-method-throws.js @@ -0,0 +1,28 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Iterator has throwing return +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, class] +---*/ +class IteratorThrows extends Iterator { + next() { + return { + done: false, + value: 0, + }; + } + return() { + throw new Test262Error(); + } +} + +let iterator = new IteratorThrows().chunks(1); + +assert.throws(Test262Error, function () { + iterator.return(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/length.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/length.js new file mode 100644 index 0000000000000..9421650afe0a5 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/length.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Iterator.prototype.chunks has a "length" property whose value is 1. +info: | + ECMAScript Standard Built-in Objects + + Unless otherwise specified, the length property of a built-in + Function object has the attributes { [[Writable]]: false, [[Enumerable]]: + false, [[Configurable]]: true }. +features: [iterator-chunking] +includes: [propertyHelper.js] +---*/ + +verifyProperty(Iterator.prototype.chunks, 'length', { + value: 1, + writable: false, + enumerable: false, + configurable: true, +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/name.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/name.js new file mode 100644 index 0000000000000..e022d94611fa8 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/name.js @@ -0,0 +1,27 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + The "name" property of Iterator.prototype.chunks +info: | + 17 ECMAScript Standard Built-in Objects + + Every built-in Function object, including constructors, that is not + identified as an anonymous function has a name property whose value is a + String. Unless otherwise specified, this value is the name that is given to + the function in this specification. + + Unless otherwise specified, the name property of a built-in Function + object, if it exists, has the attributes { [[Writable]]: false, + [[Enumerable]]: false, [[Configurable]]: true }. +features: [iterator-chunking] +includes: [propertyHelper.js] +---*/ + +verifyProperty(Iterator.prototype.chunks, 'name', { + value: 'chunks', + writable: false, + enumerable: false, + configurable: true, +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-returns-non-object.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-returns-non-object.js new file mode 100644 index 0000000000000..0074519606aa0 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-returns-non-object.js @@ -0,0 +1,25 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Underlying iterator next returns non-object +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, class] +---*/ +class NonObjectIterator extends Iterator { + next() { + return null; + } + get return() { + throw new Test262Error(); + } +} + +let iterator = new NonObjectIterator().chunks(1); + +assert.throws(TypeError, function () { + iterator.next(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-returns-throwing-done.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-returns-throwing-done.js new file mode 100644 index 0000000000000..b93c9c795dc8e --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-returns-throwing-done.js @@ -0,0 +1,30 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Underlying iterator next returns object with throwing done getter +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, class] +---*/ +class ThrowingIterator extends Iterator { + next() { + return { + get done() { + throw new Test262Error(); + }, + value: 1, + }; + } + get return() { + throw new Test262Error(); + } +} + +let iterator = new ThrowingIterator().chunks(1); + +assert.throws(Test262Error, function () { + iterator.next(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-returns-throwing-value-done.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-returns-throwing-value-done.js new file mode 100644 index 0000000000000..813dbda78d3a0 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-returns-throwing-value-done.js @@ -0,0 +1,28 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Underlying iterator next returns object with throwing value getter, but is + already done +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, class] +---*/ +class ThrowingIterator extends Iterator { + next() { + return { + done: true, + get value() { + throw new Test262Error(); + } + }; + } + get return() { + throw new Test262Error(); + } +} + +let iterator = new ThrowingIterator().chunks(1); +iterator.next(); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-returns-throwing-value.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-returns-throwing-value.js new file mode 100644 index 0000000000000..a71253885f2bf --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-returns-throwing-value.js @@ -0,0 +1,30 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Underlying iterator next returns object with throwing value getter +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, class] +---*/ +class ThrowingIterator extends Iterator { + next() { + return { + done: false, + get value() { + throw new Test262Error(); + } + }; + } + get return() { + throw new TypeError(); + } +} + +let iterator = new ThrowingIterator().chunks(1); + +assert.throws(Test262Error, function () { + iterator.next(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-throws.js new file mode 100644 index 0000000000000..b47bd729955dc --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/next-method-throws.js @@ -0,0 +1,25 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Underlying iterator next throws +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, class] +---*/ +class ThrowingIterator extends Iterator { + next() { + throw new Test262Error(); + } + get return() { + throw new TypeError(); + } +} + +let iterator = new ThrowingIterator().chunks(1); + +assert.throws(Test262Error, function () { + iterator.next(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/non-constructible.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/non-constructible.js new file mode 100644 index 0000000000000..c6dca22f2dcaa --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/non-constructible.js @@ -0,0 +1,26 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Iterator.prototype.chunks is not constructible. + + Built-in function objects that are not identified as constructors do not + implement the [[Construct]] internal method unless otherwise specified in + the description of a particular function. +features: [iterator-chunking, generators, class] +---*/ +function* g() {} +let iter = g(); + +assert.throws(TypeError, () => { + new iter.chunks(1); +}); + +assert.throws(TypeError, () => { + new Iterator.prototype.chunks(1); +}); + +assert.throws(TypeError, () => { + new class extends Iterator {}.chunks(1); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/prop-desc.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/prop-desc.js new file mode 100644 index 0000000000000..ddc6729d222e6 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/prop-desc.js @@ -0,0 +1,23 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Property descriptor of Iterator.prototype.chunks +info: | + Iterator.prototype.chunks + + 17 ECMAScript Standard Built-in Objects + + Every other data property described in clauses 18 through 26 and in Annex B.2 + has the attributes { [[Writable]]: true, [[Enumerable]]: false, + [[Configurable]]: true } unless otherwise specified. +features: [iterator-chunking] +includes: [propertyHelper.js] +---*/ + +verifyProperty(Iterator.prototype, 'chunks', { + writable: true, + enumerable: false, + configurable: true, +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/proto.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/proto.js new file mode 100644 index 0000000000000..0e131414adac7 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/proto.js @@ -0,0 +1,11 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + The value of the [[Prototype]] internal slot of Iterator.prototype.chunks is the + intrinsic object %FunctionPrototype%. +features: [iterator-chunking] +---*/ + +assert.sameValue(Object.getPrototypeOf(Iterator.prototype.chunks), Function.prototype); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/result-is-iterator.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/result-is-iterator.js new file mode 100644 index 0000000000000..58bcd01c21fe1 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/result-is-iterator.js @@ -0,0 +1,18 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + The value returned by Iterator.prototype.chunks is an Iterator instance +info: | + Iterator.prototype.chunks ( chunkSize ) + + 6. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterators]] »). + +features: [iterator-chunking, generators] +---*/ + +assert( + (function* () {})().chunks(1) instanceof Iterator, + 'function*(){}().chunks(1) must return an Iterator' +); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/return-is-forwarded-to-underlying-iterator.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/return-is-forwarded-to-underlying-iterator.js new file mode 100644 index 0000000000000..8becdf56c75ab --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/return-is-forwarded-to-underlying-iterator.js @@ -0,0 +1,32 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Underlying iterator return is called when result iterator is closed +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, class] +---*/ +let returnCount = 0; + +class TestIterator extends Iterator { + next() { + return { + done: false, + value: 1, + }; + } + return() { + ++returnCount; + return {}; + } +} + +let iterator = new TestIterator().chunks(2); +assert.sameValue(returnCount, 0); +iterator.return(); +assert.sameValue(returnCount, 1); +iterator.return(); +assert.sameValue(returnCount, 1); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/return-is-not-forwarded-after-exhaustion.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/return-is-not-forwarded-after-exhaustion.js new file mode 100644 index 0000000000000..7d0b34db51fb1 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/return-is-not-forwarded-after-exhaustion.js @@ -0,0 +1,35 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Underlying iterator return is not called after result iterator observes + that underlying iterator is exhausted +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, class] +---*/ + +class TestIterator extends Iterator { + next() { + return { + done: true, + value: undefined, + }; + } + return() { + throw new Test262Error(); + } +} + +let iterator = new TestIterator().chunks(1); +assert.throws(Test262Error, function () { + iterator.return(); +}); +iterator.next(); +iterator.return(); + +iterator = new TestIterator().chunks(1); +iterator.next(); +iterator.return(); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/this-non-callable-next.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/this-non-callable-next.js new file mode 100644 index 0000000000000..8e5926b857cd4 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/this-non-callable-next.js @@ -0,0 +1,19 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Iterator.prototype.chunks throws TypeError when its this value is an object + with a non-callable next +info: | + Iterator.prototype.chunks ( chunkSize ) + + 5. Set iterated to ? GetIteratorDirect(O). + +features: [iterator-chunking] +---*/ +let iter = Iterator.prototype.chunks.call({ next: 0 }, 1); + +assert.throws(TypeError, function () { + iter.next(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/this-non-object.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/this-non-object.js new file mode 100644 index 0000000000000..60dac4996d7e4 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/this-non-object.js @@ -0,0 +1,26 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Iterator.prototype.chunks throws TypeError when its this value is a non-object +info: | + Iterator.prototype.chunks ( chunkSize ) + + 1. Let O be the this value. + 2. If O is not an Object, throw a TypeError exception. + +features: [iterator-chunking] +---*/ +assert.throws(TypeError, function () { + Iterator.prototype.chunks.call(null, 1); +}); + +Object.defineProperty(Number.prototype, 'next', { + get: function () { + throw new Test262Error(); + } +}); +assert.throws(TypeError, function () { + Iterator.prototype.chunks.call(0, 1); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/this-plain-iterator.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/this-plain-iterator.js new file mode 100644 index 0000000000000..3d39614ed07b7 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/this-plain-iterator.js @@ -0,0 +1,36 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Iterator.prototype.chunks supports a this value that does not inherit from + Iterator.prototype but implements the iterator protocol +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking] +includes: [compareArray.js] +---*/ +let iter = { + get next() { + let count = 3; + return function () { + --count; + return count >= 0 ? { done: false, value: count } : { done: true, value: undefined }; + }; + } +}; + +let chunked = Iterator.prototype.chunks.call(iter, 2); + +let result = chunked.next(); +assert.compareArray(result.value, [2, 1]); +assert.sameValue(result.done, false); + +result = chunked.next(); +assert.compareArray(result.value, [0]); +assert.sameValue(result.done, false); + +result = chunked.next(); +assert.sameValue(result.value, undefined); +assert.sameValue(result.done, true); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/throws-typeerror-when-generator-is-running.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/throws-typeerror-when-generator-is-running.js new file mode 100644 index 0000000000000..660cc161aca0b --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/throws-typeerror-when-generator-is-running.js @@ -0,0 +1,42 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Throws a TypeError when the closure generator is already running. +info: | + %IteratorHelperPrototype%.next ( ) + 1. Return ? GeneratorResume(this value, undefined, "Iterator Helper"). + + 27.5.3.3 GeneratorResume ( generator, value, generatorBrand ) + 1. Let state be ? GeneratorValidate(generator, generatorBrand). + ... + + 27.5.3.2 GeneratorValidate ( generator, generatorBrand ) + ... + 6. If state is executing, throw a TypeError exception. + ... + +features: [iterator-chunking] +---*/ + +var loopCount = 0; + +var iter; +var iterator = { + get next() { + return function () { + loopCount++; + iter.next(); + return { done: false, value: 0 }; + }; + } +}; + +iter = Iterator.prototype.chunks.call(iterator, 1); + +assert.throws(TypeError, function () { + iter.next(); +}); + +assert.sameValue(loopCount, 1); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/underlying-iterator-advanced-in-parallel.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/underlying-iterator-advanced-in-parallel.js new file mode 100644 index 0000000000000..c35cd79cf69a8 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/underlying-iterator-advanced-in-parallel.js @@ -0,0 +1,39 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Underlying iterator is advanced after calling chunks +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, generators] +includes: [compareArray.js] +---*/ +let iterator = (function* () { + for (let i = 0; i < 6; ++i) { + yield i; + } +})(); + +let chunked = iterator.chunks(2); + +let result = chunked.next(); +assert.compareArray(result.value, [0, 1]); +assert.sameValue(result.done, false); + +let { value, done } = iterator.next(); +assert.sameValue(value, 2); +assert.sameValue(done, false); + +result = chunked.next(); +assert.compareArray(result.value, [3, 4]); +assert.sameValue(result.done, false); + +result = chunked.next(); +assert.compareArray(result.value, [5]); +assert.sameValue(result.done, false); + +result = chunked.next(); +assert.sameValue(result.value, undefined); +assert.sameValue(result.done, true); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/underlying-iterator-closed-in-parallel.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/underlying-iterator-closed-in-parallel.js new file mode 100644 index 0000000000000..fc2044ed11a1d --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/underlying-iterator-closed-in-parallel.js @@ -0,0 +1,25 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Underlying iterator is closed after calling chunks +info: | + Iterator.prototype.chunks ( chunkSize ) + +features: [iterator-chunking, generators] +---*/ +let iterator = (function* () { + for (let i = 0; i < 5; ++i) { + yield i; + } +})(); + +let chunked = iterator.chunks(2); + +iterator.return(); + +let { value, done } = chunked.next(); + +assert.sameValue(value, undefined); +assert.sameValue(done, true); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/chunks/yields-distinct-arrays.js b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/yields-distinct-arrays.js new file mode 100644 index 0000000000000..122d85c7986f4 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/chunks/yields-distinct-arrays.js @@ -0,0 +1,26 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.chunks +description: > + Each yielded chunk is a distinct new Array object +info: | + Iterator.prototype.chunks ( chunkSize ) + + 6.a.iv.a. Let completion be Completion(Yield(CreateArrayFromList(buffer))). + +features: [iterator-chunking, generators] +---*/ +function* g() { + yield 0; + yield 1; + yield 2; + yield 3; +} + +let chunks = Array.from(g().chunks(2)); + +assert.sameValue(chunks.length, 2); +assert(Array.isArray(chunks[0]), 'chunks[0] is an Array'); +assert(Array.isArray(chunks[1]), 'chunks[1] is an Array'); +assert.notSameValue(chunks[0], chunks[1], 'each chunk is a distinct array'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/drop/argument-effect-order.js b/JSTests/test262/test/built-ins/Iterator/prototype/drop/argument-effect-order.js index 0917e8b3d0f7a..e3a09bcfcf5c9 100644 --- a/JSTests/test262/test/built-ins/Iterator/prototype/drop/argument-effect-order.js +++ b/JSTests/test262/test/built-ins/Iterator/prototype/drop/argument-effect-order.js @@ -1,11 +1,11 @@ // Copyright (C) 2023 Michael Ficarra. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- -esid: sec-iteratorprototype.drop +esid: sec-iterator.prototype.drop description: > Arguments and this value are evaluated in the correct order info: | - %Iterator.prototype%.drop ( limit ) + Iterator.prototype.drop ( limit ) includes: [compareArray.js] features: [iterator-helpers] @@ -47,6 +47,29 @@ assert.compareArray(effects, []); effects = []; +assert.throws(RangeError, function () { + Iterator.prototype.drop.call( + { + get next() { + effects.push('get next'); + return function () { + return { done: true, value: undefined }; + }; + }, + }, + { + valueOf() { + effects.push('ToNumber limit'); + return Number.MAX_SAFE_INTEGER + 1; + }, + } + ); +}); + +assert.compareArray(effects, ['ToNumber limit']); + +effects = []; + assert.throws(RangeError, function () { Iterator.prototype.drop.call( { diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/drop/argument-validation-failure-closes-underlying.js b/JSTests/test262/test/built-ins/Iterator/prototype/drop/argument-validation-failure-closes-underlying.js index cc8367df129f7..97a85471bfe4b 100644 --- a/JSTests/test262/test/built-ins/Iterator/prototype/drop/argument-validation-failure-closes-underlying.js +++ b/JSTests/test262/test/built-ins/Iterator/prototype/drop/argument-validation-failure-closes-underlying.js @@ -1,11 +1,11 @@ // Copyright (C) 2024 Kevin Gibbons. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- -esid: sec-iteratorprototype.drop +esid: sec-iterator.prototype.drop description: > Underlying iterator is closed when argument validation fails info: | - %Iterator.prototype%.drop ( limit ) + Iterator.prototype.drop ( limit ) features: [iterator-helpers] flags: [] @@ -34,6 +34,12 @@ assert.throws(RangeError, function() { }); assert.sameValue(closed, true); +closed = false; +assert.throws(RangeError, function() { + closable.drop(Number.MAX_SAFE_INTEGER + 1); +}); +assert.sameValue(closed, true); + closed = false; assert.throws(RangeError, function() { closable.drop(-1); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/drop/limit-rangeerror.js b/JSTests/test262/test/built-ins/Iterator/prototype/drop/limit-rangeerror.js index f30ea6ba87e1e..214d022fffc7a 100644 --- a/JSTests/test262/test/built-ins/Iterator/prototype/drop/limit-rangeerror.js +++ b/JSTests/test262/test/built-ins/Iterator/prototype/drop/limit-rangeerror.js @@ -1,15 +1,23 @@ // Copyright (C) 2020 Rick Waldron. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- -esid: sec-iteratorprototype.drop +esid: sec-iterator.prototype.drop description: > - Throws a RangeError exception when limit argument is NaN or less than 0. + Throws a RangeError exception when limit argument is NaN, less than 0, or + finite and greater than Number.MAX_SAFE_INTEGER. info: | - %Iterator.prototype%.drop ( limit ) + Iterator.prototype.drop ( limit ) - 3. If numLimit is NaN, throw a RangeError exception. - 4. Let integerLimit be ! ToIntegerOrInfinity(numLimit). - 5. If integerLimit < 0, throw a RangeError exception. + 6. If numLimit is NaN, then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + 7. If numLimit is finite and numLimit > 𝔽(2**53 - 1), then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + 8. Let integerLimit be ! ToIntegerOrInfinity(numLimit). + 9. If integerLimit < 0, then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). features: [iterator-helpers] ---*/ @@ -18,6 +26,8 @@ let iterator = (function* () {})(); iterator.drop(0); iterator.drop(-0.5); iterator.drop(null); +iterator.drop(Number.MAX_SAFE_INTEGER); +iterator.drop(Infinity); assert.throws(RangeError, () => { iterator.drop(-1); @@ -34,3 +44,7 @@ assert.throws(RangeError, () => { assert.throws(RangeError, () => { iterator.drop(NaN); }); + +assert.throws(RangeError, () => { + iterator.drop(Number.MAX_SAFE_INTEGER + 1); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/argument-effect-order.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/argument-effect-order.js new file mode 100644 index 0000000000000..47544a148a40d --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/argument-effect-order.js @@ -0,0 +1,96 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Arguments and this value are validated in the correct order +info: | + Iterator.prototype.includes ( searchElement [ , skippedElements ] ) + + 1. Let O be the this value. + 2. If O is not an Object, throw a TypeError exception. + 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + 4. If skippedElements is undefined, let toSkip be 0. + 5. Else if skippedElements is not one of +Infinity, -Infinity, or an integral Number, + a. Let error be ThrowCompletion(a newly created TypeError object). + b. Return ? IteratorClose(iterated, error). + 6. Else, let toSkip be skippedElements. + 7. If toSkip < -0F, then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + 8. If toSkip is finite and toSkip > F(2**53 - 1), then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + 9. Let skipped be +0F. + 10. Set iterated to ? GetIteratorDirect(O). + +includes: [compareArray.js] +features: [iterator-includes] +---*/ + +assert.throws(TypeError, function() { + Iterator.prototype.includes.call(null, 0, NaN); +}); + +let effects = []; + +assert.throws(TypeError, function() { + Iterator.prototype.includes.call( + { + get next() { + effects.push('get next'); + return function() { + return { done: true, value: undefined }; + }; + }, + return() { + effects.push('return'); + return {}; + }, + }, + 0, + NaN + ); +}); + +assert.compareArray(effects, ['return']); + +effects = []; + +assert.throws(RangeError, function() { + Iterator.prototype.includes.call( + { + get next() { + effects.push('get next'); + return function() { + return { done: true, value: undefined }; + }; + }, + return() { + effects.push('return'); + return {}; + }, + }, + 0, + Number.MAX_SAFE_INTEGER + 1 + ); +}); + +assert.compareArray(effects, ['return']); + +effects = []; + +Iterator.prototype.includes.call( + { + get next() { + effects.push('get next'); + return function() { + return { done: true, value: undefined }; + }; + }, + }, + 0, + 0 +); + +assert.compareArray(effects, ['get next']); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/argument-validation-failure-closes-underlying.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/argument-validation-failure-closes-underlying.js new file mode 100644 index 0000000000000..9402d065cd43e --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/argument-validation-failure-closes-underlying.js @@ -0,0 +1,37 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Underlying iterator is closed when skippedElements validation fails +features: [iterator-includes] +---*/ + +let closed = false; +let closable = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error('next should not be read'); + }, + return() { + closed = true; + return {}; + }, +}; + +assert.throws(RangeError, function() { + closable.includes(null, -2); +}); +assert.sameValue(closed, true, 'iterator closed for negative skippedElements'); + +closed = false; +assert.throws(RangeError, function() { + closable.includes(null, Number.MAX_SAFE_INTEGER + 1); +}); +assert.sameValue(closed, true, 'iterator closed for too-large skippedElements'); + +closed = false; +assert.throws(TypeError, function() { + closable.includes(null, 'a string'); +}); +assert.sameValue(closed, true, 'iterator closed when skippedElements validation produces a TypeError'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/basic-match-and-miss.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/basic-match-and-miss.js new file mode 100644 index 0000000000000..e0d66365f271b --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/basic-match-and-miss.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Basic positive and negative matches +features: [iterator-includes] +---*/ + +let arr = [3, 6, 9]; + +assert.sameValue(arr.values().includes(0), false); +assert.sameValue(arr.values().includes(1), false); +assert.sameValue(arr.values().includes(2), false); +assert.sameValue(arr.values().includes(3), true); +assert.sameValue(arr.values().includes(4), false); +assert.sameValue(arr.values().includes(5), false); +assert.sameValue(arr.values().includes(6), true); +assert.sameValue(arr.values().includes(7), false); +assert.sameValue(arr.values().includes(8), false); +assert.sameValue(arr.values().includes(9), true); +assert.sameValue(arr.values().includes(10), false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/callable.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/callable.js new file mode 100644 index 0000000000000..726c61b23030d --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/callable.js @@ -0,0 +1,16 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator.prototype.includes is callable +features: [iterator-includes] +---*/ + +function* g() { + yield 0; +} + +Iterator.prototype.includes.call(g(), 0); + +g().includes(0); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/closes-on-match.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/closes-on-match.js new file mode 100644 index 0000000000000..898059ab6ee88 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/closes-on-match.js @@ -0,0 +1,26 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator.prototype.includes closes the iterator after a successful match +features: [iterator-includes] +---*/ + +let closed = false; +let i = 0; +let iter = { + __proto__: Iterator.prototype, + next() { + ++i; + return { done: false, value: i }; + }, + return() { + closed = true; + return {}; + }, +}; + +assert.sameValue(iter.includes(5), true); +assert.sameValue(closed, true); +assert.sameValue(i, 5); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/exhaustion-does-not-call-return.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/exhaustion-does-not-call-return.js new file mode 100644 index 0000000000000..1d82922b9d638 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/exhaustion-does-not-call-return.js @@ -0,0 +1,28 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Exhausting the iterator without finding a match does not call return +features: [iterator-includes] +---*/ + +let index = 0; +let returnCalls = 0; + +let iterator = { + __proto__: Iterator.prototype, + next() { + ++index; + if (index <= 3) { + return { done: false, value: index }; + } + return { done: true, value: undefined }; + }, + get return() { + throw new Test262Error('return should not be read'); + }, +}; + +assert.sameValue(iterator.includes(99), false); +assert.sameValue(returnCalls, 0); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/get-next-method-only-once.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/get-next-method-only-once.js new file mode 100644 index 0000000000000..84e21ad3bd820 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/get-next-method-only-once.js @@ -0,0 +1,28 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Gets the next method from the iterator only once +features: [iterator-includes] +---*/ + +let nextGets = 0; + +let iterator = { + __proto__: Iterator.prototype, + get next() { + ++nextGets; + let counter = 5; + return function() { + if (counter < 0) { + return { done: true, value: undefined }; + } + return { done: false, value: --counter }; + }; + } +}; + +assert.sameValue(nextGets, 0); +assert.sameValue(iterator.includes(3), true); +assert.sameValue(nextGets, 1); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/get-next-method-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/get-next-method-throws.js new file mode 100644 index 0000000000000..e640d4f1086c0 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/get-next-method-throws.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator has throwing next getter +features: [iterator-includes] +---*/ + +let iterator = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error(); + }, + get return() { + throw new TypeError(); + } +}; + +assert.throws(Test262Error, function() { + iterator.includes(0); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/get-return-method-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/get-return-method-throws.js new file mode 100644 index 0000000000000..70a9769b07dec --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/get-return-method-throws.js @@ -0,0 +1,31 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator has throwing return getter +features: [iterator-includes] +---*/ + +let counter = 0; +let iterator = { + __proto__: Iterator.prototype, + next() { + if (counter === 0) { + ++counter; + return { done: false, value: 0 }; + } else { + return { done: true, value: undefined }; + } + }, + get return() { + throw new Test262Error(); + } +}; + +assert.sameValue(iterator.includes(1), false); +counter = 0; + +assert.throws(Test262Error, function() { + iterator.includes(0); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/infinite-iterator.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/infinite-iterator.js new file mode 100644 index 0000000000000..47b27bb53a95d --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/infinite-iterator.js @@ -0,0 +1,16 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Includes can find elements in an infinite iterator +features: [iterator-includes] +---*/ + +let gen = function* () { + for (let i = 0; ; ++i) { + yield i; + } +}; + +assert.sameValue(gen().includes(1000), true); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/is-function.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/is-function.js new file mode 100644 index 0000000000000..f9f6081d35aa2 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/is-function.js @@ -0,0 +1,10 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator.prototype.includes is a function +features: [iterator-includes] +---*/ + +assert.sameValue(typeof Iterator.prototype.includes, 'function'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/iterator-already-exhausted.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/iterator-already-exhausted.js new file mode 100644 index 0000000000000..32a254a165f27 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/iterator-already-exhausted.js @@ -0,0 +1,16 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator.prototype.includes returns false when the iterator has already been exhausted +features: [iterator-includes] +---*/ + +let iterator = (function* () {})(); + +let step = iterator.next(); +assert.sameValue(step.value, undefined); +assert.sameValue(step.done, true); + +assert.sameValue(iterator.includes(0), false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/iterator-has-no-return.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/iterator-has-no-return.js new file mode 100644 index 0000000000000..83afcd7a58ce1 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/iterator-has-no-return.js @@ -0,0 +1,21 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + The underlying iterator may be unable to be closed (has no return method) +features: [iterator-includes] +---*/ + +let iterator = [1, 2, 3, 4, 5].values(); + +assert.sameValue(iterator.return, undefined); +assert.sameValue(iterator.includes(4), true); + +let step = iterator.next(); +assert.sameValue(step.done, false); +assert.sameValue(step.value, 5); + +step = iterator.next(); +assert.sameValue(step.done, true); +assert.sameValue(step.value, undefined); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/iterator-return-method-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/iterator-return-method-throws.js new file mode 100644 index 0000000000000..d226775b714fb --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/iterator-return-method-throws.js @@ -0,0 +1,31 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator has throwing return method +features: [iterator-includes] +---*/ + +let counter = 0; +let iterator = { + __proto__: Iterator.prototype, + next() { + if (counter === 0) { + ++counter; + return { done: false, value: 0 }; + } else { + return { done: true, value: undefined }; + } + }, + return() { + throw new Test262Error(); + } +}; + +assert.sameValue(iterator.includes(1), false); +counter = 0; + +assert.throws(Test262Error, function() { + iterator.includes(0); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/length.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/length.js new file mode 100644 index 0000000000000..c852730e6a8fa --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/length.js @@ -0,0 +1,23 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator.prototype.includes has a "length" property whose value is 1. +info: | + ECMAScript Standard Built-in Objects + + Unless otherwise specified, the length property of a built-in + Function object has the attributes { [[Writable]]: false, [[Enumerable]]: + false, [[Configurable]]: true }. + +includes: [propertyHelper.js] +features: [iterator-includes] +---*/ + +verifyProperty(Iterator.prototype.includes, 'length', { + value: 1, + writable: false, + enumerable: false, + configurable: true, +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/name.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/name.js new file mode 100644 index 0000000000000..e2cd0ed1d4794 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/name.js @@ -0,0 +1,28 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + The "name" property of Iterator.prototype.includes +info: | + ECMAScript Standard Built-in Objects + + Every built-in Function object, including constructors, that is not + identified as an anonymous function has a name property whose value is a + String. Unless otherwise specified, this value is the name that is given to + the function in this specification. + + Unless otherwise specified, the name property of a built-in Function + object, if it exists, has the attributes { [[Writable]]: false, + [[Enumerable]]: false, [[Configurable]]: true }. + +includes: [propertyHelper.js] +features: [iterator-includes] +---*/ + +verifyProperty(Iterator.prototype.includes, 'name', { + value: 'includes', + writable: false, + enumerable: false, + configurable: true, +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-returns-non-object.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-returns-non-object.js new file mode 100644 index 0000000000000..c574679aa1ff3 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-returns-non-object.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Underlying iterator next returns non-object +features: [iterator-includes] +---*/ + +let iterator = { + __proto__: Iterator.prototype, + next() { + return null; + }, + get return() { + throw new Test262Error(); + } +}; + +assert.throws(TypeError, function() { + iterator.includes(0); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-returns-throwing-done.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-returns-throwing-done.js new file mode 100644 index 0000000000000..c67ec2909b1f1 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-returns-throwing-done.js @@ -0,0 +1,27 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Underlying iterator next returns object with throwing done getter +features: [iterator-includes] +---*/ + +let iterator = { + __proto__: Iterator.prototype, + next() { + return { + get done() { + throw new Test262Error(); + }, + value: 1, + }; + }, + get return() { + throw new TypeError(); + } +}; + +assert.throws(Test262Error, function() { + iterator.includes(0); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-returns-throwing-value-done.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-returns-throwing-value-done.js new file mode 100644 index 0000000000000..fe23837b85898 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-returns-throwing-value-done.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Underlying iterator next returns object with throwing value getter, but is already done +features: [iterator-includes] +---*/ + +let iterator = { + __proto__: Iterator.prototype, + next() { + return { + done: true, + get value() { + throw new Test262Error(); + }, + }; + } +}; + +assert.sameValue(iterator.includes(0), false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-returns-throwing-value.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-returns-throwing-value.js new file mode 100644 index 0000000000000..86fd7c305a31d --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-returns-throwing-value.js @@ -0,0 +1,27 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Underlying iterator next returns object with throwing value getter +features: [iterator-includes] +---*/ + +let iterator = { + __proto__: Iterator.prototype, + next() { + return { + done: false, + get value() { + throw new Test262Error(); + }, + }; + }, + get return() { + throw new TypeError(); + } +}; + +assert.throws(Test262Error, function() { + iterator.includes(0); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-throws.js new file mode 100644 index 0000000000000..efdcb6efc0f73 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/next-method-throws.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Underlying iterator has throwing next method +features: [iterator-includes] +---*/ + +let iterator = { + __proto__: Iterator.prototype, + next() { + throw new Test262Error(); + }, + get return() { + throw new TypeError(); + } +}; + +assert.throws(Test262Error, function() { + iterator.includes(0); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/non-constructible.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/non-constructible.js new file mode 100644 index 0000000000000..bf100e9b9d1f5 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/non-constructible.js @@ -0,0 +1,30 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator.prototype.includes is not constructible. + + Built-in function objects that are not identified as constructors do not + implement the [[Construct]] internal method unless otherwise specified in the + description of a particular function. +features: [iterator-includes] +---*/ + +function* g() { + yield 0; +} + +let iter = g(); + +assert.throws(TypeError, function() { + new iter.includes(0); +}); + +assert.throws(TypeError, function() { + new iter.includes(0, 0); +}); + +assert.throws(TypeError, function() { + new Iterator.prototype.includes(0); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/object-identity.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/object-identity.js new file mode 100644 index 0000000000000..9a2d7de7ab6d0 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/object-identity.js @@ -0,0 +1,33 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Includes compares objects by identity +features: [iterator-includes] +---*/ + +let o = { + get toString() { + throw new Test262Error(); + }, + get valueOf() { + throw new Test262Error(); + } +}; +let arr = [o]; + +assert.sameValue(arr.values().includes({ + get toString() { + throw new Test262Error(); + }, + get valueOf() { + throw new Test262Error(); + } +}), false); + +assert.sameValue(arr.values().includes(""), false); + +assert.sameValue(arr.values().includes(o), true); + +assert.sameValue([].values().includes(o), false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/prop-desc.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/prop-desc.js new file mode 100644 index 0000000000000..6858438c4a5cd --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/prop-desc.js @@ -0,0 +1,24 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Property descriptor of Iterator.prototype.includes +info: | + Iterator.prototype.includes + + 17 ECMAScript Standard Built-in Objects + + Every other data property described in clauses 18 through 28 and in Annex B.2 + has the attributes { [[Writable]]: true, [[Enumerable]]: false, + [[Configurable]]: true } unless otherwise specified. + +includes: [propertyHelper.js] +features: [iterator-includes] +---*/ + +verifyProperty(Iterator.prototype, 'includes', { + writable: true, + enumerable: false, + configurable: true, +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/proto.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/proto.js new file mode 100644 index 0000000000000..ee5dd8f15958b --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/proto.js @@ -0,0 +1,11 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + The value of the [[Prototype]] internal slot of Iterator.prototype.includes is + the intrinsic object %Function.prototype%. +features: [iterator-includes] +---*/ + +assert.sameValue(Object.getPrototypeOf(Iterator.prototype.includes), Function.prototype); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/result-is-boolean.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/result-is-boolean.js new file mode 100644 index 0000000000000..c66557cb948ea --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/result-is-boolean.js @@ -0,0 +1,14 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator.prototype.includes returns a boolean +features: [iterator-includes] +---*/ + +function* g() {} + +let iter = g(); + +assert.sameValue(typeof iter.includes(0), 'boolean'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/samevaluezero-nan.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/samevaluezero-nan.js new file mode 100644 index 0000000000000..11b34ac58db01 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/samevaluezero-nan.js @@ -0,0 +1,14 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Includes uses SameValueZero for NaN +features: [iterator-includes] +---*/ + +let arr = [NaN]; + +assert.sameValue(arr.values().includes(0), false); +assert.sameValue(arr.values().includes(NaN), true); +assert.sameValue([].values().includes(NaN), false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/samevaluezero-zeroes.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/samevaluezero-zeroes.js new file mode 100644 index 0000000000000..6351b8ec98a80 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/samevaluezero-zeroes.js @@ -0,0 +1,16 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Includes uses SameValueZero for +0 and -0 +features: [iterator-includes] +---*/ + +let positive = [+0]; +let negative = [-0]; + +assert.sameValue(positive.values().includes(+0), true); +assert.sameValue(positive.values().includes(-0), true); +assert.sameValue(negative.values().includes(+0), true); +assert.sameValue(negative.values().includes(-0), true); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-default.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-default.js new file mode 100644 index 0000000000000..26882fc4306a3 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-default.js @@ -0,0 +1,16 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Omitted or undefined skippedElements behaves as 0 +features: [iterator-includes] +---*/ + +assert.sameValue([4, 5, 6, 7].values().includes(4), true); +assert.sameValue([4, 5, 6, 7].values().includes(4, undefined), true); +assert.sameValue([4, 5, 6, 7].values().includes(4, 0), true); + +assert.sameValue([4, 5, 6, 7].values().includes(8), false); +assert.sameValue([4, 5, 6, 7].values().includes(8, undefined), false); +assert.sameValue([4, 5, 6, 7].values().includes(8, 0), false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-max-safe-integer.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-max-safe-integer.js new file mode 100644 index 0000000000000..5cd01ebbef3bb --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-max-safe-integer.js @@ -0,0 +1,29 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + skippedElements of Number.MAX_SAFE_INTEGER is accepted +features: [iterator-includes] +---*/ + +let returnCalls = 0; +let nextCalls = 0; +let iter = { + __proto__: Iterator.prototype, + next() { + ++nextCalls; + if (nextCalls === 1) { + return { done: false, value: 0 }; + } + return { done: true, value: undefined }; + }, + return() { + ++returnCalls; + return {}; + }, +}; + +assert.sameValue(iter.includes(0, Number.MAX_SAFE_INTEGER), false); +assert.sameValue(returnCalls, 0); +assert.sameValue(nextCalls, 2); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-nan-typeerror.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-nan-typeerror.js new file mode 100644 index 0000000000000..a22cb71d07077 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-nan-typeerror.js @@ -0,0 +1,26 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + skippedElements of NaN throws TypeError and closes the iterator +features: [iterator-includes] +---*/ + +let closed = false; +let iterator = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error('next should not be read'); + }, + return() { + closed = true; + return {}; + }, +}; + +assert.throws(TypeError, function() { + iterator.includes(0, NaN); +}); + +assert.sameValue(closed, true); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-negative-infinity-rangeerror.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-negative-infinity-rangeerror.js new file mode 100644 index 0000000000000..faad01a2c1ae9 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-negative-infinity-rangeerror.js @@ -0,0 +1,26 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + skippedElements of -Infinity throws RangeError and closes the iterator +features: [iterator-includes] +---*/ + +let closed = false; +let iterator = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error('next should not be read'); + }, + return() { + closed = true; + return {}; + }, +}; + +assert.throws(RangeError, function() { + iterator.includes(0, -Infinity); +}); + +assert.sameValue(closed, true); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-negative-integral-rangeerror.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-negative-integral-rangeerror.js new file mode 100644 index 0000000000000..1742f5994ec7c --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-negative-integral-rangeerror.js @@ -0,0 +1,26 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Negative integral skippedElements throws RangeError and closes the iterator +features: [iterator-includes] +---*/ + +let closed = false; +let iterator = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error('next should not be read'); + }, + return() { + closed = true; + return {}; + }, +}; + +assert.throws(RangeError, function() { + iterator.includes(0, -1); +}); + +assert.sameValue(closed, true); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-no-coercion.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-no-coercion.js new file mode 100644 index 0000000000000..22ce7ebffe7aa --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-no-coercion.js @@ -0,0 +1,45 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + skippedElements is not coerced; non-Number values throw before valueOf/toString +features: [iterator-includes] +---*/ + +let closed = false; +let iterator = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error('next should not be read'); + }, + return() { + closed = true; + return {}; + }, +}; + +let valueOfCalled = false; +assert.throws(TypeError, function() { + iterator.includes(0, { + valueOf() { + valueOfCalled = true; + return 0; + }, + }); +}); +assert.sameValue(valueOfCalled, false, 'valueOf must not be called'); +assert.sameValue(closed, true, 'iterator closed when skippedElements validation fails'); + +closed = false; +let toStringCalled = false; +assert.throws(TypeError, function() { + iterator.includes(0, { + toString() { + toStringCalled = true; + return '0'; + }, + }); +}); +assert.sameValue(toStringCalled, false, 'toString must not be called'); +assert.sameValue(closed, true, 'iterator closed when skippedElements validation fails'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-non-integral-typeerror.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-non-integral-typeerror.js new file mode 100644 index 0000000000000..077a002203e4f --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-non-integral-typeerror.js @@ -0,0 +1,31 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Non-integral Number skippedElements values throw TypeError and close the iterator +features: [iterator-includes] +---*/ + +let closed = false; +let iterator = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error('next should not be read'); + }, + return() { + closed = true; + return {}; + }, +}; + +assert.throws(TypeError, function() { + iterator.includes(0, -0.1); +}); +assert.sameValue(closed, true, 'closed on -0.1'); + +closed = false; +assert.throws(TypeError, function() { + iterator.includes(0, 0.1); +}); +assert.sameValue(closed, true, 'closed on 0.1'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-not-a-number.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-not-a-number.js new file mode 100644 index 0000000000000..1c9f07ae55226 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-not-a-number.js @@ -0,0 +1,36 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + skippedElements values that are not Numbers throw TypeError and close the iterator +features: [iterator-includes] +---*/ + +function assertTypeErrorAndClosed(skippedElements, label) { + let closed = false; + let iterator = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error('next should not be read'); + }, + return() { + closed = true; + return {}; + }, + }; + + assert.throws(TypeError, function() { + iterator.includes(0, skippedElements); + }, label + ': throws TypeError'); + + assert.sameValue(closed, true, label + ': iterator closed'); +} + +assertTypeErrorAndClosed(true, 'boolean'); +assertTypeErrorAndClosed(null, 'null'); +assertTypeErrorAndClosed('1', 'string'); +assertTypeErrorAndClosed({}, 'object'); +assertTypeErrorAndClosed([], 'array'); +assertTypeErrorAndClosed(Symbol(), 'Symbol'); +assertTypeErrorAndClosed(1n, 'BigInt'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-positive-infinity.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-positive-infinity.js new file mode 100644 index 0000000000000..3fe433e8de2ce --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-positive-infinity.js @@ -0,0 +1,30 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + skippedElements of +Infinity always returns false after natural exhaustion +features: [iterator-includes] +---*/ + +let returnCalls = 0; +let count = 0; + +let iter = { + __proto__: Iterator.prototype, + next() { + ++count; + if (count < 4) { + return { done: false, value: count }; + } + return { done: true, value: undefined }; + }, + return() { + ++returnCalls; + return {}; + }, +}; + +assert.sameValue(iter.includes(1, Infinity), false); +assert.sameValue(returnCalls, 0); +assert.sameValue(count, 4); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-positive-integral.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-positive-integral.js new file mode 100644 index 0000000000000..e4290c3ae3458 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-positive-integral.js @@ -0,0 +1,32 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Positive integral skippedElements skips that many iterated values +features: [iterator-includes] +---*/ + +assert.sameValue([4, 5, 6, 7].values().includes(4, 1), false); +assert.sameValue([4, 5, 6, 7].values().includes(4, 2), false); +assert.sameValue([4, 5, 6, 7].values().includes(4, 3), false); +assert.sameValue([4, 5, 6, 7].values().includes(4, 4), false); +assert.sameValue([4, 5, 6, 7].values().includes(4, 5), false); + +assert.sameValue([4, 5, 6, 7].values().includes(5, 1), true); +assert.sameValue([4, 5, 6, 7].values().includes(5, 2), false); +assert.sameValue([4, 5, 6, 7].values().includes(5, 3), false); +assert.sameValue([4, 5, 6, 7].values().includes(5, 4), false); +assert.sameValue([4, 5, 6, 7].values().includes(5, 5), false); + +assert.sameValue([4, 5, 6, 7].values().includes(6, 1), true); +assert.sameValue([4, 5, 6, 7].values().includes(6, 2), true); +assert.sameValue([4, 5, 6, 7].values().includes(6, 3), false); +assert.sameValue([4, 5, 6, 7].values().includes(6, 4), false); +assert.sameValue([4, 5, 6, 7].values().includes(6, 5), false); + +assert.sameValue([4, 5, 6, 7].values().includes(7, 1), true); +assert.sameValue([4, 5, 6, 7].values().includes(7, 2), true); +assert.sameValue([4, 5, 6, 7].values().includes(7, 3), true); +assert.sameValue([4, 5, 6, 7].values().includes(7, 4), false); +assert.sameValue([4, 5, 6, 7].values().includes(7, 5), false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-too-large-rangeerror.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-too-large-rangeerror.js new file mode 100644 index 0000000000000..6996982be8536 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-too-large-rangeerror.js @@ -0,0 +1,31 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Finite skippedElements values greater than Number.MAX_SAFE_INTEGER throw RangeError and close the iterator +features: [iterator-includes] +---*/ + +function assertRangeErrorAndClosed(skippedElements, label) { + let closed = false; + let iterator = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error('next should not be read'); + }, + return() { + closed = true; + return {}; + }, + }; + + assert.throws(RangeError, function() { + iterator.includes(0, skippedElements); + }, label + ': throws RangeError'); + + assert.sameValue(closed, true, label + ': iterator closed'); +} + +assertRangeErrorAndClosed(Number.MAX_SAFE_INTEGER + 1, 'Number.MAX_SAFE_INTEGER + 1'); +assertRangeErrorAndClosed(Number.MAX_SAFE_INTEGER + 3, 'Number.MAX_SAFE_INTEGER + 3'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-zero-and-negative-zero.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-zero-and-negative-zero.js new file mode 100644 index 0000000000000..b18b610ebcd48 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/skipped-elements-zero-and-negative-zero.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + skippedElements of 0 and -0 starts searching from the beginning +features: [iterator-includes] +---*/ + +assert.sameValue([4, 5, 6, 7].values().includes(8, 0), false); +assert.sameValue([4, 5, 6, 7].values().includes(7, 0), true); +assert.sameValue([4, 5, 6, 7].values().includes(6, 0), true); +assert.sameValue([4, 5, 6, 7].values().includes(5, 0), true); +assert.sameValue([4, 5, 6, 7].values().includes(4, 0), true); +assert.sameValue([4, 5, 6, 7].values().includes(3, 0), false); + +assert.sameValue([4, 5, 6, 7].values().includes(8, -0), false); +assert.sameValue([4, 5, 6, 7].values().includes(7, -0), true); +assert.sameValue([4, 5, 6, 7].values().includes(6, -0), true); +assert.sameValue([4, 5, 6, 7].values().includes(5, -0), true); +assert.sameValue([4, 5, 6, 7].values().includes(4, -0), true); +assert.sameValue([4, 5, 6, 7].values().includes(3, -0), false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/symbol-identity.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/symbol-identity.js new file mode 100644 index 0000000000000..3dbad8dff524c --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/symbol-identity.js @@ -0,0 +1,15 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Includes compares symbols by identity +features: [iterator-includes] +---*/ + +let s = Symbol('test'); +let arr = [s]; + +assert.sameValue(arr.values().includes(Symbol('test')), false); +assert.sameValue(arr.values().includes(s), true); +assert.sameValue([].values().includes(s), false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/this-non-callable-next.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/this-non-callable-next.js new file mode 100644 index 0000000000000..74208e1f5ffa1 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/this-non-callable-next.js @@ -0,0 +1,13 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator.prototype.includes throws TypeError when its this value is an object + with a non-callable next +features: [iterator-includes] +---*/ + +assert.throws(TypeError, function() { + Iterator.prototype.includes.call({ next: 0 }, 0); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/this-non-object.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/this-non-object.js new file mode 100644 index 0000000000000..9a30a9cc93339 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/this-non-object.js @@ -0,0 +1,28 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator.prototype.includes throws TypeError when its this value is a non-object +info: | + Iterator.prototype.includes ( searchElement [ , skippedElements ] ) + + 1. Let O be the this value. + 2. If O is not an Object, throw a TypeError exception. + +features: [iterator-includes] +---*/ + +assert.throws(TypeError, function() { + Iterator.prototype.includes.call(null, 0); +}); + +Object.defineProperty(Number.prototype, 'next', { + get: function() { + throw new Test262Error(); + }, +}); + +assert.throws(TypeError, function() { + Iterator.prototype.includes.call(0, 0); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/includes/this-plain-iterator.js b/JSTests/test262/test/built-ins/Iterator/prototype/includes/this-plain-iterator.js new file mode 100644 index 0000000000000..f7397c7133c45 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/includes/this-plain-iterator.js @@ -0,0 +1,23 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.includes +description: > + Iterator.prototype.includes supports a this value that does not inherit from + Iterator.prototype but implements the iterator protocol +features: [iterator-includes] +---*/ + +let iter = { + get next() { + let count = 3; + return function() { + --count; + return count >= 0 ? { done: false, value: count } : { done: true, value: undefined }; + }; + }, +}; + +let result = Iterator.prototype.includes.call(iter, 0); + +assert.sameValue(result, true); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/closes-on-contents-coercion-exception.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/closes-on-contents-coercion-exception.js new file mode 100644 index 0000000000000..33b8e84519ad3 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/closes-on-contents-coercion-exception.js @@ -0,0 +1,36 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join closes its receiver if coercing a value from the iterator throws. +features: [Iterator.prototype.join] +---*/ + +var throwy = { + toString: function () { + throw new Test262Error(); + }, +}; + +var calledNextCount = 0; +var calledReturn = false; +var it = { + next: function () { + ++calledNextCount; + if (calledNextCount > 1) { + return { done: true, value: undefined }; + } + return { done: false, value: throwy }; + }, + return: function () { + calledReturn = true; + }, +}; + +assert.throws(Test262Error, function () { + Iterator.prototype.join.call(it); +}); + +assert.sameValue(calledNextCount, 1); +assert(calledReturn); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/closes-on-separator-coercion-exception.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/closes-on-separator-coercion-exception.js new file mode 100644 index 0000000000000..d08025d885d05 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/closes-on-separator-coercion-exception.js @@ -0,0 +1,33 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join closes its receiver if coercing the separator throws. +features: [Iterator.prototype.join] +---*/ + +var throwy = { + toString: function () { + throw new Test262Error(); + }, +}; + +var gotNext = false; +var calledReturn = false; +var it = { + get next() { + // we use a variable instead of simply throwing because throwing is expected in this test + gotNext = true; + }, + return: function () { + calledReturn = true; + }, +}; + +assert.throws(Test262Error, function () { + Iterator.prototype.join.call(it, throwy); +}); + +assert.sameValue(gotNext, false); +assert(calledReturn); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/contents-nullish.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/contents-nullish.js new file mode 100644 index 0000000000000..763cc8d182ee9 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/contents-nullish.js @@ -0,0 +1,18 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join formats nullish iterator contents as an empty string. +features: [Iterator.prototype.join] +---*/ + +assert.sameValue( + ['one', null, 'two', undefined].values().join(), + 'one,,two,' +); + +assert.sameValue( + ['one', null, 'two', undefined, 'three'].values().join(), + 'one,,two,,three' +); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/contents-tostring.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/contents-tostring.js new file mode 100644 index 0000000000000..c721124473b04 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/contents-tostring.js @@ -0,0 +1,22 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join coerces non-nullish iterator contents to string. +features: [Iterator.prototype.join] +---*/ + +var called = false; +var coercible = { + toString: function () { + if (called) { + throw new Test262Error('toString should be called exactly once'); + } + called = true; + return 'value'; + }, +}; + +assert.sameValue([coercible, 0, true].values().join(), 'value,0,true'); +assert(called); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/descriptor.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/descriptor.js new file mode 100644 index 0000000000000..48c95f1e58e81 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/descriptor.js @@ -0,0 +1,15 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join has default data property attributes. +includes: [propertyHelper.js] +features: [Iterator.prototype.join] +---*/ + +verifyProperty(Iterator.prototype, 'join', { + enumerable: false, + writable: true, + configurable: true +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-error.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-error.js new file mode 100644 index 0000000000000..7f487ac474db5 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-error.js @@ -0,0 +1,24 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join does not close its receiver if the iterator itself throws. +features: [Iterator.prototype.join] +---*/ + +var gotReturn = false; +var it = { + next: function () { + throw new Test262Error(); + }, + get return() { + gotReturn = true; + }, +}; + +assert.throws(Test262Error, function () { + Iterator.prototype.join.call(it); +}); + +assert.sameValue(gotReturn, false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-exhaustion.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-exhaustion.js new file mode 100644 index 0000000000000..cafadddfc7bfb --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-exhaustion.js @@ -0,0 +1,29 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join does not close its receiver if the iterator is exhausted. +features: [Iterator.prototype.join] +---*/ + +var calledNextCount = 0; +var gotReturn = false; +var it = { + next: function () { + ++calledNextCount; + if (calledNextCount > 2) { + return { done: true, value: undefined }; + } + return { done: false, value: 'ES' }; + }, + get return() { + gotReturn = true; + }, +}; + +assert.sameValue(Iterator.prototype.join.call(it), 'ES,ES'); + +assert.sameValue(calledNextCount, 3); + +assert.sameValue(gotReturn, false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-protocol-violation.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-protocol-violation.js new file mode 100644 index 0000000000000..fcb8e0804f77d --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/does-not-close-on-iterator-protocol-violation.js @@ -0,0 +1,24 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join does not close its receiver if the iterator itself violates the iterator protocol. +features: [Iterator.prototype.join] +---*/ + +var gotReturn = false; +var it = { + next: function () { + return null; + }, + get return() { + gotReturn = true; + }, +}; + +assert.throws(TypeError, function () { + Iterator.prototype.join.call(it); +}); + +assert.sameValue(gotReturn, false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/does-not-close-on-next-getter-error.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/does-not-close-on-next-getter-error.js new file mode 100644 index 0000000000000..ae44b3b74a7d4 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/does-not-close-on-next-getter-error.js @@ -0,0 +1,24 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join does not close its receiver if looking up `next` throws. +features: [Iterator.prototype.join] +---*/ + +var gotReturn = false; +var it = { + get next() { + throw new Test262Error(); + }, + get return() { + gotReturn = true; + }, +}; + +assert.throws(Test262Error, function () { + Iterator.prototype.join.call(it); +}); + +assert.sameValue(gotReturn, false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/length.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/length.js new file mode 100644 index 0000000000000..8d32126b02ba2 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/length.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join.length is 1. +includes: [propertyHelper.js] +features: [Iterator.prototype.join] +---*/ + +verifyProperty(Iterator.prototype.join, 'length', { + value: 1, + enumerable: false, + writable: false, + configurable: true +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/name.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/name.js new file mode 100644 index 0000000000000..1e94ebd0c91f8 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/name.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join.name is "join". +includes: [propertyHelper.js] +features: [Iterator.prototype.join] +---*/ + +verifyProperty(Iterator.prototype.join, 'name', { + value: 'join', + enumerable: false, + writable: false, + configurable: true +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/next-lookup-after-separator-tostring.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/next-lookup-after-separator-tostring.js new file mode 100644 index 0000000000000..ff65affcd0272 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/next-lookup-after-separator-tostring.js @@ -0,0 +1,39 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join looks up `next` on its receiver only after coercing the separator. +features: [Iterator.prototype.join] +---*/ + +var effects = []; + +var separator = { + toString: function () { + effects.push('toString'); + return '&&'; + }, +}; + +var calledNextCount = 0; +var it = { + get next() { + effects.push('get next'); + return function () { + ++calledNextCount; + if (calledNextCount === 1) { + return { done: false, value: 'one' }; + } else if (calledNextCount === 2) { + return { done: false, value: 'two' }; + } else { + return { done: true, value: undefined }; + } + }; + }, +}; + +assert.sameValue(Iterator.prototype.join.call(it, separator), 'one&&two'); +assert.sameValue(calledNextCount, 3); + +assert.compareArray(effects, ['toString', 'get next']); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/not-a-constructor.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/not-a-constructor.js new file mode 100644 index 0000000000000..0929c2b95852a --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/not-a-constructor.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join is not a constructor +includes: [isConstructor.js] +features: [Iterator.prototype.join, Reflect.construct] +---*/ + +assert(!isConstructor(Iterator.prototype.join), "Iterator.prototype.join should not be a constructor"); + +assert.throws(TypeError, function() { + var iterator = [].values(); + new iterator.join(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/receiver-not-object.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/receiver-not-object.js new file mode 100644 index 0000000000000..15b543299b6a4 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/receiver-not-object.js @@ -0,0 +1,38 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join throws if the receiver is not an object. +features: [Iterator.prototype.join] +---*/ + +var it = [].values(); + +assert.throws(TypeError, function () { + it.join.call(undefined); +}); + +assert.throws(TypeError, function () { + it.join.call(null); +}); + +assert.throws(TypeError, function () { + it.join.call(false); +}); + +assert.throws(TypeError, function () { + it.join.call(0); +}); + +assert.throws(TypeError, function () { + it.join.call(0n); +}); + +assert.throws(TypeError, function () { + it.join.call(""); +}); + +assert.throws(TypeError, function () { + it.join.call(Symbol()); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/results-empty-separator.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/results-empty-separator.js new file mode 100644 index 0000000000000..ed1ea2adab5bf --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/results-empty-separator.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join allows empty string as separator. +features: [Iterator.prototype.join] +---*/ + +assert.sameValue([].values().join(''), ''); + +assert.sameValue(['one'].values().join(''), 'one'); + +assert.sameValue(['one', 'two'].values().join(''), 'onetwo'); + +assert.sameValue(['one', 'two', 'three'].values().join(''), 'onetwothree'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/results-no-separator.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/results-no-separator.js new file mode 100644 index 0000000000000..231bbba8f5f15 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/results-no-separator.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join joins using a comma if no separator is passed. +features: [Iterator.prototype.join] +---*/ + +assert.sameValue([].values().join(), ''); + +assert.sameValue(['one'].values().join(), 'one'); + +assert.sameValue(['one', 'two'].values().join(), 'one,two'); + +assert.sameValue(['one', 'two', 'three'].values().join(), 'one,two,three'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/results-nonempty-separator.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/results-nonempty-separator.js new file mode 100644 index 0000000000000..3ed3424ad5c2d --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/results-nonempty-separator.js @@ -0,0 +1,16 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join joins using the passed separator. +features: [Iterator.prototype.join] +---*/ + +assert.sameValue([].values().join('&&'), ''); + +assert.sameValue(['one'].values().join('&&'), 'one'); + +assert.sameValue(['one', 'two'].values().join('&&'), 'one&&two'); + +assert.sameValue(['one', 'two', 'three'].values().join('&&'), 'one&&two&&three'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/join/separator-tostring.js b/JSTests/test262/test/built-ins/Iterator/prototype/join/separator-tostring.js new file mode 100644 index 0000000000000..8c750ae3c0837 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/join/separator-tostring.js @@ -0,0 +1,26 @@ +// Copyright (C) 2025 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-iterator.prototype.join +description: Iterator.prototype.join coerces the passed separator to a string. +features: [Iterator.prototype.join] +---*/ + +var called = false; +var coercible = { + toString: function () { + if (called) { + throw new Test262Error('toString should be called exactly once'); + } + called = true; + return '&&'; + }, +}; + +assert.sameValue(['one', 'two', 'three'].values().join(coercible), 'one&&two&&three'); +assert(called); + +assert.sameValue(['one', 'two', 'three'].values().join(undefined), 'one,two,three'); + +assert.sameValue(['one', 'two', 'three'].values().join(null), 'onenulltwonullthree'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/take/argument-effect-order.js b/JSTests/test262/test/built-ins/Iterator/prototype/take/argument-effect-order.js index d37557cd7393a..70a464824dc8b 100644 --- a/JSTests/test262/test/built-ins/Iterator/prototype/take/argument-effect-order.js +++ b/JSTests/test262/test/built-ins/Iterator/prototype/take/argument-effect-order.js @@ -1,19 +1,28 @@ // Copyright (C) 2023 Michael Ficarra. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- -esid: sec-iteratorprototype.take +esid: sec-iterator.prototype.take description: > Arguments and this value are evaluated in the correct order info: | - %Iterator.prototype%.take ( limit ) + Iterator.prototype.take ( limit ) - 1. Let O be the this value. - 2. If O is not an Object, throw a TypeError exception. - 3. Let numLimit be ? ToNumber(limit). - 4. If numLimit is NaN, throw a RangeError exception. - 5. Let integerLimit be ! ToIntegerOrInfinity(numLimit). - 6. If integerLimit < 0, throw a RangeError exception. - 7. Let iterated be ? GetIteratorDirect(O). + 1. Let obj be the this value. + 2. If obj is not an Object, throw a TypeError exception. + 3. Let iterated be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: undefined, [[Done]]: false }. + 4. Let numLimit be Completion(ToNumber(limit)). + 5. IfAbruptCloseIterator(numLimit, iterated). + 6. If numLimit is NaN, then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + 7. If numLimit is finite and numLimit > 𝔽(2**53 - 1), then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + 8. Let integerLimit be ! ToIntegerOrInfinity(numLimit). + 9. If integerLimit < 0, then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + 10. Set iterated to ? GetIteratorDirect(obj). includes: [compareArray.js] features: [iterator-helpers] @@ -55,6 +64,29 @@ assert.compareArray(effects, []); effects = []; +assert.throws(RangeError, function () { + Iterator.prototype.take.call( + { + get next() { + effects.push('get next'); + return function () { + return { done: true, value: undefined }; + }; + }, + }, + { + valueOf() { + effects.push('ToNumber limit'); + return Number.MAX_SAFE_INTEGER + 1; + }, + } + ); +}); + +assert.compareArray(effects, ['ToNumber limit']); + +effects = []; + assert.throws(RangeError, function () { Iterator.prototype.take.call( { diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/take/argument-validation-failure-closes-underlying.js b/JSTests/test262/test/built-ins/Iterator/prototype/take/argument-validation-failure-closes-underlying.js index 7c9d4a326440b..a049ad24d4ff3 100644 --- a/JSTests/test262/test/built-ins/Iterator/prototype/take/argument-validation-failure-closes-underlying.js +++ b/JSTests/test262/test/built-ins/Iterator/prototype/take/argument-validation-failure-closes-underlying.js @@ -1,11 +1,11 @@ // Copyright (C) 2024 Kevin Gibbons. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- -esid: sec-iteratorprototype.take +esid: sec-iterator.prototype.take description: > Underlying iterator is closed when argument validation fails info: | - %Iterator.prototype%.take ( limit ) + Iterator.prototype.take ( limit ) features: [iterator-helpers] flags: [] @@ -34,6 +34,12 @@ assert.throws(RangeError, function() { }); assert.sameValue(closed, true); +closed = false; +assert.throws(RangeError, function() { + closable.take(Number.MAX_SAFE_INTEGER + 1); +}); +assert.sameValue(closed, true); + closed = false; assert.throws(RangeError, function() { closable.take(-1); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/take/limit-rangeerror.js b/JSTests/test262/test/built-ins/Iterator/prototype/take/limit-rangeerror.js index f303d192d7472..81fb313bc2830 100644 --- a/JSTests/test262/test/built-ins/Iterator/prototype/take/limit-rangeerror.js +++ b/JSTests/test262/test/built-ins/Iterator/prototype/take/limit-rangeerror.js @@ -1,15 +1,23 @@ // Copyright (C) 2020 Rick Waldron. All rights reserved. // This code is governed by the BSD license found in the LICENSE file. /*--- -esid: sec-iteratorprototype.take +esid: sec-iterator.prototype.take description: > - Throws a RangeError exception when limit argument is NaN or less than 0. + Throws a RangeError exception when limit argument is NaN, less than 0, or + finite and greater than Number.MAX_SAFE_INTEGER. info: | - %Iterator.prototype%.take ( limit ) + Iterator.prototype.take ( limit ) - 4. If numLimit is NaN, throw a RangeError exception. - 5. Let integerLimit be ! ToIntegerOrInfinity(numLimit). - 6. If integerLimit < 0, throw a RangeError exception. + 6. If numLimit is NaN, then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + 7. If numLimit is finite and numLimit > 𝔽(2**53 - 1), then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + 8. Let integerLimit be ! ToIntegerOrInfinity(numLimit). + 9. If integerLimit < 0, then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). features: [iterator-helpers] ---*/ @@ -18,6 +26,8 @@ let iterator = (function* () {})(); iterator.take(0); iterator.take(-0.5); iterator.take(null); +iterator.take(Number.MAX_SAFE_INTEGER); +iterator.take(Infinity); assert.throws(RangeError, () => { iterator.take(-1); @@ -34,3 +44,7 @@ assert.throws(RangeError, () => { assert.throws(RangeError, () => { iterator.take(NaN); }); + +assert.throws(RangeError, () => { + iterator.take(Number.MAX_SAFE_INTEGER + 1); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/argument-effect-order.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/argument-effect-order.js new file mode 100644 index 0000000000000..089840bb907d3 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/argument-effect-order.js @@ -0,0 +1,83 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Arguments and this value are validated in the correct order +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 1. Let O be the this value. + 2. If O is not an Object, throw a TypeError exception. + 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + 4. If windowSize is not a Number, throw a TypeError ... IteratorClose. + 5. If windowSize is not an integral Number, throw a TypeError ... IteratorClose. + 6. If windowSize is not in the inclusive interval from 1𝔽 to 𝔽(2^32 - 1), then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + 7. If undersized is undefined, set undersized to "only-full". + 8. If undersized is neither "only-full" nor "allow-partial", then + a. Let error be ThrowCompletion(a newly created TypeError object). + b. Return ? IteratorClose(iterated, error). + 9. Set iterated to ? GetIteratorDirect(O). + +includes: [compareArray.js] +features: [iterator-chunking] +---*/ +let effects = []; + +// TypeError for non-object this before windowSize is examined +assert.throws(TypeError, function () { + Iterator.prototype.windows.call(null, 0, 'bad'); +}); + +// RangeError for invalid windowSize before undersized is examined +assert.throws(RangeError, function () { + Iterator.prototype.windows.call( + { + get next() { + effects.push('get next'); + return function () { + return { done: true, value: undefined }; + }; + } + }, + 0, + 'bad' + ); +}); + +assert.compareArray(effects, []); + +// TypeError for invalid undersized before next is accessed +assert.throws(TypeError, function () { + Iterator.prototype.windows.call( + { + get next() { + effects.push('get next'); + return function () { + return { done: true, value: undefined }; + }; + } + }, + 1, + 'bad' + ); +}); + +assert.compareArray(effects, []); + +// With all valid args, next getter IS accessed (GetIteratorDirect runs) +Iterator.prototype.windows.call( + { + get next() { + effects.push('get next'); + return function () { + return { done: true, value: undefined }; + }; + } + }, + 1 +); + +assert.compareArray(effects, ['get next']); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/argument-validation-failure-close-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/argument-validation-failure-close-throws.js new file mode 100644 index 0000000000000..f3a94e158c61e --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/argument-validation-failure-close-throws.js @@ -0,0 +1,40 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Original validation error is preserved when closing the underlying iterator + throws +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + 4. If windowSize is not a Number, throw a TypeError exception ... IteratorClose(iterated, error). + 5. If windowSize is not an integral Number, throw a TypeError exception ... IteratorClose(iterated, error). + 6. If windowSize is not in the inclusive interval from 1𝔽 to 𝔽(2^32 - 1), then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + ... + 8. If undersized is neither "only-full" nor "allow-partial", then + a. Let error be ThrowCompletion(a newly created TypeError object). + b. Return ? IteratorClose(iterated, error). + +features: [iterator-chunking] +---*/ + +let returnGets = 0; +let closable = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error('next should not be read'); + }, + get return() { + ++returnGets; + throw new Test262Error('return getter error should be masked'); + }, +}; + +assert.throws(TypeError, function () { + closable.windows(1, 'bad'); +}); +assert.sameValue(returnGets, 1, 'return getter is still consulted'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/argument-validation-failure-closes-underlying.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/argument-validation-failure-closes-underlying.js new file mode 100644 index 0000000000000..5fe46291c35e3 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/argument-validation-failure-closes-underlying.js @@ -0,0 +1,71 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Underlying iterator is closed when argument validation fails +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }. + 4. If windowSize is not a Number, throw a TypeError exception ... IteratorClose(iterated, error). + 5. If windowSize is not an integral Number, throw a TypeError exception ... IteratorClose(iterated, error). + 6. If windowSize is not in the inclusive interval from 1𝔽 to 𝔽(2^32 - 1), then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + ... + 8. If undersized is neither "only-full" nor "allow-partial", then + a. Let error be ThrowCompletion(a newly created TypeError object). + b. Return ? IteratorClose(iterated, error). + +features: [iterator-chunking] +---*/ + +let closed = false; +let closable = { + __proto__: Iterator.prototype, + get next() { + throw new Test262Error('next should not be read'); + }, + return() { + closed = true; + return {}; + } +}; + +// windowSize validation failure closes +assert.throws(TypeError, function () { + closable.windows(); +}); +assert.sameValue(closed, true, 'iterator closed when windowSize is undefined'); + +closed = false; +assert.throws(RangeError, function () { + closable.windows(0); +}); +assert.sameValue(closed, true, 'iterator closed when windowSize is 0'); + +closed = false; +assert.throws(TypeError, function () { + closable.windows(NaN); +}); +assert.sameValue(closed, true, 'iterator closed when windowSize is NaN'); + +closed = false; +assert.throws(TypeError, function () { + closable.windows('1'); +}); +assert.sameValue(closed, true, 'iterator closed when windowSize is a string'); + +// undersized validation failure closes +closed = false; +assert.throws(TypeError, function () { + closable.windows(1, null); +}); +assert.sameValue(closed, true, 'iterator closed when undersized is null'); + +closed = false; +assert.throws(TypeError, function () { + closable.windows(1, 'bad'); +}); +assert.sameValue(closed, true, 'iterator closed when undersized is invalid string'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/callable.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/callable.js new file mode 100644 index 0000000000000..a179cf197767b --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/callable.js @@ -0,0 +1,13 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows is callable +features: [iterator-chunking, generators] +---*/ +function* g() {} +Iterator.prototype.windows.call(g(), 1); + +let iter = g(); +iter.windows(1); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/exhaustion-does-not-call-return.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/exhaustion-does-not-call-return.js new file mode 100644 index 0000000000000..bce877113031c --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/exhaustion-does-not-call-return.js @@ -0,0 +1,33 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Underlying iterator return is not called when result iterator is exhausted +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, generators, class] +---*/ +function* g() { + yield 0; + yield 1; + yield 2; +} + +class TestIterator extends Iterator { + get next() { + let n = g(); + return function () { + return n.next(); + }; + } + return() { + throw new Test262Error(); + } +} + +let iterator = new TestIterator().windows(2); +iterator.next(); +iterator.next(); +iterator.next(); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/get-next-method-only-once.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/get-next-method-only-once.js new file mode 100644 index 0000000000000..37283f0c570ef --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/get-next-method-only-once.js @@ -0,0 +1,40 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Gets the next method from the underlying iterator only once +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 7. Set iterated to ? GetIteratorDirect(O). + +features: [iterator-chunking, generators, class] +---*/ +let nextGets = 0; +let nextCalls = 0; + +class CountingIterator extends Iterator { + get next() { + ++nextGets; + let iter = (function* () { + for (let i = 1; i < 5; ++i) { + yield i; + } + })(); + return function () { + ++nextCalls; + return iter.next(); + }; + } +} + +let iterator = new CountingIterator(); + +assert.sameValue(nextGets, 0); +assert.sameValue(nextCalls, 0); + +for (const value of iterator.windows(2)); + +assert.sameValue(nextGets, 1); +assert.sameValue(nextCalls, 5); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/get-next-method-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/get-next-method-throws.js new file mode 100644 index 0000000000000..80579e064e86a --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/get-next-method-throws.js @@ -0,0 +1,27 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Throws when getting the next method from the underlying iterator throws +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 7. Set iterated to ? GetIteratorDirect(O). + +features: [iterator-chunking, class] +---*/ +class ThrowingIterator extends Iterator { + get next() { + throw new Test262Error(); + } + get return() { + throw new TypeError(); + } +} + +let iter = new ThrowingIterator(); + +assert.throws(Test262Error, function () { + iter.windows(1); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/get-return-method-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/get-return-method-throws.js new file mode 100644 index 0000000000000..976e846792fa5 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/get-return-method-throws.js @@ -0,0 +1,29 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Underlying iterator return is a throwing getter +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, class] +---*/ +class TestIterator extends Iterator { + next() { + return { + done: false, + value: 1, + }; + } + get return() { + throw new Test262Error(); + } +} + +let iterator = new TestIterator().windows(1); +iterator.next(); + +assert.throws(Test262Error, function () { + iterator.return(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/is-function.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/is-function.js new file mode 100644 index 0000000000000..1297bf16f5329 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/is-function.js @@ -0,0 +1,10 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows is a built-in function +features: [iterator-chunking] +---*/ + +assert.sameValue(typeof Iterator.prototype.windows, 'function'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/iterator-already-exhausted.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/iterator-already-exhausted.js new file mode 100644 index 0000000000000..dc4d177b55ba5 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/iterator-already-exhausted.js @@ -0,0 +1,27 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows yields no windows when the iterator is already + exhausted, regardless of undersized mode +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 8.a.i. Let value be ? IteratorStepValue(iterated). + 8.a.ii. If value is ~done~, then + 8.a.ii.a. If undersized is "allow-partial", buffer is not empty, and ... + 8.a.ii.b. Return ReturnCompletion(undefined). + +features: [iterator-chunking, generators] +---*/ +function* g() {} + +let windows = Array.from(g().windows(2)); +assert.sameValue(windows.length, 0, 'default undersized on empty iterator'); + +windows = Array.from(g().windows(2, 'only-full')); +assert.sameValue(windows.length, 0, '"only-full" on empty iterator'); + +windows = Array.from(g().windows(2, 'allow-partial')); +assert.sameValue(windows.length, 0, '"allow-partial" on empty iterator'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/iterator-return-method-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/iterator-return-method-throws.js new file mode 100644 index 0000000000000..785d8548a4090 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/iterator-return-method-throws.js @@ -0,0 +1,28 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator has throwing return +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, class] +---*/ +class IteratorThrows extends Iterator { + next() { + return { + done: false, + value: 0, + }; + } + return() { + throw new Test262Error(); + } +} + +let iterator = new IteratorThrows().windows(1); + +assert.throws(Test262Error, function () { + iterator.return(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/length.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/length.js new file mode 100644 index 0000000000000..bda069304092b --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/length.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows has a "length" property whose value is 1. +info: | + ECMAScript Standard Built-in Objects + + Unless otherwise specified, the length property of a built-in + Function object has the attributes { [[Writable]]: false, [[Enumerable]]: + false, [[Configurable]]: true }. +features: [iterator-chunking] +includes: [propertyHelper.js] +---*/ + +verifyProperty(Iterator.prototype.windows, 'length', { + value: 1, + writable: false, + enumerable: false, + configurable: true, +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/name.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/name.js new file mode 100644 index 0000000000000..1b877069336d8 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/name.js @@ -0,0 +1,27 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + The "name" property of Iterator.prototype.windows +info: | + 17 ECMAScript Standard Built-in Objects + + Every built-in Function object, including constructors, that is not + identified as an anonymous function has a name property whose value is a + String. Unless otherwise specified, this value is the name that is given to + the function in this specification. + + Unless otherwise specified, the name property of a built-in Function + object, if it exists, has the attributes { [[Writable]]: false, + [[Enumerable]]: false, [[Configurable]]: true }. +features: [iterator-chunking] +includes: [propertyHelper.js] +---*/ + +verifyProperty(Iterator.prototype.windows, 'name', { + value: 'windows', + writable: false, + enumerable: false, + configurable: true, +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-returns-non-object.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-returns-non-object.js new file mode 100644 index 0000000000000..df1f9b8ab8933 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-returns-non-object.js @@ -0,0 +1,25 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Underlying iterator next returns non-object +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, class] +---*/ +class NonObjectIterator extends Iterator { + next() { + return null; + } + get return() { + throw new Test262Error(); + } +} + +let iterator = new NonObjectIterator().windows(1); + +assert.throws(TypeError, function () { + iterator.next(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-returns-throwing-done.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-returns-throwing-done.js new file mode 100644 index 0000000000000..33c285aef65f7 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-returns-throwing-done.js @@ -0,0 +1,30 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Underlying iterator next returns object with throwing done getter +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, class] +---*/ +class ThrowingIterator extends Iterator { + next() { + return { + get done() { + throw new Test262Error(); + }, + value: 1, + }; + } + get return() { + throw new Test262Error(); + } +} + +let iterator = new ThrowingIterator().windows(1); + +assert.throws(Test262Error, function () { + iterator.next(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-returns-throwing-value-done.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-returns-throwing-value-done.js new file mode 100644 index 0000000000000..30c77fea563aa --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-returns-throwing-value-done.js @@ -0,0 +1,28 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Underlying iterator next returns object with throwing value getter, but is + already done +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, class] +---*/ +class ThrowingIterator extends Iterator { + next() { + return { + done: true, + get value() { + throw new Test262Error(); + } + }; + } + get return() { + throw new Test262Error(); + } +} + +let iterator = new ThrowingIterator().windows(1); +iterator.next(); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-returns-throwing-value.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-returns-throwing-value.js new file mode 100644 index 0000000000000..62ed5d1ed256f --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-returns-throwing-value.js @@ -0,0 +1,30 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Underlying iterator next returns object with throwing value getter +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, class] +---*/ +class ThrowingIterator extends Iterator { + next() { + return { + done: false, + get value() { + throw new Test262Error(); + } + }; + } + get return() { + throw new TypeError(); + } +} + +let iterator = new ThrowingIterator().windows(1); + +assert.throws(Test262Error, function () { + iterator.next(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-throws.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-throws.js new file mode 100644 index 0000000000000..4e338e39a47a4 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/next-method-throws.js @@ -0,0 +1,25 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Underlying iterator next throws +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, class] +---*/ +class ThrowingIterator extends Iterator { + next() { + throw new Test262Error(); + } + get return() { + throw new TypeError(); + } +} + +let iterator = new ThrowingIterator().windows(1); + +assert.throws(Test262Error, function () { + iterator.next(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/non-constructible.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/non-constructible.js new file mode 100644 index 0000000000000..90f5f0c6aecc8 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/non-constructible.js @@ -0,0 +1,26 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows is not constructible. + + Built-in function objects that are not identified as constructors do not + implement the [[Construct]] internal method unless otherwise specified in + the description of a particular function. +features: [iterator-chunking, generators] +---*/ +function* g() {} +let iter = g(); + +assert.throws(TypeError, () => { + new iter.windows(1); +}); + +assert.throws(TypeError, () => { + new Iterator.prototype.windows(1); +}); + +assert.throws(TypeError, () => { + new class extends Iterator {}.windows(1); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/prop-desc.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/prop-desc.js new file mode 100644 index 0000000000000..7a7dd12d061c3 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/prop-desc.js @@ -0,0 +1,23 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Property descriptor of Iterator.prototype.windows +info: | + Iterator.prototype.windows + + 17 ECMAScript Standard Built-in Objects + + Every other data property described in clauses 18 through 26 and in Annex B.2 + has the attributes { [[Writable]]: true, [[Enumerable]]: false, + [[Configurable]]: true } unless otherwise specified. +features: [iterator-chunking] +includes: [propertyHelper.js] +---*/ + +verifyProperty(Iterator.prototype, 'windows', { + writable: true, + enumerable: false, + configurable: true, +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/proto.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/proto.js new file mode 100644 index 0000000000000..93f4cbf44a14c --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/proto.js @@ -0,0 +1,11 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + The value of the [[Prototype]] internal slot of Iterator.prototype.windows is the + intrinsic object %FunctionPrototype%. +features: [iterator-chunking] +---*/ + +assert.sameValue(Object.getPrototypeOf(Iterator.prototype.windows), Function.prototype); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/result-is-iterator.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/result-is-iterator.js new file mode 100644 index 0000000000000..b8ddbab315d94 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/result-is-iterator.js @@ -0,0 +1,18 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + The value returned by Iterator.prototype.windows is an Iterator instance +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 8. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterators]] »). + +features: [iterator-chunking, generators] +---*/ + +assert( + (function* () {})().windows(1) instanceof Iterator, + 'function*(){}().windows(1) must return an Iterator' +); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/return-is-forwarded-to-underlying-iterator.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/return-is-forwarded-to-underlying-iterator.js new file mode 100644 index 0000000000000..4ee9f8a1824d6 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/return-is-forwarded-to-underlying-iterator.js @@ -0,0 +1,32 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Underlying iterator return is called when result iterator is closed +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, class] +---*/ +let returnCount = 0; + +class TestIterator extends Iterator { + next() { + return { + done: false, + value: 1, + }; + } + return() { + ++returnCount; + return {}; + } +} + +let iterator = new TestIterator().windows(2); +assert.sameValue(returnCount, 0); +iterator.return(); +assert.sameValue(returnCount, 1); +iterator.return(); +assert.sameValue(returnCount, 1); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/return-is-not-forwarded-after-exhaustion.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/return-is-not-forwarded-after-exhaustion.js new file mode 100644 index 0000000000000..daa201cf203aa --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/return-is-not-forwarded-after-exhaustion.js @@ -0,0 +1,35 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Underlying iterator return is not called after result iterator observes + that underlying iterator is exhausted +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, class] +---*/ + +class TestIterator extends Iterator { + next() { + return { + done: true, + value: undefined, + }; + } + return() { + throw new Test262Error(); + } +} + +let iterator = new TestIterator().windows(1); +assert.throws(Test262Error, function () { + iterator.return(); +}); +iterator.next(); +iterator.return(); + +iterator = new TestIterator().windows(1); +iterator.next(); +iterator.return(); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/this-non-callable-next.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/this-non-callable-next.js new file mode 100644 index 0000000000000..3db8dbcc7df90 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/this-non-callable-next.js @@ -0,0 +1,19 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows throws TypeError when its this value is an object + with a non-callable next +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 7. Set iterated to ? GetIteratorDirect(O). + +features: [iterator-chunking] +---*/ +let iter = Iterator.prototype.windows.call({ next: 0 }, 1); + +assert.throws(TypeError, function () { + iter.next(); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/this-non-object.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/this-non-object.js new file mode 100644 index 0000000000000..0fa9b72881e91 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/this-non-object.js @@ -0,0 +1,26 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows throws TypeError when its this value is a non-object +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 1. Let O be the this value. + 2. If O is not an Object, throw a TypeError exception. + +features: [iterator-chunking] +---*/ +assert.throws(TypeError, function () { + Iterator.prototype.windows.call(null, 1); +}); + +Object.defineProperty(Number.prototype, 'next', { + get: function () { + throw new Test262Error(); + } +}); +assert.throws(TypeError, function () { + Iterator.prototype.windows.call(0, 1); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/this-plain-iterator.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/this-plain-iterator.js new file mode 100644 index 0000000000000..064f9346e310f --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/this-plain-iterator.js @@ -0,0 +1,36 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows supports a this value that does not inherit from + Iterator.prototype but implements the iterator protocol +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking] +includes: [compareArray.js] +---*/ +let iter = { + get next() { + let count = 3; + return function () { + --count; + return count >= 0 ? { done: false, value: count } : { done: true, value: undefined }; + }; + } +}; + +let windowed = Iterator.prototype.windows.call(iter, 2); + +let result = windowed.next(); +assert.compareArray(result.value, [2, 1]); +assert.sameValue(result.done, false); + +result = windowed.next(); +assert.compareArray(result.value, [1, 0]); +assert.sameValue(result.done, false); + +result = windowed.next(); +assert.sameValue(result.value, undefined); +assert.sameValue(result.done, true); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/throws-typeerror-when-generator-is-running.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/throws-typeerror-when-generator-is-running.js new file mode 100644 index 0000000000000..0f24dc2fa7eab --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/throws-typeerror-when-generator-is-running.js @@ -0,0 +1,42 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Throws a TypeError when the closure generator is already running. +info: | + %IteratorHelperPrototype%.next ( ) + 1. Return ? GeneratorResume(this value, undefined, "Iterator Helper"). + + 27.5.3.3 GeneratorResume ( generator, value, generatorBrand ) + 1. Let state be ? GeneratorValidate(generator, generatorBrand). + ... + + 27.5.3.2 GeneratorValidate ( generator, generatorBrand ) + ... + 6. If state is executing, throw a TypeError exception. + ... + +features: [iterator-chunking] +---*/ + +var loopCount = 0; + +var iter; +var iterator = { + get next() { + return function () { + loopCount++; + iter.next(); + return { done: false, value: 0 }; + }; + } +}; + +iter = Iterator.prototype.windows.call(iterator, 1); + +assert.throws(TypeError, function () { + iter.next(); +}); + +assert.sameValue(loopCount, 1); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/underlying-iterator-advanced-in-parallel.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/underlying-iterator-advanced-in-parallel.js new file mode 100644 index 0000000000000..0a869ffdb4484 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/underlying-iterator-advanced-in-parallel.js @@ -0,0 +1,35 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Underlying iterator is advanced after calling windows +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, generators] +includes: [compareArray.js] +---*/ +let iterator = (function* () { + for (let i = 0; i < 6; ++i) { + yield i; + } +})(); + +let windowed = iterator.windows(2); + +let result = windowed.next(); +assert.compareArray(result.value, [0, 1]); +assert.sameValue(result.done, false); + +let { value, done } = iterator.next(); +assert.sameValue(value, 2); +assert.sameValue(done, false); + +result = windowed.next(); +assert.compareArray(result.value, [1, 3]); +assert.sameValue(result.done, false); + +result = windowed.next(); +assert.compareArray(result.value, [3, 4]); +assert.sameValue(result.done, false); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/underlying-iterator-closed-in-parallel.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/underlying-iterator-closed-in-parallel.js new file mode 100644 index 0000000000000..8b0d0986f42d9 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/underlying-iterator-closed-in-parallel.js @@ -0,0 +1,25 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Underlying iterator is closed after calling windows +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, generators] +---*/ +let iterator = (function* () { + for (let i = 0; i < 5; ++i) { + yield i; + } +})(); + +let windowed = iterator.windows(2); + +iterator.return(); + +let { value, done } = windowed.next(); + +assert.sameValue(value, undefined); +assert.sameValue(done, true); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/undersized-default.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/undersized-default.js new file mode 100644 index 0000000000000..d98a2de8c6cb7 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/undersized-default.js @@ -0,0 +1,33 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + When undersized is undefined or omitted, it defaults to "only-full" behavior +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 5. If undersized is undefined, set undersized to "only-full". + +features: [iterator-chunking, generators] +---*/ +function* g() { + yield 0; + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; +} + +// With windowSize larger than iterator, "only-full" yields nothing +let result; + +result = Array.from(g().windows(100)); +assert.sameValue(result.length, 0, 'omitted undersized defaults to "only-full"'); + +result = Array.from(g().windows(100, undefined)); +assert.sameValue(result.length, 0, 'explicit undefined defaults to "only-full"'); + +result = Array.from(g().windows(100, 'only-full')); +assert.sameValue(result.length, 0, 'explicit "only-full" yields nothing'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/undersized-invalid.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/undersized-invalid.js new file mode 100644 index 0000000000000..90b21feecfb44 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/undersized-invalid.js @@ -0,0 +1,50 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows throws TypeError when undersized is not a valid + value +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 5. If undersized is undefined, set undersized to "only-full". + 6. If undersized is neither "only-full" nor "allow-partial", then + a. Let error be ThrowCompletion(a newly created TypeError object). + b. Return ? IteratorClose(iterated, error). + +features: [iterator-chunking, generators] +---*/ +let iterator = (function* () {})(); + +assert.throws(TypeError, () => { + iterator.windows(1, null); +}); + +assert.throws(TypeError, () => { + iterator.windows(1, ''); +}); + +assert.throws(TypeError, () => { + iterator.windows(1, 'something else'); +}); + +assert.throws(TypeError, () => { + iterator.windows(1, 0); +}); + +assert.throws(TypeError, () => { + iterator.windows(1, true); +}); + +assert.throws(TypeError, () => { + iterator.windows(1, false); +}); + +assert.throws(TypeError, () => { + iterator.windows(1, {}); +}); + +assert.throws(TypeError, () => { + iterator.windows(1, Symbol()); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/windowSize-no-coercion.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windowSize-no-coercion.js new file mode 100644 index 0000000000000..9f36740c40391 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windowSize-no-coercion.js @@ -0,0 +1,38 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows does not coerce windowSize using ToNumber; the + argument must already be a Number. Unlike take/drop, valueOf and toString + are never called. +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 4. If windowSize is not a Number, throw a TypeError exception. + +features: [iterator-chunking, generators] +---*/ +let iterator = (function* () {})(); + +let valueOfCalled = false; +assert.throws(TypeError, () => { + iterator.windows({ + valueOf() { + valueOfCalled = true; + return 2; + } + }); +}); +assert.sameValue(valueOfCalled, false, 'valueOf must not be called'); + +let toStringCalled = false; +assert.throws(TypeError, () => { + iterator.windows({ + toString() { + toStringCalled = true; + return '2'; + } + }); +}); +assert.sameValue(toStringCalled, false, 'toString must not be called'); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/windowSize-not-a-number.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windowSize-not-a-number.js new file mode 100644 index 0000000000000..bfa86bdf3fad1 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windowSize-not-a-number.js @@ -0,0 +1,68 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows throws TypeError when windowSize is not an + integral Number +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 4. If windowSize is not a Number, throw a TypeError exception. + 5. If windowSize is not an integral Number, throw a TypeError exception. + +features: [iterator-chunking, generators] +---*/ +let iterator = (function* () {})(); + +assert.throws(TypeError, () => { + iterator.windows(); +}); + +assert.throws(TypeError, () => { + iterator.windows(undefined); +}); + +assert.throws(TypeError, () => { + iterator.windows('1'); +}); + +assert.throws(TypeError, () => { + iterator.windows(true); +}); + +assert.throws(TypeError, () => { + iterator.windows(null); +}); + +assert.throws(TypeError, () => { + iterator.windows({}); +}); + +assert.throws(TypeError, () => { + iterator.windows(Symbol()); +}); + +assert.throws(TypeError, () => { + iterator.windows([2]); +}); + +assert.throws(TypeError, () => { + iterator.windows(NaN); +}); + +assert.throws(TypeError, () => { + iterator.windows(0.5); +}); + +assert.throws(TypeError, () => { + iterator.windows(1.5); +}); + +assert.throws(TypeError, () => { + iterator.windows(Infinity); +}); + +assert.throws(TypeError, () => { + iterator.windows(-Infinity); +}); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/windowSize-out-of-range.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windowSize-out-of-range.js new file mode 100644 index 0000000000000..d111424f85212 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windowSize-out-of-range.js @@ -0,0 +1,41 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows throws RangeError when windowSize is an integral + Number outside the valid range [1, 2^32 - 1] +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 6. If windowSize is not in the inclusive interval from 1𝔽 to 𝔽(2^32 - 1), then + a. Let error be ThrowCompletion(a newly created RangeError object). + b. Return ? IteratorClose(iterated, error). + +features: [iterator-chunking, generators] +---*/ +let iterator = (function* () {})(); + +assert.throws(RangeError, () => { + iterator.windows(0); +}); + +assert.throws(RangeError, () => { + iterator.windows(-0); +}); + +assert.throws(RangeError, () => { + iterator.windows(-1); +}); + +assert.throws(RangeError, () => { + iterator.windows(2 ** 32); +}); + +assert.throws(RangeError, () => { + iterator.windows(2 ** 53); +}); + +// Boundary: valid values do not throw +iterator.windows(1); +iterator.windows(2 ** 32 - 1); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/windows-allow-partial.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windows-allow-partial.js new file mode 100644 index 0000000000000..85f106697c0ea --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windows-allow-partial.js @@ -0,0 +1,31 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + When undersized is "allow-partial", a partial final window is yielded if + the buffer is non-empty and smaller than windowSize at exhaustion +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 8.a.ii. If value is ~done~, then + 8.a.ii.a. If undersized is "allow-partial", buffer is not empty, and the number of elements in buffer < ℝ(windowSize), then + 8.a.ii.a.i. Perform Completion(Yield(CreateArrayFromList(buffer))). + 8.a.ii.b. Return ReturnCompletion(undefined). + +features: [iterator-chunking, generators] +includes: [compareArray.js] +---*/ +function* g() { + yield 0; + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; +} + +let windows = Array.from(g().windows(100, 'allow-partial')); + +assert.sameValue(windows.length, 1); +assert.compareArray(windows[0], [0, 1, 2, 3, 4, 5]); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/windows-basic.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windows-basic.js new file mode 100644 index 0000000000000..5642ef9123996 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windows-basic.js @@ -0,0 +1,33 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows yields sliding windows of the specified size +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 8.a.iii. If the number of elements in buffer is ℝ(windowSize), then + 8.a.iii.a. Remove the first element from buffer. + 8.a.iv. Append value to buffer. + 8.a.v. If the number of elements in buffer is ℝ(windowSize), then + 8.a.v.a. Let completion be Completion(Yield(CreateArrayFromList(buffer))). + +features: [iterator-chunking, generators] +includes: [compareArray.js] +---*/ +function* g() { + yield 0; + yield 1; + yield 2; + yield 3; + yield 4; +} + +let windows = Array.from(g().windows(2)); + +assert.sameValue(windows.length, 4); +assert.compareArray(windows[0], [0, 1]); +assert.compareArray(windows[1], [1, 2]); +assert.compareArray(windows[2], [2, 3]); +assert.compareArray(windows[3], [3, 4]); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/windows-size-1.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windows-size-1.js new file mode 100644 index 0000000000000..69ce25729758e --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windows-size-1.js @@ -0,0 +1,28 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + When windowSize is 1, each element is yielded as a single-element array +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, generators] +includes: [compareArray.js] +---*/ +function* g() { + yield 0; + yield 1; + yield 2; + yield 3; + yield 4; +} + +let windows = Array.from(g().windows(1)); + +assert.sameValue(windows.length, 5); +assert.compareArray(windows[0], [0]); +assert.compareArray(windows[1], [1]); +assert.compareArray(windows[2], [2]); +assert.compareArray(windows[3], [3]); +assert.compareArray(windows[4], [4]); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/windows-size-3.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windows-size-3.js new file mode 100644 index 0000000000000..a9d521acfe109 --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/windows-size-3.js @@ -0,0 +1,28 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Iterator.prototype.windows with windowSize 3 +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + +features: [iterator-chunking, generators] +includes: [compareArray.js] +---*/ +function* g() { + yield 0; + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; +} + +let windows = Array.from(g().windows(3)); + +assert.sameValue(windows.length, 4); +assert.compareArray(windows[0], [0, 1, 2]); +assert.compareArray(windows[1], [1, 2, 3]); +assert.compareArray(windows[2], [2, 3, 4]); +assert.compareArray(windows[3], [3, 4, 5]); diff --git a/JSTests/test262/test/built-ins/Iterator/prototype/windows/yields-distinct-arrays.js b/JSTests/test262/test/built-ins/Iterator/prototype/windows/yields-distinct-arrays.js new file mode 100644 index 0000000000000..cd769999c413a --- /dev/null +++ b/JSTests/test262/test/built-ins/Iterator/prototype/windows/yields-distinct-arrays.js @@ -0,0 +1,29 @@ +// Copyright (C) 2026 Michael Ficarra. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-iterator.prototype.windows +description: > + Each yielded window is a distinct new Array object +info: | + Iterator.prototype.windows ( windowSize [ , undersized ] ) + + 8.a.v.a. Let completion be Completion(Yield(CreateArrayFromList(buffer))). + +features: [iterator-chunking, generators] +---*/ +function* g() { + yield 0; + yield 1; + yield 2; + yield 3; +} + +let windows = Array.from(g().windows(2)); + +assert.sameValue(windows.length, 3); +assert(Array.isArray(windows[0]), 'windows[0] is an Array'); +assert(Array.isArray(windows[1]), 'windows[1] is an Array'); +assert(Array.isArray(windows[2]), 'windows[2] is an Array'); +assert.notSameValue(windows[0], windows[1], 'windows[0] !== windows[1]'); +assert.notSameValue(windows[1], windows[2], 'windows[1] !== windows[2]'); +assert.notSameValue(windows[0], windows[2], 'windows[0] !== windows[2]'); diff --git a/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-1.js b/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-1.js index 0d8e14854be54..48c022c88ea72 100644 --- a/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-1.js +++ b/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-1.js @@ -8,4 +8,4 @@ description: > undefined ---*/ -Object.freeze(undefined); +assert.sameValue(Object.freeze(undefined), undefined); diff --git a/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-2.js b/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-2.js index 91a2e93a14d40..d749baa3e1f99 100644 --- a/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-2.js +++ b/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-2.js @@ -8,4 +8,4 @@ description: > null ---*/ -Object.freeze(null); +assert.sameValue(Object.freeze(null), null); diff --git a/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-3.js b/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-3.js index 6a97759d838ff..71b83113340f1 100644 --- a/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-3.js +++ b/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-3.js @@ -8,5 +8,5 @@ description: > boolean primitive ---*/ -Object.freeze(false); -Object.freeze(true); +assert.sameValue(Object.freeze(false), false); +assert.sameValue(Object.freeze(true), true); diff --git a/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-4.js b/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-4.js index cd3b224034f81..c8a929b71f342 100644 --- a/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-4.js +++ b/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1-4.js @@ -8,4 +8,4 @@ description: > string primitive ---*/ -Object.freeze("abc"); +assert.sameValue(Object.freeze("abc"), "abc"); diff --git a/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1.js b/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1.js index d606706a2a2ca..be0d64f499a15 100644 --- a/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1.js +++ b/JSTests/test262/test/built-ins/Object/freeze/15.2.3.9-1.js @@ -8,4 +8,4 @@ description: > not Object ---*/ -Object.freeze(0); +assert.sameValue(Object.freeze(0), 0); diff --git a/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-1.js b/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-1.js index a1dac5f7f4ba3..67c23e3a5e847 100644 --- a/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-1.js +++ b/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-1.js @@ -6,4 +6,4 @@ es5id: 15.2.3.13-1-1 description: Object.isExtensible does not throw TypeError if 'O' is undefined ---*/ -Object.isExtensible(undefined); +assert.sameValue(Object.isExtensible(undefined), false); diff --git a/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-2.js b/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-2.js index bbc81c9cfef82..d9114cfc65b38 100644 --- a/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-2.js +++ b/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-2.js @@ -6,4 +6,4 @@ es5id: 15.2.3.13-1-2 description: Object.isExtensible does not throw TypeError if 'O' is null ---*/ -Object.isExtensible(null); +assert.sameValue(Object.isExtensible(null), false); diff --git a/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-3.js b/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-3.js index 1703a565f7529..fa4f342184725 100644 --- a/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-3.js +++ b/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-3.js @@ -6,4 +6,4 @@ es5id: 15.2.3.13-1-3 description: Object.isExtensible does not throw TypeError if 'O' is a boolean ---*/ -Object.isExtensible(true); +assert.sameValue(Object.isExtensible(true), false); diff --git a/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-4.js b/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-4.js index 9bada76d86d25..8ca2fe9923210 100644 --- a/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-4.js +++ b/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1-4.js @@ -6,4 +6,4 @@ es5id: 15.2.3.13-1-4 description: Object.isExtensible does not throw TypeError if 'O' is a string ---*/ -Object.isExtensible("abc"); +assert.sameValue(Object.isExtensible("abc"), false); diff --git a/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1.js b/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1.js index 3368997793925..57010622ca3d8 100644 --- a/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1.js +++ b/JSTests/test262/test/built-ins/Object/isExtensible/15.2.3.13-1.js @@ -8,4 +8,4 @@ description: > param is not Object ---*/ -Object.isExtensible(0); +assert.sameValue(Object.isExtensible(0), false); diff --git a/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-1.js b/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-1.js index 4bcccdf038483..f781970ba87f1 100644 --- a/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-1.js +++ b/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-1.js @@ -8,4 +8,4 @@ description: > is undefined ---*/ -Object.isFrozen(undefined); +assert.sameValue(Object.isFrozen(undefined), true); diff --git a/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-2.js b/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-2.js index 06329b88a331e..fa35331265eb7 100644 --- a/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-2.js +++ b/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-2.js @@ -8,4 +8,4 @@ description: > is null ---*/ -Object.isFrozen(null); +assert.sameValue(Object.isFrozen(null), true); diff --git a/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-3.js b/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-3.js index 6f11d3568688e..9a9208e15bc7a 100644 --- a/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-3.js +++ b/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-3.js @@ -8,4 +8,4 @@ description: > is a boolean ---*/ -Object.isFrozen(true); +assert.sameValue(Object.isFrozen(true), true); diff --git a/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-4.js b/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-4.js index 109ed310ea0ee..0bcfc68a16e83 100644 --- a/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-4.js +++ b/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1-4.js @@ -8,4 +8,4 @@ description: > is a string ---*/ -Object.isFrozen("abc"); +assert.sameValue(Object.isFrozen("abc"), true); diff --git a/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1.js b/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1.js index 86e9f06081366..fbdbf2ca47dfb 100644 --- a/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1.js +++ b/JSTests/test262/test/built-ins/Object/isFrozen/15.2.3.12-1.js @@ -8,4 +8,4 @@ description: > not Object ---*/ -Object.isFrozen(0); +assert.sameValue(Object.isFrozen(0), true); diff --git a/JSTests/test262/test/built-ins/Object/isSealed/15.2.3.11-1.js b/JSTests/test262/test/built-ins/Object/isSealed/15.2.3.11-1.js index f0a14907dac53..cdb31c94b09d0 100644 --- a/JSTests/test262/test/built-ins/Object/isSealed/15.2.3.11-1.js +++ b/JSTests/test262/test/built-ins/Object/isSealed/15.2.3.11-1.js @@ -8,4 +8,4 @@ description: > not Object ---*/ -Object.isSealed(0); +assert.sameValue(Object.isSealed(0), true); diff --git a/JSTests/test262/test/built-ins/Object/keys/15.2.3.14-1-1.js b/JSTests/test262/test/built-ins/Object/keys/15.2.3.14-1-1.js index 280af951ed877..d136d09898bb1 100644 --- a/JSTests/test262/test/built-ins/Object/keys/15.2.3.14-1-1.js +++ b/JSTests/test262/test/built-ins/Object/keys/15.2.3.14-1-1.js @@ -6,6 +6,7 @@ es5id: 15.2.3.14-1-1 description: > Object.keys does not throw TypeError if type of first param is not Object +includes: [compareArray.js] ---*/ -Object.keys(0); +assert.compareArray(Object.keys(0), []); diff --git a/JSTests/test262/test/built-ins/Object/keys/15.2.3.14-1-2.js b/JSTests/test262/test/built-ins/Object/keys/15.2.3.14-1-2.js index 2bce7c0fd44c5..9547d3fecb5f7 100644 --- a/JSTests/test262/test/built-ins/Object/keys/15.2.3.14-1-2.js +++ b/JSTests/test262/test/built-ins/Object/keys/15.2.3.14-1-2.js @@ -6,6 +6,7 @@ es5id: 15.2.3.14-1-2 description: > Object.keys does not throw TypeError if type of first param is not Object (boolean) +includes: [compareArray.js] ---*/ -Object.keys(true); +assert.compareArray(Object.keys(true), []); diff --git a/JSTests/test262/test/built-ins/Object/keys/15.2.3.14-1-3.js b/JSTests/test262/test/built-ins/Object/keys/15.2.3.14-1-3.js index 57c584576856e..b49ece9a3a7f5 100644 --- a/JSTests/test262/test/built-ins/Object/keys/15.2.3.14-1-3.js +++ b/JSTests/test262/test/built-ins/Object/keys/15.2.3.14-1-3.js @@ -6,6 +6,7 @@ es5id: 15.2.3.14-1-3 description: > Object.keys does not throw TypeError if type of first param is not Object (string) +includes: [compareArray.js] ---*/ -Object.keys('abc'); +assert.compareArray(Object.keys('abc'), ["0", "1", "2"]); diff --git a/JSTests/test262/test/built-ins/Object/seal/seal-boolean-literal.js b/JSTests/test262/test/built-ins/Object/seal/seal-boolean-literal.js index 3a7fdcee49d60..90d95e6ec260a 100644 --- a/JSTests/test262/test/built-ins/Object/seal/seal-boolean-literal.js +++ b/JSTests/test262/test/built-ins/Object/seal/seal-boolean-literal.js @@ -33,4 +33,4 @@ info: | ---*/ -Object.seal(true); +assert.sameValue(Object.seal(true), true); diff --git a/JSTests/test262/test/built-ins/Object/seal/seal-infinity.js b/JSTests/test262/test/built-ins/Object/seal/seal-infinity.js index aa2bd6735c3b9..0e891c3f717a2 100644 --- a/JSTests/test262/test/built-ins/Object/seal/seal-infinity.js +++ b/JSTests/test262/test/built-ins/Object/seal/seal-infinity.js @@ -33,4 +33,4 @@ info: | ---*/ -Object.seal(Infinity); +assert.sameValue(Object.seal(Infinity), Infinity); diff --git a/JSTests/test262/test/built-ins/Object/seal/seal-nan.js b/JSTests/test262/test/built-ins/Object/seal/seal-nan.js index 52edbfb460e9f..ede10ce8cc423 100644 --- a/JSTests/test262/test/built-ins/Object/seal/seal-nan.js +++ b/JSTests/test262/test/built-ins/Object/seal/seal-nan.js @@ -33,4 +33,4 @@ info: | ---*/ -Object.seal(NaN); +assert.sameValue(Object.seal(NaN), NaN); diff --git a/JSTests/test262/test/built-ins/Object/seal/seal-null.js b/JSTests/test262/test/built-ins/Object/seal/seal-null.js index 2478bf2d3390a..40f0fc36649b1 100644 --- a/JSTests/test262/test/built-ins/Object/seal/seal-null.js +++ b/JSTests/test262/test/built-ins/Object/seal/seal-null.js @@ -33,4 +33,4 @@ info: | ---*/ -Object.seal(null); +assert.sameValue(Object.seal(null), null); diff --git a/JSTests/test262/test/built-ins/Object/seal/seal-symbol.js b/JSTests/test262/test/built-ins/Object/seal/seal-symbol.js index ad4c896e0423c..3534cb324f400 100644 --- a/JSTests/test262/test/built-ins/Object/seal/seal-symbol.js +++ b/JSTests/test262/test/built-ins/Object/seal/seal-symbol.js @@ -33,4 +33,5 @@ info: | ---*/ -Object.seal(Symbol()); +var s = Symbol(); +assert.sameValue(Object.seal(s), s); diff --git a/JSTests/test262/test/built-ins/Object/seal/seal-undefined.js b/JSTests/test262/test/built-ins/Object/seal/seal-undefined.js index 0971231effb07..5e0afc96277ac 100644 --- a/JSTests/test262/test/built-ins/Object/seal/seal-undefined.js +++ b/JSTests/test262/test/built-ins/Object/seal/seal-undefined.js @@ -33,4 +33,4 @@ info: | ---*/ -Object.seal(undefined); +assert.sameValue(Object.seal(undefined), undefined); diff --git a/JSTests/test262/test/built-ins/Promise/allSettledKeyed/result-property-descriptors.js b/JSTests/test262/test/built-ins/Promise/allSettledKeyed/result-property-descriptors.js index d7c6fdf0457e7..fdff67ea5118c 100644 --- a/JSTests/test262/test/built-ins/Promise/allSettledKeyed/result-property-descriptors.js +++ b/JSTests/test262/test/built-ins/Promise/allSettledKeyed/result-property-descriptors.js @@ -39,38 +39,43 @@ asyncTest(function() { assert.sameValue(Object.getPrototypeOf(result.fulfilled), Object.prototype, "fulfilled entry prototype"); assert.sameValue(Object.getPrototypeOf(result.rejected), Object.prototype, "rejected entry prototype"); + // Capture the properties eagerly because `verifyProperty` will delete them + // as part of the `configurable` check. + var fulfilledEntry = result.fulfilled; + var rejectedEntry = result.rejected; + verifyProperty(result, "fulfilled", { - value: result.fulfilled, + value: fulfilledEntry, writable: true, enumerable: true, configurable: true }); verifyProperty(result, "rejected", { - value: result.rejected, + value: rejectedEntry, writable: true, enumerable: true, configurable: true }); - verifyProperty(result.fulfilled, "status", { + verifyProperty(fulfilledEntry, "status", { value: "fulfilled", writable: true, enumerable: true, configurable: true }); - verifyProperty(result.fulfilled, "value", { + verifyProperty(fulfilledEntry, "value", { value: 1, writable: true, enumerable: true, configurable: true }); - verifyProperty(result.rejected, "status", { + verifyProperty(rejectedEntry, "status", { value: "rejected", writable: true, enumerable: true, configurable: true }); - verifyProperty(result.rejected, "reason", { + verifyProperty(rejectedEntry, "reason", { value: error, writable: true, enumerable: true, diff --git a/JSTests/test262/test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-offset.js b/JSTests/test262/test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-offset.js index d7770d1fb4b88..d5e5ffe9eadf7 100644 --- a/JSTests/test262/test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-offset.js +++ b/JSTests/test262/test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-offset.js @@ -42,4 +42,4 @@ testWithTypedArrayConstructors(function(TA, makeCtorArg) { assert.compareArray(result, [ 20, 20, 20, 60, ]); -}); +}, null, null, ["immutable"]); diff --git a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/BigInt/index-prop-desc.js b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/BigInt/index-prop-desc.js index a8c91cec542a8..12a8dd1791e63 100644 --- a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/BigInt/index-prop-desc.js +++ b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/BigInt/index-prop-desc.js @@ -33,4 +33,4 @@ testWithBigIntTypedArrayConstructors(function(TA, makeCtorArg) { assert.sameValue(descriptor1.configurable, true); assert.sameValue(descriptor1.enumerable, true); assert.sameValue(descriptor1.writable, true); -}); +}, null, null, ["immutable"]); diff --git a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/index-prop-desc.js b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/index-prop-desc.js index c7f18eaa6f5e8..3cf85e0e2c513 100644 --- a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/index-prop-desc.js +++ b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/GetOwnProperty/index-prop-desc.js @@ -35,4 +35,4 @@ testWithTypedArrayConstructors(function(TA, makeCtorArg) { assert.sameValue(descriptor1.configurable, true); assert.sameValue(descriptor1.enumerable, true); assert.sameValue(descriptor1.writable, true); -}); +}, null, null, ["immutable"]); diff --git a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/null-tobigint.js b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/null-tobigint.js index 500e99db5f360..aad8b2eb307cf 100644 --- a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/null-tobigint.js +++ b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/null-tobigint.js @@ -58,4 +58,4 @@ testWithBigIntTypedArrayConstructors(function(TA, makeCtorArg) { typedArray[0] = null; }); -}); +}, null, null, ["immutable"]); diff --git a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/number-tobigint.js b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/number-tobigint.js index 89e39ea4cdb1e..57057bf3b352e 100644 --- a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/number-tobigint.js +++ b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/number-tobigint.js @@ -82,4 +82,4 @@ testWithBigIntTypedArrayConstructors(function(TA, makeCtorArg) { typedArray[0] = NaN; }); -}); +}, null, null, ["immutable"]); diff --git a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/string-nan-tobigint.js b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/string-nan-tobigint.js index ac112ff2827c3..277b9265598cc 100644 --- a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/string-nan-tobigint.js +++ b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/string-nan-tobigint.js @@ -62,4 +62,4 @@ testWithBigIntTypedArrayConstructors(function(TA, makeCtorArg) { typedArray[0] = "definately not a number"; }); -}); +}, null, null, ["immutable"]); diff --git a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/symbol-tobigint.js b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/symbol-tobigint.js index e6a655ecfbd99..de4ad741cc10d 100644 --- a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/symbol-tobigint.js +++ b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/symbol-tobigint.js @@ -60,4 +60,4 @@ testWithBigIntTypedArrayConstructors(function(TA, makeCtorArg) { typedArray[0] = s; }); -}); +}, null, null, ["immutable"]); diff --git a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/tonumber-value-throws.js b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/tonumber-value-throws.js index 17bcb4e8d7b79..b4e87fbb01a3f 100644 --- a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/tonumber-value-throws.js +++ b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/tonumber-value-throws.js @@ -57,4 +57,4 @@ testWithBigIntTypedArrayConstructors(function(TA, makeCtorArg) { assert.throws(Test262Error, function() { sample['2'] = obj; }, '`sample["2"] = obj` throws Test262Error'); -}); +}, null, null, ["immutable"]); diff --git a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/undefined-tobigint.js b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/undefined-tobigint.js index d8507a8f82afd..168186e7f424f 100644 --- a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/undefined-tobigint.js +++ b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/BigInt/undefined-tobigint.js @@ -59,4 +59,4 @@ testWithBigIntTypedArrayConstructors(function(TA, makeCtorArg) { typedArray[0] = undefined; }); -}); +}, null, null, ["immutable"]); diff --git a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/bigint-tonumber.js b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/bigint-tonumber.js index bb6851ef4f989..c64b92637587f 100644 --- a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/bigint-tonumber.js +++ b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/bigint-tonumber.js @@ -56,4 +56,4 @@ testWithTypedArrayConstructors(function(TA, makeCtorArg) { assert.throws(TypeError, function() { typedArray[0] = 1n; }); -}); +}, null, null, ["immutable"]); diff --git a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-throws.js b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-throws.js index beabcd95fcb86..5f30b83f9c047 100644 --- a/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-throws.js +++ b/JSTests/test262/test/built-ins/TypedArrayConstructors/internals/Set/tonumber-value-throws.js @@ -58,4 +58,4 @@ testWithTypedArrayConstructors(function(TA, makeCtorArg) { assert.throws(Test262Error, function() { sample["2"] = obj; }); -}); +}, null, null, ["immutable"]); diff --git a/JSTests/test262/test/intl402/Locale/prototype/getCalendars/likely-subtags-region.js b/JSTests/test262/test/intl402/Locale/prototype/getCalendars/likely-subtags-region.js new file mode 100644 index 0000000000000..8462822065887 --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getCalendars/likely-subtags-region.js @@ -0,0 +1,66 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-Intl.Locale.prototype.getCalendars +description: > + When the locale has no region, the Add Likely Subtags algorithm is used to + derive the region whose calendar preference is returned. +info: | + RegionPreference ( locale ) + ... + 2. If _region_ is *undefined*, then + a. Set _region_ to CanonicalUnicodeSubdivision(_locale_, *"sd"*). + b. If _region_ is *undefined*, then + i. Let _maximal_ be the result of the Add Likely Subtags algorithm applied + to _locale_. If an error is signaled, set _maximal_ to _locale_. + ii. Set _maximal_ to CanonicalizeUnicodeLocaleId(_maximal_). + iii. Set _region_ to GetLocaleRegion(_maximal_). + iv. If _region_ is *undefined*, then + 1. Set _region_ to *"001"*. + ... +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// In order not to rely on a particular implementation's locale data, this test +// searches for a suitable locale without a region subtag, but where the likely +// region would influence the supported calendars. +// +// Each candidate below is a language subtag together with the region that the +// Add Likely Subtags algorithm derives for it (e.g. "th" maximizes to +// "th-Thai-TH", so "th-TH"). A language tag without a region, subdivision ("sd" +// keyword), or region override ("rg" keyword) passed to RegionPreference must +// apply Add Likely Subtags and arrive at the paired region. Its calendars must +// therefore be identical to those of the candidate locale. An incorrect +// implementation might instead fall back to the region "001" in step 2.b.iv. +// +// We could assume even less about the locale data by not hardcoding these +// likely regions and instead checking all available locales with the result of +// maximize() on each one, but that assumes the implementation has a working +// maximize(), and if that's the case then it probably implements this step +// correctly as well. +function findSuitableTestData() { + const candidates = ["th-TH", "fa-IR", "ja-JP", "sa-IN", "ps-AF"]; + + for (const candidate of candidates) { + const locale = new Intl.Locale(candidate); + const calendarsWithLikelyRegion = locale.getCalendars(); + + const bareLanguage = locale.language; + const fallbackLocale = new Intl.Locale(`${bareLanguage}-001`); + if (!compareArray(fallbackLocale.getCalendars(), calendarsWithLikelyRegion)) { + return [bareLanguage, calendarsWithLikelyRegion]; + } + } + + assert(false, "No suitable test data found in this implementation. Consider updating the candidate list"); +} + +const [language, expectedCalendars] = findSuitableTestData(); + +const locale = new Intl.Locale(language); +assert.compareArray( + locale.getCalendars(), + expectedCalendars, + `getCalendars() for "${language}" should equal getCalendars() for locale with likely region` +); diff --git a/JSTests/test262/test/intl402/Locale/prototype/getCalendars/region-override.js b/JSTests/test262/test/intl402/Locale/prototype/getCalendars/region-override.js new file mode 100644 index 0000000000000..c59972e678049 --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getCalendars/region-override.js @@ -0,0 +1,68 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-Intl.Locale.prototype.getCalendars +description: > + When the locale has a region override ("rg") keyword, its region overrides + the region subtag when deriving the calendar preference. +info: | + CalendarsOfLocale ( loc ) + ... + 2. Let _preference_ be RegionPreference(_loc_.[[Locale]]). + 3. Let _region_ be _preference_.[[Region]]. + 4. Let _regionOverride_ be _preference_.[[RegionOverride]]. + 5. If _regionOverride_ is not *undefined* and calendar preference data for + _regionOverride_ are available, then + a. Let _lookupRegion_ be _regionOverride_. + 6. Else, + a. Let _lookupRegion_ be _region_. + ... + + RegionPreference ( locale ) + ... + 3. Let _regionOverride_ be CanonicalUnicodeSubdivision(_locale_, *"rg"*). + 4. Return { [[Region]]: _region_, [[RegionOverride]]: _regionOverride_ }. +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// In order not to rely on a particular implementation's locale data, this test +// searches for a suitable locale with a region subtag together with a region +// override ("rg" extension) whose region would influence the supported +// calendars. +// +// Each candidate below is a locale with a region override extension, together +// with a locale that names the override's region explicitly (e.g. +// "en-US-u-rg-thzzzz" overrides the region with "TH", so "en-TH"). Because the +// region override is set, CalendarsOfLocale must use it as the lookup region +// instead of the region subtag. Its calendars must therefore be identical to +// those of the paired override-region locale. An incorrect implementation might +// instead ignore the "rg" keyword and use the region subtag. +function findSuitableTestData() { + const candidates = [ + ["en-US-u-rg-thzzzz", "en-TH"], + ["en-US-u-rg-jpzzzz", "en-JP"], + ["en-US-u-rg-inzzzz", "en-IN"], + ["en-US-u-rg-irzzzz", "en-IR"], + ]; + + for (const [overrideTag, regionTag] of candidates) { + const calendarsWithOverrideRegion = new Intl.Locale(regionTag).getCalendars(); + + const withoutOverride = new Intl.Locale(new Intl.Locale(overrideTag).baseName); + if (!compareArray(withoutOverride.getCalendars(), calendarsWithOverrideRegion)) { + return [overrideTag, calendarsWithOverrideRegion]; + } + } + + assert(false, "No suitable test data found in this implementation. Consider updating the candidate list"); +} + +const [overrideTag, expectedCalendars] = findSuitableTestData(); + +const locale = new Intl.Locale(overrideTag); +assert.compareArray( + locale.getCalendars(), + expectedCalendars, + `getCalendars() for "${overrideTag}" should equal getCalendars() for the override region` +); diff --git a/JSTests/test262/test/intl402/Locale/prototype/getCalendars/region-priority.js b/JSTests/test262/test/intl402/Locale/prototype/getCalendars/region-priority.js new file mode 100644 index 0000000000000..2eebfc8c217ef --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getCalendars/region-priority.js @@ -0,0 +1,86 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-Intl.Locale.prototype.getCalendars +description: > + The region used to look up the calendar preference is chosen from the + available signals in the correct priority order. +info: | + CalendarsOfLocale ( loc ) + ... + 2. Let _preference_ be RegionPreference(_loc_.[[Locale]]). + 3. Let _region_ be _preference_.[[Region]]. + 4. Let _regionOverride_ be _preference_.[[RegionOverride]]. + 5. If _regionOverride_ is not *undefined* and calendar preference data for + _regionOverride_ are available, then + a. Let _lookupRegion_ be _regionOverride_. + 6. Else, + a. Let _lookupRegion_ be _region_. + ... + + RegionPreference ( locale ) + 1. Let _region_ be GetLocaleRegion(_locale_). + 2. If _region_ is *undefined*, then + a. Set _region_ to CanonicalUnicodeSubdivision(_locale_, *"sd"*). + b. If _region_ is *undefined*, then + i. Let _maximal_ be the result of the Add Likely Subtags algorithm applied + to _locale_. If an error is signaled, set _maximal_ to _locale_. + ii. Set _maximal_ to CanonicalizeUnicodeLocaleId(_maximal_). + iii. Set _region_ to GetLocaleRegion(_maximal_). + iv. If _region_ is *undefined*, then + 1. Set _region_ to *"001"*. + 3. Let _regionOverride_ be CanonicalUnicodeSubdivision(_locale_, *"rg"*). + 4. Return { [[Region]]: _region_, [[RegionOverride]]: _regionOverride_ }. +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// Together, CalendarsOfLocale and RegionPreference select the region used to +// look up the calendar preference from the available signals in this priority +// order (highest first): +// +// 1. the "rg" region override keyword (when calendar data for it exists) +// 2. the region subtag +// 3. the "sd" subdivision keyword +// 4. the region computed by the Add Likely Subtags algorithm +// 5. the "001" (world) region +// +// Each entry below is a locale that carries the signal for its priority level +// on top of all lower-priority signals, paired with a locale that names the +// region that signal must resolve to. The paired locale uses the same base +// language so that any language-dependent locale data is held constant. For +// example, "fa-JP-u-sd-inka-rg-thzzzz" carries an "rg" override of region "TH", +// a region subtag "JP", an "sd" subdivision in region "IN", and a base language +// "fa" whose likely region is "IR"; the "rg" override has the highest priority, +// so the locale must behave like "fa-TH". +// +// The last entry has no lower-priority signals: the language "eo" (Esperanto) +// has no likely region, so its region falls back to "001". +const levels = [ + { description: 'the "rg" region override', locale: "fa-JP-u-sd-inka-rg-thzzzz", region: "fa-TH" }, + { description: "the region subtag", locale: "fa-JP-u-sd-inka", region: "fa-JP" }, + { description: 'the "sd" subdivision', locale: "fa-u-sd-inka", region: "fa-IN" }, + { description: "the Add Likely Subtags region", locale: "fa", region: "fa-IR" }, + { description: 'the "001" region', locale: "eo", region: "eo-001" }, +]; + +// For each priority level to be observable, the region it selects must have +// different calendars than the region selected by the next lower level in this +// implementation; otherwise the test could not tell them apart. +for (let i = 0; i < levels.length - 1; i++) { + const higher = new Intl.Locale(levels[i].region).getCalendars(); + const lower = new Intl.Locale(levels[i + 1].region).getCalendars(); + assert( + !compareArray(higher, lower), + `Inconclusive: ${levels[i].description} and ${levels[i + 1].description} ` + + "select regions with identical calendars in this implementation. Consider updating the test data" + ); +} + +for (const { description, locale, region } of levels) { + assert.compareArray( + new Intl.Locale(locale).getCalendars(), + new Intl.Locale(region).getCalendars(), + `getCalendars() for "${locale}" should use ${description}, like "${region}"` + ); +} diff --git a/JSTests/test262/test/intl402/Locale/prototype/getCalendars/subdivision-region.js b/JSTests/test262/test/intl402/Locale/prototype/getCalendars/subdivision-region.js new file mode 100644 index 0000000000000..ac25581061567 --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getCalendars/subdivision-region.js @@ -0,0 +1,63 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-Intl.Locale.prototype.getCalendars +description: > + When the locale has no region but has a subdivision ("sd") keyword, the + subdivision's region is used to derive the calendar preference. +info: | + RegionPreference ( locale ) + 1. Let _region_ be GetLocaleRegion(_locale_). + 2. If _region_ is *undefined*, then + a. Set _region_ to CanonicalUnicodeSubdivision(_locale_, *"sd"*). + b. If _region_ is *undefined*, then + i. Let _maximal_ be the result of the Add Likely Subtags algorithm applied + to _locale_. If an error is signaled, set _maximal_ to _locale_. + ... + ... +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// In order not to rely on a particular implementation's locale data, this test +// searches for a suitable locale without a region subtag, but with a +// subdivision ("sd" extension) whose region would influence the supported +// calendars. +// +// Each candidate below is a locale with a subdivision extension but no region +// subtag, together with a locale that names that subdivision's region +// explicitly (e.g. "en-u-sd-th10" has subdivision "th10", whose region is "TH", +// so "en-TH"). Because such a locale has no region subtag, RegionPreference +// must derive its region from the subdivision. Its calendars must therefore be +// identical to those of the paired region locale. An incorrect implementation +// might instead ignore the "sd" extension and apply Add Likely Subtags to the +// bare language, giving "en-Latn-US". +function findSuitableTestData() { + const candidates = [ + ["en-u-sd-th10", "en-TH"], + ["en-u-sd-jp13", "en-JP"], + ["en-u-sd-inka", "en-IN"], + ["en-u-sd-irthr", "en-IR"], + ]; + + for (const [subdivisionTag, regionTag] of candidates) { + const calendarsWithSubdivisionRegion = new Intl.Locale(regionTag).getCalendars(); + + const bareLanguage = new Intl.Locale(subdivisionTag).baseName; + const fallbackLocale = new Intl.Locale(bareLanguage).maximize(); + if (!compareArray(fallbackLocale.getCalendars(), calendarsWithSubdivisionRegion)) { + return [subdivisionTag, calendarsWithSubdivisionRegion]; + } + } + + assert(false, "No suitable test data found in this implementation. Consider updating the candidate list"); +} + +const [subdivisionTag, expectedCalendars] = findSuitableTestData(); + +const locale = new Intl.Locale(subdivisionTag); +assert.compareArray( + locale.getCalendars(), + expectedCalendars, + `getCalendars() for "${subdivisionTag}" should equal getCalendars() for the subdivision's region` +); diff --git a/JSTests/test262/test/intl402/Locale/prototype/getCollations/collation-keyword.js b/JSTests/test262/test/intl402/Locale/prototype/getCollations/collation-keyword.js new file mode 100644 index 0000000000000..7b5629b4183fb --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getCollations/collation-keyword.js @@ -0,0 +1,40 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-Intl.Locale.prototype.getCollations +description: > + u-co extension keyword and collation option override language lookup +info: | + CollationsOfLocale ( loc ) + + 1. If _loc_.[[Collation]] is not *undefined*, then + a. Return CreateArrayFromList(« _loc_.[[Collation]] »). +features: [Intl.Locale,Intl.Locale-info] +locale: [en, de, zh] +---*/ + +var testCases = [ + ["en", "phonebk"], + ["de", "phonebk"], + ["zh", "stroke"], + ["und", "pinyin"], + ["und", "emoji"] +]; + +for (var i = 0; i < testCases.length; i++) { + var baseName = testCases[i][0]; + var collation = testCases[i][1]; + var fullTag = baseName + "-u-co-" + collation; + + assert.compareArray( + new Intl.Locale(fullTag).getCollations(), + [collation], + "getCollations() for " + fullTag + " returns only " + collation + ); + assert.compareArray( + new Intl.Locale(baseName, { collation: collation }).getCollations(), + [collation], + "getCollations() for " + baseName + " with { collation: " + collation + " } returns only " + collation + ); +} diff --git a/JSTests/test262/test/intl402/Locale/prototype/getCollations/output-array-sorted.js b/JSTests/test262/test/intl402/Locale/prototype/getCollations/output-array-sorted.js new file mode 100644 index 0000000000000..ffd4bd8e036ec --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getCollations/output-array-sorted.js @@ -0,0 +1,28 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-Intl.Locale.prototype.getCollations +description: The returned array is sorted in lexicographic code unit order +info: | + CollationsOfLocale ( loc ) + + 5. Let _sorted_ be a copy of _list_, sorted according to lexicographic code + unit order. + 6. Return CreateArrayFromList(_sorted_). +features: [Intl.Locale,Intl.Locale-info] +locale: [ar, de, en, ja, ko, sv, tr, zh] +---*/ + +var tags = ["ar", "de", "en", "ja", "ko", "sv", "tr", "zh"]; + +for (var i = 0; i < tags.length; i++) { + var tag = tags[i]; + var collations = new Intl.Locale(tag).getCollations(); + var sortedCollations = [].concat(new Intl.Locale(tag).getCollations()).sort(); + assert.compareArray( + collations, + sortedCollations, + "getCollations() for " + tag + " should be sorted in lexicographic code unit order" + ); +} diff --git a/JSTests/test262/test/intl402/Locale/prototype/getCollations/output-array-values.js b/JSTests/test262/test/intl402/Locale/prototype/getCollations/output-array-values.js index 119f79617cc50..f80198a4afd0c 100644 --- a/JSTests/test262/test/intl402/Locale/prototype/getCollations/output-array-values.js +++ b/JSTests/test262/test/intl402/Locale/prototype/getCollations/output-array-values.js @@ -3,23 +3,27 @@ /*--- esid: sec-intl.locale.prototype.collations -description: > - Checks that the return value of Intl.Locale.prototype.collations is an Array - that does not contain invalid values. +description: The return value does not contain invalid values info: | - CollationsOfLocale ( loc ) - ... - 4. Let list be a List of 1 or more unique collation identifiers, which must - be lower case String values conforming to the type sequence from UTS 35 - Unicode Locale Identifier, section 3.2, sorted in descending preference of - those in common use for string comparison in locale. The values "standard" - and "search" must be excluded from list. + 10.2.3 Internal Slots + - The values *"standard"* and *"search"* must not be used as elements in any + [[SortLocaleData]].[[]].[[co]] and + [[SearchLocaleData]].[[]].[[co]] List. features: [Intl.Locale, Intl.Locale-info, Array.prototype.includes] +locale: [ar, de, en, ja, ko, sv, tr, zh] ---*/ -const output = new Intl.Locale('en').getCollations(); -assert(output.length > 0, 'array has at least one element'); -output.forEach(c => { - if(['standard', 'search'].includes(c)) - throw new Test262Error(); -}); +var tags = ["ar", "de", "en", "ja", "ko", "sv", "tr", "zh"]; + +for (var i = 0; i < tags.length; i++) { + var tag = tags[i]; + var collations = new Intl.Locale(tag).getCollations(); + assert.notSameValue(collations.length, 0, + "getCollations() for " + tag + " has at least one element"); + for (var j = 0; j < collations.length; j++) { + assert.notSameValue(collations[j], "standard", + "getCollations() for " + tag + " must not contain 'standard'"); + assert.notSameValue(collations[j], "search", + "getCollations() for " + tag + " must not contain 'search'"); + } +} diff --git a/JSTests/test262/test/intl402/Locale/prototype/getCollations/output-array.js b/JSTests/test262/test/intl402/Locale/prototype/getCollations/output-array.js index e18d58da29435..8d94deb24378c 100644 --- a/JSTests/test262/test/intl402/Locale/prototype/getCollations/output-array.js +++ b/JSTests/test262/test/intl402/Locale/prototype/getCollations/output-array.js @@ -3,13 +3,21 @@ /*--- esid: sec-intl.locale.prototype.getCollations -description: > - Checks that the return value of Intl.Locale.prototype.getCollations is an Array. +description: The return value is an Array info: | CollationsOfLocale ( loc ) ... - 5. Return ! CreateArrayFromListAndPreferred( list, preferred ). + 6. Return CreateArrayFromList(_sorted_). features: [Intl.Locale,Intl.Locale-info] +locale: [ar, de, en, ja, ko, sv, tr, zh] ---*/ -assert(Array.isArray(new Intl.Locale('en').getCollations())); +var tags = ["ar", "de", "en", "ja", "ko", "sv", "tr", "zh"]; + +for (var i = 0; i < tags.length; i++) { + var tag = tags[i]; + assert( + Array.isArray(new Intl.Locale(tag).getCollations()), + "getCollations() for " + tag + " must return an array" + ); +} diff --git a/JSTests/test262/test/intl402/Locale/prototype/getCollations/und-language.js b/JSTests/test262/test/intl402/Locale/prototype/getCollations/und-language.js new file mode 100644 index 0000000000000..2063000bb5205 --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getCollations/und-language.js @@ -0,0 +1,65 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-Intl.Locale.prototype.getCollations +description: > + Return value for tags not matching any available locale is hardcoded in spec +info: | + CollationsOfLocale ( loc ) + + 2. Let _match_ be LookupMatchingLocaleByPrefix( + %Intl.Collator%.[[AvailableLocales]], « _loc_.[[Locale]] »). + 3. If _match_ is not *undefined*, then + ... + 4. Else, + a. Let _list_ be « *"emoji"*, *"eor"* ». +features: [Intl.Locale,Intl.Locale-info] +---*/ + +var undLocales = [ + "und", + "und-US", + "und-Latn", + "und-Latn-US", + "und-u-ca-gregory", + "und-US-u-nu-latn", +]; + +for (var i = 0; i < undLocales.length; i++) { + var tag = undLocales[i]; + var locale = new Intl.Locale(tag); + assert.sameValue( + locale.language, + "und", + tag + " must have language subtag 'und'" + ); + assert.compareArray( + locale.getCollations(), + ["emoji", "eor"], + "getCollations() for " + tag + " must return the root collations" + ); +} + +// Unreserved private-use language subtags (qfz..qtz) are guaranteed to have no +// semantics in any CLDR release, so we assume they will fall back to the +// hardcoded root collations. +// https://www.unicode.org/reports/tr35/tr35.html#Private_Use_Codes +// +// Note regarding normative change https://github.com/tc39/ecma402/pull/1072: +// this test might pass or fail if the normative change is not implemented, +// depending on what the environment's default locale is. It should always pass +// regardless of the environment's default locale if the normative change is +// implemented correctly. Try running it with LC_ALL=de in the environment, for +// example. + +var privateUseTags = ["qfz", "qga-DE", "qgb-ES", "qgc-KR", "qtz-CN"]; + +for (var i = 0; i < privateUseTags.length; i++) { + var tag = privateUseTags[i]; + assert.compareArray( + new Intl.Locale(tag).getCollations(), + ["emoji", "eor"], + "getCollations() for unavailable locale " + tag + " must return the root collations" + ); +} diff --git a/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/language-priority.js b/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/language-priority.js new file mode 100644 index 0000000000000..242a12e579df3 --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/language-priority.js @@ -0,0 +1,68 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-Intl.Locale.prototype.getHourCycles +description: > + The language subtag is taken into account when looking up the hour cycle + preference +info: | + HourCyclesOfLocale ( loc ) + ... + 5. Let _language_ be GetLocaleLanguage(_loc_.[[Locale]]). + 6. For each String _region_ of _preferredRegions_, do + a. Let _locale_ be the string-concatenation of _language_, *"-"*, and + _region_. + b. If _hourCycles_ is empty and time data for locale _locale_ are available, + then + i. Set _hourCycles_ to a List of unique hour cycle identifiers, which must + be lower case Strings indicating either the 12-hour format (*"h11"*, + *"h12"*) or the 24-hour format (*"h23"*, *"h24"*), sorted in descending + preference of those in common use for date and time formatting in + locale _locale_. + c. If _hourCycles_ is empty and time data for region _region_ are available, + then + i. Set _hourCycles_ to a List of unique hour cycle identifiers, which must + be lower case Strings indicating either the 12-hour format (*"h11"*, + *"h12"*) or the 24-hour format (*"h23"*, *"h24"*), sorted in descending + preference of those in common use for date and time formatting in + region _region_. + ... +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// Some of CLDR's hour cycle preference data is indexed by language and region +// in addition to the region-only entries. When such language-region data are +// available, getHourCycles() must use them in preference to the region-only +// data. For example, "fr-CA" is listed as preferring a 24-hour clock while +// "en-CA" is listed as preferring a 12-hour clock, so these locales must return +// different hour cycles even though they share the region "CA". +// +// To avoid relying on a particular implementation's locale data, the test tries +// several candidates. Each candidate below is a region paired with two +// languages where language-region time data exists for one. One of them should +// return different hour-cycle data than the control locale consisting of +// language "und". An implementation that ignored the language subtag would look +// up only the region and make all three locales agree. +const candidates = [ + { region: "CA", languages: ["fr", "en"] }, + { region: "SY", languages: ["ku", "ar"] }, + { region: "001", languages: ["en", "de"] }, + { region: "001", languages: ["ar", "fr"] }, +]; + +let found = false; +for (const { region, languages: [lang1, lang2] } of candidates) { + const control = new Intl.Locale(`und-${region}`); + const hourCycles = control.getHourCycles(); + const locale1 = new Intl.Locale(`${lang1}-${region}`); + const locale2 = new Intl.Locale(`${lang2}-${region}`); + const lang1Different = !compareArray(locale1.getHourCycles(), hourCycles); + const lang2Different = !compareArray(locale2.getHourCycles(), hourCycles); + if (lang1Different || lang2Different) { + found = true; + break; + } +} + +assert(found, "Either the feature is not implemented correctly, or no suitable test data found in this implementation. If the latter, consider updating the candidate list"); diff --git a/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/likely-subtags-region.js b/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/likely-subtags-region.js new file mode 100644 index 0000000000000..9a187c6c03d58 --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/likely-subtags-region.js @@ -0,0 +1,75 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-Intl.Locale.prototype.getHourCycles +description: > + When the locale has no region, the Add Likely Subtags algorithm is used to + derive the region whose hour cycle preference is returned. +info: | + RegionPreference ( locale ) + ... + 2. If _region_ is *undefined*, then + a. Set _region_ to CanonicalUnicodeSubdivision(_locale_, *"sd"*). + b. If _region_ is *undefined*, then + i. Let _maximal_ be the result of the Add Likely Subtags algorithm applied + to _locale_. If an error is signaled, set _maximal_ to _locale_. + ii. Set _maximal_ to CanonicalizeUnicodeLocaleId(_maximal_). + iii. Set _region_ to GetLocaleRegion(_maximal_). + iv. If _region_ is *undefined*, then + 1. Set _region_ to *"001"*. + ... +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// In order not to rely on a particular implementation's locale data, this test +// searches for a suitable locale without a region subtag, but where the likely +// region would influence the supported hour cycles. +// +// Each candidate below is a language subtag together with the region that the +// Add Likely Subtags algorithm derives for it (e.g. "am" maximizes to +// "am-Ethi-ET", so "am-ET"). A language tag without a region, subdivision ("sd" +// keyword), or region override ("rg" keyword) passed to RegionPreference must +// apply Add Likely Subtags and arrive at the paired region. Its hour cycles +// must therefore be identical to those of the candidate locale. An incorrect +// implementation might instead fall back to the region "001" in step 2.b.iv. +// +// We could assume even less about the locale data by not hardcoding these +// likely regions and instead checking all available locales with the result of +// maximize() on each one, but that assumes the implementation has a working +// maximize(), and if that's the case then it probably implements this step +// correctly as well. +function findSuitableTestData() { + const candidates = [ + "am-ET", + "bn-BD", + "el-GR", + "fil-PH", + "hi-IN", + "ko-KR", + "ms-MY", + "ur-PK", + ]; + + for (const candidate of candidates) { + const locale = new Intl.Locale(candidate); + const hourCyclesWithLikelyRegion = locale.getHourCycles(); + + const bareLanguage = locale.language; + const fallbackLocale = new Intl.Locale(`${bareLanguage}-001`); + if (!compareArray(fallbackLocale.getHourCycles(), hourCyclesWithLikelyRegion)) { + return [bareLanguage, hourCyclesWithLikelyRegion]; + } + } + + assert(false, "No suitable test data found in this implementation. Consider updating the candidate list"); +} + +const [language, expectedHourCycles] = findSuitableTestData(); + +const locale = new Intl.Locale(language); +assert.compareArray( + locale.getHourCycles(), + expectedHourCycles, + `getHourCycles() for "${language}" should equal getHourCycles() for locale with likely region` +); diff --git a/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/region-override.js b/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/region-override.js new file mode 100644 index 0000000000000..6a913428e8c41 --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/region-override.js @@ -0,0 +1,67 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-Intl.Locale.prototype.getHourCycles +description: > + When the locale has a region override ("rg") keyword, its region overrides + the region subtag when deriving the hour cycle preference. +info: | + HourCyclesOfLocale ( loc ) + ... + 2. Let _preference_ be RegionPreference(_loc_.[[Locale]]). + 3. Let _region_ be _preference_.[[Region]]. + 4. Let _regionOverride_ be _preference_.[[RegionOverride]]. + 5. If _regionOverride_ is not *undefined* and time data for _regionOverride_ + are available, then + a. Let _lookupRegion_ be _regionOverride_. + 6. Else, + a. Let _lookupRegion_ be _region_. + ... + + RegionPreference ( locale ) + ... + 3. Let _regionOverride_ be CanonicalUnicodeSubdivision(_locale_, *"rg"*). + 4. Return { [[Region]]: _region_, [[RegionOverride]]: _regionOverride_ }. +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// In order not to rely on a particular implementation's locale data, this test +// searches for a suitable locale with a region subtag together with a region +// override ("rg" extension) whose region would influence the supported hour +// cycles. +// +// Each candidate below is a locale with a region override extension, together +// with a locale that names the override's region explicitly (e.g. +// "en-US-u-rg-gbzzzz" overrides the region with "GB", so "en-GB"). Because the +// region override is set, HourCyclesOfLocale must use it as the lookup region +// instead of the region subtag. Its hour cycles must therefore be identical to +// those of the paired override-region locale. An incorrect implementation might +// instead ignore the "rg" keyword and use the region subtag. +function findSuitableTestData() { + const candidates = [ + ["en-US-u-rg-gbzzzz", "en-GB"], + ["en-US-u-rg-dezzzz", "en-DE"], + ["en-US-u-rg-frzzzz", "en-FR"], + ]; + + for (const [overrideTag, regionTag] of candidates) { + const hourCyclesWithOverrideRegion = new Intl.Locale(regionTag).getHourCycles(); + + const withoutOverride = new Intl.Locale(new Intl.Locale(overrideTag).baseName); + if (!compareArray(withoutOverride.getHourCycles(), hourCyclesWithOverrideRegion)) { + return [overrideTag, hourCyclesWithOverrideRegion]; + } + } + + assert(false, "No suitable test data found in this implementation. Consider updating the candidate list"); +} + +const [overrideTag, expectedHourCycles] = findSuitableTestData(); + +const locale = new Intl.Locale(overrideTag); +assert.compareArray( + locale.getHourCycles(), + expectedHourCycles, + `getHourCycles() for "${overrideTag}" should equal getHourCycles() for the override region` +); diff --git a/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/region-priority.js b/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/region-priority.js new file mode 100644 index 0000000000000..d6cfc22679349 --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/region-priority.js @@ -0,0 +1,86 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-Intl.Locale.prototype.getHourCycles +description: > + The region used to look up the hour cycle preference is chosen from the + available signals in the correct priority order. +info: | + HourCyclesOfLocale ( loc ) + ... + 2. Let _preference_ be RegionPreference(_loc_.[[Locale]]). + 3. Let _region_ be _preference_.[[Region]]. + 4. Let _regionOverride_ be _preference_.[[RegionOverride]]. + 5. If _regionOverride_ is not *undefined* and time data for _regionOverride_ + are available, then + a. Let _lookupRegion_ be _regionOverride_. + 6. Else, + a. Let _lookupRegion_ be _region_. + ... + + RegionPreference ( locale ) + 1. Let _region_ be GetLocaleRegion(_locale_). + 2. If _region_ is *undefined*, then + a. Set _region_ to CanonicalUnicodeSubdivision(_locale_, *"sd"*). + b. If _region_ is *undefined*, then + i. Let _maximal_ be the result of the Add Likely Subtags algorithm applied + to _locale_. If an error is signaled, set _maximal_ to _locale_. + ii. Set _maximal_ to CanonicalizeUnicodeLocaleId(_maximal_). + iii. Set _region_ to GetLocaleRegion(_maximal_). + iv. If _region_ is *undefined*, then + 1. Set _region_ to *"001"*. + 3. Let _regionOverride_ be CanonicalUnicodeSubdivision(_locale_, *"rg"*). + 4. Return { [[Region]]: _region_, [[RegionOverride]]: _regionOverride_ }. +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// Together, HourCyclesOfLocale and RegionPreference select the region used to +// look up the hour cycle preference from the available signals in this priority +// order (highest first): +// +// 1. the "rg" region override keyword (when time data for it exists) +// 2. the region subtag +// 3. the "sd" subdivision keyword +// 4. the region computed by the Add Likely Subtags algorithm +// 5. the "001" (world) region +// +// Each entry below is a locale that carries the signal for its priority level +// on top of all lower-priority signals, paired with a locale that names the +// region that signal must resolve to. The paired locale uses the same base +// language so that any language-dependent locale data is held constant. For +// example, "en-US-u-sd-gbeng-rg-gbzzzz" carries an "rg" override of region "GB", +// a region subtag "US", an "sd" subdivision in region "GB", and a base language +// "en" whose likely region is "US"; the "rg" override has the highest priority, +// so the locale must behave like "en-GB". +// +// The last entry has no lower-priority signals: the language "eo" (Esperanto) +// has no likely region, so its region falls back to "001". +const levels = [ + { description: 'the "rg" region override', locale: "en-US-u-sd-gbeng-rg-gbzzzz", region: "en-GB" }, + { description: "the region subtag", locale: "en-US-u-sd-gbeng", region: "en-US" }, + { description: 'the "sd" subdivision', locale: "en-u-sd-gbeng", region: "en-GB" }, + { description: "the Add Likely Subtags region", locale: "en", region: "en-US" }, + { description: 'the "001" region', locale: "eo", region: "eo-001" }, +]; + +// For each priority level to be observable, the region it selects must have +// different hour cycles than the region selected by the next lower level in this +// implementation; otherwise the test could not tell them apart. +for (let i = 0; i < levels.length - 1; i++) { + const higher = new Intl.Locale(levels[i].region).getHourCycles(); + const lower = new Intl.Locale(levels[i + 1].region).getHourCycles(); + assert( + !compareArray(higher, lower), + `Inconclusive: ${levels[i].description} and ${levels[i + 1].description} ` + + "select regions with identical hour cycles in this implementation. Consider updating the test data" + ); +} + +for (const { description, locale, region } of levels) { + assert.compareArray( + new Intl.Locale(locale).getHourCycles(), + new Intl.Locale(region).getHourCycles(), + `getHourCycles() for "${locale}" should use ${description}, like "${region}"` + ); +} diff --git a/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/subdivision-region.js b/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/subdivision-region.js new file mode 100644 index 0000000000000..263b69cc9e32a --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getHourCycles/subdivision-region.js @@ -0,0 +1,62 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-Intl.Locale.prototype.getHourCycles +description: > + When the locale has no region but has a subdivision ("sd") keyword, the + subdivision's region is used to derive the hour cycle preference. +info: | + RegionPreference ( locale ) + 1. Let _region_ be GetLocaleRegion(_locale_). + 2. If _region_ is *undefined*, then + a. Set _region_ to CanonicalUnicodeSubdivision(_locale_, *"sd"*). + b. If _region_ is *undefined*, then + i. Let _maximal_ be the result of the Add Likely Subtags algorithm applied + to _locale_. If an error is signaled, set _maximal_ to _locale_. + ... + ... +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// In order not to rely on a particular implementation's locale data, this test +// searches for a suitable locale without a region subtag, but with a +// subdivision ("sd" extension) whose region would influence the supported hour +// cycles. +// +// Each candidate below is a locale with a subdivision extension but no region +// subtag, together with a locale that names that subdivision's region +// explicitly (e.g. "en-u-sd-gbeng" has subdivision "gbeng", whose region is +// "GB", so "en-GB"). Because such a locale has no region subtag, +// RegionPreference must derive its region from the subdivision. Its hour cycles +// must therefore be identical to those of the paired region locale. An +// incorrect implementation might instead ignore the "sd" extension and apply +// Add Likely Subtags to the bare language, giving "en-Latn-US". +function findSuitableTestData() { + const candidates = [ + ["en-u-sd-gbeng", "en-GB"], + ["en-u-sd-fridf", "en-FR"], + ["en-u-sd-debe", "en-DE"], + ]; + + for (const [subdivisionTag, regionTag] of candidates) { + const hourCyclesWithSubdivisionRegion = new Intl.Locale(regionTag).getHourCycles(); + + const bareLanguage = new Intl.Locale(subdivisionTag).baseName; + const fallbackLocale = new Intl.Locale(bareLanguage).maximize(); + if (!compareArray(fallbackLocale.getHourCycles(), hourCyclesWithSubdivisionRegion)) { + return [subdivisionTag, hourCyclesWithSubdivisionRegion]; + } + } + + assert(false, "No suitable test data found in this implementation. Consider updating the candidate list"); +} + +const [subdivisionTag, expectedHourCycles] = findSuitableTestData(); + +const locale = new Intl.Locale(subdivisionTag); +assert.compareArray( + locale.getHourCycles(), + expectedHourCycles, + `getHourCycles() for "${subdivisionTag}" should equal getHourCycles() for the subdivision's region` +); diff --git a/JSTests/test262/test/intl402/Locale/prototype/getWeekInfo/likely-subtags-region.js b/JSTests/test262/test/intl402/Locale/prototype/getWeekInfo/likely-subtags-region.js new file mode 100644 index 0000000000000..0f7669a75f170 --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getWeekInfo/likely-subtags-region.js @@ -0,0 +1,75 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-intl.locale.prototype.getWeekInfo +description: > + When the locale has no region, the Add Likely Subtags algorithm is used to + derive the region whose week info is returned. +info: | + RegionPreference ( locale ) + ... + 2. If _region_ is *undefined*, then + a. Set _region_ to CanonicalUnicodeSubdivision(_locale_, *"sd"*). + b. If _region_ is *undefined*, then + i. Let _maximal_ be the result of the Add Likely Subtags algorithm applied + to _locale_. If an error is signaled, set _maximal_ to _locale_. + ii. Set _maximal_ to CanonicalizeUnicodeLocaleId(_maximal_). + iii. Set _region_ to GetLocaleRegion(_maximal_). + iv. If _region_ is *undefined*, then + 1. Set _region_ to *"001"*. + ... +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// In order not to rely on a particular implementation's locale data, this test +// searches for a suitable locale without a region subtag, but where the likely +// region would influence the supported week info. +// +// Each candidate below is a language subtag together with the region that the +// Add Likely Subtags algorithm derives for it (e.g. "th" maximizes to +// "th-Thai-TH", so "th-TH"). A language tag without a region, subdivision ("sd" +// keyword), or region override ("rg" keyword) passed to RegionPreference must +// apply Add Likely Subtags and arrive at the paired region. Its week info must +// therefore be identical to those of the candidate locale. An incorrect +// implementation might instead fall back to the region "001" in step 2.b.iv. +// +// We could assume even less about the locale data by not hardcoding these +// likely regions and instead checking all available locales with the result of +// maximize() on each one, but that assumes the implementation has a working +// maximize(), and if that's the case then it probably implements this step +// correctly as well. +function weekInfoEqual(a, b) { + return a.firstDay === b.firstDay && compareArray(a.weekend, b.weekend); +} + +function findSuitableTestData() { + const candidates = ["th-TH", "fa-IR", "ja-JP", "sa-IN", "ps-AF"]; + + for (const regionTag of candidates) { + const weekInfoWithLikelyRegion = new Intl.Locale(regionTag).getWeekInfo(); + + const bareLanguage = regionTag.replace(/-.*/, ""); + const fallbackLocale = new Intl.Locale(`${bareLanguage}-001`); + if (!weekInfoEqual(fallbackLocale.getWeekInfo(), weekInfoWithLikelyRegion)) { + return [bareLanguage, weekInfoWithLikelyRegion]; + } + } + + assert(false, "No suitable test data found in this implementation. Consider updating the candidate list"); +} + +const [language, expectedWeekInfo] = findSuitableTestData(); + +const locale = new Intl.Locale(language); +const weekInfo = locale.getWeekInfo(); +assert.sameValue( + weekInfo.firstDay, + expectedWeekInfo.firstDay, + `getWeekInfo() for "${language}" should return firstDay that matches the likely region` +); +assert.compareArray( + weekInfo.weekend, + expectedWeekInfo.weekend, + `getWeekInfo() for "${language}" should return weekend that matches the likely region` +); diff --git a/JSTests/test262/test/intl402/Locale/prototype/getWeekInfo/region-override.js b/JSTests/test262/test/intl402/Locale/prototype/getWeekInfo/region-override.js new file mode 100644 index 0000000000000..3a5e78ce887ad --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getWeekInfo/region-override.js @@ -0,0 +1,79 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-intl.locale.prototype.getWeekInfo +description: > + When the locale has a region override ("rg") keyword, its region overrides + the region subtag when deriving the week info. +info: | + WeekInfoOfLocale ( loc ) + ... + 2. Let _preference_ be RegionPreference(_loc_.[[Locale]]). + 3. Let _region_ be _preference_.[[Region]]. + 4. Let _regionOverride_ be _preference_.[[RegionOverride]]. + 5. If _regionOverride_ is not *undefined* and week data for _regionOverride_ + are available, then + a. Let _lookupRegion_ be _regionOverride_. + 6. Else, + a. Let _lookupRegion_ be _region_. + ... + + RegionPreference ( locale ) + ... + 3. Let _regionOverride_ be CanonicalUnicodeSubdivision(_locale_, *"rg"*). + 4. Return { [[Region]]: _region_, [[RegionOverride]]: _regionOverride_ }. +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// In order not to rely on a particular implementation's locale data, this test +// searches for a suitable locale with a region subtag together with a region +// override ("rg" extension) whose region would influence the supported week +// info. +// +// Each candidate below is a locale with a region override extension, together +// with a locale that names the override's region explicitly (e.g. +// "en-US-u-rg-inzzzz" overrides the region with "IN", so "en-IN"). Because the +// region override is set, WeekInfoOfLocale must use it as the lookup region +// instead of the region subtag. Its week info must therefore be identical to +// those of the paired override-region locale. An incorrect implementation might +// instead ignore the "rg" keyword and use the region subtag. +function weekInfoEqual(a, b) { + return a.firstDay === b.firstDay && compareArray(a.weekend, b.weekend); +} + +function findSuitableTestData() { + const candidates = [ + ["en-US-u-rg-dezzzz", "en-DE"], + ["en-US-u-rg-inzzzz", "en-IN"], + ["en-US-u-rg-irzzzz", "en-IR"], + ["en-US-u-rg-afzzzz", "en-AF"], + ]; + + for (const [overrideTag, regionTag] of candidates) { + const weekInfoWithOverrideRegion = new Intl.Locale(regionTag).getWeekInfo(); + + const baseName = overrideTag.replace(/-u-.*/, ""); + const baseLocale = new Intl.Locale(baseName); + if (!weekInfoEqual(baseLocale.getWeekInfo(), weekInfoWithOverrideRegion)) { + return [overrideTag, weekInfoWithOverrideRegion]; + } + } + + assert(false, "No suitable test data found in this implementation. Consider updating the candidate list"); +} + +const [overrideTag, expectedWeekInfo] = findSuitableTestData(); + +const locale = new Intl.Locale(overrideTag); +const weekInfo = locale.getWeekInfo(); +assert.sameValue( + weekInfo.firstDay, + expectedWeekInfo.firstDay, + `getWeekInfo() for "${overrideTag}" should return firstDay that matches the override region` +); +assert.compareArray( + weekInfo.weekend, + expectedWeekInfo.weekend, + `getWeekInfo() for "${overrideTag}" should return weekend that matches the override region` +); diff --git a/JSTests/test262/test/intl402/Locale/prototype/getWeekInfo/region-priority.js b/JSTests/test262/test/intl402/Locale/prototype/getWeekInfo/region-priority.js new file mode 100644 index 0000000000000..0b4cb8fb832a8 --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getWeekInfo/region-priority.js @@ -0,0 +1,97 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-intl.locale.prototype.getWeekInfo +description: > + The region used to look up the week info is chosen from the available signals + in the correct priority order. +info: | + WeekInfoOfLocale ( loc ) + ... + 2. Let _preference_ be RegionPreference(_loc_.[[Locale]]). + 3. Let _region_ be _preference_.[[Region]]. + 4. Let _regionOverride_ be _preference_.[[RegionOverride]]. + 5. If _regionOverride_ is not *undefined* and week data for _regionOverride_ + are available, then + a. Let _lookupRegion_ be _regionOverride_. + 6. Else, + a. Let _lookupRegion_ be _region_. + ... + + RegionPreference ( locale ) + 1. Let _region_ be GetLocaleRegion(_locale_). + 2. If _region_ is *undefined*, then + a. Set _region_ to CanonicalUnicodeSubdivision(_locale_, *"sd"*). + b. If _region_ is *undefined*, then + i. Let _maximal_ be the result of the Add Likely Subtags algorithm applied + to _locale_. If an error is signaled, set _maximal_ to _locale_. + ii. Set _maximal_ to CanonicalizeUnicodeLocaleId(_maximal_). + iii. Set _region_ to GetLocaleRegion(_maximal_). + iv. If _region_ is *undefined*, then + 1. Set _region_ to *"001"*. + 3. Let _regionOverride_ be CanonicalUnicodeSubdivision(_locale_, *"rg"*). + 4. Return { [[Region]]: _region_, [[RegionOverride]]: _regionOverride_ }. +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// Together, WeekInfoOfLocale and RegionPreference select the region used to +// look up the week info from the available signals in this priority order +// (highest first): +// +// 1. the "rg" region override keyword (when week data for it exists) +// 2. the region subtag +// 3. the "sd" subdivision keyword +// 4. the region computed by the Add Likely Subtags algorithm +// 5. the "001" (world) region +// +// Each entry below is a locale that carries the signal for its priority level +// on top of all lower-priority signals, paired with a locale that names the +// region that signal must resolve to. The paired locale uses the same base +// language so that any language-dependent locale data is held constant. For +// example, "fa-JP-u-sd-inka-rg-afzzzz" carries an "rg" override of region "AF", +// a region subtag "JP", an "sd" subdivision in region "IN", and a base language +// "fa" whose likely region is "IR"; the "rg" override has the highest priority, +// so the locale must behave like "fa-AF". +// +// The last entry has no lower-priority signals: the language "eo" (Esperanto) +// has no likely region, so its region falls back to "001". +const levels = [ + { description: 'the "rg" region override', locale: "fa-JP-u-sd-inka-rg-afzzzz", region: "fa-AF" }, + { description: "the region subtag", locale: "fa-JP-u-sd-inka", region: "fa-JP" }, + { description: 'the "sd" subdivision', locale: "fa-u-sd-inka", region: "fa-IN" }, + { description: "the Add Likely Subtags region", locale: "fa", region: "fa-IR" }, + { description: 'the "001" region', locale: "eo", region: "eo-001" }, +]; + +function weekInfoEqual(a, b) { + return a.firstDay === b.firstDay && compareArray(a.weekend, b.weekend); +} + +// For each priority level to be observable, the region it selects must have +// different week info than the region selected by the next lower level in this +// implementation; otherwise the test could not tell them apart. +for (let i = 0; i < levels.length - 1; i++) { + const higher = new Intl.Locale(levels[i].region).getWeekInfo(); + const lower = new Intl.Locale(levels[i + 1].region).getWeekInfo(); + assert( + !weekInfoEqual(higher, lower), + `Inconclusive: ${levels[i].description} and ${levels[i + 1].description} ` + + "selected regions with identical week info in this implementation. Consider updating the test data" + ); +} + +for (const { description, locale, region } of levels) { + const weekInfo = new Intl.Locale(locale).getWeekInfo(); + const expected = new Intl.Locale(region).getWeekInfo(); + assert.sameValue( + weekInfo.firstDay, + expected.firstDay, + `getWeekInfo() for "${locale}" should use ${description}, like "${region}": firstDay mismatch` + ); + assert.compareArray( + weekInfo.weekend, + expected.weekend, + `getWeekInfo() for "${locale}" should use ${description}, like "${region}": weekend mismatch` + ); +} diff --git a/JSTests/test262/test/intl402/Locale/prototype/getWeekInfo/subdivision-region.js b/JSTests/test262/test/intl402/Locale/prototype/getWeekInfo/subdivision-region.js new file mode 100644 index 0000000000000..4722902e8e2e0 --- /dev/null +++ b/JSTests/test262/test/intl402/Locale/prototype/getWeekInfo/subdivision-region.js @@ -0,0 +1,72 @@ +// Copyright 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-intl.locale.prototype.getWeekInfo +description: > + When the locale has no region but has a subdivision ("sd") keyword, the + subdivision's region is used to derive the week info. +info: | + RegionPreference ( locale ) + 1. Let _region_ be GetLocaleRegion(_locale_). + 2. If _region_ is *undefined*, then + a. Set _region_ to CanonicalUnicodeSubdivision(_locale_, *"sd"*). + b. If _region_ is *undefined*, then + i. Let _maximal_ be the result of the Add Likely Subtags algorithm applied + to _locale_. If an error is signaled, set _maximal_ to _locale_. + ... + ... +features: [Intl.Locale, Intl.Locale-info] +---*/ + +// In order not to rely on a particular implementation's locale data, this test +// searches for a suitable locale without a region subtag, but with a +// subdivision ("sd" extension) whose region would influence the supported week +// info. +// +// Each candidate below is a locale with a subdivision extension but no region +// subtag, together with a locale that names that subdivision's region +// explicitly (e.g. "en-u-sd-inka" has subdivision "inka", whose region is "IN", +// so "en-IN"). Because such a locale has no region subtag, RegionPreference +// must derive its region from the subdivision. Its week info must therefore be +// identical to those of the paired region locale. An incorrect implementation +// might instead ignore the "sd" extension and apply Add Likely Subtags to the +// bare language, giving "en-Latn-US". +function weekInfoEqual(a, b) { + return a.firstDay === b.firstDay && compareArray(a.weekend, b.weekend); +} + +function findSuitableTestData() { + const candidates = [ + ["en-u-sd-inka", "en-IN"], + ["en-u-sd-irthr", "en-IR"], + ["en-u-sd-afgh", "en-AF"], + ]; + + for (const [subdivisionTag, regionTag] of candidates) { + const weekInfoWithSubdivisionRegion = new Intl.Locale(regionTag).getWeekInfo(); + + const baseName = subdivisionTag.replace(/-u-.*/, ""); + const fallbackLocale = new Intl.Locale(baseName).maximize(); + if (!weekInfoEqual(fallbackLocale.getWeekInfo(), weekInfoWithSubdivisionRegion)) { + return [subdivisionTag, weekInfoWithSubdivisionRegion]; + } + } + + assert(false, "No suitable test data found in this implementation. Consider updating the candidate list"); +} + +const [subdivisionTag, expectedWeekInfo] = findSuitableTestData(); + +const locale = new Intl.Locale(subdivisionTag); +const weekInfo = locale.getWeekInfo(); +assert.sameValue( + weekInfo.firstDay, + expectedWeekInfo.firstDay, + `getWeekInfo() for "${subdivisionTag}" should return firstDay that matches the subdivision's region` +); +assert.compareArray( + weekInfo.weekend, + expectedWeekInfo.weekend, + `getWeekInfo() for "${subdivisionTag}" should return weekend that matches the subdivision's region` +); diff --git a/JSTests/test262/test/language/expressions/assignment/dstr/array-rest-elision-invalid.js b/JSTests/test262/test/language/expressions/assignment/dstr/array-rest-elision-invalid.js deleted file mode 100644 index bd7efd0c73def..0000000000000 --- a/JSTests/test262/test/language/expressions/assignment/dstr/array-rest-elision-invalid.js +++ /dev/null @@ -1,23 +0,0 @@ -// This file was procedurally generated from the following sources: -// - src/dstr-assignment/array-rest-elision-invalid.case -// - src/dstr-assignment/syntax/assignment-expr.template -/*--- -description: ArrayAssignmentPattern may not include elisions following an AssignmentRestElement in a AssignmentElementList. (AssignmentExpression) -esid: sec-variable-statement-runtime-semantics-evaluation -features: [destructuring-binding] -flags: [generated] -negative: - phase: parse - type: SyntaxError -info: | - VariableDeclaration : BindingPattern Initializer - - 1. Let rhs be the result of evaluating Initializer. - 2. Let rval be GetValue(rhs). - 3. ReturnIfAbrupt(rval). - 4. Return the result of performing BindingInitialization for - BindingPattern passing rval and undefined as arguments. ----*/ -$DONOTEVALUATE(); - -0, [...x,] = []; diff --git a/JSTests/test262/test/language/expressions/assignment/dstr/obj-rest-before-comma-invalid.js b/JSTests/test262/test/language/expressions/assignment/dstr/obj-rest-before-comma-invalid.js new file mode 100644 index 0000000000000..aa175e17f740e --- /dev/null +++ b/JSTests/test262/test/language/expressions/assignment/dstr/obj-rest-before-comma-invalid.js @@ -0,0 +1,25 @@ +// This file was procedurally generated from the following sources: +// - src/dstr-assignment/obj-rest-before-comma-invalid.case +// - src/dstr-assignment/syntax/assignment-expr.template +/*--- +description: Object rest element cannot be followed by a comma in ObjectAssignmentPattern. (AssignmentExpression) +esid: sec-variable-statement-runtime-semantics-evaluation +features: [object-rest, destructuring-binding] +flags: [generated] +negative: + phase: parse + type: SyntaxError +info: | + VariableDeclaration : BindingPattern Initializer + + 1. Let rhs be the result of evaluating Initializer. + 2. Let rval be GetValue(rhs). + 3. ReturnIfAbrupt(rval). + 4. Return the result of performing BindingInitialization for + BindingPattern passing rval and undefined as arguments. +---*/ +$DONOTEVALUATE(); +var rest; + +0, {...rest,} = {} +; diff --git a/JSTests/test262/test/language/expressions/assignment/dstr/obj-rest-not-last-element-invalid.js b/JSTests/test262/test/language/expressions/assignment/dstr/obj-rest-not-last-element-invalid.js index 4c59914efce25..45325c42d5f5b 100644 --- a/JSTests/test262/test/language/expressions/assignment/dstr/obj-rest-not-last-element-invalid.js +++ b/JSTests/test262/test/language/expressions/assignment/dstr/obj-rest-not-last-element-invalid.js @@ -2,7 +2,7 @@ // - src/dstr-assignment/obj-rest-not-last-element-invalid.case // - src/dstr-assignment/syntax/assignment-expr.template /*--- -description: Object rest element needs to be the last AssignmenProperty in ObjectAssignmentPattern. (AssignmentExpression) +description: Object rest element needs to be the last AssignmentProperty in ObjectAssignmentPattern. (AssignmentExpression) esid: sec-variable-statement-runtime-semantics-evaluation features: [object-rest, destructuring-binding] flags: [generated] diff --git a/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-a_FIXTURE.js b/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-a_FIXTURE.js new file mode 100644 index 0000000000000..01d57b530743a --- /dev/null +++ b/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-a_FIXTURE.js @@ -0,0 +1,6 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +import "./import-fulfilled-member-of-errored-cycle-b_FIXTURE.js"; + +await Promise.resolve(0); diff --git a/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-b_FIXTURE.js b/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-b_FIXTURE.js new file mode 100644 index 0000000000000..963e97d1f14c0 --- /dev/null +++ b/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-b_FIXTURE.js @@ -0,0 +1,8 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +import "./import-fulfilled-member-of-errored-cycle-c_FIXTURE.js"; + +await Promise.resolve(0); + +throw new Error("async error in B"); diff --git a/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-c_FIXTURE.js b/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-c_FIXTURE.js new file mode 100644 index 0000000000000..db8e66741b909 --- /dev/null +++ b/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-c_FIXTURE.js @@ -0,0 +1,6 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +import "./import-fulfilled-member-of-errored-cycle-a_FIXTURE.js"; + +await Promise.resolve(0); diff --git a/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-main_FIXTURE.js b/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-main_FIXTURE.js new file mode 100644 index 0000000000000..8579c8c0e5604 --- /dev/null +++ b/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-main_FIXTURE.js @@ -0,0 +1,5 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +import "./import-fulfilled-member-of-errored-cycle-b_FIXTURE.js"; +import "./import-fulfilled-member-of-errored-cycle-x_FIXTURE.js"; diff --git a/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-x_FIXTURE.js b/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-x_FIXTURE.js new file mode 100644 index 0000000000000..db8e66741b909 --- /dev/null +++ b/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle-x_FIXTURE.js @@ -0,0 +1,6 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +import "./import-fulfilled-member-of-errored-cycle-a_FIXTURE.js"; + +await Promise.resolve(0); diff --git a/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle.js b/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle.js new file mode 100644 index 0000000000000..f2b4dbf76f0e6 --- /dev/null +++ b/JSTests/test262/test/language/expressions/dynamic-import/import-fulfilled-member-of-errored-cycle.js @@ -0,0 +1,82 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-moduleevaluation +description: > + Dynamic import of a fulfilled member of an errored async cycle rejects with + the recorded evaluation error +info: | + When the top-level-await cycle {A, B, C} rejects (B throws after its await), + only B and its async parent modules (X and main) record the evaluation + error. A and C had already finished executing and stay EVALUATED with an + empty [[EvaluationError]]. The cycle is first evaluated starting from B (the + first dependency of main), so B is the [[CycleRoot]] of {A, B, C} and, not + being an evaluation entry point itself, it has no [[TopLevelCapability]]. + + A later dynamic import of C redirects to its [[CycleRoot]] B, whose + [[Status]] is EVALUATED with a non-empty [[EvaluationError]] and no + [[TopLevelCapability]]. Evaluate() is well-defined on such case, thus a + fresh capability is created and installed as B's [[TopLevelCapability]], + InnerModuleEvaluation returns the recorded [[EvaluationError]], and the + capability is rejected with it. This makes the import of C reject with + the same error with which the cycle originally failed. + + Evaluate ( ) + 1. ... + 1. If _module_.[[Status]] is either EVALUATING-ASYNC or EVALUATED, then + 1. If module.[[CycleRoot]] is not empty, then + 1. Set _module_ to _module_.[[CycleRoot]]. + 1. Else, + 1. ... + 1. If _module_.[[TopLevelCapability]] is not EMPTY, then + 1. Return _module_.[[TopLevelCapability]].[[Promise]]. + 1. Let _stack_ be a new empty List. + 1. Let _capability_ be ! NewPromiseCapability(%Promise%). + 1. Set _module_.[[TopLevelCapability]] to _capability_. + 1. Let _result_ be Completion(InnerModuleEvaluation(_module_, _stack_, 0)). + 1. If _result_ is an abrupt completion, then + 1. ... + 1. Perform ! Call(_capability_.[[Reject]], *undefined*, « _result_.[[Value]] »). + 1. ... + 1. Return _capability_.[[Promise]]. + + InnerModuleEvaluation ( _module_, _stack_, _index_ ) + 1. ... + 1. If _module_.[[Status]] is either EVALUATING-ASYNC or EVALUATED, then + 1. If _module_.[[EvaluationError]] is EMPTY, return _index_. + 1. Return ? _module_.[[EvaluationError]]. + 1. ... +flags: [async] +features: [top-level-await, dynamic-import] +includes: [asyncHelpers.js] +---*/ + +asyncTest(async function () { + var errorFromMain = null; + try { + await import("./import-fulfilled-member-of-errored-cycle-main_FIXTURE.js"); + } catch (err) { + errorFromMain = err; + } + assert.notSameValue(errorFromMain, null, "The import of main should reject"); + assert.sameValue( + errorFromMain instanceof Error, + true, + "The import of main should reject with the error thrown in B" + ); + assert.sameValue(errorFromMain.message, "async error in B"); + + var errorFromC = null; + try { + await import("./import-fulfilled-member-of-errored-cycle-c_FIXTURE.js"); + } catch (err) { + errorFromC = err; + } + assert.notSameValue(errorFromC, null, "The import of C should reject"); + assert.sameValue( + errorFromC, + errorFromMain, + "The import of C should reject with the [[EvaluationError]] recorded for the cycle root B" + ); +}); diff --git a/JSTests/test262/test/language/import/import-defer/deferred-namespace-object/json-module.js b/JSTests/test262/test/language/import/import-defer/deferred-namespace-object/json-module.js new file mode 100644 index 0000000000000..e6328876dcb2b --- /dev/null +++ b/JSTests/test262/test/language/import/import-defer/deferred-namespace-object/json-module.js @@ -0,0 +1,44 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-modulenamespacecreate +description: > + Deferred namespaces of JSON modules have a "Deferred Module" @@toStringTag + and expose the parsed JSON value as their "default" export +info: | + ModuleNamespaceCreate ( _module_, _exports_, _phase_ ) + 1. ... + 1. Let _M_ be MakeBasicObject(_internalSlotsList_). + 1. ... + 1. If _phase_ is ~defer~, then + 1. ... + 1. Let _toStringTag_ be *"Deferred Module"*. + 1. Else, + 1. ... + 1. Create an own data property of _M_ named %Symbol.toStringTag% whose [[Value]] + is _toStringTag_ and whose [[Writable]], [[Enumerable]], and [[Configurable]] + attributes are false. + 1. Return _M_. + + ParseJSONModule ( _source_ ) + 1. Let _json_ be ? Call(%JSON.parse%, *undefined*, « _source_ »). + 1. Return CreateDefaultExportSyntheticModule(_json_). +flags: [module] +features: [import-defer, import-attributes, json-modules] +includes: [propertyHelper.js] +---*/ + +import defer * as ns from "./json-module_FIXTURE.json" with { type: "json" }; + +verifyProperty(ns, Symbol.toStringTag, { + value: "Deferred Module", + writable: false, + enumerable: false, + configurable: false, +}); + +assert.sameValue(typeof ns.default, "object", "The default export is the parsed JSON object"); +assert.sameValue(Object.getPrototypeOf(ns.default), Object.prototype); +assert.sameValue(ns.default.test262, "JSON module"); +assert.sameValue(ns.default.number, 42); diff --git a/JSTests/test262/test/language/import/import-defer/deferred-namespace-object/json-module_FIXTURE.json b/JSTests/test262/test/language/import/import-defer/deferred-namespace-object/json-module_FIXTURE.json new file mode 100644 index 0000000000000..42f48a13b3ea3 --- /dev/null +++ b/JSTests/test262/test/language/import/import-defer/deferred-namespace-object/json-module_FIXTURE.json @@ -0,0 +1,4 @@ +{ + "test262": "JSON module", + "number": 42 +} diff --git a/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/a-tla_FIXTURE.js b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/a-tla_FIXTURE.js new file mode 100644 index 0000000000000..c3c6b468acdc4 --- /dev/null +++ b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/a-tla_FIXTURE.js @@ -0,0 +1,10 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +import { blocker, aStarted } from "./setup_FIXTURE.js"; +import "./b_FIXTURE.js"; + +globalThis.evaluations.push("A-before-await"); +aStarted.resolve(); +await blocker.promise; +globalThis.evaluations.push("A-after-await"); diff --git a/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/b_FIXTURE.js b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/b_FIXTURE.js new file mode 100644 index 0000000000000..fe545630769b2 --- /dev/null +++ b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/b_FIXTURE.js @@ -0,0 +1,6 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +import "./a-tla_FIXTURE.js"; + +globalThis.evaluations.push("B"); diff --git a/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/c_FIXTURE.js b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/c_FIXTURE.js new file mode 100644 index 0000000000000..d0119af5fc35a --- /dev/null +++ b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/c_FIXTURE.js @@ -0,0 +1,9 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +// Middle's Evaluate is invoked first, while A is still suspended on the +// blocker, and only then is the blocker resolved to let A finish. +import "./middle_FIXTURE.js"; +import "./resolve-blocker_FIXTURE.js"; + +globalThis.evaluations.push("C"); diff --git a/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/d_FIXTURE.js b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/d_FIXTURE.js new file mode 100644 index 0000000000000..0291f662d70f1 --- /dev/null +++ b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/d_FIXTURE.js @@ -0,0 +1,6 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +import "./b_FIXTURE.js"; + +globalThis.evaluations.push("D"); diff --git a/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/main.js b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/main.js new file mode 100644 index 0000000000000..95965d95134b0 --- /dev/null +++ b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/main.js @@ -0,0 +1,87 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-IsModuleSCCEvaluated +description: > + Deferred evaluation waits for an in-flight async cycle in the deferred graph +info: | + In this test, the module A contains top-level await and forms a cycle with + the module B, and the module Middle defers the module D, whose only + dependency is B. This test first dynamically imports A and waits until A's + evaluation has started and is suspended on the "blocker" promise, so A is + the [[CycleRoot]] of the strongly connected component {A, B} and is + guaranteed to still be EVALUATING-ASYNC. It then dynamically imports C, + which imports Middle followed by ResolveBlocker. When Middle's evaluation + starts, B is already EVALUATED, but its cycle root A is still + EVALUATING-ASYNC, so Middle must wait for the whole cycle {A, B} instead of + considering only B's individual status. ResolveBlocker then resolves the + blocker, allowing A to finish; only after that may Middle's body execute, + and accessing the deferred namespace of D must then only evaluate D itself. + + IsModuleSCCEvaluated ( _module_ ) + 1. If _module_.[[CycleRoot]] is not EMPTY, then + 1. If _module_.[[CycleRoot]].[[Status]] is EVALUATED, return true. + 1. Return false. + 1. If _module_.[[Status]] is EVALUATED, return true. + 1. Return false. + + GatherAsynchronousTransitiveDependencies ( _module_, [ _seen_ ] ) + 1. If _seen_ is not specified, let _seen_ be a new empty List. + 1. Let _result_ be a new empty List. + 1. If _seen_ contains _module_, return _result_. + 1. Append _module_ to _seen_. + 1. If _module_ is not a Cyclic Module Record, return _result_. + 1. If _module_.[[Status]] is either EVALUATING or IsModuleSCCEvaluated(_module_), return _result_. + 1. If _module_.[[HasTLA]] is *true*, then + 1. Append _module_ to _result_. + 1. Return _result_. + 1. For each ModuleRequest Record _required_ of _module_.[[RequestedModules]], do + 1. Let _requiredModule_ be GetImportedModule(_module_, _required_.[[Specifier]]). + 1. Let _additionalModules_ be GatherAsynchronousTransitiveDependencies(_requiredModule_, _seen_). + 1. For each Module Record _m_ of _additionalModules_, do + 1. If _result_ does not contain _m_, append _m_ to _result_. + 1. Return _result_. + + ReadyForSyncExecution ( _module_ [ , _seen_ ] ) + 1. If _module_ is not a Cyclic Module Record, return true. + 1. If _seen_ is not present, set _seen_ to a new empty List. + 1. If _seen_ contains module, return true. + 1. Append _module_ to _seen_. + 1. If IsModuleSCCEvaluated(_module_), return true. + 1. If _module_.[[Status]] is EVALUATING or EVALUATING-ASYNC, return false. + 1. Assert: _module_.[[Status]] is LINKED. + 1. If _module_.[[HasTLA]] is true, return false. + 1. For each ModuleRequest Record request of _module_.[[RequestedModules]], do + 1. Let _requiredModule_ be GetImportedModule(_module_, _request_). + 1. If ReadyForSyncExecution(_requiredModule_, _seen_) is false, then + 1. Return false. + 1. Return true. +flags: [module, async] +features: [import-defer, top-level-await, dynamic-import, promise-with-resolvers] +---*/ + +import { aStarted } from "./setup_FIXTURE.js"; + +const pA = import("./a-tla_FIXTURE.js"); + +// Wait until A's evaluation has started and is suspended on the blocker, so +// that A is guaranteed to be EVALUATING-ASYNC when Middle starts evaluating. +await aStarted.promise; + +const pC = import("./c_FIXTURE.js"); + +await Promise.all([pA, pC]); + +assert.compareArray(globalThis.evaluations, [ + "B", + "A-before-await", + "resolve-blocker", + "A-after-await", + "Middle-before-nsD.z", + "D", + "Middle-after-nsD.z", + "C", +]); + +$DONE(); diff --git a/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/middle_FIXTURE.js b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/middle_FIXTURE.js new file mode 100644 index 0000000000000..c7bc37c44c83c --- /dev/null +++ b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/middle_FIXTURE.js @@ -0,0 +1,8 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +import defer * as nsD from "./d_FIXTURE.js"; + +globalThis.evaluations.push("Middle-before-nsD.z"); +nsD.z; +globalThis.evaluations.push("Middle-after-nsD.z"); diff --git a/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/resolve-blocker_FIXTURE.js b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/resolve-blocker_FIXTURE.js new file mode 100644 index 0000000000000..0b450f9a6b497 --- /dev/null +++ b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/resolve-blocker_FIXTURE.js @@ -0,0 +1,7 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +import { blocker } from "./setup_FIXTURE.js"; + +globalThis.evaluations.push("resolve-blocker"); +blocker.resolve(); diff --git a/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/setup_FIXTURE.js b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/setup_FIXTURE.js new file mode 100644 index 0000000000000..05af2048e2fe5 --- /dev/null +++ b/JSTests/test262/test/language/import/import-defer/evaluation-top-level-await/async-cycle-dependency-of-deferred-module/setup_FIXTURE.js @@ -0,0 +1,10 @@ +// Copyright (C) 2026 Caio Lima. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +globalThis.evaluations = []; + +// Promise that keeps A suspended until resolve-blocker_FIXTURE.js runs. +export const blocker = Promise.withResolvers(); + +// Promise used to signal that A's evaluation has started. +export const aStarted = Promise.withResolvers(); diff --git a/JSTests/test262/test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-imported-module.js b/JSTests/test262/test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-imported-module.js new file mode 100644 index 0000000000000..ea7cde9d0d49e --- /dev/null +++ b/JSTests/test262/test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-imported-module.js @@ -0,0 +1,14 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-source-text-module-record-execute-module +description: Initialized value is disposed at end of Module imported from other module (Symbol.asyncDispose) +flags: [module, async] +features: [explicit-resource-management, top-level-await] +---*/ + +import { disposed, resource } from './initializer-Symbol.asyncDispose-disposed-at-end-of-imported-module_FIXTURE.js'; + +assert(disposed, 'resource should be disposed once imported module evaluation finishes'); +$DONE(); diff --git a/JSTests/test262/test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-imported-module_FIXTURE.js b/JSTests/test262/test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-imported-module_FIXTURE.js new file mode 100644 index 0000000000000..17bb9018f36de --- /dev/null +++ b/JSTests/test262/test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-imported-module_FIXTURE.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +export let disposed = false; + +await using resource = { + async [Symbol.asyncDispose]() { + if (disposed) { + throw new Error('resource disposed multiple times'); + } + // wait a few ticks to ensure the Promise returned by this function is fully awaited before evaluation is considered complete + await 0; + await 0; + await 0; + disposed = true; + } +}; +export { resource }; + +if (disposed) { + throw new Error('resource disposed before module evaluation completed'); +} diff --git a/JSTests/test262/test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-module.js b/JSTests/test262/test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-module.js new file mode 100644 index 0000000000000..733e8a6eeaff8 --- /dev/null +++ b/JSTests/test262/test/language/statements/await-using/initializer-Symbol.asyncDispose-disposed-at-end-of-module.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-source-text-module-record-execute-module +description: Initialized value is disposed at end of Module (Symbol.asyncDispose) +flags: [module, async] +features: [explicit-resource-management, top-level-await] +---*/ + +var disposed = false; +var resource = { + async [Symbol.asyncDispose]() { + assert.sameValue(disposed, false, 'disposal should happen once'); + disposed = true; + $DONE(); + } +}; + +await using _ = resource; + +assert.sameValue(disposed, false, 'resources should not be disposed until module evaluation finishes'); diff --git a/JSTests/test262/test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-imported-module.js b/JSTests/test262/test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-imported-module.js new file mode 100644 index 0000000000000..3ff15bddcf4cf --- /dev/null +++ b/JSTests/test262/test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-imported-module.js @@ -0,0 +1,14 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-source-text-module-record-execute-module +description: Initialized value is disposed at end of Module imported from other module (Symbol.dispose) +flags: [module, async] +features: [explicit-resource-management, top-level-await] +---*/ + +import { disposed, resource } from './initializer-Symbol.dispose-disposed-at-end-of-imported-module_FIXTURE.js'; + +assert(disposed, 'resource should be disposed once imported module evaluation finishes'); +$DONE(); diff --git a/JSTests/test262/test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-imported-module_FIXTURE.js b/JSTests/test262/test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-imported-module_FIXTURE.js new file mode 100644 index 0000000000000..1acfd6c089fb4 --- /dev/null +++ b/JSTests/test262/test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-imported-module_FIXTURE.js @@ -0,0 +1,18 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +export let disposed = false; + +await using resource = { + [Symbol.dispose]() { + if (disposed) { + throw new Error('resource disposed multiple times'); + } + disposed = true; + } +}; +export { resource }; + +if (disposed) { + throw new Error('resource disposed before module evaluation completed'); +} diff --git a/JSTests/test262/test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-module.js b/JSTests/test262/test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-module.js new file mode 100644 index 0000000000000..0c9e8a7957829 --- /dev/null +++ b/JSTests/test262/test/language/statements/await-using/initializer-Symbol.dispose-disposed-at-end-of-module.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-source-text-module-record-execute-module +description: Initialized value is disposed at end of Module (Symbol.dispose) +flags: [module, async] +features: [explicit-resource-management, top-level-await] +---*/ + +var disposed = false; +var resource = { + [Symbol.dispose]() { + assert.sameValue(disposed, false, 'disposal should happen once'); + disposed = true; + $DONE(); + } +}; + +await using _ = resource; + +assert.sameValue(disposed, false, 'resources should not be disposed until module evaluation finishes'); diff --git a/JSTests/test262/test/language/statements/for-in/dstr/array-rest-elision-invalid.js b/JSTests/test262/test/language/statements/for-in/dstr/array-rest-elision-invalid.js deleted file mode 100644 index cceb9a930f52e..0000000000000 --- a/JSTests/test262/test/language/statements/for-in/dstr/array-rest-elision-invalid.js +++ /dev/null @@ -1,32 +0,0 @@ -// This file was procedurally generated from the following sources: -// - src/dstr-assignment/array-rest-elision-invalid.case -// - src/dstr-assignment/syntax/for-in.template -/*--- -description: ArrayAssignmentPattern may not include elisions following an AssignmentRestElement in a AssignmentElementList. (For..in statement) -esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation -features: [destructuring-binding] -flags: [generated] -negative: - phase: parse - type: SyntaxError -info: | - IterationStatement : - for ( LeftHandSideExpression of AssignmentExpression ) Statement - - 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« », - AssignmentExpression, iterate). - 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement, - keyResult, assignment, labelSet). - - 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation - - [...] - 4. If destructuring is true and if lhsKind is assignment, then - a. Assert: lhs is a LeftHandSideExpression. - b. Let assignmentPattern be the parse of the source text corresponding to - lhs using AssignmentPattern as the goal symbol. - [...] ----*/ -$DONOTEVALUATE(); - -for ([...x,] in [[]]) ; diff --git a/JSTests/test262/test/language/statements/for-in/dstr/obj-rest-before-comma-invalid.js b/JSTests/test262/test/language/statements/for-in/dstr/obj-rest-before-comma-invalid.js new file mode 100644 index 0000000000000..1ad00c6852d0e --- /dev/null +++ b/JSTests/test262/test/language/statements/for-in/dstr/obj-rest-before-comma-invalid.js @@ -0,0 +1,34 @@ +// This file was procedurally generated from the following sources: +// - src/dstr-assignment/obj-rest-before-comma-invalid.case +// - src/dstr-assignment/syntax/for-in.template +/*--- +description: Object rest element cannot be followed by a comma in ObjectAssignmentPattern. (For..in statement) +esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation +features: [object-rest, destructuring-binding] +flags: [generated] +negative: + phase: parse + type: SyntaxError +info: | + IterationStatement : + for ( LeftHandSideExpression of AssignmentExpression ) Statement + + 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« », + AssignmentExpression, iterate). + 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement, + keyResult, assignment, labelSet). + + 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation + + [...] + 4. If destructuring is true and if lhsKind is assignment, then + a. Assert: lhs is a LeftHandSideExpression. + b. Let assignmentPattern be the parse of the source text corresponding to + lhs using AssignmentPattern as the goal symbol. + [...] +---*/ +$DONOTEVALUATE(); +var rest; + +for ({...rest,} in [{} +]) ; diff --git a/JSTests/test262/test/language/statements/for-in/dstr/obj-rest-not-last-element-invalid.js b/JSTests/test262/test/language/statements/for-in/dstr/obj-rest-not-last-element-invalid.js index d619477e281f8..79be68ede988b 100644 --- a/JSTests/test262/test/language/statements/for-in/dstr/obj-rest-not-last-element-invalid.js +++ b/JSTests/test262/test/language/statements/for-in/dstr/obj-rest-not-last-element-invalid.js @@ -2,7 +2,7 @@ // - src/dstr-assignment/obj-rest-not-last-element-invalid.case // - src/dstr-assignment/syntax/for-in.template /*--- -description: Object rest element needs to be the last AssignmenProperty in ObjectAssignmentPattern. (For..in statement) +description: Object rest element needs to be the last AssignmentProperty in ObjectAssignmentPattern. (For..in statement) esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation features: [object-rest, destructuring-binding] flags: [generated] diff --git a/JSTests/test262/test/language/statements/for-in/return-from-catch.js b/JSTests/test262/test/language/statements/for-in/return-from-catch.js new file mode 100644 index 0000000000000..e98c8adc624ec --- /dev/null +++ b/JSTests/test262/test/language/statements/for-in/return-from-catch.js @@ -0,0 +1,31 @@ +// Copyright (C) 2026 Luna Pfeiffer. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-runtime-semantics-forinofloopevaluation +description: > + Control flow during body evaluation should honor `return` statements within + the `catch` block of `try` statements. +---*/ + +var obj = { name: "Luna" }; +var i = 0; + +var result = (function() { + for (var x in obj) { + try { + throw new Error(); + } catch(err) { + i++; + return 42; + + $DONOTEVALUATE() + } + + $DONOTEVALUATE() + } + + $DONOTEVALUATE() +})(); + +assert.sameValue(result, 42); +assert.sameValue(i, 1); diff --git a/JSTests/test262/test/language/statements/for-in/return-from-finally.js b/JSTests/test262/test/language/statements/for-in/return-from-finally.js new file mode 100644 index 0000000000000..d7deff498dea9 --- /dev/null +++ b/JSTests/test262/test/language/statements/for-in/return-from-finally.js @@ -0,0 +1,30 @@ +// Copyright (C) 2026 Luna Pfeiffer. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-runtime-semantics-forinofloopevaluation +description: > + Control flow during body evaluation should honor `return` statements within + the `finally` block of `try` statements. +---*/ + +var obj = { name: "Luna" }; +var i = 0; + +var result = (function() { + for (var x in obj) { + try { + } finally { + i++; + return 42; + + $DONOTEVALUATE(); + } + + $DONOTEVALUATE(); + } + + $DONOTEVALUATE(); +})(); + +assert.sameValue(result, 42); +assert.sameValue(i, 1); diff --git a/JSTests/test262/test/language/statements/for-in/return-from-try.js b/JSTests/test262/test/language/statements/for-in/return-from-try.js new file mode 100644 index 0000000000000..3d571fc4af8e9 --- /dev/null +++ b/JSTests/test262/test/language/statements/for-in/return-from-try.js @@ -0,0 +1,31 @@ +// Copyright (C) 2026 Luna Pfeiffer. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-runtime-semantics-forinofloopevaluation +description: > + Control flow during body evaluation should honor `return` statements within + `try` blocks. +---*/ + +var obj = { name: "Luna" }; +var i = 0; + +var result = (function() { + for (var x in obj) { + try { + i++; + return 42; + + $DONOTEVALUATE(); + } catch(err) { + $DONOTEVALUATE(); + } + + $DONOTEVALUATE(); + } + + $DONOTEVALUATE(); +})(); + +assert.sameValue(result, 42); +assert.sameValue(i, 1); diff --git a/JSTests/test262/test/language/statements/for-in/return.js b/JSTests/test262/test/language/statements/for-in/return.js new file mode 100644 index 0000000000000..9cd7c31fdd2db --- /dev/null +++ b/JSTests/test262/test/language/statements/for-in/return.js @@ -0,0 +1,24 @@ +// Copyright (C) 2026 Luna Pfeiffer. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-runtime-semantics-forinofloopevaluation +description: > + Control flow during body evaluation should honor `return` statements. +---*/ + +var obj = { name: "Luna" }; +var i = 0; + +var result = (function() { + for (var x in obj) { + i++; + return 42; + + $DONOTEVALUATE(); + } + + $DONOTEVALUATE(); +})(); + +assert.sameValue(result, 42); +assert.sameValue(i, 1); diff --git a/JSTests/test262/test/language/statements/for-of/dstr/array-rest-elision-invalid.js b/JSTests/test262/test/language/statements/for-of/dstr/array-rest-elision-invalid.js deleted file mode 100644 index f404e2943fed9..0000000000000 --- a/JSTests/test262/test/language/statements/for-of/dstr/array-rest-elision-invalid.js +++ /dev/null @@ -1,32 +0,0 @@ -// This file was procedurally generated from the following sources: -// - src/dstr-assignment/array-rest-elision-invalid.case -// - src/dstr-assignment/syntax/for-of.template -/*--- -description: ArrayAssignmentPattern may not include elisions following an AssignmentRestElement in a AssignmentElementList. (For..of statement) -esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation -features: [destructuring-binding] -flags: [generated] -negative: - phase: parse - type: SyntaxError -info: | - IterationStatement : - for ( LeftHandSideExpression of AssignmentExpression ) Statement - - 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« », - AssignmentExpression, iterate). - 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement, - keyResult, assignment, labelSet). - - 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation - - [...] - 4. If destructuring is true and if lhsKind is assignment, then - a. Assert: lhs is a LeftHandSideExpression. - b. Let assignmentPattern be the parse of the source text corresponding to - lhs using AssignmentPattern as the goal symbol. - [...] ----*/ -$DONOTEVALUATE(); - -for ([...x,] of [[]]) ; diff --git a/JSTests/test262/test/language/statements/for-of/dstr/obj-rest-before-comma-invalid.js b/JSTests/test262/test/language/statements/for-of/dstr/obj-rest-before-comma-invalid.js new file mode 100644 index 0000000000000..b661507912d69 --- /dev/null +++ b/JSTests/test262/test/language/statements/for-of/dstr/obj-rest-before-comma-invalid.js @@ -0,0 +1,34 @@ +// This file was procedurally generated from the following sources: +// - src/dstr-assignment/obj-rest-before-comma-invalid.case +// - src/dstr-assignment/syntax/for-of.template +/*--- +description: Object rest element cannot be followed by a comma in ObjectAssignmentPattern. (For..of statement) +esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation +features: [object-rest, destructuring-binding] +flags: [generated] +negative: + phase: parse + type: SyntaxError +info: | + IterationStatement : + for ( LeftHandSideExpression of AssignmentExpression ) Statement + + 1. Let keyResult be the result of performing ? ForIn/OfHeadEvaluation(« », + AssignmentExpression, iterate). + 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement, + keyResult, assignment, labelSet). + + 13.7.5.13 Runtime Semantics: ForIn/OfBodyEvaluation + + [...] + 4. If destructuring is true and if lhsKind is assignment, then + a. Assert: lhs is a LeftHandSideExpression. + b. Let assignmentPattern be the parse of the source text corresponding to + lhs using AssignmentPattern as the goal symbol. + [...] +---*/ +$DONOTEVALUATE(); +var rest; + +for ({...rest,} of [{} +]) ; diff --git a/JSTests/test262/test/language/statements/for-of/dstr/obj-rest-not-last-element-invalid.js b/JSTests/test262/test/language/statements/for-of/dstr/obj-rest-not-last-element-invalid.js index 887e958bbb921..86451aca3e970 100644 --- a/JSTests/test262/test/language/statements/for-of/dstr/obj-rest-not-last-element-invalid.js +++ b/JSTests/test262/test/language/statements/for-of/dstr/obj-rest-not-last-element-invalid.js @@ -2,7 +2,7 @@ // - src/dstr-assignment/obj-rest-not-last-element-invalid.case // - src/dstr-assignment/syntax/for-of.template /*--- -description: Object rest element needs to be the last AssignmenProperty in ObjectAssignmentPattern. (For..of statement) +description: Object rest element needs to be the last AssignmentProperty in ObjectAssignmentPattern. (For..of statement) esid: sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation features: [object-rest, destructuring-binding] flags: [generated] diff --git a/JSTests/test262/test/language/statements/using/initializer-disposed-at-end-of-imported-module.js b/JSTests/test262/test/language/statements/using/initializer-disposed-at-end-of-imported-module.js new file mode 100644 index 0000000000000..8c31fccb16785 --- /dev/null +++ b/JSTests/test262/test/language/statements/using/initializer-disposed-at-end-of-imported-module.js @@ -0,0 +1,14 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-source-text-module-record-execute-module +description: Initialized value is disposed at end of Module imported from other module +flags: [module, async] +features: [explicit-resource-management] +---*/ + +import { disposed, resource } from './initializer-disposed-at-end-of-imported-module_FIXTURE.js'; + +assert(disposed, 'resource should be disposed once imported module evaluation finishes'); +$DONE(); diff --git a/JSTests/test262/test/language/statements/using/initializer-disposed-at-end-of-imported-module_FIXTURE.js b/JSTests/test262/test/language/statements/using/initializer-disposed-at-end-of-imported-module_FIXTURE.js new file mode 100644 index 0000000000000..d8a517df2353e --- /dev/null +++ b/JSTests/test262/test/language/statements/using/initializer-disposed-at-end-of-imported-module_FIXTURE.js @@ -0,0 +1,18 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +export let disposed = false; + +using resource = { + [Symbol.dispose]() { + if (disposed) { + throw new Error('resource disposed multiple times'); + } + disposed = true; + } +}; +export { resource }; + +if (disposed) { + throw new Error('resource disposed before module evaluation completed'); +} diff --git a/JSTests/test262/test/language/statements/using/initializer-disposed-at-end-of-module.js b/JSTests/test262/test/language/statements/using/initializer-disposed-at-end-of-module.js new file mode 100644 index 0000000000000..5bc7df43e83bf --- /dev/null +++ b/JSTests/test262/test/language/statements/using/initializer-disposed-at-end-of-module.js @@ -0,0 +1,22 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-source-text-module-record-execute-module +description: Initialized value is disposed at end of Module +flags: [module, async] +features: [explicit-resource-management] +---*/ + +var disposed = false; +var resource = { + [Symbol.dispose]() { + assert.sameValue(disposed, false, 'disposal should happen once'); + disposed = true; + $DONE(); + } +}; + +using _ = resource; + +assert.sameValue(disposed, false, 'resources should not be disposed until module evaluation finishes'); diff --git a/JSTests/test262/test/staging/source-phase-imports/module-source-prototype-chain.js b/JSTests/test262/test/staging/source-phase-imports/module-source-prototype-chain.js new file mode 100644 index 0000000000000..2c47c3d6f7aa1 --- /dev/null +++ b/JSTests/test262/test/staging/source-phase-imports/module-source-prototype-chain.js @@ -0,0 +1,35 @@ +// Copyright (C) 2025 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: > + The [[ModuleSource]] object's [[Prototype]]'s [[Prototype]] must be + %AbstractModuleSource%.prototype. +esid: sec-abstract-module-records +info: | + Table 3: Module Record Fields + + [[ModuleSource]] + The Module Source Object corresponding to this source Module Record's + source phase, or ~empty~ if it is not available for this module kind. + When not ~empty~, it must be an object whose initial [[Prototype]] is + an object whose initial [[Prototype]] is %AbstractModuleSource%.prototype. + +features: [source-phase-imports, source-phase-imports-module-source] +flags: [async] +includes: [asyncHelpers.js] +---*/ + +asyncTest(async function () { + const moduleSource = await import.source(''); + + assert.sameValue(typeof moduleSource, 'object', + 'import.source() must resolve to an object'); + + const proto = Object.getPrototypeOf(moduleSource); + assert.notSameValue(proto, null, + 'The [[ModuleSource]] object must have a [[Prototype]]'); + + const protoProto = Object.getPrototypeOf(proto); + assert.sameValue(protoProto, $262.AbstractModuleSource.prototype, + 'The [[Prototype]] of the [[ModuleSource]] object\'s [[Prototype]] must be %AbstractModuleSource%.prototype'); +}); diff --git a/JSTests/test262/test262-Revision.txt b/JSTests/test262/test262-Revision.txt index 98cba569bffc0..bc9d1a37ece24 100644 --- a/JSTests/test262/test262-Revision.txt +++ b/JSTests/test262/test262-Revision.txt @@ -1,2 +1,2 @@ test262 remote url: https://github.com/tc39/test262.git -test262 revision: 7a096c205fd422ecba49a407d5ac4d1b3f842296 +test262 revision: be13516fb6441b950ba8a3df97eb34062c186972 diff --git a/JSTests/wasm/gc/no-gc-structure-transition.js b/JSTests/wasm/gc/no-gc-structure-transition.js index 98e53561a23ea..df033b66c3bfa 100644 --- a/JSTests/wasm/gc/no-gc-structure-transition.js +++ b/JSTests/wasm/gc/no-gc-structure-transition.js @@ -680,6 +680,63 @@ function testPropertyEnumeration() { verifyStructIntact(m, obj); } +// https://bugs.webkit.org/show_bug.cgi?id=319087 +function testEnsureArrayStorage() { + const ms = makeStruct(); + const s = ms.exports.make(); + ensureArrayStorage(s); + verifyStructIntact(ms, s); + + const ma = makeArray(); + const a = ma.exports.make(); + ensureArrayStorage(a); + verifyArrayIntact(ma, a); +} + +function testEnsureArrayStorageViaWasmImport() { + const m = instantiate(` + (module + (type $s (struct (field i32))) + (import "imports" "ensure" (func $ensure (param (ref $s)) (result i32))) + (func (export "make") (result (ref $s)) + (struct.new $s (i32.const 42))) + (func (export "run") (param (ref $s)) (result i32) + (call $ensure (local.get 0))) + (func (export "get") (param (ref $s)) (result i32) + (struct.get $s 0 (local.get 0))) + ) + `, { + imports: { + ensure(obj) { + ensureArrayStorage(obj); + return 1; + } + } + }); + + const obj = m.exports.make(); + assert.eq(m.exports.run(obj), 1); + assert.eq(m.exports.get(obj), 42); +} + +// https://bugs.webkit.org/show_bug.cgi?id=320759 +function testDollarVMDictionaryHelpers() { + if (typeof $vm === "undefined") + return; + + const ms = makeStruct(); + const s = ms.exports.make(); + $vm.toCacheableDictionary(s); + $vm.toUncacheableDictionary(s); + verifyStructIntact(ms, s); + + const ma = makeArray(); + const a = ma.exports.make(); + $vm.toCacheableDictionary(a); + $vm.toUncacheableDictionary(a); + verifyArrayIntact(ma, a); +} + // Run all tests testPropertyAdditionNamed(); testPropertyAdditionIndexed(); @@ -714,3 +771,6 @@ testPrototypeReplacement(); testDeepPrototypeChain(); testIsExtensible(); testPropertyEnumeration(); +testEnsureArrayStorage(); +testEnsureArrayStorageViaWasmImport(); +testDollarVMDictionaryHelpers(); diff --git a/JSTests/wasm/js-api/memory-toResizableBuffer.js b/JSTests/wasm/js-api/memory-toResizableBuffer.js index ccdfc79f7f6cd..86fc739728262 100644 --- a/JSTests/wasm/js-api/memory-toResizableBuffer.js +++ b/JSTests/wasm/js-api/memory-toResizableBuffer.js @@ -67,21 +67,21 @@ function assertSharedGrowableBufferOfPageSize(pageCount, buffer, maxPageCount) { assertTrue(memory.buffer !== buffer); } -// A resize whose page-aligned growth delta exceeds the maximum representable -// PageCount must throw a catchable RangeError, not hit a release assertion. +// A resize whose page-aligned growth delta exceeds what a memory32 may declare +// must throw a catchable RangeError, not hit a release assertion. // https://bugs.webkit.org/show_bug.cgi?id=318524 { let memory = new WebAssembly.Memory({ initial: 1, maximum: 65536 }); let buffer = memory.toResizableBuffer(); assertIsolatedResizableBufferOfPageSize(1, buffer, 65536); - // Growth delta of 65537 pages is not representable as a PageCount. + // 65538 pages is not a page count a memory32 may reach at all. assertThrows(() => buffer.resize(65538 * pageSize), RangeError, ""); // The buffer is unchanged and remains usable after the failed resize. assertIsolatedResizableBufferOfPageSize(1, buffer, 65536); - // A representable delta that would still exceed the declared maximum must - // also throw catchably. + // A page count a memory32 may reach, but which still exceeds the declared + // maximum, must also throw catchably. assertThrows(() => buffer.resize(65537 * pageSize), RangeError, ""); assertIsolatedResizableBufferOfPageSize(1, buffer, 65536); diff --git a/JSTests/wasm/js-api/memory64-disabled.js b/JSTests/wasm/js-api/memory64-disabled.js new file mode 100644 index 0000000000000..cb044010eca21 --- /dev/null +++ b/JSTests/wasm/js-api/memory64-disabled.js @@ -0,0 +1,23 @@ +//@ requireOptions("--useWasmMemory64=false") +import * as assert from "../assert.js"; + +// With Memory64 disabled, the JS API must not hand out i64 memories or tables either. Otherwise a +// feature check succeeds and the module that would consume it fails to compile. + +assert.throws(() => new WebAssembly.Memory({ initial: 1n, address: "i64" }), TypeError, + "WebAssembly.Memory 'address' of 'i64' requires Memory64 to be enabled"); +assert.throws(() => new WebAssembly.Table({ initial: 1n, element: "externref", address: "i64" }), TypeError, + "WebAssembly.Table 'address' of 'i64' requires Memory64 to be enabled"); + +// i32 memories and tables are unaffected, including an explicit address. +new WebAssembly.Memory({ initial: 1 }); +new WebAssembly.Memory({ initial: 1, address: "i32" }); +new WebAssembly.Table({ initial: 1, element: "externref" }); +new WebAssembly.Table({ initial: 1, element: "externref", address: "i32" }); + +// A module declaring an i64 memory or table is still rejected. +function declaresI64Memory() { + // (module (memory i64 1)) -- limits flags 0x04 selects the i64 index type. + return new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x05, 0x03, 0x01, 0x04, 0x01]); +} +assert.throws(() => new WebAssembly.Module(declaresI64Memory()), WebAssembly.CompileError, "Memory64 is not enabled"); diff --git a/JSTests/wasm/js-api/memory64-js-api-errors.js b/JSTests/wasm/js-api/memory64-js-api-errors.js new file mode 100644 index 0000000000000..e225147ad9b9f --- /dev/null +++ b/JSTests/wasm/js-api/memory64-js-api-errors.js @@ -0,0 +1,71 @@ +//@ requireOptions("--useWasmJSTypes=1") +import * as assert from "../assert.js"; + +// The JS API surface for i64 memories and tables: error types, and the address type of reported +// sizes. https://webassembly.github.io/memory64/js-api/ + +// A bad `address` is a failed WebIDL enum conversion, which is a TypeError. +for (const address of ["i65", "", "I64", "u64", null, 1, {}]) { + assert.throws(() => new WebAssembly.Memory({ initial: 1n, address }), TypeError, + "WebAssembly.Memory 'address' must be a string of value 'i32' or 'i64'"); + assert.throws(() => new WebAssembly.Table({ initial: 1n, element: "externref", address }), TypeError, + "WebAssembly.Table 'address' must be a string of value 'i32' or 'i64'"); +} + +// An absent or undefined `address` defaults to i32, which keeps taking Numbers. +{ + const mem = new WebAssembly.Memory({ initial: 1 }); + assert.eq(mem.type().address, "i32"); + assert.eq(typeof mem.grow(1), "number"); + assert.eq(new WebAssembly.Memory({ initial: 1, address: undefined }).type().address, "i32"); +} + +// An i64 memory takes and returns BigInts; a Number is a TypeError. +{ + const mem = new WebAssembly.Memory({ initial: 1n, maximum: 4n, address: "i64" }); + assert.eq(mem.type().address, "i64"); + assert.eq(mem.type().minimum, 1n); + assert.eq(mem.type().maximum, 4n); + assert.eq(typeof mem.grow(1n), "bigint"); + assert.eq(mem.grow(0n), 2n); + assert.throws(() => mem.grow(1), TypeError, "Invalid argument type in ToBigInt operation"); + assert.throws(() => new WebAssembly.Memory({ initial: 1, address: "i64" }), TypeError, "Invalid argument type in ToBigInt operation"); + assert.throws(() => new WebAssembly.Memory({ initial: 1n, address: "i32" }), TypeError, "Conversion from 'BigInt' to 'number' is not allowed."); +} + +// A table64 reports its length as a BigInt, matching grow()'s return value. +{ + const table = new WebAssembly.Table({ initial: 3n, maximum: 10n, element: "externref", address: "i64" }); + assert.eq(table.type().address, "i64"); + assert.eq(typeof table.length, "bigint"); + assert.eq(table.length, 3n); + assert.eq(table.grow(1n), 3n); + assert.eq(table.length, 4n); + + const table32 = new WebAssembly.Table({ initial: 3, element: "externref" }); + assert.eq(typeof table32.length, "number"); + assert.eq(table32.length, 3); +} + +// A delta that cannot grow the table is a RangeError, whatever its magnitude. +{ + const table = new WebAssembly.Table({ initial: 1n, element: "externref", address: "i64" }); + assert.throws(() => table.grow(2n ** 33n), RangeError, + "WebAssembly.Table.prototype.grow could not grow the table"); + // Outside the u64 range is a failed AddressValueToU64 instead, i.e. a TypeError. + assert.throws(() => table.grow(2n ** 64n), TypeError, "Expect an integer argument in the range: [0, 2^64 - 1]"); + assert.throws(() => table.grow(-1n), TypeError, "Expect an integer argument in the range: [0, 2^64 - 1]"); +} + +// A memory64's declared maximum may exceed the memory32 page limit, up to the 2**48 pages spanning its +// address space. +{ + const mem = new WebAssembly.Memory({ initial: 1n, maximum: 131072n, address: "i64" }); + assert.eq(mem.type().maximum, 131072n); + assert.eq(new WebAssembly.Memory({ initial: 0n, maximum: 1n << 48n, address: "i64" }).type().maximum, 1n << 48n); + assert.throws(() => new WebAssembly.Memory({ initial: 1n, maximum: (1n << 48n) + 1n, address: "i64" }), RangeError, + "WebAssembly.Memory 'maximum' page count is too large"); + // An i32 memory keeps the memory32 limit. + assert.throws(() => new WebAssembly.Memory({ initial: 1, maximum: 65537 }), RangeError, + "WebAssembly.Memory 'maximum' page count is too large"); +} diff --git a/JSTests/wasm/js-api/memory64-js-api.js b/JSTests/wasm/js-api/memory64-js-api.js index 5f95b94fc0976..56fc201b412e7 100644 --- a/JSTests/wasm/js-api/memory64-js-api.js +++ b/JSTests/wasm/js-api/memory64-js-api.js @@ -51,22 +51,34 @@ const pageSize = 64 * 1024; ); } -// Constructor: initial page count too large throws +// Constructor: initial page count too large throws. An initial page count is declarative in the same +// way a maximum is, so the limit is the largest count a module may declare for an i64 memory, which is +// the 2**48 pages spanning its address space. A count within that bound is refused at allocation time +// instead, with an out of memory RangeError. { assert.throws( - () => new WebAssembly.Memory({ initial: 65537n, address: "i64" }), + () => new WebAssembly.Memory({ initial: (1n << 48n) + 1n, address: "i64" }), RangeError, "WebAssembly.Memory 'initial' page count is too large" ); + assert.throws( + () => new WebAssembly.Memory({ initial: 1n << 48n, address: "i64" }), + RangeError, + "Out of memory" + ); } -// Constructor: maximum page count too large throws +// Constructor: maximum page count too large throws. A maximum is bounded by that same i64 limit +// rather than by the memory32 page limit. { assert.throws( - () => new WebAssembly.Memory({ initial: 1n, maximum: 65537n, address: "i64" }), + () => new WebAssembly.Memory({ initial: 1n, maximum: (1n << 48n) + 1n, address: "i64" }), RangeError, "WebAssembly.Memory 'maximum' page count is too large" ); + const memory = new WebAssembly.Memory({ initial: 1n, maximum: 65537n, address: "i64" }); + assert.eq(memory.type().maximum, 65537n); + assert.eq(new WebAssembly.Memory({ initial: 1n, maximum: 1n << 48n, address: "i64" }).type().maximum, 1n << 48n); } // Constructor: passing invalid address diff --git a/JSTests/wasm/js-api/table64-js-api.js b/JSTests/wasm/js-api/table64-js-api.js index 0ca22478d7404..fbf252194b3a3 100644 --- a/JSTests/wasm/js-api/table64-js-api.js +++ b/JSTests/wasm/js-api/table64-js-api.js @@ -70,12 +70,21 @@ import * as assert from "../assert.js"; new WebAssembly.Table({initial: BigInt(2**20), maximum: BigInt(2**64) - 1n, element: "funcref", address: "i64"}); } +{ + // A maximum above 2^32 must be reflected in full, not truncated. + const table = new WebAssembly.Table({initial: 1n, maximum: 4294967301n, element: "funcref", address: "i64"}); + assert.eq(table.type().maximum, 4294967301n); + + const wide = new WebAssembly.Table({initial: 1n, maximum: BigInt(2**64) - 1n, element: "funcref", address: "i64"}); + assert.eq(wide.type().maximum, 18446744073709551615n); +} + { const table = new WebAssembly.Table({element: "funcref", initial: 20n, maximum: 30n, address: "i64"}); assert.eq(20n, table.grow(0n)); - assert.eq(20, table.length); + assert.eq(20n, table.length); assert.eq(20n, table.grow(1n)); - assert.eq(21, table.length); + assert.eq(21n, table.length); } { @@ -90,7 +99,7 @@ import * as assert from "../assert.js"; let called = false; table.grow({valueOf() { called = true; return 42n; }}); assert.truthy(called); - assert.eq(62, table.length); + assert.eq(62n, table.length); } { @@ -107,3 +116,20 @@ import * as assert from "../assert.js"; table.set(BigInt(i), null); } +{ + // A delta no table could ever satisfy is a failure to grow, not a bad argument. + const table = new WebAssembly.Table({element: "funcref", initial: 1n, maximum: BigInt(2**64) - 1n, address: "i64"}); + assert.throws(() => table.grow(4294967296n), RangeError, "WebAssembly.Table.prototype.grow could not grow the table"); + assert.throws(() => table.grow(BigInt(2**64) - 1n), RangeError, "WebAssembly.Table.prototype.grow could not grow the table"); +} + +{ + // length reports the same kind of value that grow() and type() do. + const table = new WebAssembly.Table({element: "funcref", initial: 3n, address: "i64"}); + assert.eq(table.length, 3n); + assert.eq(typeof table.length, typeof table.grow(0n)); + + const table32 = new WebAssembly.Table({element: "funcref", initial: 3, address: "i32"}); + assert.eq(table32.length, 3); + assert.eq(typeof table32.length, typeof table32.grow(0)); +} diff --git a/JSTests/wasm/js-api/type-reflection-exports.js b/JSTests/wasm/js-api/type-reflection-exports.js index 517b82cf3a837..de8d8dde246bb 100644 --- a/JSTests/wasm/js-api/type-reflection-exports.js +++ b/JSTests/wasm/js-api/type-reflection-exports.js @@ -52,9 +52,9 @@ async function test() { { parameters: ["f32", "f32"], results: ["f32"] }, { parameters: ["i32"], results: ["i32"] }, { parameters: ["f64"], results: ["f64"] }, - { maximum: 8, minimum: 1, shared: false }, - { maximum: 2, minimum: 1, element: "funcref" }, - { maximum: 2, minimum: 1, element: "externref" }, + { maximum: 8, minimum: 1, shared: false, address: "i32" }, + { maximum: 2, minimum: 1, element: "funcref", address: "i32" }, + { maximum: 2, minimum: 1, element: "externref", address: "i32" }, { mutable: true, value: "i32" }, { mutable: false, value: "i32" }, { mutable: true, value: "f32" }, diff --git a/JSTests/wasm/js-api/type-reflection-imports.js b/JSTests/wasm/js-api/type-reflection-imports.js index 7f010f3c882b0..755f32ccbb841 100644 --- a/JSTests/wasm/js-api/type-reflection-imports.js +++ b/JSTests/wasm/js-api/type-reflection-imports.js @@ -43,9 +43,9 @@ async function test() { { parameters: ["f32", "f32"], results: ["f32"] }, { parameters: ["i32"], results: ["i32"] }, { parameters: ["f64"], results: ["f64"] }, - { maximum: 8, minimum: 1, shared: false }, - { maximum: 2, minimum: 1, element: "funcref" }, - { maximum: 2, minimum: 1, element: "externref" }, + { maximum: 8, minimum: 1, shared: false, address: "i32" }, + { maximum: 2, minimum: 1, element: "funcref", address: "i32" }, + { maximum: 2, minimum: 1, element: "externref", address: "i32" }, { mutable: true, value: "i32" }, { mutable: false, value: "i32" }, { mutable: true, value: "f32" }, diff --git a/JSTests/wasm/stress/array-init-elem-wrapper-alloc-frame-tracer.js b/JSTests/wasm/stress/array-init-elem-wrapper-alloc-frame-tracer.js new file mode 100644 index 0000000000000..b0f1754bc8e09 --- /dev/null +++ b/JSTests/wasm/stress/array-init-elem-wrapper-alloc-frame-tracer.js @@ -0,0 +1,80 @@ +//@ requireOptions("--alwaysUseShadowChicken=true", "--slowPathAllocsBetweenGCs=1", "--forceGCSlowPaths=true") + +import * as assert from "../assert.js" + +// Companion to ref-func-wrapper-alloc-frame-tracer.js, for array.init_elem. +// +// array.init_elem copies a funcref elem segment through copyElementSegment, which +// materializes each element's JS wrapper. Unlike array.new_elem it does not +// allocate the array itself, so the earlier Wasm GC slow path audit missed it. +// +// Kept as bytes rather than WAT: the Wasm GC text format needs gc/wast.js, whose +// 6.6MB of JS takes ~45s to load under this test's forced-GC options. +// +// (module +// (type $arr (array (mut funcref))) +// (import "m" "f" (func $import)) +// (elem $e funcref (ref.func $f0) (ref.func $f1) (ref.func $f2) (ref.func $f3) (ref.func $f4) (ref.func $f5) (ref.func $f6) (ref.func $f7)) +// (func $f0) +// (func $f1) +// (func $f2) +// (func $f3) +// (func $f4) +// (func $f5) +// (func $f6) +// (func $f7) +// (func (export "test") +// (local $a (ref null $arr)) +// (local.set $a (array.new_default $arr (i32.const 1))) +// (call $import) +// (array.init_elem $arr $e (local.get $a) (i32.const 0) (i32.const 0) (i32.const 1)) +// (call $import) +// (array.init_elem $arr $e (local.get $a) (i32.const 0) (i32.const 1) (i32.const 1)) +// (call $import) +// (array.init_elem $arr $e (local.get $a) (i32.const 0) (i32.const 2) (i32.const 1)) +// (call $import) +// (array.init_elem $arr $e (local.get $a) (i32.const 0) (i32.const 3) (i32.const 1)) +// (call $import) +// (array.init_elem $arr $e (local.get $a) (i32.const 0) (i32.const 4) (i32.const 1)) +// (call $import) +// (array.init_elem $arr $e (local.get $a) (i32.const 0) (i32.const 5) (i32.const 1)) +// (call $import) +// (array.init_elem $arr $e (local.get $a) (i32.const 0) (i32.const 6) (i32.const 1)) +// (call $import) +// (array.init_elem $arr $e (local.get $a) (i32.const 0) (i32.const 7) (i32.const 1)) +// ) +// ) + +const wasmBytes = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x87, 0x80, 0x80, 0x80, 0x00, 0x02, 0x5e, + 0x70, 0x01, 0x60, 0x00, 0x00, 0x02, 0x87, 0x80, 0x80, 0x80, 0x00, 0x01, 0x01, 0x6d, 0x01, 0x66, + 0x00, 0x01, 0x03, 0x8a, 0x80, 0x80, 0x80, 0x00, 0x09, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x07, 0x88, 0x80, 0x80, 0x80, 0x00, 0x01, 0x04, 0x74, 0x65, 0x73, 0x74, 0x00, 0x09, + 0x09, 0x9c, 0x80, 0x80, 0x80, 0x00, 0x01, 0x05, 0x70, 0x08, 0xd2, 0x01, 0x0b, 0xd2, 0x02, 0x0b, + 0xd2, 0x03, 0x0b, 0xd2, 0x04, 0x0b, 0xd2, 0x05, 0x0b, 0xd2, 0x06, 0x0b, 0xd2, 0x07, 0x0b, 0xd2, + 0x08, 0x0b, 0x0a, 0xba, 0x81, 0x80, 0x80, 0x00, 0x09, 0x82, 0x80, 0x80, 0x80, 0x00, 0x00, 0x0b, + 0x82, 0x80, 0x80, 0x80, 0x00, 0x00, 0x0b, 0x82, 0x80, 0x80, 0x80, 0x00, 0x00, 0x0b, 0x82, 0x80, + 0x80, 0x80, 0x00, 0x00, 0x0b, 0x82, 0x80, 0x80, 0x80, 0x00, 0x00, 0x0b, 0x82, 0x80, 0x80, 0x80, + 0x00, 0x00, 0x0b, 0x82, 0x80, 0x80, 0x80, 0x00, 0x00, 0x0b, 0x82, 0x80, 0x80, 0x80, 0x00, 0x00, + 0x0b, 0xfc, 0x80, 0x80, 0x80, 0x00, 0x01, 0x01, 0x63, 0x00, 0x41, 0x01, 0xfb, 0x07, 0x00, 0x21, + 0x00, 0x10, 0x00, 0x20, 0x00, 0x41, 0x00, 0x41, 0x00, 0x41, 0x01, 0xfb, 0x13, 0x00, 0x00, 0x10, + 0x00, 0x20, 0x00, 0x41, 0x00, 0x41, 0x01, 0x41, 0x01, 0xfb, 0x13, 0x00, 0x00, 0x10, 0x00, 0x20, + 0x00, 0x41, 0x00, 0x41, 0x02, 0x41, 0x01, 0xfb, 0x13, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x41, + 0x00, 0x41, 0x03, 0x41, 0x01, 0xfb, 0x13, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x41, 0x00, 0x41, + 0x04, 0x41, 0x01, 0xfb, 0x13, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x41, 0x00, 0x41, 0x05, 0x41, + 0x01, 0xfb, 0x13, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x41, 0x00, 0x41, 0x06, 0x41, 0x01, 0xfb, + 0x13, 0x00, 0x00, 0x10, 0x00, 0x20, 0x00, 0x41, 0x00, 0x41, 0x07, 0x41, 0x01, 0xfb, 0x13, 0x00, + 0x00, 0x0b, +]); + +const numFuncs = 8; +const numInstances = 8; + +let calls = 0; +const imports = { m: { f: () => { ++calls; } } }; +const module = new WebAssembly.Module(wasmBytes); + +for (let i = 0; i < numInstances; ++i) + new WebAssembly.Instance(module, imports).exports.test(); + +assert.eq(calls, numFuncs * numInstances); diff --git a/JSTests/wasm/stress/const-expr-i32-wrap.js b/JSTests/wasm/stress/const-expr-i32-wrap.js index 49cd2aa41fa36..fdc8600c2466a 100644 --- a/JSTests/wasm/stress/const-expr-i32-wrap.js +++ b/JSTests/wasm/stress/const-expr-i32-wrap.js @@ -53,5 +53,3 @@ const extended = { extended_const: true }; `, {}, extended); assert.eq(instance.exports.table.get(0)(), 42); } - -print("const-expr-i32-wrap: ok"); diff --git a/JSTests/wasm/stress/data-segment-offset-2gb.js b/JSTests/wasm/stress/data-segment-offset-2gb.js index dcd41de727f3c..4ca011499fa2a 100644 --- a/JSTests/wasm/stress/data-segment-offset-2gb.js +++ b/JSTests/wasm/stress/data-segment-offset-2gb.js @@ -76,4 +76,3 @@ try { } assert.eq(instance.exports.load(), 42); -print("data-segment-offset-2gb: ok"); diff --git a/JSTests/wasm/stress/exception-trace-stack.js b/JSTests/wasm/stress/exception-trace-stack.js index 3a418f5751761..93523da820e86 100644 --- a/JSTests/wasm/stress/exception-trace-stack.js +++ b/JSTests/wasm/stress/exception-trace-stack.js @@ -36,4 +36,3 @@ testDefaultHasNoStack(); testTraceStackFalse(); testTraceStackTrue(); testConstructorLength(); -print("exception-trace-stack: ok"); diff --git a/JSTests/wasm/stress/instance-anchor.js b/JSTests/wasm/stress/instance-anchor.js index b2b59cb78eddb..596df5211558c 100644 --- a/JSTests/wasm/stress/instance-anchor.js +++ b/JSTests/wasm/stress/instance-anchor.js @@ -34,9 +34,6 @@ function main() { instanceA.exports.foo(); gc(); - - print("done (should have crashed above)"); - } main(); diff --git a/JSTests/wasm/stress/memarg-offset-u64-encoding.js b/JSTests/wasm/stress/memarg-offset-u64-encoding.js new file mode 100644 index 0000000000000..b7241795aa05d --- /dev/null +++ b/JSTests/wasm/stress/memarg-offset-u64-encoding.js @@ -0,0 +1,87 @@ +import * as assert from "../assert.js"; + +// memarg's offset field is a u64 for every memory; only its value is restricted by the memory's +// address type. An in-range offset written with a wider-than-minimal LEB is still well formed. + +const SECTION_TYPE = 1; +const SECTION_FUNCTION = 3; +const SECTION_MEMORY = 5; +const SECTION_CODE = 10; + +function leb(value) { + let bytes = []; + do { + let byte = value & 0x7f; + value >>>= 7; + if (value) + byte |= 0x80; + bytes.push(byte); + } while (value); + return bytes; +} + +// The value's minimal encoding, padded out to byteCount bytes with redundant continuation bytes. +function paddedLeb(value, byteCount) { + let bytes = []; + for (let i = 0; i < byteCount; ++i) { + bytes.push(Number(value & 0x7fn) | (i + 1 < byteCount ? 0x80 : 0)); + value >>= 7n; + } + if (value) + throw new Error(`${value} does not fit in ${byteCount} LEB bytes`); + return bytes; +} + +function section(id, payload) { + return [id, ...leb(payload.length), ...payload]; +} + +function moduleBytes(isMemory64, body) { + let bytes = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]; + bytes.push(...section(SECTION_TYPE, [0x01, 0x60, 0x00, 0x00])); + bytes.push(...section(SECTION_FUNCTION, [0x01, 0x00])); + bytes.push(...section(SECTION_MEMORY, [0x01, isMemory64 ? 0x04 : 0x00, 0x01])); + const code = [0x00, ...body, 0x0b]; + bytes.push(...section(SECTION_CODE, [0x01, ...leb(code.length), ...code])); + return new Uint8Array(bytes); +} + +// i32.const 0 / i32.load align=4 offset= / drop +const load32 = (offsetBytes) => [0x41, 0x00, 0x28, 0x02, ...offsetBytes, 0x1a]; +// i64.const 0 / i32.load align=4 offset= / drop +const load64 = (offsetBytes) => [0x42, 0x00, 0x28, 0x02, ...offsetBytes, 0x1a]; + +function assertValid(description, isMemory64, body) { + try { + new WebAssembly.Module(moduleBytes(isMemory64, body)); + } catch (error) { + throw new Error(`${description}: expected to compile, got ${error}`); + } +} + +function assertInvalid(description, isMemory64, body) { + try { + new WebAssembly.Module(moduleBytes(isMemory64, body)); + } catch (error) { + assert.truthy(error instanceof WebAssembly.CompileError, `${description}: expected CompileError, got ${error}`); + return; + } + throw new Error(`${description}: module was accepted but is invalid`); +} + +// An in-range offset is accepted at every encoded width, up to u64's maximum of ten bytes. +for (let byteCount = 1; byteCount <= 10; ++byteCount) { + assertValid(`memory32 offset 0 in ${byteCount} LEB bytes`, false, load32(paddedLeb(0n, byteCount))); + assertValid(`memory64 offset 0 in ${byteCount} LEB bytes`, true, load64(paddedLeb(0n, byteCount))); +} +for (let byteCount = 5; byteCount <= 10; ++byteCount) + assertValid(`memory32 offset 0xffffffff in ${byteCount} LEB bytes`, false, load32(paddedLeb(0xffffffffn, byteCount))); + +// An offset that does not fit the memory's address type is still rejected. +assertInvalid("memory32 offset 2^32", false, load32(paddedLeb(0x100000000n, 5))); +assertInvalid("memory32 offset 2^64-1", false, load32(paddedLeb(0xffffffffffffffffn, 10))); +assertValid("memory64 offset 2^32", true, load64(paddedLeb(0x100000000n, 5))); +assertValid("memory64 offset 2^64-1", true, load64(paddedLeb(0xffffffffffffffffn, 10))); + +// Eleven bytes cannot encode a u64. +assertInvalid("memory64 offset in 11 LEB bytes", true, load64([...paddedLeb(0n, 10).slice(0, 9), 0x80, 0x00])); diff --git a/JSTests/wasm/stress/memory-size-grow-non-minimal-memory-index.js b/JSTests/wasm/stress/memory-size-grow-non-minimal-memory-index.js new file mode 100644 index 0000000000000..417020511275c --- /dev/null +++ b/JSTests/wasm/stress/memory-size-grow-non-minimal-memory-index.js @@ -0,0 +1,50 @@ +//@ requireOptions("--useWasmMultiMemory=1") + +import * as assert from "../assert.js"; + +// A memidx is a u32 LEB128, so a non-minimal encoding of it is still valid. Only memory.size and +// memory.grow ever read one outside a memarg, and IPInt has to advance past however many bytes the +// encoding actually took. + +function leb(value, byteCount) { + const bytes = []; + do { + bytes.push(value & 0x7f); + value >>>= 7; + } while (value || bytes.length < byteCount); + for (let i = 0; i < bytes.length - 1; ++i) + bytes[i] |= 0x80; + return bytes; +} + +function build(memoryIndexBytes) { + const body = [ + 0x00, // no locals + 0x3f, ...memoryIndexBytes, // memory.size + 0x41, 0x01, // i32.const 1 + 0x40, ...memoryIndexBytes, // memory.grow + 0x1a, // drop + 0x0b, + ]; + const codeSection = [0x01, ...leb(body.length), ...body]; + return new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f, // type 0: () -> i32 + 0x03, 0x02, 0x01, 0x00, // func 0: type 0 + 0x05, 0x07, 0x02, 0x01, 0x01, 0x04, 0x01, 0x01, 0x04, // memory 0 and memory 1, each 1..4 pages + 0x07, 0x08, 0x01, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x00, // export "size" -> func 0 + 0x0a, ...leb(codeSection.length), ...codeSection, + ]); +} + +for (const byteCount of [1, 2, 3, 5]) { + for (const memoryIndex of [0, 1]) { + const instance = new WebAssembly.Instance(new WebAssembly.Module(build(leb(memoryIndex, byteCount)).buffer)); + assert.eq(instance.exports.size(), 1); + assert.eq(instance.exports.size(), 2); + } +} + +// Six bytes is past what a u32 can encode. +assert.throws(() => new WebAssembly.Module(build(leb(0, 6)).buffer), WebAssembly.CompileError, + "WebAssembly.Module doesn't parse at byte 7: can't get memory index, in function at index 0"); diff --git a/JSTests/wasm/stress/memory-type-reflects-current-size.js b/JSTests/wasm/stress/memory-type-reflects-current-size.js new file mode 100644 index 0000000000000..21721ec6c44b5 --- /dev/null +++ b/JSTests/wasm/stress/memory-type-reflects-current-size.js @@ -0,0 +1,51 @@ +//@ requireOptions("--useWasmJSTypes=1", "--useSharedArrayBuffer=1") +//@ skip if $addressBits <= 32 +import { instantiate } from "../wabt-wrapper.js"; +import * as assert from "../assert.js"; + +// The minimum a memory or table reflects is its current size, not the size it was declared with, so +// that feeding a reflected type back into the constructor reproduces the object it came from. + +function check(object, expected) { + const type = object.type(); + assert.eq(type.minimum, expected.minimum); + assert.eq(type.maximum, expected.maximum); + assert.eq(type.address, expected.address); +} + +for (const shared of [false, true]) { + const memory = new WebAssembly.Memory({ initial: 1, maximum: 10, shared }); + check(memory, { minimum: 1, maximum: 10, address: "i32" }); + memory.grow(3); + check(memory, { minimum: 4, maximum: 10, address: "i32" }); + assert.eq(memory.buffer.byteLength, 4 * 65536); + assert.eq(new WebAssembly.Memory(memory.type()).buffer.byteLength, memory.buffer.byteLength); + + const memory64 = new WebAssembly.Memory({ address: "i64", initial: 1n, maximum: 10n, shared }); + check(memory64, { minimum: 1n, maximum: 10n, address: "i64" }); + memory64.grow(3n); + check(memory64, { minimum: 4n, maximum: 10n, address: "i64" }); + assert.eq(new WebAssembly.Memory(memory64.type()).buffer.byteLength, memory64.buffer.byteLength); +} + +// A memory grown by the wasm memory.grow instruction reflects the new size too. +for (const addressType of ["i32", "i64"]) { + const numOrBig = (val) => addressType == "i32" ? Number(val) : BigInt(val); + const { grow, memory } = (await instantiate(` +(module + (memory (export "memory") ${addressType} 1 10) + (func (export "grow") (param ${addressType}) (result ${addressType}) + (memory.grow (local.get 0))) +)`, {}, { memory64: true })).exports; + check(memory, { minimum: numOrBig(1), maximum: numOrBig(10), address: addressType }); + assert.eq(grow(numOrBig(2)), numOrBig(1)); + check(memory, { minimum: numOrBig(3), maximum: numOrBig(10), address: addressType }); +} + +// A memory with no declared maximum still reflects its current size. +{ + const memory = new WebAssembly.Memory({ initial: 2 }); + check(memory, { minimum: 2, maximum: undefined, address: "i32" }); + memory.grow(1); + check(memory, { minimum: 3, maximum: undefined, address: "i32" }); +} diff --git a/JSTests/wasm/stress/memory64-bbq-bounds-checking.js b/JSTests/wasm/stress/memory64-bbq-bounds-checking.js new file mode 100644 index 0000000000000..b1be8392a1d27 --- /dev/null +++ b/JSTests/wasm/stress/memory64-bbq-bounds-checking.js @@ -0,0 +1,39 @@ +//@ skip if $addressBits <= 32 +//@ requireOptions("--useWasmMemory64=1") +//@ runDefaultWasm("-m", "--useWasmMemory64=1", "--useOMGJIT=0", "--thresholdForBBQOptimizeAfterWarmUp=0") +// https://bugs.webkit.org/show_bug.cgi?id=308683 +// Memory64 cannot use signaling memory; BBQ must emit explicit bounds checks. +import { instantiate } from "../wabt-wrapper.js"; +import * as assert from "../assert.js"; + +const { exports } = await instantiate(` +(module + (memory i64 1) + (func (export "load") (param i64) (result i32) + (i32.load (local.get 0))) + (func (export "store") (param i64) + (i32.store (local.get 0) (i32.const 7))) + (func (export "constLoad") (result i32) + (i32.load (i64.const -1))) + (func (export "largeOffsetLoad") (result i32) + (i32.load offset=0xffffffff (i64.const 0))) +) +`, {}, { memory64: true }); + +function test() { + assert.throws(() => exports.load(0xffffffffffffffffn), WebAssembly.RuntimeError, "Out of bounds"); + assert.throws(() => exports.load(0xfffffffffffffffen), WebAssembly.RuntimeError, "Out of bounds"); + assert.throws(() => exports.store(0xffffffffffffffffn), WebAssembly.RuntimeError, "Out of bounds"); + assert.throws(() => exports.constLoad(), WebAssembly.RuntimeError, "Out of bounds"); + assert.throws(() => exports.largeOffsetLoad(), WebAssembly.RuntimeError, "Out of bounds"); + // One page = 65536 bytes; i32 load needs 4 bytes. + assert.throws(() => exports.load(65536n), WebAssembly.RuntimeError, "Out of bounds"); + assert.throws(() => exports.load(65535n), WebAssembly.RuntimeError, "Out of bounds"); + assert.throws(() => exports.load(65533n), WebAssembly.RuntimeError, "Out of bounds"); + assert.eq(exports.load(65532n), 0); + exports.store(0n); + assert.eq(exports.load(0n), 7); +} + +for (let i = 0; i < wasmTestLoopCount; ++i) + test(); diff --git a/JSTests/wasm/stress/memory64-bulk-memory.js b/JSTests/wasm/stress/memory64-bulk-memory.js index 977fdb5599e7b..576deed1bf4ea 100644 --- a/JSTests/wasm/stress/memory64-bulk-memory.js +++ b/JSTests/wasm/stress/memory64-bulk-memory.js @@ -1,5 +1,5 @@ //@ skip if $addressBits <= 32 -import { instantiate } from "../wabt-wrapper.js"; +import { compile, instantiate } from "../wabt-wrapper.js"; import * as assert from "../assert.js"; let wat = ` @@ -129,3 +129,43 @@ function testI32CopyOOB() { for (let i = 0; i < wasmTestLoopCount; i++) testI32CopyOOB(); + +// memory.init is typed [at i32 i32]: only the destination takes the memory's address type. The +// source offset and length index into a data segment, and a segment's size is capped at 32 bits by +// the module encoding, so they stay i32 no matter how wide the memory is. Contrast memory.fill and +// memory.copy above, whose lengths are memory lengths and so do widen to i64. +async function testInitOperandTypes() { + const validationError = (detail) => + `WebAssembly.Module doesn't validate: memory.init ${detail}, in function at index 0`; + + await compile(` + (module (memory i64 1) (data "hello") + (func (memory.init 0 (i64.const 0) (i32.const 0) (i32.const 5)))) + `, { memory64: true }); + + await assert.throwsAsync(compile(` + (module (memory i64 1) (data "hello") + (func (memory.init 0 (i64.const 0) (i64.const 0) (i32.const 5)))) + `, { memory64: true }), WebAssembly.CompileError, + validationError("src address to type I64 expected I32")); + + await assert.throwsAsync(compile(` + (module (memory i64 1) (data "hello") + (func (memory.init 0 (i64.const 0) (i32.const 0) (i64.const 5)))) + `, { memory64: true }), WebAssembly.CompileError, + validationError("length to type I64 expected I32")); + + await assert.throwsAsync(compile(` + (module (memory i64 1) (data "hello") + (func (memory.init 0 (i32.const 0) (i32.const 0) (i32.const 5)))) + `, { memory64: true }), WebAssembly.CompileError, + validationError("dst address to type I32 expected I64")); + + await assert.throwsAsync(compile(` + (module (memory 1) (data "hello") + (func (memory.init 0 (i64.const 0) (i32.const 0) (i32.const 5)))) + `, { memory64: true }), WebAssembly.CompileError, + validationError("dst address to type I64 expected I32")); +} + +await testInitOperandTypes(); diff --git a/JSTests/wasm/stress/memory64-grow-and-size.js b/JSTests/wasm/stress/memory64-grow-and-size.js index 8f0ae64db72a8..498aff5f4d023 100644 --- a/JSTests/wasm/stress/memory64-grow-and-size.js +++ b/JSTests/wasm/stress/memory64-grow-and-size.js @@ -78,7 +78,7 @@ async function testGrowByZero() { ) `; - const instance = await instantiate(wat, {}, {reference_types: true}); + const instance = await instantiate(wat, {}, {memory64: true}); const { getSize, grow } = instance.exports; for (let i = 0; i < wasmTestLoopCount; i++) { @@ -109,7 +109,7 @@ async function testNoMaximum() { ) `; - const instance = await instantiate(wat, {}, {reference_types: true}); + const instance = await instantiate(wat, {}, {memory64: true}); const { getSize, grow } = instance.exports; for (let i = 0; i < wasmTestLoopCount; i++) { @@ -142,7 +142,7 @@ async function testLargeGrowValue() { ) `; - const instance = await instantiate(wat, {}, {reference_types: true}); + const instance = await instantiate(wat, {}, {memory64: true}); const { getSize, grow } = instance.exports; // Try to grow by a large amount at once diff --git a/JSTests/wasm/stress/memory64-grow-past-4gb.js b/JSTests/wasm/stress/memory64-grow-past-4gb.js new file mode 100644 index 0000000000000..629b32b149fa9 --- /dev/null +++ b/JSTests/wasm/stress/memory64-grow-past-4gb.js @@ -0,0 +1,88 @@ +//@ memoryHog! +//@ skip if $addressBits <= 32 +import { instantiate } from "../wabt-wrapper.js"; +import * as assert from "../assert.js"; + +// A memory64 exists to address more than the 4GiB a memory32 can, so both the wasm-level grow and +// the resizable-buffer path must cross that boundary. + +const options = { memory64: true, threads: true }; +const pageSize = 65536; +const maxMemory64Pages = 262144; +const fourGiB = 4 * 1024 * 1024 * 1024; +const pastFourGiB = fourGiB + pageSize; + +// A memory with no declared maximum reports the most this platform could grow it to, which is what +// bounds every maxByteLength below. +const ceilingBytes = new WebAssembly.Memory({ initial: 1n, address: "i64" }).toResizableBuffer().maxByteLength; + +// A memory whose initial size is already past 4GiB proves this port can host one at all. Where it +// can, growing past 4GiB below must succeed; where it cannot, refusing cleanly is the only option. +// Only the constructor belongs in the try: an assertion inside it would be swallowed by the catch. +let probe; +try { + probe = new WebAssembly.Memory({ initial: BigInt(pastFourGiB / pageSize), address: "i64" }); +} catch (e) { + assert.truthy(e instanceof RangeError && e.message === "Out of memory", `expected an out of memory RangeError, got ${e}`); +} +const canHostPastFourGiB = probe !== undefined; +if (canHostPastFourGiB) + assert.eq(probe.buffer.byteLength, pastFourGiB); +// Nothing below needs the probe, and holding 4GiB across the rest of the file would starve it. +probe = undefined; + +for (const shared of [false, true]) { + let mem; + try { + mem = (await instantiate(` + (module + (memory (export "mem") i64 1 ${pastFourGiB / pageSize} ${shared ? "shared" : ""})) + `, {}, options)).exports.mem; + } catch (e) { + // A shared memory reserves its whole maximum up front, which this port may not be able to spare. + assert.truthy(e instanceof RangeError && e.message === "Out of memory", `expected an out of memory RangeError, got ${e}`); + continue; + } + + const buffer = mem.toResizableBuffer(); + assert.eq(buffer.maxByteLength, Math.min(pastFourGiB, ceilingBytes)); + + const grow = shared ? size => buffer.grow(size) : size => buffer.resize(size); + if (canHostPastFourGiB) { + // The non-shared path grows by copying, so it holds the old and new regions at once. Hosting one + // 4GiB memory does not imply room for both. + let grown = true; + try { + grow(pastFourGiB); + } catch (e) { + assert.truthy(e instanceof RangeError, `expected a RangeError, got ${e}`); + grown = false; + } + if (grown) + assert.eq(buffer.byteLength, pastFourGiB); + } else { + assert.throws(() => grow(pastFourGiB), RangeError, "failed with new byte length"); + assert.eq(buffer.byteLength, pageSize); + } + + // Past the declared maximum is a clean RangeError either way. The shared and non-shared paths + // word it differently ("grow failed ...", "ArrayBuffer resize failed ...") around a common core. + assert.throws(() => grow(pastFourGiB + pageSize), RangeError, "failed with new byte length"); +} + +// A shared memory's buffer reports the maximum it can actually reach, and growth within the mapping +// succeeds. +{ + let memory; + try { + memory = new WebAssembly.Memory({ initial: 1n, maximum: BigInt(maxMemory64Pages), address: "i64", shared: true }); + } catch (e) { + assert.truthy(e instanceof RangeError && e.message === "Out of memory", `expected an out of memory RangeError, got ${e}`); + } + if (memory !== undefined) { + const buffer = memory.toResizableBuffer(); + assert.eq(buffer.maxByteLength, Math.min(maxMemory64Pages * pageSize, ceilingBytes)); + buffer.grow(2 * pageSize); + assert.eq(buffer.byteLength, 2 * pageSize); + } +} diff --git a/JSTests/wasm/stress/memory64-maximum-limits.js b/JSTests/wasm/stress/memory64-maximum-limits.js new file mode 100644 index 0000000000000..b6c23872ce2ae --- /dev/null +++ b/JSTests/wasm/stress/memory64-maximum-limits.js @@ -0,0 +1,62 @@ +//@ requireOptions("--useWasmJSTypes=1") +//@ memoryHog! +//@ skip if $addressBits <= 32 +import { instantiate } from "../wabt-wrapper.js"; +import * as assert from "../assert.js"; + +// A memory64 may declare a maximum anywhere in its 2**48-page address space, well past what any port +// could map. What its buffer reports is bounded twice over: by the largest representable ArrayBuffer +// byte length, and by the address space one reservation may claim on this platform, so that a resize +// within maxByteLength never fails deterministically. + +const options = { memory64: true, threads: true }; +const pageSize = 65536; +const maxMemory64Pages = 2 ** 48; +const maxRepresentablePages = 262144; +const maxMemory32Pages = 65536; + +// A memory with no declared maximum reports its own address type's ceiling, so it discovers what this +// platform will reserve. A memory32's ceiling is reservable in full everywhere. +const ceilingBytes = new WebAssembly.Memory({ initial: 1n, address: "i64" }).toResizableBuffer().maxByteLength; +assert.truthy(ceilingBytes <= maxRepresentablePages * pageSize, `memory64 ceiling ${ceilingBytes} is not a representable byte length`); +assert.truthy(ceilingBytes >= maxMemory32Pages * pageSize, `memory64 ceiling ${ceilingBytes} is below the memory32 ceiling`); +assert.eq(new WebAssembly.Memory({ initial: 1 }).toResizableBuffer().maxByteLength, maxMemory32Pages * pageSize); + +// A declared maximum at the largest representable byte length is accepted, and the buffer reports it +// clamped to that ceiling. The shared memory reserves the whole thing up front, so this arm is why the +// file is a memory hog, and a port that cannot spare the address space must refuse cleanly rather than +// fail the test. +for (const shared of [false, true]) { + let mem; + try { + mem = (await instantiate(` + (module + (memory (export "mem") i64 1 ${maxRepresentablePages} ${shared ? "shared" : ""})) + `, {}, options)).exports.mem; + } catch (e) { + assert.truthy(e instanceof RangeError && e.message === "Out of memory", `expected an out of memory RangeError, got ${e}`); + continue; + } + + assert.eq(mem.type().maximum, BigInt(maxRepresentablePages)); + assert.eq(mem.type().address, "i64"); + assert.eq(mem.toResizableBuffer().maxByteLength, Math.min(maxRepresentablePages * pageSize, ceilingBytes)); +} + +// A maximum past that is a declaration and nothing more: it is reported back verbatim, and the buffer +// still only advertises what could be reached. A non-shared memory reserves nothing up front, so this +// costs no address space. +{ + const mem = new WebAssembly.Memory({ initial: 1n, maximum: BigInt(maxMemory64Pages), address: "i64" }); + assert.eq(mem.type().maximum, BigInt(maxMemory64Pages)); + assert.eq(mem.toResizableBuffer().maxByteLength, ceilingBytes); +} + +// The JS constructor applies the same bound. Module decoding is covered by +// memory64-oversized-limits.js. +assert.throws(() => new WebAssembly.Memory({ initial: 1n, maximum: BigInt(maxMemory64Pages) + 1n, address: "i64" }), + RangeError, "WebAssembly.Memory 'maximum' page count is too large"); + +// A memory32 is bounded by the memory32 page limit rather than the memory64 one. +assert.throws(() => new WebAssembly.Memory({ initial: 1, maximum: maxMemory32Pages + 1 }), + RangeError, "WebAssembly.Memory 'maximum' page count is too large"); diff --git a/JSTests/wasm/stress/memory64-multi-memory-rejected.js b/JSTests/wasm/stress/memory64-multi-memory-rejected.js new file mode 100644 index 0000000000000..7d9cb1d52c4d2 --- /dev/null +++ b/JSTests/wasm/stress/memory64-multi-memory-rejected.js @@ -0,0 +1,30 @@ +//@ skip if $addressBits <= 32 +import { compile } from "../wabt-wrapper.js"; + +// A memory64 currently forces a single-memory module, because IPInt derives the address width of +// every access from memory 0. The restriction has to hold whichever order the memories appear in. + +const options = { memory64: true, multi_memory: true }; + +async function assertRejected(wat) { + try { + await compile(wat, options); + } catch (e) { + if (e instanceof WebAssembly.CompileError && e.message.includes("if using memory64 then multiple memories are illegal for now")) + return; + throw new Error(`Wrong error for ${wat}: ${e}`); + } + throw new Error(`Expected a CompileError for ${wat}`); +} + +await assertRejected(`(module (memory i64 1) (memory 1))`); +await assertRejected(`(module (memory 1) (memory i64 1))`); +await assertRejected(`(module (memory i64 1) (memory i64 1))`); +await assertRejected(`(module (memory i64 1) (memory 1) (memory 1))`); +await assertRejected(`(module (import "m" "a" (memory i64 1)) (import "m" "b" (memory 1)))`); +await assertRejected(`(module (import "m" "a" (memory 1)) (import "m" "b" (memory i64 1)))`); +await assertRejected(`(module (import "m" "a" (memory i64 1)) (memory 1))`); + +// Multiple memory32s remain legal, and so does a lone memory64. +await compile(`(module (memory 1) (memory 1))`, options); +await compile(`(module (memory i64 1))`, options); diff --git a/JSTests/wasm/stress/memory64-oversized-limits.js b/JSTests/wasm/stress/memory64-oversized-limits.js index 2df92a33b8c06..5df3b88c7d13d 100644 --- a/JSTests/wasm/stress/memory64-oversized-limits.js +++ b/JSTests/wasm/stress/memory64-oversized-limits.js @@ -1,3 +1,4 @@ +//@ memoryHog! //@ skip if $addressBits <= 32 import * as assert from "../assert.js"; @@ -14,38 +15,64 @@ function leb128(value) { return bytes; } -// A module whose only content is a memory with the given limits. +// A module whose only content is a memory with the given limits, exported as "mem". function moduleBytesWithMemoryLimits({ initial, maximum, is64bit = true }) { const hasMaximum = maximum !== undefined; const limitsFlags = (is64bit ? 0x04 : 0x00) | (hasMaximum ? 0x01 : 0x00); // bit 2: 64-bit index type, bit 0: has a maximum const limits = [limitsFlags, ...leb128(initial), ...(hasMaximum ? leb128(maximum) : [])]; const memorySectionBody = [0x01, ...limits]; // 1 memory + const exportSectionBody = [0x01, 0x03, 0x6d, 0x65, 0x6d, 0x02, 0x00]; // 1 export: "mem" names memory 0 return new Uint8Array([ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, // magic + version 0x05, ...leb128(memorySectionBody.length), ...memorySectionBody, // memory section + 0x07, ...leb128(exportSectionBody.length), ...exportSectionBody, // export section ]); } -// The JS API caps a memory64 memory's limits at 2**37 - 1 pages, far more than can be allocated. -// https://www.w3.org/TR/wasm-js-api-2/#limits -const maxMemory64Pages = (1n << 37n) - 1n; -const formerInvalidPageCountSentinel = (1n << 32n) - 1n; +// A memory's declared limits are bounded by its address type: an i64 memory may declare up to 2**48 +// pages, the whole of its 2**64-byte address space, and an i32 memory 2**16. A declaration is only a +// declaration, so anything within the bound compiles no matter how far past what a port could map; +// what cannot be allocated is refused at instantiation instead. +const maxMemory64Pages = 1n << 48n; +const pageSize = 65536; -// An initial page count that can be declared but not allocated compiles, and must be rejected at -// instantiation rather than truncated to an allocatable size. -for (const initial of [maxMemory64Pages, 1n << 32n, (1n << 32n) + 5n, formerInvalidPageCountSentinel]) { +// An initial page count is checked against that bound and nothing else, so every count up to it +// compiles, and instantiation must honor it verbatim rather than clamp it to a smaller size. +for (const initial of [65537n, 262144n]) { const module = new WebAssembly.Module(moduleBytesWithMemoryLimits({ initial })); + let memory; + try { + memory = new WebAssembly.Instance(module).exports.mem; + } catch (e) { + // A port whose address space cannot host this much refuses cleanly instead. + assert.truthy(e instanceof RangeError && e.message === "Out of memory", `expected an out of memory RangeError, got ${e}`); + continue; + } + assert.eq(memory.buffer.byteLength, Number(initial) * pageSize); +} + +// No port can host the largest declarable initial size, so that one compiles and then always fails to +// instantiate. It must not wrap around to a size that looks allocatable. +{ + const module = new WebAssembly.Module(moduleBytesWithMemoryLimits({ initial: maxMemory64Pages })); assert.throws(() => new WebAssembly.Instance(module), RangeError, "Out of memory"); } -// A maximum is only a declaration: an unallocatable one doesn't prevent instantiation. -for (const maximum of [maxMemory64Pages, 1n << 32n, formerInvalidPageCountSentinel]) - new WebAssembly.Instance(new WebAssembly.Module(moduleBytesWithMemoryLimits({ initial: 1n, maximum }))); +// A maximum is only a declaration, so even one at the bound costs nothing to instantiate: the memory +// is created at its initial size. +for (const maximum of [65537n, 262144n, 1n << 32n, (1n << 37n) - 1n, maxMemory64Pages]) { + const { mem } = new WebAssembly.Instance(new WebAssembly.Module(moduleBytesWithMemoryLimits({ initial: 1n, maximum }))).exports; + assert.eq(mem.buffer.byteLength, pageSize); +} -// Anything above the limit is invalid, and so is a memory32 above 2**16 pages. +// Anything above the bound is invalid, including the UINT64_MAX that PageCount reserves to mean "no +// page count". A memory32 above 2**16 pages is invalid too. +const pageCountSentinel = (1n << 64n) - 1n; for (const [limits, message] of [ [{ initial: maxMemory64Pages + 1n }, `Memory's initial page count of ${maxMemory64Pages + 1n} is invalid`], [{ initial: 0n, maximum: maxMemory64Pages + 1n }, `Memory's maximum page count of ${maxMemory64Pages + 1n} is invalid`], + [{ initial: pageCountSentinel }, `Memory's initial page count of ${pageCountSentinel} is invalid`], + [{ initial: 0n, maximum: pageCountSentinel }, `Memory's maximum page count of ${pageCountSentinel} is invalid`], [{ initial: 65537n, is64bit: false }, "Memory's initial page count of 65537 is invalid"], ]) assert.throws(() => new WebAssembly.Module(moduleBytesWithMemoryLimits(limits)), WebAssembly.CompileError, message); diff --git a/JSTests/wasm/stress/memory64-shared-broadcast-address-type.js b/JSTests/wasm/stress/memory64-shared-broadcast-address-type.js new file mode 100644 index 0000000000000..e77408fb91b3c --- /dev/null +++ b/JSTests/wasm/stress/memory64-shared-broadcast-address-type.js @@ -0,0 +1,41 @@ +//@ requireOptions("--useWasmJSTypes=1") +//@ skip if $addressBits <= 32 +import * as assert from "../assert.js"; + +// A shared memory handed to another agent has to arrive with the address type it was created with. +// Nothing about the shared contents records it, and a memory declared with a maximum of zero has no +// shared contents at all. + +const agentSource = ` +$.agent.receiveBroadcast(function (memory) { + let report = []; + report.push("address=" + memory.type().address); + report.push("minimum=" + typeof memory.type().minimum); + try { + report.push("grow(0n)=" + typeof memory.grow(0n)); + } catch (error) { + report.push("grow(0n) threw " + error.name); + } + $.agent.report(report.join(" ")); + $.agent.leaving(); +}); +`; + +function broadcastAndCollect(memory) { + $.agent.start(agentSource); + $.agent.broadcast(memory); + let report = null; + while ((report = $.agent.getReport()) === null) + $.agent.sleep(1); + return report; +} + +assert.eq(broadcastAndCollect(new WebAssembly.Memory({ address: "i64", initial: 1n, maximum: 4n, shared: true })), + "address=i64 minimum=bigint grow(0n)=bigint"); + +// Zero maximum: there are no shared contents for the address type to travel with. +assert.eq(broadcastAndCollect(new WebAssembly.Memory({ address: "i64", initial: 0n, maximum: 0n, shared: true })), + "address=i64 minimum=bigint grow(0n)=bigint"); + +assert.eq(broadcastAndCollect(new WebAssembly.Memory({ initial: 1, maximum: 4, shared: true })), + "address=i32 minimum=number grow(0n) threw TypeError"); diff --git a/JSTests/wasm/stress/memory64-unreachable-immediates.js b/JSTests/wasm/stress/memory64-unreachable-immediates.js new file mode 100644 index 0000000000000..23ddaf30d8d94 --- /dev/null +++ b/JSTests/wasm/stress/memory64-unreachable-immediates.js @@ -0,0 +1,75 @@ +//@ skip if $addressBits <= 32 +import { compile } from "../wabt-wrapper.js"; + +// Memory immediates have to be decoded the same way in unreachable code as in reachable code: a +// memory64 offset is a u64, and an atomic's align byte can carry a memory index. Getting this wrong +// rejects valid modules. + +const options = { memory64: true, multi_memory: true, threads: true }; + +// A memory64 atomic offset above 2^32, in unreachable code. +await compile(` +(module + (memory i64 1 1 shared) + (func + unreachable + (drop (i32.atomic.load offset=0x100000000 (i64.const 0))))) +`, options); + +// The same offset in reachable code has always worked; keep them side by side. +await compile(` +(module + (memory i64 1 1 shared) + (func (result i32) + (i32.atomic.load offset=0x100000000 (i64.const 0)))) +`, options); + +// An atomic naming a non-default memory, in unreachable code. +await compile(` +(module + (memory 1 1 shared) + (memory $m 1 1 shared) + (func + unreachable + (drop (i32.atomic.load $m (i32.const 0))))) +`, options); + +// memory.size and memory.grow naming a non-default memory, in unreachable code. +await compile(` +(module + (memory 1) + (memory $m 1) + (func + unreachable + (drop (memory.size $m)) + (drop (memory.grow $m (i32.const 1))))) +`, options); + +// A memory64 offset above 2^32 on a plain load in unreachable code. +await compile(` +(module + (memory i64 1) + (func + unreachable + (drop (i32.load offset=0x100000000 (i64.const 0))))) +`, options); + +// An out-of-range memory index in unreachable code is still an error. +async function assertRejected(wat, needle) { + try { + await compile(wat, options); + } catch (e) { + if (e instanceof WebAssembly.CompileError && e.message.includes(needle)) + return; + throw new Error(`Wrong error: ${e}`); + } + throw new Error(`Expected a CompileError for ${wat}`); +} + +await assertRejected(` +(module + (memory 1) + (func + unreachable + (drop (memory.size 3)))) +`, "memory index 3 is out of range"); diff --git a/JSTests/wasm/stress/memory64-write-to-address-over-4-gigs.js b/JSTests/wasm/stress/memory64-write-to-address-over-4-gigs.js index 03bdf5faf3cfe..01ea543400148 100644 --- a/JSTests/wasm/stress/memory64-write-to-address-over-4-gigs.js +++ b/JSTests/wasm/stress/memory64-write-to-address-over-4-gigs.js @@ -13,7 +13,7 @@ let wat = ` ) )`; -const instance = await instantiate(wat, {}, {reference_types: true}); +const instance = await instantiate(wat, {}, {memory64: true}); const {write, read} = instance.exports; const writeAddr = BigInt(Number.MAX_SAFE_INTEGER + 1); diff --git a/JSTests/wasm/stress/multimemory-grow-during-partial-link.js b/JSTests/wasm/stress/multimemory-grow-during-partial-link.js new file mode 100644 index 0000000000000..213cf12ae7221 --- /dev/null +++ b/JSTests/wasm/stress/multimemory-grow-during-partial-link.js @@ -0,0 +1,36 @@ +//@ requireOptions("--useWasmMultiMemory=1") + +import * as assert from "../assert.js"; +import { compile } from "../wabt-wrapper.js"; + +// A grow reaching an instance whose memory imports were not all resolved (LinkError midway, +// or a re-entrant grow from an import getter) must not touch the still-empty memory slots. + +const wat = ` +(module + (import "env" "m0" (memory $m0 1 8)) + (import "env" "m1" (memory $m1 1 8)) + (func (export "size0") (result i32) (memory.size $m0)) + (func (export "load0") (param $a i32) (result i32) (i32.load $m0 (local.get $a))) + (func (export "load1") (param $a i32) (result i32) (i32.load $m1 (local.get $a))))`; +const module = await compile(wat, { multi_memory: true }); + +// 1. The second memory import fails, so linking throws after the first memory was set. +{ + const mem0 = new WebAssembly.Memory({ initial: 1, maximum: 8 }); + assert.throws(() => new WebAssembly.Instance(module, { env: { m0: mem0, m1: 42 } }), WebAssembly.LinkError, "Memory import env:m1 is not an instance of WebAssembly.Memory"); + assert.eq(mem0.grow(0), 1); + assert.eq(mem0.grow(1), 1); + assert.eq(mem0.buffer.byteLength, 2 * 65536); +} + +// 2. The getter of the second memory import grows the already-linked first memory. +{ + const mem0 = new WebAssembly.Memory({ initial: 1, maximum: 8 }); + const mem1 = new WebAssembly.Memory({ initial: 1, maximum: 8 }); + const importObject = { env: { m0: mem0, get m1() { assert.eq(mem0.grow(1), 1); return mem1; } } }; + const instance = new WebAssembly.Instance(module, importObject); + assert.eq(instance.exports.size0(), 2); + assert.eq(instance.exports.load0(65536), 0); + assert.eq(instance.exports.load1(0), 0); +} diff --git a/JSTests/wasm/stress/ref-func-wrapper-alloc-frame-tracer.js b/JSTests/wasm/stress/ref-func-wrapper-alloc-frame-tracer.js new file mode 100644 index 0000000000000..7886495bcd9a3 --- /dev/null +++ b/JSTests/wasm/stress/ref-func-wrapper-alloc-frame-tracer.js @@ -0,0 +1,43 @@ +//@ requireOptions("--alwaysUseShadowChicken=true", "--slowPathAllocsBetweenGCs=1", "--forceGCSlowPaths=true") + +import { compile } from "../wabt-wrapper.js" +import * as assert from "../assert.js" + +// ref.func materializes the funcref's JS wrapper via ensureFunctionWrapper, which +// allocates and so can GC. Wasm tiers update topCallFrame only just-in-time, so +// after a JS import returns it points at dead native state, and a GC there makes +// ShadowChicken read that as a JS CallFrame. +// +// Each ref.func targets a distinct function, preceded by an import call to leave +// topCallFrame stale. Fresh instances have empty wrapper caches, so later +// iterations rerun the path in whichever tier the function reached. + +const numFuncs = 8; +const numInstances = 8; + +let funcs = ""; +let declares = ""; +let body = ""; +for (let i = 0; i < numFuncs; ++i) { + funcs += ` (func $f${i})\n`; + declares += ` $f${i}`; + body += ` (call $import)\n (drop (ref.func $f${i}))\n`; +} + +let wat = ` +(module + (import "m" "f" (func $import)) + (elem declare func${declares}) +${funcs} (func (export "test") +${body} ) +) +`; + +let calls = 0; +const imports = { m: { f: () => { ++calls; } } }; +const module = await compile(wat); + +for (let i = 0; i < numInstances; ++i) + new WebAssembly.Instance(module, imports).exports.test(); + +assert.eq(calls, numFuncs * numInstances); diff --git a/JSTests/wasm/stress/table-copy-aliased-import.js b/JSTests/wasm/stress/table-copy-aliased-import.js new file mode 100644 index 0000000000000..3676791d1e252 --- /dev/null +++ b/JSTests/wasm/stress/table-copy-aliased-import.js @@ -0,0 +1,58 @@ +//@ skip if $addressBits <= 32 +import { instantiate } from "../wabt-wrapper.js"; +import * as assert from "../assert.js"; + +// Supplying one JS table for two table imports makes two table indices name the same table, so +// table.copy between those indices has to behave like memmove. + +let wat = (addressType) => ` +(module + (import "m" "a" (table $a ${addressType} 10 externref)) + (import "m" "b" (table $b ${addressType} 10 externref)) + (func (export "copyAB") (param $d ${addressType}) (param $s ${addressType}) (param $n ${addressType}) + (local.get $d) + (local.get $s) + (local.get $n) + (table.copy $a $b) + ) + (func (export "copyAA") (param $d ${addressType}) (param $s ${addressType}) (param $n ${addressType}) + (local.get $d) + (local.get $s) + (local.get $n) + (table.copy $a $a) + ) +)`; + +function reset(table, numOrBig) { + for (let i = 0; i < 10; ++i) + table.set(numOrBig(i), "v" + i); +} + +function contents(table, numOrBig) { + let result = []; + for (let i = 0; i < 10; ++i) + result.push(table.get(numOrBig(i))); + return result.join(","); +} + +function test(copy, table, numOrBig, d, s, n, expected) { + reset(table, numOrBig); + copy(numOrBig(d), numOrBig(s), numOrBig(n)); + assert.eq(contents(table, numOrBig), expected); +} + +for (const addressType of ["i32", "i64"]) { + const numOrBig = (val) => addressType == "i32" ? Number(val) : BigInt(val); + const table = new WebAssembly.Table({ element: "externref", initial: numOrBig(10), address: addressType }); + const instance = await instantiate(wat(addressType), { m: { a: table, b: table } }, { memory64: true }); + const { copyAB, copyAA } = instance.exports; + + for (let i = 0; i < wasmTestLoopCount; i++) { + for (const copy of [copyAB, copyAA]) { + test(copy, table, numOrBig, 2, 0, 5, "v0,v1,v0,v1,v2,v3,v4,v7,v8,v9"); + test(copy, table, numOrBig, 0, 2, 5, "v2,v3,v4,v5,v6,v5,v6,v7,v8,v9"); + test(copy, table, numOrBig, 2, 2, 5, "v0,v1,v2,v3,v4,v5,v6,v7,v8,v9"); + test(copy, table, numOrBig, 0, 0, 0, "v0,v1,v2,v3,v4,v5,v6,v7,v8,v9"); + } + } +} diff --git a/JSTests/wasm/stress/table-copy-mixed-address-types.js b/JSTests/wasm/stress/table-copy-mixed-address-types.js new file mode 100644 index 0000000000000..aebe68774ccea --- /dev/null +++ b/JSTests/wasm/stress/table-copy-mixed-address-types.js @@ -0,0 +1,40 @@ +//@ skip if $addressBits <= 32 +import { instantiate } from "../wabt-wrapper.js"; +import * as assert from "../assert.js"; + +// table.copy between tables with different address types: each offset is typed +// by its own table, and the length by the narrower of the two. +let wat = (dstAddressType, srcAddressType) => ` +(module + (table $dst (export "dst") ${dstAddressType} 8 funcref) + (table $src ${srcAddressType} 8 funcref) + (elem (table $src) (${srcAddressType}.const 1) func $f) + (func $f (result i32) + (i32.const 42) + ) + (type $ft (func (result i32))) + (func (export "copy") (param $dstOffset ${dstAddressType}) (param $srcOffset ${srcAddressType}) (param $length ${dstAddressType === "i64" && srcAddressType === "i64" ? "i64" : "i32"}) + (local.get $dstOffset) + (local.get $srcOffset) + (local.get $length) + (table.copy $dst $src) + ) + (func (export "callDst") (param $i ${dstAddressType}) (result i32) + (local.get $i) + (call_indirect $dst (type $ft)) + ) +)`; + +for (const dstAddressType of ["i32", "i64"]) { + for (const srcAddressType of ["i32", "i64"]) { + const instance = await instantiate(wat(dstAddressType, srcAddressType), {}, { memory64: true }); + const dstNum = dstAddressType === "i32" ? Number : BigInt; + const srcNum = srcAddressType === "i32" ? Number : BigInt; + const lengthNum = dstAddressType === "i64" && srcAddressType === "i64" ? BigInt : Number; + + for (let i = 0; i < wasmTestLoopCount; ++i) { + instance.exports.copy(dstNum(3), srcNum(1), lengthNum(1)); + assert.eq(instance.exports.callDst(dstNum(3)), 42); + } + } +} diff --git a/JSTests/wasm/stress/table-get-wrapper-alloc-frame-tracer.js b/JSTests/wasm/stress/table-get-wrapper-alloc-frame-tracer.js new file mode 100644 index 0000000000000..72305d707fbc3 --- /dev/null +++ b/JSTests/wasm/stress/table-get-wrapper-alloc-frame-tracer.js @@ -0,0 +1,41 @@ +//@ requireOptions("--alwaysUseShadowChicken=true", "--slowPathAllocsBetweenGCs=1", "--forceGCSlowPaths=true") + +import { compile } from "../wabt-wrapper.js" +import * as assert from "../assert.js" + +// Companion to ref-func-wrapper-alloc-frame-tracer.js, for table.get. +// +// An active elem segment of non-imported funcrefs installs only Wasm-side metadata +// (FuncRefTable::setLazy); FuncRefTable::get materializes the JS wrapper on first +// read. Each index is read once per instance, so every table.get allocates. + +const numFuncs = 8; +const numInstances = 8; + +let funcs = ""; +let elems = ""; +let body = ""; +for (let i = 0; i < numFuncs; ++i) { + funcs += ` (func $f${i})\n`; + elems += ` $f${i}`; + body += ` (call $import)\n (drop (table.get $t (i32.const ${i})))\n`; +} + +let wat = ` +(module + (import "m" "f" (func $import)) + (table $t ${numFuncs} funcref) + (elem (i32.const 0)${elems}) +${funcs} (func (export "test") +${body} ) +) +`; + +let calls = 0; +const imports = { m: { f: () => { ++calls; } } }; +const module = await compile(wat); + +for (let i = 0; i < numInstances; ++i) + new WebAssembly.Instance(module, imports).exports.test(); + +assert.eq(calls, numFuncs * numInstances); diff --git a/JSTests/wasm/stress/table-oversized-initial-reflection.js b/JSTests/wasm/stress/table-oversized-initial-reflection.js new file mode 100644 index 0000000000000..06c2e87cd21c5 --- /dev/null +++ b/JSTests/wasm/stress/table-oversized-initial-reflection.js @@ -0,0 +1,78 @@ +//@ skip if $addressBits <= 32 +import * as assert from "../assert.js"; + +// A table may declare a size larger than this implementation can create. That is a compile-time +// success and an instantiation-time failure, so the type reflected by Module.imports() and +// Module.exports() has to be the size the module declared, not one clamped to what is creatable. + +const SECTION_TYPE = 1; +const SECTION_IMPORT = 2; +const SECTION_TABLE = 4; +const SECTION_EXPORT = 7; + +function leb(value) { + let bytes = []; + do { + let byte = Number(value & 0x7fn); + value >>= 7n; + if (value) + byte |= 0x80; + bytes.push(byte); + } while (value); + return bytes; +} + +function section(id, payload) { + return [id, ...leb(BigInt(payload.length)), ...payload]; +} + +const name = (text) => [text.length, ...Array.from(text, (c) => c.charCodeAt(0))]; + +// flags: 0x00 min only i32, 0x04 min only i64 +function importedTable(initial, isTable64) { + let bytes = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]; + bytes.push(...section(SECTION_TYPE, [0x00])); + bytes.push(...section(SECTION_IMPORT, [0x01, ...name("m"), ...name("t"), 0x01, 0x70, isTable64 ? 0x04 : 0x00, ...leb(initial)])); + return new Uint8Array(bytes); +} + +function exportedTable(initial, isTable64) { + let bytes = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]; + bytes.push(...section(SECTION_TYPE, [0x00])); + bytes.push(...section(SECTION_TABLE, [0x01, 0x70, isTable64 ? 0x04 : 0x00, ...leb(initial)])); + bytes.push(...section(SECTION_EXPORT, [0x01, ...name("t"), 0x01, 0x00])); + return new Uint8Array(bytes); +} + +function checkMinimum(bytes, expected, description) { + const module = new WebAssembly.Module(bytes); + const imports = WebAssembly.Module.imports(module); + const exports = WebAssembly.Module.exports(module); + const descriptor = imports.length ? imports[0] : exports[0]; + assert.eq(descriptor.kind, "table"); + assert.eq(descriptor.type.minimum, expected, description); +} + +// Sizes above the creatable limit keep their declared value. +checkMinimum(importedTable(1099511627776n, true), 1099511627776n, "table64 import, 2^40"); +checkMinimum(importedTable(4294967301n, true), 4294967301n, "table64 import, 2^32 + 5"); +checkMinimum(importedTable(18446744073709551615n, true), 18446744073709551615n, "table64 import, 2^64 - 1"); +checkMinimum(importedTable(20000000n, false), 20000000, "table32 import, 20000000"); +checkMinimum(exportedTable(4294967301n, true), 4294967301n, "table64 definition, 2^32 + 5"); +checkMinimum(exportedTable(20000000n, false), 20000000, "table32 definition, 20000000"); + +// Sizes at or below it are unaffected. +checkMinimum(importedTable(10n, true), 10n, "table64 import, 10"); +checkMinimum(importedTable(10n, false), 10, "table32 import, 10"); +checkMinimum(exportedTable(9999999n, false), 9999999, "table32 definition, 9999999"); + +// Such a table still cannot be created, and no JS table can satisfy the import. +for (const isTable64 of [false, true]) { + const initial = isTable64 ? 4294967301n : 20000000n; + assert.throws(() => new WebAssembly.Instance(new WebAssembly.Module(exportedTable(initial, isTable64))), + WebAssembly.LinkError, "couldn't create Table"); + + const table = new WebAssembly.Table({ element: "anyfunc", initial: isTable64 ? 10n : 10, address: isTable64 ? "i64" : "i32" }); + assert.throws(() => new WebAssembly.Instance(new WebAssembly.Module(importedTable(initial, isTable64)), { m: { t: table } }), + WebAssembly.LinkError, "Table import m:t provided an 'initial' that is too small"); +} diff --git a/JSTests/wasm/stress/table64-element-offset.js b/JSTests/wasm/stress/table64-element-offset.js new file mode 100644 index 0000000000000..6d9ae1a19c923 --- /dev/null +++ b/JSTests/wasm/stress/table64-element-offset.js @@ -0,0 +1,54 @@ +//@ skip if $addressBits <= 32 +import { instantiate } from "../wabt-wrapper.js"; +import * as assert from "../assert.js"; + +const options = { memory64: true }; + +// An active element segment on a table64 has a u64 offset. Truncating it to 32 +// bits turns an out-of-bounds segment into one that silently initializes the +// wrong slots instead of failing instantiation. +for (const offset of ["4294967296", "4294967301", "18446744073709551615"]) { + await assert.throwsAsync( + instantiate(` + (module + (table i64 10 funcref) + (elem (i64.const ${offset}) $f) + (func $f) + )`, {}, options), + WebAssembly.RuntimeError, + "Element is trying to set an out of bounds table index"); +} + +// Same, with the offset coming from an imported i64 global. +await assert.throwsAsync( + instantiate(` + (module + (import "m" "g" (global $g i64)) + (table i64 10 funcref) + (elem (global.get $g) $f) + (func $f) + )`, { m: { g: new WebAssembly.Global({ value: "i64" }, 4294967296n) } }, options), + WebAssembly.RuntimeError, + "Element is trying to set an out of bounds table index"); + +// An in-bounds offset above 2^32 is impossible, but offsets that fit must still +// work, and an empty segment is allowed to start exactly at the table's end. +{ + const instance = await instantiate(` + (module + (table (export "table") i64 10 funcref) + (elem (i64.const 7) $f) + (elem (i64.const 10)) + (type $ft (func (result i32))) + (func $f (result i32) + (i32.const 42) + ) + (func (export "callAt") (param $i i64) (result i32) + (local.get $i) + (call_indirect (type $ft)) + ) + )`, {}, options); + + assert.eq(instance.exports.callAt(7n), 42); + assert.eq(instance.exports.table.get(0n), null); +} diff --git a/JSTests/wasm/stress/table64-growable-import.js b/JSTests/wasm/stress/table64-growable-import.js new file mode 100644 index 0000000000000..d1857b4f8eea7 --- /dev/null +++ b/JSTests/wasm/stress/table64-growable-import.js @@ -0,0 +1,50 @@ +//@ skip if $addressBits <= 32 +import { instantiate } from "../wabt-wrapper.js"; +import * as assert from "../assert.js"; + +// A module whose imported table declares initial == maximum is compiled on the +// assumption that the table can never be resized: BBQ and OMG fold its length +// into a constant and treat its function buffer as immutable. Comparing the +// declared and the provided maximum as u32 let a table64 whose real maximum is +// above 2^32 satisfy such an import and then grow underneath compiled code. +{ + const table = new WebAssembly.Table({ element: "funcref", initial: 5n, maximum: 5n, address: "i64" }); + const growable = new WebAssembly.Table({ element: "funcref", initial: 5n, maximum: 4294967301n, address: "i64" }); + const wat = ` + (module + (import "m" "t" (table $t i64 5 5 funcref)) + )`; + + await instantiate(wat, { m: { t: table } }, { memory64: true }); + await assert.throwsAsync( + instantiate(wat, { m: { t: growable } }, { memory64: true }), + WebAssembly.LinkError, + "Imported Table m:t 'maximum' is larger than the module's expected 'maximum'"); +} + +// A table whose maximum genuinely matches the declared one stays resizable, and +// every tier must see the length it grew to rather than the declared minimum. +{ + const helper = await instantiate(` + (module + (func (export "f") (result i32) + (i32.const 42) + ) + )`, {}, {}); + + const table = new WebAssembly.Table({ element: "funcref", initial: 5n, maximum: 4294967301n, address: "i64" }); + const instance = await instantiate(` + (module + (import "m" "t" (table $t i64 5 4294967301 funcref)) + (type $ft (func (result i32))) + (func (export "callAt") (param $i i64) (result i32) + (local.get $i) + (call_indirect $t (type $ft)) + ) + )`, { m: { t: table } }, { memory64: true }); + + table.grow(5n); + table.set(7n, helper.exports.f); + for (let i = 0; i < wasmTestLoopCount; ++i) + assert.eq(instance.exports.callAt(7n), 42); +} diff --git a/JSTests/wasm/stress/table64-maximum.js b/JSTests/wasm/stress/table64-maximum.js new file mode 100644 index 0000000000000..0b1bf7daefc6a --- /dev/null +++ b/JSTests/wasm/stress/table64-maximum.js @@ -0,0 +1,54 @@ +//@ skip if $addressBits <= 32 +import { instantiate } from "../wabt-wrapper.js"; +import * as assert from "../assert.js"; + +// A table64's maximum is a u64. Narrowing it to 32 bits leaves the table unable +// to grow, and when the narrowed value lands below the initial size it also +// breaks Wasm::Table's maximum >= length invariant. +for (const maximum of ["4294967296", "4294967301", "18446744073709551615"]) { + const instance = await instantiate(` + (module + (table (export "table") i64 1 ${maximum} funcref) + (func (export "size") (result i64) + (table.size 0) + ) + (func (export "grow") (param $delta i64) (result i64) + (ref.null func) + (local.get $delta) + (table.grow 0) + ) + )`, {}, { memory64: true }); + + assert.eq(instance.exports.size(), 1n); + assert.eq(instance.exports.grow(9n), 1n); + assert.eq(instance.exports.size(), 10n); + assert.eq(instance.exports.grow(0n), 10n); +} + +// However large the declared maximum is, growth is still bounded by the number of +// entries a table may hold (Wasm::Table::isValidLength, maxTableEntries). Only +// rejected grows are exercised here; reaching that bound would allocate 10M entries. +{ + const instance = await instantiate(` + (module + (table (export "table") i64 1 18446744073709551615 funcref) + (func (export "size") (result i64) + (table.size 0) + ) + (func (export "grow") (param $delta i64) (result i64) + (ref.null func) + (local.get $delta) + (table.grow 0) + ) + )`, {}, { memory64: true }); + + // 9999999 lands exactly on the bound, 10000000 just past it, and the last delta + // overflows the u64 length computation. + for (const delta of [9999999n, 10000000n, 18446744073709551615n]) { + assert.eq(instance.exports.grow(delta), -1n); + assert.eq(instance.exports.size(), 1n); + } + + assert.throws(() => instance.exports.table.grow(9999999n), RangeError, "WebAssembly.Table.prototype.grow could not grow the table"); + assert.eq(instance.exports.table.length, 1n); +} diff --git a/JSTests/wasm/stress/type-index-abstract-heap-types-concrete-vs-abstract.js b/JSTests/wasm/stress/type-index-abstract-heap-types-concrete-vs-abstract.js index 2bd9f7be23062..d840b317fa7ba 100644 --- a/JSTests/wasm/stress/type-index-abstract-heap-types-concrete-vs-abstract.js +++ b/JSTests/wasm/stress/type-index-abstract-heap-types-concrete-vs-abstract.js @@ -1,7 +1,5 @@ -//@ skip if $hostOS == "linux" //@ slow! // https://bugs.webkit.org/show_bug.cgi?id=247454 -// https://bugs.webkit.org/show_bug.cgi?id=320559 import * as assert from "../assert.js"; import { compile, instantiate } from "../gc/wast-wrapper.js"; diff --git a/JSTests/wasm/stress/type-index-abstract-heap-types-globals-and-tables.js b/JSTests/wasm/stress/type-index-abstract-heap-types-globals-and-tables.js index 0f0568c0ce2ba..0992e07c10e0b 100644 --- a/JSTests/wasm/stress/type-index-abstract-heap-types-globals-and-tables.js +++ b/JSTests/wasm/stress/type-index-abstract-heap-types-globals-and-tables.js @@ -1,7 +1,5 @@ -//@ skip if $hostOS == "linux" //@ slow! // https://bugs.webkit.org/show_bug.cgi?id=247454 -// https://bugs.webkit.org/show_bug.cgi?id=320559 import * as assert from "../assert.js"; import { compile, instantiate } from "../gc/wast-wrapper.js"; diff --git a/JSTests/wasm/stress/type-index-abstract-heap-types-nulls-and-casts.js b/JSTests/wasm/stress/type-index-abstract-heap-types-nulls-and-casts.js index cbe20205fa5d4..5d2bef1838145 100644 --- a/JSTests/wasm/stress/type-index-abstract-heap-types-nulls-and-casts.js +++ b/JSTests/wasm/stress/type-index-abstract-heap-types-nulls-and-casts.js @@ -1,7 +1,5 @@ -//@ skip if $hostOS == "linux" //@ slow! // https://bugs.webkit.org/show_bug.cgi?id=247454 -// https://bugs.webkit.org/show_bug.cgi?id=320559 import * as assert from "../assert.js"; import { compile, instantiate } from "../gc/wast-wrapper.js"; diff --git a/JSTests/wasm/stress/type-index-abstract-heap-types-subtype-validation.js b/JSTests/wasm/stress/type-index-abstract-heap-types-subtype-validation.js index 5a68e49d2c702..193112affb0a9 100644 --- a/JSTests/wasm/stress/type-index-abstract-heap-types-subtype-validation.js +++ b/JSTests/wasm/stress/type-index-abstract-heap-types-subtype-validation.js @@ -1,7 +1,5 @@ -//@ skip if $hostOS == "linux" //@ slow! // https://bugs.webkit.org/show_bug.cgi?id=247454 -// https://bugs.webkit.org/show_bug.cgi?id=320559 import * as assert from "../assert.js"; import { compile, instantiate } from "../gc/wast-wrapper.js"; diff --git a/JSTests/wasm/stress/unreachable-immediates-validation.js b/JSTests/wasm/stress/unreachable-immediates-validation.js new file mode 100644 index 0000000000000..dd22a8ae1f783 --- /dev/null +++ b/JSTests/wasm/stress/unreachable-immediates-validation.js @@ -0,0 +1,79 @@ +import * as assert from "../assert.js"; + +// Immediates in unreachable code are still validated: the spec's validation rules do not depend on +// reachability. These modules are assembled by hand because a text assembler rejects them outright. + +const SECTION_TYPE = 1; +const SECTION_FUNCTION = 3; +const SECTION_TABLE = 4; +const SECTION_MEMORY = 5; +const SECTION_CODE = 10; + +function leb(value) { + let bytes = []; + do { + let byte = value & 0x7f; + value >>>= 7; + if (value) + byte |= 0x80; + bytes.push(byte); + } while (value); + return bytes; +} + +function section(id, payload) { + return [id, ...leb(payload.length), ...payload]; +} + +function moduleBytes({ withMemory = false, withTable = false, body }) { + let bytes = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]; + bytes.push(...section(SECTION_TYPE, [0x01, 0x60, 0x00, 0x00])); // one type: () -> () + bytes.push(...section(SECTION_FUNCTION, [0x01, 0x00])); + if (withTable) + bytes.push(...section(SECTION_TABLE, [0x01, 0x70, 0x00, 0x01])); // one funcref table, min 1 + if (withMemory) + bytes.push(...section(SECTION_MEMORY, [0x01, 0x00, 0x01])); // one memory, min 1 + const code = [0x00, 0x00, ...body, 0x0b]; // no locals, unreachable, body, end + bytes.push(...section(SECTION_CODE, [0x01, ...leb(code.length), ...code])); + return new Uint8Array(bytes); +} + +function assertInvalid(description, options) { + const bytes = moduleBytes(options); + try { + new WebAssembly.Module(bytes); + } catch (error) { + assert.truthy(error instanceof WebAssembly.CompileError, `${description}: expected CompileError, got ${error}`); + return; + } + throw new Error(`${description}: module was accepted but is invalid`); +} + +function assertValid(description, options) { + new WebAssembly.Module(moduleBytes(options)); +} + +// A memarg alignment above the access's natural alignment is invalid. +assertInvalid("i32.load align=8", { withMemory: true, body: [0x28, 0x03, 0x00] }); +assertInvalid("i32.load align=2^63", { withMemory: true, body: [0x28, 0x3f, 0x00] }); +assertInvalid("i32.store align=8", { withMemory: true, body: [0x36, 0x03, 0x00] }); +assertInvalid("i32.load8_s align=2", { withMemory: true, body: [0x2c, 0x01, 0x00] }); +assertInvalid("i64.load align=16", { withMemory: true, body: [0x29, 0x04, 0x00] }); +assertValid("i32.load align=4", { withMemory: true, body: [0x28, 0x02, 0x00] }); +assertValid("i32.load align=1", { withMemory: true, body: [0x28, 0x00, 0x00] }); + +// A table index must exist. Only index 0 does here. +assertInvalid("table.get 5", { withTable: true, body: [0x25, 0x05] }); +assertInvalid("table.set 5", { withTable: true, body: [0x26, 0x05] }); +assertInvalid("table.get 0, no table section", { body: [0x25, 0x00] }); +assertValid("table.get 0", { withTable: true, body: [0x25, 0x00] }); + +// call_indirect validates both of its immediates, and needs a table at all. +assertInvalid("call_indirect type 0 table 7", { withTable: true, body: [0x11, 0x00, 0x07] }); +assertInvalid("call_indirect type 99 table 0", { withTable: true, body: [0x11, 0x63, 0x00] }); +assertInvalid("call_indirect with no table section", { body: [0x11, 0x00, 0x00] }); +assertValid("call_indirect type 0 table 0", { withTable: true, body: [0x11, 0x00, 0x00] }); + +// ref.func needs an index inside the function index space, and a declaration. +assertInvalid("ref.func 99", { body: [0xd2, 0x63, 0x1a] }); +assertInvalid("ref.func 0 undeclared", { body: [0xd2, 0x00, 0x1a] }); diff --git a/JSTests/wasm/v8/memory64.js b/JSTests/wasm/v8/memory64.js index 87533eb12eb4f..fb3930b518d10 100644 --- a/JSTests/wasm/v8/memory64.js +++ b/JSTests/wasm/v8/memory64.js @@ -1,14 +1,5 @@ -//@ requireOptions("--useBBQJIT=1") -//@ skip -// Failure: -// Exception: CompileError: WebAssembly.Module doesn't parse at byte 30: resizable limits flag should be 0x00, 0x01, or 0x03 but 0x05 (evaluating 'new WebAssembly.Module(this.toBuffer(debug))') -// Module@[native code] -// toModule@.tests/wasm.yaml/wasm/v8/wasm-module-builder.js:2082:34 -// instantiate@.tests/wasm.yaml/wasm/v8/wasm-module-builder.js:2071:31 -// BasicMemory64Tests@memory64.js:50:35 -// TestSmallMemory@memory64.js:106:21 -// global code@memory64.js:107:3 - +//@ memoryHog! +//@ skip if $addressBits <= 32 // Copyright 2021 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -53,19 +44,10 @@ function BasicMemory64Tests(num_pages) { let store = module.exports.store; assertEquals(num_bytes, memory.buffer.byteLength); - // TODO(v8:4153): Enable for all sizes once the TypedArray size limit is - // raised. - const kMaxTypedArraySize = Math.pow(2, 32); - if (num_bytes > kMaxTypedArraySize) { - // TODO(v8:4153): Fix the error message below, if we don't decide to bump - // the limit soon. - assertThrows( - () => new Int8Array(memory.buffer), RangeError, - 'Invalid typed array length: undefined'); - } else { - let array = new Int8Array(memory.buffer); - assertEquals(num_bytes, array.length); - } + // JSC's array buffer byte length limit is 2**34, which is also the largest memory64, so every size + // reachable here also fits a typed array. V8 caps buffers at 2**32 and skips the big sizes instead. + let array = new Int8Array(memory.buffer); + assertEquals(num_bytes, array.length); assertEquals(0, load(num_bytes - 4)); assertThrows(() => load(num_bytes - 3)); @@ -132,28 +114,27 @@ function allowOOM(fn) { allowOOM(() => BasicMemory64Tests(max_num_pages)); })(); -(function TestTooBigDeclaredInitial() { +// A page count is a declaration bounded only by the i64 address space, so a count past the 16GB that +// can be allocated still validates and compiles; allocating it is what fails. The bound itself is +// covered by JSTests/wasm/stress/memory64-oversized-limits.js, which can encode a 2**48 page count. +(function TestDeclaredInitialPastWhatCanBeAllocated() { // print(arguments.callee.name); let builder = new WasmModuleBuilder(); builder.addMemory64(max_num_pages + 1); - assertFalse(WebAssembly.validate(builder.toBuffer())); - assertThrows( - () => builder.toModule(), WebAssembly.CompileError, - 'WebAssembly.Module(): initial memory size (262145 pages) is larger ' + - 'than implementation limit (262144 pages) @+12'); + assertTrue(WebAssembly.validate(builder.toBuffer())); + builder.toModule(); + assertThrows(() => builder.instantiate(), RangeError, /Out of memory/); })(); -(function TestTooBigDeclaredMaximum() { +(function TestDeclaredMaximumPastWhatCanBeAllocated() { // print(arguments.callee.name); let builder = new WasmModuleBuilder(); builder.addMemory64(1, max_num_pages + 1); - assertFalse(WebAssembly.validate(builder.toBuffer())); - assertThrows( - () => builder.toModule(), WebAssembly.CompileError, - 'WebAssembly.Module(): maximum memory size (262145 pages) is larger ' + - 'than implementation limit (262144 pages) @+13'); + // A maximum costs nothing until it is reached, so this instantiates at its initial size. + assertTrue(WebAssembly.validate(builder.toBuffer())); + builder.instantiate(); })(); (function TestGrow64() { @@ -307,6 +288,7 @@ function allowOOM(fn) { assertEquals(0, instance.exports.load(0n)); })(); +/* (function TestMemory64SharedBetweenWorkers() { // print(arguments.callee.name); // Generate a shared memory64 by instantiating an module that exports one. @@ -382,3 +364,4 @@ function allowOOM(fn) { assertEquals(kValue, instance.exports.load(kOffset2)); assertEquals(5n, instance.exports.grow(1n)); })(); +*/ diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index 8b92727bc6d6b..a84d85bbe6bc5 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -16,6 +16,9 @@ accessibility/ios-simulator [ Skip ] accessibility/gtk [ Skip ] accessibility/mac [ Skip ] accessibility/win [ Skip ] +# The isolated tree only ships in Safari on macOS, so these run there alone +# (re-enabled in platform/mac/TestExpectations). +accessibility/isolated-tree [ Skip ] http/tests/site-isolation/accessibility/client [ Skip ] # Exercises iOS-specific remote-frame accessibility search behavior; enabled only on iOS (see platform/ios/TestExpectations). http/tests/site-isolation/accessibility/heading-search-returns-remote-frame.html [ Skip ] @@ -393,9 +396,6 @@ media/deactivate-audio-session.html [ Skip ] # Skip the test which hits a debug assertion for now. [ Debug ] media/track/webvtt-parser-does-not-leak.html [ Skip ] -# Skip isolated-tree specific tests. -accessibility/isolated-tree [ Skip ] - # ApplePay is only available on iOS (greater than iOS 10) and macOS (greater than macOS 10.12) and only for WebKit2. fast/css/appearance-apple-pay-button.html [ Skip ] fast/css/appearance-apple-pay-button-div.html [ Skip ] @@ -541,12 +541,10 @@ imported/w3c/web-platform-tests/html/webappapis/dynamic-markup-insertion/opening imported/w3c/web-platform-tests/html/webappapis/scripting/event-loops/fully_active_document.window.html [ Skip ] # Skip WPT webaudio tests that are timing out. -imported/w3c/web-platform-tests/webaudio/the-audio-api/the-mediastreamaudiosourcenode-interface/mediastreamaudiosourcenode-routing.html [ Skip ] webkit.org/b/280303 imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audiocontext-interface/audiocontext-sinkid-state-change.https.html [ Skip ] imported/w3c/web-platform-tests/webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/setSinkId-with-MediaElementAudioSourceNode.https.html [ Skip ] imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audiocontext-interface/audiocontext-suspend-resume-close.html [ Skip ] imported/w3c/web-platform-tests/webaudio/the-audio-api/the-analysernode-interface/test-analyser-resume-after-suspended.html [ Skip ] -imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworkletglobalscope-creation-time.https.html [ Skip ] imported/w3c/web-platform-tests/webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html [ Skip ] # This test is timing out due to lack of support for SharedArrayBuffer. @@ -639,6 +637,10 @@ imported/w3c/web-platform-tests/webvtt/rendering/cues-with-video [ ImageOnlyFail imported/w3c/web-platform-tests/webvtt/rendering/cues-with-video/processing-model/2_cues_overlapping_partially_move_down.html [ Pass ] imported/w3c/web-platform-tests/webvtt/rendering/cues-with-video/processing-model/2_tracks.html [ Pass ] imported/w3c/web-platform-tests/webvtt/rendering/cues-with-video/processing-model/3_tracks.html [ Pass ] +imported/w3c/web-platform-tests/webvtt/rendering/cues-with-video/processing-model/align_end.html [ Pass ] +imported/w3c/web-platform-tests/webvtt/rendering/cues-with-video/processing-model/align_end_wrapped.html [ Pass ] +imported/w3c/web-platform-tests/webvtt/rendering/cues-with-video/processing-model/align_start.html [ Pass ] +imported/w3c/web-platform-tests/webvtt/rendering/cues-with-video/processing-model/align_start_wrapped.html [ Pass ] imported/w3c/web-platform-tests/webvtt/rendering/cues-with-video/processing-model/audio_has_no_subtitles.html [ Pass ] imported/w3c/web-platform-tests/webvtt/rendering/cues-with-video/processing-model/bidi/u06E9_no_strong_dir.html [ Pass ] imported/w3c/web-platform-tests/webvtt/rendering/cues-with-video/processing-model/cue_too_long.html [ Pass ] @@ -823,6 +825,19 @@ imported/w3c/web-platform-tests/html/browsers/browsing-the-web/history-traversal imported/w3c/web-platform-tests/html/browsers/browsing-the-web/history-traversal/pageswap/pageswap-traverse-navigation-cross-origin-redirect-no-bfcache.https.sub.html [ Skip ] imported/w3c/web-platform-tests/html/browsers/history/the-history-interface/history_pushstate_url.html [ Skip ] imported/w3c/web-platform-tests/html/browsers/history/the-history-interface/traverse_the_history_3.html [ Skip ] + +# webkit.org/b/321061 These tests time out now that the back/forward cache is enabled for web-platform-tests. +imported/w3c/web-platform-tests/html/browsers/browsing-the-web/history-traversal/srcdoc/srcdoc-history-entries.html [ Skip ] +imported/w3c/web-platform-tests/html/browsers/history/the-history-interface/history_back_1.html [ Skip ] +imported/w3c/web-platform-tests/html/browsers/history/the-history-interface/history_forward_1.html [ Skip ] +imported/w3c/web-platform-tests/html/browsers/history/the-history-interface/history_go_no_argument.html [ Skip ] +imported/w3c/web-platform-tests/html/browsers/history/the-history-interface/history_go_to_uri.html [ Skip ] +imported/w3c/web-platform-tests/html/browsers/history/the-history-interface/history_go_undefined.html [ Skip ] +imported/w3c/web-platform-tests/html/browsers/history/the-history-interface/traverse_the_history_unload_1.html [ Skip ] +imported/w3c/web-platform-tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1.html [ Skip ] +imported/w3c/web-platform-tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_2.html [ Skip ] +imported/w3c/web-platform-tests/html/cross-origin-opener-policy/coop-navigated-history-popup.https.html [ Skip ] + imported/w3c/web-platform-tests/html/browsers/origin/origin-keyed-agent-clusters/going-back.sub.https.html [ Skip ] imported/w3c/web-platform-tests/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_feature_policy.tentative.sub.html [ Skip ] imported/w3c/web-platform-tests/html/browsers/sandboxing/sandbox-document-open.html [ Skip ] @@ -1078,7 +1093,6 @@ webkit.org/b/279748 imported/w3c/web-platform-tests/html/dom/elements/global-att imported/w3c/web-platform-tests/html/dom/elements/global-attributes/dir-shadow-39.html [ ImageOnlyFailure ] # csp tests which fail stress test mode. -imported/w3c/web-platform-tests/content-security-policy/generic/image-document-ignores-csp.html [ ImageOnlyFailure Pass ] imported/w3c/web-platform-tests/content-security-policy/font-src/font-match-allowed.sub.html [ Failure Pass ] [ Debug ] imported/w3c/web-platform-tests/service-workers/service-worker/getregistrations.https.html [ Slow ] @@ -1189,6 +1203,12 @@ imported/w3c/web-platform-tests/content-security-policy/svg/including.sub.svg [ imported/w3c/web-platform-tests/content-security-policy/reporting-api/dedicated-worker-correct-url-in-report.html [ Skip ] # Skip Content Security Policy tests that time out +imported/w3c/web-platform-tests/content-security-policy/sandbox/autoplay-disabled-by-csp.html [ Skip ] +imported/w3c/web-platform-tests/content-security-policy/script-src/script-src-trusted_types_eval_DedicatedWorker.html [ Skip ] +imported/w3c/web-platform-tests/content-security-policy/script-src/tentative/script-url-blocked-report-contains-hash-dedicated-worker.https.html [ Skip ] +imported/w3c/web-platform-tests/content-security-policy/script-src/tentative/script-url-blocked-report-contains-hash-service-worker.https.html [ Skip ] +imported/w3c/web-platform-tests/content-security-policy/script-src/tentative/script-url-blocked-report-contains-hash-shared-worker.https.html [ Skip ] +imported/w3c/web-platform-tests/content-security-policy/script-src/tentative/script-url-blocked-report-contains-hash.https.html [ Skip ] imported/w3c/web-platform-tests/content-security-policy/child-src/child-src-cross-origin-load.sub.html [ Skip ] imported/w3c/web-platform-tests/content-security-policy/connect-src/connect-src-text-import-blocked.sub.html [ Skip ] imported/w3c/web-platform-tests/content-security-policy/connect-src/connect-src-webtransport-allowed.sub.https.html [ Skip ] @@ -1264,15 +1284,20 @@ webkit.org/b/246440 imported/w3c/web-platform-tests/content-security-policy/wasm webkit.org/b/246440 imported/w3c/web-platform-tests/content-security-policy/wasm-unsafe-eval/script-src-spv-asynch.any.worker.html [ Skip ] # FIXME: Skip Content Security Policy tests whose output is non-deterministic +imported/w3c/web-platform-tests/content-security-policy/inheritance/blob-url-inherits-from-initiator.sub.html [ Skip ] imported/w3c/web-platform-tests/content-security-policy/reporting/multiple-report-policies.html [ Skip ] imported/w3c/web-platform-tests/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html [ Skip ] +webkit.org/b/321358 [ Debug ] imported/w3c/web-platform-tests/content-security-policy/inheritance/history.sub.html [ Crash Pass ] + # Content Security Policy: Embedded Enforcement is not supported imported/w3c/web-platform-tests/content-security-policy/embedded-enforcement # Test is timing out. imported/w3c/web-platform-tests/content-security-policy/securitypolicyviolation/inside-shared-worker.html +imported/w3c/web-platform-tests/content-security-policy/generic/image-document-ignores-csp-for-loading-main-image.tentative.html [ ImageOnlyFailure ] + # Only relevant on macOS css3/color-filters/punch-out-white-backgrounds.html [ Skip ] @@ -2321,6 +2346,9 @@ imported/w3c/web-platform-tests/mathml/relations/css-styling/first-line-first-le imported/w3c/web-platform-tests/mathml/relations/css-styling/first-line-first-letter-pseudo-elements-004.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/mathml/relations/css-styling/table-width-3.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/mathml/relations/html5-tree/href-navigation.html [ Skip ] # timeout +imported/w3c/web-platform-tests/mathml/relations/text-and-math/non-mathml-children-in-annotation.tentative.html [ ImageOnlyFailure ] + # This MathML test should be rewritten. webkit.org/b/201356 mathml/presentation/stretchy-depth-height-symmetric.html [ Skip ] @@ -2552,7 +2580,6 @@ imported/w3c/web-platform-tests/svg/painting/reftests/non-scaling-stroke-003.htm imported/w3c/web-platform-tests/svg/painting/reftests/paint-context-004.svg [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/painting/reftests/paint-context-006.svg [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/painting/reftests/paint-context-007.svg [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/svg/painting/reftests/pattern-external-reference.svg [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/painting/reftests/marker-context-fill-transform.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/animations/seeking-events-2.html [ Failure Pass ] imported/w3c/web-platform-tests/svg/animations/dependent-begin-on-syncbase.html [ Failure Pass ] @@ -2560,7 +2587,6 @@ imported/w3c/web-platform-tests/svg/animations/dependent-end-on-syncbase.html [ imported/w3c/web-platform-tests/svg/path/bearing/absolute.svg [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/path/bearing/relative.svg [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/path/bearing/zero.svg [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/svg/path/closepath/segment-completing.svg [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/path/distance/pathLength-zero-percentage.svg [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/path/distance/pathlength-rect-mutating.svg [ Failure Pass ] imported/w3c/web-platform-tests/svg/animations/seeking-events-1.html [ Failure Pass ] @@ -2574,11 +2600,6 @@ imported/w3c/web-platform-tests/svg/extensibility/foreignObject/isolation-with-s imported/w3c/web-platform-tests/svg/painting/reftests/display-none-mask.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/coordinate-systems/viewBox-synthesized-in-img-001.tentative.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/styling/image-sizing-min-content.tentative.svg [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/svg/styling/nested-svg-sizing-calc-size.tentative.svg [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/svg/styling/nested-svg-sizing-fit-content.tentative.svg [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/svg/styling/nested-svg-sizing-max-content.tentative.svg [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/svg/styling/nested-svg-sizing-min-content.tentative.svg [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/svg/styling/nested-svg-sizing-stretch.tentative.svg [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/styling/nested-svg-sizing-viewport-units-with-ICB.tentative.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/styling/nested-svg-sizing-viewport-units.tentative.svg [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/styling/use-element-attr-selector-transition.tentative.html [ ImageOnlyFailure ] @@ -2615,6 +2636,8 @@ imported/w3c/web-platform-tests/svg/struct/reftests/use-external-resource-nested webkit.org/b/139595 http/tests/xmlhttprequest/workers/abort-exception-assert.html [ Pass Failure Timeout ] +webkit.org/b/321361 http/tests/xmlhttprequest/onabort-response-getters.html [ Pass Failure ] + # Debug assertions are tracked as . [ Debug ] fast/history/history-back-while-pdf-in-pagecache.html [ Skip ] webkit.org/b/121628 [ Release ] fast/history/history-back-while-pdf-in-pagecache.html [ Pass ImageOnlyFailure ] @@ -2824,7 +2847,6 @@ imported/w3c/web-platform-tests/svg/styling/css-linked-parameters/img-with-url-f imported/w3c/web-platform-tests/svg/animations/scripted/syncbase-after-removal.html [ Skip ] imported/w3c/web-platform-tests/svg/animations/media-fragment-override-animation.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/svg/interact/use-instance-hover.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/svg/linking/reftests/media-fragment-override-multiple.html [ ImageOnlyFailure ] webkit.org/b/283954 imported/w3c/web-platform-tests/svg/text/reftests/transform-dynamic-change-root.html [ ImageOnlyFailure ] @@ -3864,8 +3886,6 @@ imported/w3c/web-platform-tests/css/css-pseudo/first-line-line-height-002.html [ imported/w3c/web-platform-tests/css/css-pseudo/highlight-cascade/highlight-currentcolor-painting-properties-001.html [ ImageOnlyFailure Timeout Pass ] imported/w3c/web-platform-tests/css/css-pseudo/highlight-cascade/highlight-currentcolor-painting-properties-002.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-pseudo/highlight-cascade/highlight-currentcolor-painting-text-shadow-002.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-pseudo/highlight-cascade/highlight-currentcolor-root-explicit-default-002.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-pseudo/highlight-cascade/highlight-currentcolor-root-implicit-default-001.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-pseudo/highlight-painting-currentcolor-002.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-pseudo/highlight-painting-currentcolor-002a.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-pseudo/highlight-painting-currentcolor-002b.html [ ImageOnlyFailure ] @@ -3898,7 +3918,6 @@ imported/w3c/web-platform-tests/css/css-pseudo/slider/slider-track-003.html [ Im imported/w3c/web-platform-tests/css/css-pseudo/svg-text-selection-002.html [ ImageOnlyFailure ] # Implement highlight pseudo-element cascade / painting behaviors (webkit.org/b/278216) imported/w3c/web-platform-tests/css/css-pseudo/target-text-004.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-pseudo/target-text-009.html [ ImageOnlyFailure ] # Incorrect test, needs WPT resync (webkit.org/b/277692) imported/w3c/web-platform-tests/css/css-pseudo/selection-background-painting-order.html [ ImageOnlyFailure ] @@ -4063,9 +4082,6 @@ imported/w3c/web-platform-tests/css/css-overflow/clip-008.html [ ImageOnlyFailur imported/w3c/web-platform-tests/css/css-overflow/column-style-change-triggers-relayout.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-001.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-004.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-007.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-008.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-009.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-011.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-012.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/block-ellipsis-013.html [ ImageOnlyFailure ] @@ -4191,7 +4207,8 @@ imported/w3c/web-platform-tests/css/css-overflow/scroll-markers [ Skip ] webkit.org/b/210731 imported/w3c/web-platform-tests/IndexedDB/structured-clone.any.html [ Skip ] webkit.org/b/210731 imported/w3c/web-platform-tests/IndexedDB/structured-clone.any.worker.html [ Skip ] -imported/w3c/web-platform-tests/IndexedDB/idb-partitioned-coverage.sub.html [ Failure ] +webkit.org/b/321411 [ Debug ] imported/w3c/web-platform-tests/IndexedDB/idb-partitioned-coverage.sub.html [ Failure ] + imported/w3c/web-platform-tests/IndexedDB/interleaved-cursors-large.any.html [ Failure ] imported/w3c/web-platform-tests/IndexedDB/interleaved-cursors-large.any.serviceworker.html [ Failure ] imported/w3c/web-platform-tests/IndexedDB/interleaved-cursors-large.any.sharedworker.html [ Failure ] @@ -4916,7 +4933,6 @@ webkit.org/b/214300 imported/w3c/web-platform-tests/css/css-fonts/hiragana-katak imported/w3c/web-platform-tests/css/css-fonts/font-face-local-not-family.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-fonts/font-family-name-000.xht [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-fonts/font-palette-23b.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-fonts/font-size-adjust-metrics-override.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-fonts/font-synthesis-position-001.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-fonts/font-variant-emoji-003.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-fonts/font-variant-emoji-004.html [ ImageOnlyFailure ] @@ -4937,12 +4953,6 @@ webkit.org/b/182042 imported/w3c/web-platform-tests/css/css-fonts/font-default-0 webkit.org/b/182042 imported/w3c/web-platform-tests/css/css-fonts/font-kerning-03.html [ ImageOnlyFailure ] webkit.org/b/86071 imported/w3c/web-platform-tests/css/css-fonts/font-kerning-05.html [ ImageOnlyFailure ] -# @font-face: ascent-override, descent-override, and line-gap-override -webkit.org/b/219735 imported/w3c/web-platform-tests/css/css-fonts/ascent-descent-override.html [ ImageOnlyFailure ] -webkit.org/b/219735 imported/w3c/web-platform-tests/css/css-fonts/line-gap-override.html [ ImageOnlyFailure ] -webkit.org/b/219735 imported/w3c/web-platform-tests/css/css-fonts/font-face-unicode-range-nbsp.html [ ImageOnlyFailure ] -webkit.org/b/219735 imported/w3c/web-platform-tests/css/css-font-loading/fontface-override-descriptors.html [ ImageOnlyFailure ] - # We intentionally do not want to allow disabling required ligatures, so we don't honor this optional test. imported/w3c/web-platform-tests/css/css-fonts/font-variant-ligatures-11.optional.html [ ImageOnlyFailure ] @@ -4998,6 +5008,18 @@ webkit.org/b/292182 webgl/2.0.y/conformance2/rendering/clipping-wide-points.html webkit.org/b/313392 webgl/2.0.y/conformance2/textures/misc/tex-image-10bpc.html +# To be fixed failures after "Update WebGL conformance tests 2026-07-18 (f15a73f727d8ee66a3ec6d0bed9f02f08cacc2c1)" +webkit.org/b/320901 webgl/1.0.x/conformance/attribs/gl-get-attrib-location-errors.html [ Failure ] +webkit.org/b/320901 webgl/1.0.x/conformance/glsl/misc/shader-with-double-underscore.html [ Failure ] +webkit.org/b/320901 webgl/1.0.x/conformance/textures/misc/tex-image-svg-image-no-natural-width-and-height.html [ Failure ] +webkit.org/b/320901 webgl/1.0.x/conformance/uniforms/gl-get-uniform-location-errors.html [ Failure ] +webkit.org/b/320901 webgl/2.0.y/conformance/attribs/gl-get-attrib-location-errors.html [ Failure ] +webkit.org/b/320901 webgl/2.0.y/conformance/glsl/misc/shader-with-double-underscore.html [ Failure ] +webkit.org/b/320901 webgl/2.0.y/conformance/textures/misc/tex-image-svg-image-no-natural-width-and-height.html [ Failure ] +webkit.org/b/320901 webgl/2.0.y/conformance/uniforms/gl-get-uniform-location-errors.html [ Failure ] +webkit.org/b/320901 webgl/2.0.y/conformance2/extensions/webgl-render-shared-exponent.html [ Failure ] +webkit.org/b/320901 webgl/2.0.y/conformance2/rendering/blitframebuffer-test.html [ Failure ] + # Until more platforms ship with WebGL GPUP webgl/webgl-fail-platform-context-creation-no-crash.html [ Failure Pass ] @@ -5272,6 +5294,51 @@ webkit.org/b/214299 imported/w3c/web-platform-tests/css/css-ui/resize-child-will webkit.org/b/279302 imported/w3c/web-platform-tests/css/css-ui/negative-outline-offset.html [ ImageOnlyFailure ] webkit.org/b/279302 imported/w3c/web-platform-tests/css/css-ui/text-overflow-028.html [ ImageOnlyFailure ] +# New failure after import of css/css-ui (2026-07): +webkit.org/b/320474 imported/w3c/web-platform-tests/css/css-ui/compute-kind-widget-no-fallback-props-001.html [ ImageOnlyFailure ] +webkit.org/b/320474 imported/w3c/web-platform-tests/css/css-ui/resize-textarea-relative-to-right-001.tentative.html [ Pass Failure ] +webkit.org/b/320474 imported/w3c/web-platform-tests/css/css-ui/text-overflow-ellipsis-multiline-001.html [ ImageOnlyFailure ] + +# Missing CSS-UI-4 caret properties: +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-color-bar-shape-text-color.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-color-block-shape-text-color-001.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-color-block-shape-text-color-002.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-color-block-shape-text-color-003.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-color-block-shape-text-color-004.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-color-block-shape-text-color-005.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-color-underscore-shape-text-color.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-eol-004.tentative.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-001-rtl-sideways-lr.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-001-rtl-sideways-rl.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-001-rtl-vlr.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-001-rtl-vrl.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-001-rtl.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-001-sideways-lr.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-001-sideways-rl.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-001-vlr.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-001-vrl.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-001.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-002.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-color-001.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-color-002.tentative.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-color-003.tentative.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-color-004.tentative.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-empty-001.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-empty-002.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-fallback-001.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-block-zoom.html [ ImageOnlyFailure ] +webkit.org/b/319405 imported/w3c/web-platform-tests/css/css-ui/caret-shape-underscore-001.html [ ImageOnlyFailure ] + +# Missing text-overflow: +webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-001.html [ ImageOnlyFailure ] +webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-002.html [ ImageOnlyFailure ] +webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-003.html [ ImageOnlyFailure ] +webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-004.html [ ImageOnlyFailure ] +webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-005.html [ ImageOnlyFailure ] +webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-006.html [ ImageOnlyFailure ] +webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-007.html [ ImageOnlyFailure ] +webkit.org/b/27545 imported/w3c/web-platform-tests/css/css-ui/text-overflow-string-008.html [ ImageOnlyFailure ] + webkit.org/b/214387 imported/w3c/web-platform-tests/svg/animations/seeking-events-4.html [ Pass Failure ] webkit.org/b/214453 imported/w3c/web-platform-tests/css/css-align/baseline-rules/grid-item-input-type-number.html [ ImageOnlyFailure ] @@ -5300,8 +5367,6 @@ imported/w3c/web-platform-tests/css/css-images/repeating-conic-gradient.html [ S imported/w3c/web-platform-tests/css/css-images/tiled-conic-gradients.html [ Skip ] # Untriaged counter failures -webkit.org/b/214457 imported/w3c/web-platform-tests/css/css-lists/change-list-descendant-display.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-lists/change-list-style-position-002.html [ ImageOnlyFailure ] webkit.org/b/214457 imported/w3c/web-platform-tests/css/css-lists/inline-block-list.html [ ImageOnlyFailure ] webkit.org/b/214457 imported/w3c/web-platform-tests/css/css-lists/inline-block-list-marker.html [ ImageOnlyFailure ] webkit.org/b/214457 imported/w3c/web-platform-tests/css/css-lists/inline-list.html [ ImageOnlyFailure ] @@ -5391,7 +5456,6 @@ imported/w3c/web-platform-tests/css/css-lists/marker-counter.html [ ImageOnlyFai # list-style imported/w3c/web-platform-tests/css/css-lists/list-style-type-decimal-vertical-lr.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-lists/list-style-type-decimal-vertical-rl.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-lists/list-marker-with-lineheight-and-overflow-hidden-001.html [ ImageOnlyFailure ] # list-style bidi support webkit.org/b/202849 imported/w3c/web-platform-tests/css/css-lists/list-style-type-string-005a.html [ ImageOnlyFailure ] @@ -5475,7 +5539,6 @@ imported/w3c/web-platform-tests/css/css-multicol/multicol-width-005.html [ Image imported/w3c/web-platform-tests/css/css-multicol/with-custom-layout-on-same-element.https.html imported/w3c/web-platform-tests/css/css-multicol/multicol-span-all-children-height-003.html [ Skip ] # times out imported/w3c/web-platform-tests/css/css-multicol/multicol-breaking-006.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-multicol/multicol-fill-balance-005.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-multicol/multicol-fill-balance-nested-000.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-multicol/multicol-nested-007.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-multicol/multicol-nested-008.html [ ImageOnlyFailure ] @@ -5689,11 +5752,9 @@ webkit.org/b/217931 imported/w3c/web-platform-tests/html/semantics/scripting-1/t webkit.org/b/217931 imported/w3c/web-platform-tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-external-module.html [ Pass Failure ] webkit.org/b/217931 imported/w3c/web-platform-tests/html/semantics/scripting-1/the-script-element/moving-between-documents/move-back-iframe-success-inline-classic.html [ Pass Failure ] -webkit.org/b/220325 imported/w3c/web-platform-tests/css/css-highlight-api/highlight-text-cascade.html [ ImageOnlyFailure ] -# Tests need updating relating to Highlight Spec -imported/w3c/web-platform-tests/css/css-highlight-api/painting/custom-highlight-painting-inheritance-001.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-highlight-api/painting/custom-highlight-painting-inheritance-002.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-highlight-api/highlight-image-currentcolor.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/css-highlight-api/highlight-image-stacked.html [ ImageOnlyFailure ] http/tests/webgl/1.0.x/conformance/textures/misc/origin-clean-conformance-offscreencanvas.html [ Skip ] http/tests/webgl/2.0.y/conformance/textures/misc/origin-clean-conformance-offscreencanvas.html [ Skip ] @@ -5713,7 +5774,6 @@ imported/w3c/web-platform-tests/css/css-conditional/container-queries/canvas-as- imported/w3c/web-platform-tests/css/css-conditional/container-queries/canvas-as-container-004.html [ ImageOnlyFailure Pass ] imported/w3c/web-platform-tests/css/css-conditional/container-queries/custom-layout-container-001.https.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-conditional/container-queries/inline-size-bfc-floats.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-conditional/container-queries/pseudo-elements-010.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-conditional/container-queries/query-style-color.html [ ImageOnlyFailure ] # Scroll-state container queries (evaluation unsupported), enable parsing/serialization and computed-value tests only. @@ -5964,7 +6024,6 @@ webkit.org/b/230004 imported/w3c/web-platform-tests/css/css-pseudo/selection-tex # css/filter-effects imported/w3c/web-platform-tests/css/filter-effects/backdrop-filter-svg-foreignObject.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/filter-effects/backdrop-filter-svg.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/filter-effects/blur-text.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/filter-effects/css-filters-animation-combined-001.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/filter-effects/drop-shadow-currentcolor-dynamic-002.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/filter-effects/effect-reference-feimage-002.html [ ImageOnlyFailure ] @@ -6092,7 +6151,6 @@ imported/w3c/web-platform-tests/trusted-types/should-trusted-type-policy-creatio webkit.org/b/281223 imported/w3c/web-platform-tests/dom/nodes/moveBefore/focus-preserve-render.html [ Skip ] webkit.org/b/281223 imported/w3c/web-platform-tests/dom/nodes/moveBefore/moveBefore-option-recalc-style.html [ Skip ] webkit.org/b/281223 imported/w3c/web-platform-tests/dom/nodes/moveBefore/select-option-optgroup.html [ Skip ] -webkit.org/b/281223 imported/w3c/web-platform-tests/dom/nodes/moveBefore/relevant-mutations.html [ Skip ] # Flaky crash. webkit.org/b/315031 imported/w3c/web-platform-tests/dom/nodes/moveBefore/throws-exception.html [ Skip ] @@ -6298,7 +6356,6 @@ imported/w3c/web-platform-tests/clear-site-data/resource.html [ Skip ] imported/w3c/web-platform-tests/cookies/domain/domain-attribute-idn-host.sub.https.html [ Skip ] imported/w3c/web-platform-tests/css/css-fonts/test_datafont_same_origin.html [ Skip ] imported/w3c/web-platform-tests/css/css-scroll-snap/input/keyboard.html [ Skip ] -imported/w3c/web-platform-tests/css/css-values/dynamic-viewport-units-rule-cache.html [ Skip ] imported/w3c/web-platform-tests/css/css-variables/variable-reference-refresh.html [ Skip ] imported/w3c/web-platform-tests/fetch/api/abort/serviceworker-intercepted.https.html [ Skip ] imported/w3c/web-platform-tests/fetch/corb/preload-image-png-mislabeled-as-html-nosniff.tentative.sub.html [ Skip ] @@ -6632,11 +6689,8 @@ webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape/border-shape-overflow-solid-background.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape/border-shape-overflow.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-img-border.html [ ImageOnlyFailure ] -webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-inset-shadow.html [ ImageOnlyFailure ] -webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-notch-mixed.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-overflow-clip-margin.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-square.html [ ImageOnlyFailure ] -webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-superellipse-negative-100.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-svg-border.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-video-border.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-iframe-border.html [ ImageOnlyFailure ] @@ -6794,11 +6848,7 @@ imported/w3c/web-platform-tests/webtransport/streams-close.https.any.servicework imported/w3c/web-platform-tests/webtransport/streams-close.https.any.sharedworker.html [ Skip ] imported/w3c/web-platform-tests/webtransport/streams-close.https.any.worker.html [ Skip ] -# Backend gaps (datagram flow-control, getStats, close, historical). -imported/w3c/web-platform-tests/webtransport/datagrams.https.any.html [ Skip ] -imported/w3c/web-platform-tests/webtransport/datagrams.https.any.serviceworker.html [ Skip ] -imported/w3c/web-platform-tests/webtransport/datagrams.https.any.sharedworker.html [ Skip ] -imported/w3c/web-platform-tests/webtransport/datagrams.https.any.worker.html [ Skip ] +# Backend gaps (getStats, close, historical). imported/w3c/web-platform-tests/webtransport/stats.https.any.html [ Skip ] imported/w3c/web-platform-tests/webtransport/stats.https.any.serviceworker.html [ Skip ] imported/w3c/web-platform-tests/webtransport/stats.https.any.sharedworker.html [ Skip ] @@ -7995,7 +8045,6 @@ imported/w3c/web-platform-tests/css/CSS2/box-display/root-box-003.xht [ ImageOnl webkit.org/b/302820 imported/w3c/web-platform-tests/css/CSS2/normal-flow/crashtests/block-in-inline-ax-crash.html [ Timeout ] imported/w3c/web-platform-tests/css/CSS2/normal-flow/block-in-inline-float-between-001.xht [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/CSS2/normal-flow/block-in-inline-float-in-layer-001.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/CSS2/normal-flow/canvas-paint-order.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/CSS2/normal-flow/cross-domain-iframe-paint-order.sub.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/CSS2/normal-flow/max-height-separates-margin.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/CSS2/normal-flow/max-width-applies-to-005.xht [ ImageOnlyFailure ] @@ -8009,6 +8058,7 @@ imported/w3c/web-platform-tests/css/CSS2/normal-flow/replaced-intrinsic-001.xht imported/w3c/web-platform-tests/css/CSS2/normal-flow/replaced-intrinsic-002.xht [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/CSS2/normal-flow/resizable-iframe-paint-order.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/CSS2/normal-flow/video-paint-order.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/css/CSS2/normal-flow/video-controls-paint-order.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/intersection-observer/animating.html [ Pass Failure ] imported/w3c/web-platform-tests/intersection-observer/v2/3d-transform-occlusion.html [ Skip ] # timeout @@ -8028,8 +8078,6 @@ webkit.org/b/317545 imported/w3c/web-platform-tests/digital-credentials/webdrive webkit.org/b/306292 http/wpt/identity/formats/ISO18013/origin-binding.https.html [ Skip ] webkit.org/b/317545 imported/w3c/web-platform-tests/digital-credentials/protocol-filtering-openid4vp.https.html [ Skip ] -webkit.org/b/320546 http/wpt/identity/digital-credential-openid4vp-request-parsing.https.html [ Failure ] - # Hits assert in debug, tracked by https://bugs.webkit.org/show_bug.cgi?id=303414 [ Debug ] fullscreen/fullscreen-grid-item-container-type-crash.html [ Skip ] @@ -8176,6 +8224,7 @@ webkit.org/b/317017 imported/w3c/web-platform-tests/largest-contentful-paint/mul # Compression Dictionary Transport is not implemented, so these time out waiting for a dictionary load that never happens. webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/compressed-large-resources-001.tentative.https.html [ Skip ] webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/compressed-large-resources-002.tentative.https.html [ Skip ] +webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/compressed-large-resources-dictionary-hashes.tentative.https.html [ Skip ] webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-clear-site-data-cache.tentative.https.html [ Skip ] webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-clear-site-data-cookies.tentative.https.html [ Skip ] webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-clear-site-data-storage.tentative.https.html [ Skip ] @@ -8378,3 +8427,13 @@ imported/w3c/web-platform-tests/html/canvas/offscreen/manual/draw-element-image/ imported/w3c/web-platform-tests/html/canvas/offscreen/manual/draw-element-image/offscreenCanvas-drawElementImage.tentative.html [ Skip ] imported/w3c/web-platform-tests/html/canvas/offscreen/manual/draw-element-image/scale-worker.tentative.html [ Skip ] imported/w3c/web-platform-tests/html/canvas/offscreen/manual/draw-element-image/video-worker.tentative.html [ Skip ] + +imported/w3c/web-platform-tests/svg/linking/reftests/media-fragment-spatial-percent-no-natural-size.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/svg/linking/reftests/media-fragment-spatial-percent-viewbox-no-size.html [ ImageOnlyFailure ] +imported/w3c/web-platform-tests/svg/path/distance/path-length-css-zoom.tentative.svg [ ImageOnlyFailure ] +[ x86_64 ] imported/w3c/web-platform-tests/svg/struct/reftests/image-symbol.svg [ Skip ] # Flaky + +# Does not generate output properly and do tree dump. +imported/w3c/web-platform-tests/svg/linking/reftests/media-fragment-spatial-percent-zero-size.html [ Skip ] + +imported/w3c/web-platform-tests/scroll-animations/css/scroll-timeline-on-pseudo-002.html [ ImageOnlyFailure ] diff --git a/LayoutTests/accessibility-isolated-tree/TestExpectations b/LayoutTests/accessibility-isolated-tree/TestExpectations index 31126deba62b7..bce4a77384c8d 100644 --- a/LayoutTests/accessibility-isolated-tree/TestExpectations +++ b/LayoutTests/accessibility-isolated-tree/TestExpectations @@ -4,6 +4,7 @@ accessibility/isolated-tree [ Pass ] # # Potentially caused by: https://github.com/WebKit/WebKit/commit/2517a540e6f5a2037c6843102f3a9cb753f2f9f0 accessibility/content-editable-set-inner-text-generates-axvalue-notification.html [ Failure Pass ] +accessibility/isolated-tree/content-editable-set-inner-text-generates-axvalue-notification.html [ Failure Pass ] accessibility/mac/focus-setting-selection-syncronizing-not-clearing.html [ Crash ] # Fails because of (1) stale focus ID for the iFrame and (2) iFrame #2 being ignored. accessibility/mac/frame-with-title.html [ Failure ] @@ -26,6 +27,7 @@ accessibility/mac/style-range.html [ Pass ] # Flaky: https://bugs.webkit.org/show_bug.cgi?id=290946 accessibility/datetime/input-date-field-labels-and-value-changes.html [ Pass Failure ] +accessibility/isolated-tree/datetime/input-date-field-labels-and-value-changes.html [ Pass Failure ] # With ENABLE(ACCESSIBILITY_LOCAL_FRAME), child frames have their own AXObjectCache. # The child frame's isolated tree is created lazily, but the parent frame's tree @@ -43,6 +45,7 @@ accessibility/mac/search-text-with-image-hang.html [ Failure ] # Failures introduced by ENABLE(AX_THREAD_TEXT_APIS). accessibility/content-editable-as-textarea.html [ Failure ] +accessibility/isolated-tree/content-editable-as-textarea.html [ Failure ] accessibility/mac/attributed-string-for-text-marker-range-using-webarea.html [ Failure ] accessibility/mac/attributed-string-includes-misspelled-with-selection.html [ Failure ] accessibility/mac/attributed-string/attributed-string-does-not-includes-misspelled-for-non-editable.html [ Failure ] diff --git a/LayoutTests/accessibility/ARIA-reflection.html b/LayoutTests/accessibility/ARIA-reflection.html index 03ab33d77e1f6..61e7e441f1c76 100644 --- a/LayoutTests/accessibility/ARIA-reflection.html +++ b/LayoutTests/accessibility/ARIA-reflection.html @@ -1,7 +1,7 @@ - +
@@ -127,7 +127,6 @@ testRole(); - // There are 46 ARIA attributes in total. var count = 0; for (var propertyName in element) { if (propertyName.startsWith("aria") && !isNonReflectionMethod(propertyName)) { @@ -146,6 +145,5 @@ - diff --git a/LayoutTests/accessibility/activation-of-input-field-inside-other-element.html b/LayoutTests/accessibility/activation-of-input-field-inside-other-element.html index a154e4255f4de..d94a89e178cfb 100644 --- a/LayoutTests/accessibility/activation-of-input-field-inside-other-element.html +++ b/LayoutTests/accessibility/activation-of-input-field-inside-other-element.html @@ -1,7 +1,7 @@ - + @@ -28,7 +28,6 @@ element.press(); } - diff --git a/LayoutTests/accessibility/adjacent-continuations-cause-assertion-failure.html b/LayoutTests/accessibility/adjacent-continuations-cause-assertion-failure.html index ca4019aa7093b..ad721a493d56c 100644 --- a/LayoutTests/accessibility/adjacent-continuations-cause-assertion-failure.html +++ b/LayoutTests/accessibility/adjacent-continuations-cause-assertion-failure.html @@ -1,7 +1,7 @@ - + @@ -27,6 +27,5 @@ - diff --git a/LayoutTests/accessibility/alt-tag-on-image-with-nonimage-role.html b/LayoutTests/accessibility/alt-tag-on-image-with-nonimage-role.html index 71506f5755804..f9fbb8aeb883d 100644 --- a/LayoutTests/accessibility/alt-tag-on-image-with-nonimage-role.html +++ b/LayoutTests/accessibility/alt-tag-on-image-with-nonimage-role.html @@ -1,7 +1,7 @@ - + @@ -33,6 +33,5 @@ - diff --git a/LayoutTests/accessibility/aria-busy.html b/LayoutTests/accessibility/aria-busy.html index 2ff73b02486c7..3406ba77e4cae 100644 --- a/LayoutTests/accessibility/aria-busy.html +++ b/LayoutTests/accessibility/aria-busy.html @@ -1,7 +1,7 @@ - + @@ -19,6 +19,5 @@ debug(platformValueForW3CName(listbox) + " is busy: " + listbox.boolAttributeValue("AXElementBusy")) } - diff --git a/LayoutTests/accessibility/aria-cellspans-with-native-cellspans.html b/LayoutTests/accessibility/aria-cellspans-with-native-cellspans.html index 2a32da502e268..5859c741c3065 100644 --- a/LayoutTests/accessibility/aria-cellspans-with-native-cellspans.html +++ b/LayoutTests/accessibility/aria-cellspans-with-native-cellspans.html @@ -1,7 +1,7 @@ - +
@@ -53,6 +53,5 @@ } - diff --git a/LayoutTests/accessibility/aria-checkbox-checked.html b/LayoutTests/accessibility/aria-checkbox-checked.html index 2084cb4c484ea..8a7bb63f19002 100644 --- a/LayoutTests/accessibility/aria-checkbox-checked.html +++ b/LayoutTests/accessibility/aria-checkbox-checked.html @@ -1,7 +1,7 @@ - + @@ -42,6 +42,5 @@ - diff --git a/LayoutTests/accessibility/aria-checkbox-text.html b/LayoutTests/accessibility/aria-checkbox-text.html index e648b59a60e86..d1497ab1e31b9 100644 --- a/LayoutTests/accessibility/aria-checkbox-text.html +++ b/LayoutTests/accessibility/aria-checkbox-text.html @@ -1,7 +1,7 @@ - + @@ -31,6 +31,5 @@ - diff --git a/LayoutTests/accessibility/aria-current-global-attribute.html b/LayoutTests/accessibility/aria-current-global-attribute.html index 22abf924623e8..ef0a4aa52de41 100644 --- a/LayoutTests/accessibility/aria-current-global-attribute.html +++ b/LayoutTests/accessibility/aria-current-global-attribute.html @@ -1,7 +1,7 @@ - + @@ -33,6 +33,5 @@ - \ No newline at end of file diff --git a/LayoutTests/accessibility/aria-current-state-changed-notification.html b/LayoutTests/accessibility/aria-current-state-changed-notification.html index a3802cfee5a27..ed23bdcc7d779 100644 --- a/LayoutTests/accessibility/aria-current-state-changed-notification.html +++ b/LayoutTests/accessibility/aria-current-state-changed-notification.html @@ -1,6 +1,6 @@ - + @@ -66,6 +66,5 @@ }, 0); } - diff --git a/LayoutTests/accessibility/aria-current.html b/LayoutTests/accessibility/aria-current.html index 8f51c48ea25a2..3a3af935fb509 100644 --- a/LayoutTests/accessibility/aria-current.html +++ b/LayoutTests/accessibility/aria-current.html @@ -1,7 +1,7 @@ - + @@ -51,6 +51,5 @@ debug(output) } - diff --git a/LayoutTests/accessibility/aria-disabled-propagated-to-children.html b/LayoutTests/accessibility/aria-disabled-propagated-to-children.html index 745a35ea6f12e..9f302aa4f81f8 100644 --- a/LayoutTests/accessibility/aria-disabled-propagated-to-children.html +++ b/LayoutTests/accessibility/aria-disabled-propagated-to-children.html @@ -1,7 +1,7 @@ - + @@ -39,6 +39,5 @@ - diff --git a/LayoutTests/accessibility/aria-disabled.html b/LayoutTests/accessibility/aria-disabled.html index ce78fbdcd601f..87d017d792736 100644 --- a/LayoutTests/accessibility/aria-disabled.html +++ b/LayoutTests/accessibility/aria-disabled.html @@ -1,7 +1,7 @@ - + @@ -37,6 +37,5 @@ - diff --git a/LayoutTests/accessibility/aria-grid-column-span.html b/LayoutTests/accessibility/aria-grid-column-span.html index 173e2fde7aaca..3ef6d7e7b654a 100644 --- a/LayoutTests/accessibility/aria-grid-column-span.html +++ b/LayoutTests/accessibility/aria-grid-column-span.html @@ -1,7 +1,7 @@ - + @@ -33,6 +33,5 @@ - diff --git a/LayoutTests/accessibility/aria-table-hierarchy.html b/LayoutTests/accessibility/aria-table-hierarchy.html index 93a1dc3c4f8c2..a22a802f47926 100644 --- a/LayoutTests/accessibility/aria-table-hierarchy.html +++ b/LayoutTests/accessibility/aria-table-hierarchy.html @@ -1,7 +1,7 @@ - + - diff --git a/LayoutTests/accessibility/aria-table-with-presentational-elements.html b/LayoutTests/accessibility/aria-table-with-presentational-elements.html index 55ad54480df1e..6e1e39736ba49 100644 --- a/LayoutTests/accessibility/aria-table-with-presentational-elements.html +++ b/LayoutTests/accessibility/aria-table-with-presentational-elements.html @@ -1,7 +1,7 @@ - + @@ -38,6 +38,5 @@ } - diff --git a/LayoutTests/accessibility/aria-text-role.html b/LayoutTests/accessibility/aria-text-role.html index e39a7a63fd6a1..ea4688d42be47 100644 --- a/LayoutTests/accessibility/aria-text-role.html +++ b/LayoutTests/accessibility/aria-text-role.html @@ -1,7 +1,7 @@ - + @@ -34,6 +34,5 @@ - diff --git a/LayoutTests/accessibility/aria-used-on-image-maps.html b/LayoutTests/accessibility/aria-used-on-image-maps.html index ceb870e4fb63b..05b52196cadcf 100644 --- a/LayoutTests/accessibility/aria-used-on-image-maps.html +++ b/LayoutTests/accessibility/aria-used-on-image-maps.html @@ -1,7 +1,7 @@ - + @@ -29,6 +29,5 @@ - diff --git a/LayoutTests/accessibility/attachment-element.html b/LayoutTests/accessibility/attachment-element.html index 1a975f5e6e0f9..2570ba3ae745e 100644 --- a/LayoutTests/accessibility/attachment-element.html +++ b/LayoutTests/accessibility/attachment-element.html @@ -7,7 +7,7 @@

- + - diff --git a/LayoutTests/accessibility/axpress-on-aria-button.html b/LayoutTests/accessibility/axpress-on-aria-button.html index 3b1c64314a297..d80faed9bd020 100644 --- a/LayoutTests/accessibility/axpress-on-aria-button.html +++ b/LayoutTests/accessibility/axpress-on-aria-button.html @@ -1,7 +1,7 @@ - + @@ -39,6 +39,5 @@ } - diff --git a/LayoutTests/accessibility/braille-label-role.html b/LayoutTests/accessibility/braille-label-role.html index dcb3e1d96d792..b2945d2638456 100644 --- a/LayoutTests/accessibility/braille-label-role.html +++ b/LayoutTests/accessibility/braille-label-role.html @@ -1,7 +1,7 @@ - + @@ -19,6 +19,5 @@ shouldBe("label.stringAttributeValue('AXBrailleRoleDescription')", "'braille role'"); } - diff --git a/LayoutTests/accessibility/button-title-uses-inner-img-alt.html b/LayoutTests/accessibility/button-title-uses-inner-img-alt.html index 014f1d17a8149..174108c8644e3 100644 --- a/LayoutTests/accessibility/button-title-uses-inner-img-alt.html +++ b/LayoutTests/accessibility/button-title-uses-inner-img-alt.html @@ -1,7 +1,7 @@ - + @@ -32,6 +32,5 @@ - diff --git a/LayoutTests/accessibility/canvas-fallback-content-2.html b/LayoutTests/accessibility/canvas-fallback-content-2.html index c5018ffdb04c8..a325ae7e4164e 100644 --- a/LayoutTests/accessibility/canvas-fallback-content-2.html +++ b/LayoutTests/accessibility/canvas-fallback-content-2.html @@ -1,7 +1,7 @@ - + @@ -105,6 +105,5 @@
Heading
check("aria-link1", "aria-link2"); } - diff --git a/LayoutTests/accessibility/combobox/aria-combobox-hierarchy.html b/LayoutTests/accessibility/combobox/aria-combobox-hierarchy.html index 57a800f10ebd8..f27ba4ed46f4c 100644 --- a/LayoutTests/accessibility/combobox/aria-combobox-hierarchy.html +++ b/LayoutTests/accessibility/combobox/aria-combobox-hierarchy.html @@ -1,7 +1,7 @@ - + @@ -25,6 +25,5 @@ document.getElementById("content").style.visibility = "hidden"; } - diff --git a/LayoutTests/accessibility/combobox/aria-combobox.html b/LayoutTests/accessibility/combobox/aria-combobox.html index 74c825b67037b..35e34b28b8b1d 100644 --- a/LayoutTests/accessibility/combobox/aria-combobox.html +++ b/LayoutTests/accessibility/combobox/aria-combobox.html @@ -1,7 +1,7 @@ - + @@ -40,6 +40,5 @@ } - diff --git a/LayoutTests/accessibility/combobox/mac/combobox-value.html b/LayoutTests/accessibility/combobox/mac/combobox-value.html index 856dbd367f242..5d8c4849fff8d 100644 --- a/LayoutTests/accessibility/combobox/mac/combobox-value.html +++ b/LayoutTests/accessibility/combobox/mac/combobox-value.html @@ -1,7 +1,7 @@ - + @@ -22,6 +22,5 @@ } - diff --git a/LayoutTests/accessibility/container-node-delete-causes-crash.html b/LayoutTests/accessibility/container-node-delete-causes-crash.html index e9b39ca059eba..0ea208fcfb67b 100644 --- a/LayoutTests/accessibility/container-node-delete-causes-crash.html +++ b/LayoutTests/accessibility/container-node-delete-causes-crash.html @@ -1,7 +1,7 @@ - +
@@ -23,6 +23,5 @@ document.getElementsByTagName('use')[0].setAttribute('xlink:href', ''); - diff --git a/LayoutTests/accessibility/content-changed-notification-causes-crash.html b/LayoutTests/accessibility/content-changed-notification-causes-crash.html index 0b536ea0f6543..a5923d8d47fee 100644 --- a/LayoutTests/accessibility/content-changed-notification-causes-crash.html +++ b/LayoutTests/accessibility/content-changed-notification-causes-crash.html @@ -1,7 +1,7 @@ - + @@ -33,6 +33,5 @@ } - diff --git a/LayoutTests/accessibility/content-editable-set-inner-text-generates-axvalue-notification.html b/LayoutTests/accessibility/content-editable-set-inner-text-generates-axvalue-notification.html index 52b4f1f3f4ee8..232e7ba53c777 100644 --- a/LayoutTests/accessibility/content-editable-set-inner-text-generates-axvalue-notification.html +++ b/LayoutTests/accessibility/content-editable-set-inner-text-generates-axvalue-notification.html @@ -1,7 +1,7 @@ - + @@ -54,6 +54,5 @@ - diff --git a/LayoutTests/accessibility/contenteditable-hidden-div.html b/LayoutTests/accessibility/contenteditable-hidden-div.html index 9149bbe044b72..941719b22377a 100644 --- a/LayoutTests/accessibility/contenteditable-hidden-div.html +++ b/LayoutTests/accessibility/contenteditable-hidden-div.html @@ -1,7 +1,7 @@ - + @@ -29,6 +29,5 @@

test

- diff --git a/LayoutTests/accessibility/contenteditable-table-check-causes-crash.html b/LayoutTests/accessibility/contenteditable-table-check-causes-crash.html index 4e06cc58f17ef..07f9bf4155519 100644 --- a/LayoutTests/accessibility/contenteditable-table-check-causes-crash.html +++ b/LayoutTests/accessibility/contenteditable-table-check-causes-crash.html @@ -1,7 +1,7 @@ - + @@ -24,6 +24,5 @@

- diff --git a/LayoutTests/accessibility/corresponding-control-deleted-crash-expected.txt b/LayoutTests/accessibility/corresponding-control-deleted-crash-expected.txt deleted file mode 100644 index c02647ba0ee1a..0000000000000 --- a/LayoutTests/accessibility/corresponding-control-deleted-crash-expected.txt +++ /dev/null @@ -1,9 +0,0 @@ -Make sure that a debug assert is not triggered when a call to RenderBlock::deleteLineBoxTree calls AccessibilityRenderObject::accessibilityIsIgnored which may require the AXObject for a node that is being deleted. - -On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". - - -PASS successfullyParsed is true - -TEST COMPLETE - diff --git a/LayoutTests/accessibility/crash-determining-aria-role-when-label-present.html b/LayoutTests/accessibility/crash-determining-aria-role-when-label-present.html index c1f9bb57fb497..3d05d1f2aa01b 100644 --- a/LayoutTests/accessibility/crash-determining-aria-role-when-label-present.html +++ b/LayoutTests/accessibility/crash-determining-aria-role-when-label-present.html @@ -1,7 +1,7 @@ - + @@ -25,6 +25,5 @@ - diff --git a/LayoutTests/accessibility/crash-with-noelement-selectbox.html b/LayoutTests/accessibility/crash-with-noelement-selectbox.html index e7915e186f4f9..54909c6d3dbf8 100644 --- a/LayoutTests/accessibility/crash-with-noelement-selectbox.html +++ b/LayoutTests/accessibility/crash-with-noelement-selectbox.html @@ -1,7 +1,7 @@ - + @@ -24,6 +24,5 @@ - diff --git a/LayoutTests/accessibility/crashing-a-tag-in-map.html b/LayoutTests/accessibility/crashing-a-tag-in-map.html index 64198fea1d117..1e707e4dd0e63 100644 --- a/LayoutTests/accessibility/crashing-a-tag-in-map.html +++ b/LayoutTests/accessibility/crashing-a-tag-in-map.html @@ -1,7 +1,7 @@ - + @@ -33,6 +33,5 @@ - diff --git a/LayoutTests/accessibility/css-content-attribute.html b/LayoutTests/accessibility/css-content-attribute.html index 6d448f885c5be..043ed25a81960 100644 --- a/LayoutTests/accessibility/css-content-attribute.html +++ b/LayoutTests/accessibility/css-content-attribute.html @@ -17,7 +17,7 @@ } - + - + @@ -28,6 +28,5 @@ - diff --git a/LayoutTests/accessibility/form-control-value-settable.html b/LayoutTests/accessibility/form-control-value-settable.html index b6e7cc8bde2a0..46ef89d1b8a5e 100644 --- a/LayoutTests/accessibility/form-control-value-settable.html +++ b/LayoutTests/accessibility/form-control-value-settable.html @@ -2,7 +2,7 @@ - +
@@ -69,6 +69,5 @@ - diff --git a/LayoutTests/accessibility/generated-content-with-display-table-crash.html b/LayoutTests/accessibility/generated-content-with-display-table-crash.html index eea8661e07453..b09e6ff4c7122 100644 --- a/LayoutTests/accessibility/generated-content-with-display-table-crash.html +++ b/LayoutTests/accessibility/generated-content-with-display-table-crash.html @@ -1,7 +1,7 @@ - + + +PASS if no crash. + + +
+
+ aaa + +
+
+ diff --git a/LayoutTests/accessibility/isolated-tree/accessibility-crash-with-dynamic-inline-content-expected.txt b/LayoutTests/accessibility/isolated-tree/accessibility-crash-with-dynamic-inline-content-expected.txt new file mode 100644 index 0000000000000..eb7705ef1ce12 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/accessibility-crash-with-dynamic-inline-content-expected.txt @@ -0,0 +1,3 @@ +PASS if no crash or assert. foo +foobar + diff --git a/LayoutTests/accessibility/isolated-tree/accessibility-crash-with-dynamic-inline-content.html b/LayoutTests/accessibility/isolated-tree/accessibility-crash-with-dynamic-inline-content.html new file mode 100644 index 0000000000000..6443225239e9d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/accessibility-crash-with-dynamic-inline-content.html @@ -0,0 +1,18 @@ + + + +This tests accessibility with dynamic inline content. + + +PASS if no crash or assert. +foo
foobar
+ diff --git a/LayoutTests/accessibility/isolated-tree/accessibility-node-memory-management-expected.txt b/LayoutTests/accessibility/isolated-tree/accessibility-node-memory-management-expected.txt new file mode 100644 index 0000000000000..9e5f577cf08ae --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/accessibility-node-memory-management-expected.txt @@ -0,0 +1,8 @@ +This test makes sure that AccessibilityNodeObjects are properly detached when the node they point to is deleted. + +PASS: axButton.role === detachedRole + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/accessibility-node-memory-management.html b/LayoutTests/accessibility/isolated-tree/accessibility-node-memory-management.html new file mode 100644 index 0000000000000..9d183d4d1a214 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/accessibility-node-memory-management.html @@ -0,0 +1,75 @@ + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/accessibility-node-reparent-expected.txt b/LayoutTests/accessibility/isolated-tree/accessibility-node-reparent-expected.txt new file mode 100644 index 0000000000000..522e20bd7724c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/accessibility-node-reparent-expected.txt @@ -0,0 +1,12 @@ +This test makes sure that AccessibilityNodeObjects are properly detached when the node they point to is reparented to a location that allows them to have a renderer. + +PASS: expectedButtonRole !== expectedDetachedRole === true +PASS: canvasButtonRole === expectedButtonRole +PASS: detachedCanvasButtonRole === expectedDetachedRole +PASS: reparentedButtonRole === expectedButtonRole + +PASS successfullyParsed is true + +TEST COMPLETE + + diff --git a/LayoutTests/accessibility/isolated-tree/accessibility-node-reparent.html b/LayoutTests/accessibility/isolated-tree/accessibility-node-reparent.html new file mode 100644 index 0000000000000..d65a5cc17ac49 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/accessibility-node-reparent.html @@ -0,0 +1,78 @@ + + + + + + + + +
+ + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/accessibility-object-detached-expected.txt b/LayoutTests/accessibility/isolated-tree/accessibility-object-detached-expected.txt new file mode 100644 index 0000000000000..e9238274b8d78 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/accessibility-object-detached-expected.txt @@ -0,0 +1,8 @@ +This test makes sure that AccessibilityObjects are detached when the node they point to is detached. + +PASS: expectedButtonRole != expectedDetachedRole === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/accessibility-object-detached.html b/LayoutTests/accessibility/isolated-tree/accessibility-object-detached.html new file mode 100644 index 0000000000000..838efe08011ab --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/accessibility-object-detached.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/activation-of-input-field-inside-other-element-expected.txt b/LayoutTests/accessibility/isolated-tree/activation-of-input-field-inside-other-element-expected.txt new file mode 100644 index 0000000000000..5edd2fa801877 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/activation-of-input-field-inside-other-element-expected.txt @@ -0,0 +1,11 @@ + +This test checks whether a simulated click will activate a combobox that contains a text field. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +Combo box element WAS clicked with accessibility +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/activation-of-input-field-inside-other-element.html b/LayoutTests/accessibility/isolated-tree/activation-of-input-field-inside-other-element.html new file mode 100644 index 0000000000000..9dbeac028887a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/activation-of-input-field-inside-other-element.html @@ -0,0 +1,33 @@ + + + + + + + + + +

+
+ + + + + diff --git a/LayoutTests/accessibility/isolated-tree/active-descendant-changes-result-in-focus-changes-expected.txt b/LayoutTests/accessibility/isolated-tree/active-descendant-changes-result-in-focus-changes-expected.txt new file mode 100644 index 0000000000000..8daa2f100a15e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/active-descendant-changes-result-in-focus-changes-expected.txt @@ -0,0 +1,11 @@ +Tests that active descendant changes result in focus changes. + +PASS: accessibilityController.focusedElement.domIdentifier === 'listbox' +PASS: accessibilityController.focusedElement.domIdentifier === 'item1' +PASS: accessibilityController.focusedElement.domIdentifier === 'item2' +PASS: accessibilityController.focusedElement.domIdentifier === 'listbox' + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/active-descendant-changes-result-in-focus-changes.html b/LayoutTests/accessibility/isolated-tree/active-descendant-changes-result-in-focus-changes.html new file mode 100644 index 0000000000000..ed5f6cd9041bc --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/active-descendant-changes-result-in-focus-changes.html @@ -0,0 +1,39 @@ + + + + + + + + +
+
+
+
+ + + + diff --git a/LayoutTests/accessibility/isolated-tree/add-children-pseudo-element-expected.txt b/LayoutTests/accessibility/isolated-tree/add-children-pseudo-element-expected.txt new file mode 100644 index 0000000000000..1168ffdf87147 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/add-children-pseudo-element-expected.txt @@ -0,0 +1,11 @@ +Make sure that we are updating the render block flow element's children correctly. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS element.childrenCount is 3 +PASS element.childrenCount === 2 +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/add-children-pseudo-element.html b/LayoutTests/accessibility/isolated-tree/add-children-pseudo-element.html new file mode 100644 index 0000000000000..cfafc6697caaf --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/add-children-pseudo-element.html @@ -0,0 +1,80 @@ + + + + + + + + + +
+
+ Language + +
+ +
+ Email + +
+
+ + + + + diff --git a/LayoutTests/accessibility/isolated-tree/adjacent-continuations-cause-assertion-failure-expected.txt b/LayoutTests/accessibility/isolated-tree/adjacent-continuations-cause-assertion-failure-expected.txt new file mode 100644 index 0000000000000..5dd11d0a4ee83 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/adjacent-continuations-cause-assertion-failure-expected.txt @@ -0,0 +1,16 @@ +x +y +z +End of test +Make sure that a debug assert is not triggered when constructing the accessibility tree for this page. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +AXRole: AXWebArea + AXRole: AXSection AXValue: y + AXRole: AXSection AXValue: End of test +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/adjacent-continuations-cause-assertion-failure.html b/LayoutTests/accessibility/isolated-tree/adjacent-continuations-cause-assertion-failure.html new file mode 100644 index 0000000000000..6aa8695172e13 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/adjacent-continuations-cause-assertion-failure.html @@ -0,0 +1,31 @@ + + + + + + + + +
x
y
z
+ +
End of test
+ +

+

+
+ + + + + diff --git a/LayoutTests/accessibility/isolated-tree/alt-tag-on-image-with-nonimage-role-expected.txt b/LayoutTests/accessibility/isolated-tree/alt-tag-on-image-with-nonimage-role-expected.txt new file mode 100644 index 0000000000000..aeff3846f0649 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/alt-tag-on-image-with-nonimage-role-expected.txt @@ -0,0 +1,13 @@ + +This tests the alternative text calculation when setting a role on an img with an alt attribute. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS text.stringValue is 'AXValue: TEST1' +PASS platformValueForW3CName(group) is "TEST2" +PASS platformValueForW3CName(button) is "TEST3" +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/alt-tag-on-image-with-nonimage-role.html b/LayoutTests/accessibility/isolated-tree/alt-tag-on-image-with-nonimage-role.html new file mode 100644 index 0000000000000..076b0b73fbbaa --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/alt-tag-on-image-with-nonimage-role.html @@ -0,0 +1,37 @@ + + + + + + + + +TEST1 + +TEST2 + +TEST3 + +

+
+ + + + + diff --git a/LayoutTests/accessibility/isolated-tree/anchor-linked-anonymous-block-crash-expected.txt b/LayoutTests/accessibility/isolated-tree/anchor-linked-anonymous-block-crash-expected.txt new file mode 100644 index 0000000000000..1042c7678e4a8 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/anchor-linked-anonymous-block-crash-expected.txt @@ -0,0 +1,2 @@ +Test passes if it does not crash. + diff --git a/LayoutTests/accessibility/isolated-tree/anchor-linked-anonymous-block-crash.html b/LayoutTests/accessibility/isolated-tree/anchor-linked-anonymous-block-crash.html new file mode 100644 index 0000000000000..6394b4b3c55ea --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/anchor-linked-anonymous-block-crash.html @@ -0,0 +1,9 @@ + + +Test passes if it does not crash. + +
+ diff --git a/LayoutTests/accessibility/isolated-tree/anchor-with-click-handler-is-link-expected.txt b/LayoutTests/accessibility/isolated-tree/anchor-with-click-handler-is-link-expected.txt new file mode 100644 index 0000000000000..f0ecffba521c9 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/anchor-with-click-handler-is-link-expected.txt @@ -0,0 +1,24 @@ +This test ensures an anchor with a click handler but no href is exposed as a link. + +PASS: isExposedAsLink('anchor-with-onclick') === true +PASS: isExposedAsLink('anchor-without-handler') === false + +Dynamically added anchor with onclick is a link: +PASS: isExposedAsLink('dynamic-anchor-with-onclick') === true + +Adding a click handler to a hrefless anchor turns it into a link: +PASS: isExposedAsLink('click-toggle-anchor') === true + +Removing the only click handler reverts it to not being a link: +PASS: isExposedAsLink('click-toggle-anchor') === false + +Removing the href from a link with no click handler stops it being a link: +PASS: isExposedAsLink('href-toggle-anchor') === false + +Restoring the href makes it a link again: +PASS: isExposedAsLink('href-toggle-anchor') === true + +PASS successfullyParsed is true + +TEST COMPLETE +Anchor with onclick Anchor without click handler or href Anchor that gains then loses a click handler Anchor that loses then regains its href Dynamically added anchor with onclick diff --git a/LayoutTests/accessibility/isolated-tree/anchor-with-click-handler-is-link.html b/LayoutTests/accessibility/isolated-tree/anchor-with-click-handler-is-link.html new file mode 100644 index 0000000000000..72f03c26c3966 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/anchor-with-click-handler-is-link.html @@ -0,0 +1,62 @@ + + + + + + + + +Anchor with onclick +Anchor without click handler or href +Anchor that gains then loses a click handler +Anchor that loses then regains its href + + + + diff --git a/LayoutTests/accessibility/isolated-tree/animated-dropdown-expected.txt b/LayoutTests/accessibility/isolated-tree/animated-dropdown-expected.txt new file mode 100644 index 0000000000000..edb1728da09b5 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/animated-dropdown-expected.txt @@ -0,0 +1,47 @@ +This test ensures the accessibility tree is correct after an animated dropdown is opened via button press. + +First traversal: + +{#main AXRole: AXGroup} + +{AXRole: AXGroup} + +{#button-one AXRole: AXButton} + +{AXRole: AXGroup} + +{AXRole: AXButton} + +{AXRole: AXGroup} + +{AXRole: AXLink} + +{AXRole: AXStaticText AXValue: apple.com} + +Second traversal: + +PASS: secondTraversal.includes('This text begins as hidden') === true +PASS: !!accessibilityController.accessibleElementById('button-one').parentElement() === true + +{#main AXRole: AXGroup} + +{#button-one AXRole: AXButton} + +{AXRole: AXGroup} + +{AXRole: AXStaticText AXValue: This text begins as hidden} + +{AXRole: AXGroup} + +{AXRole: AXButton} + +{AXRole: AXGroup} + +{AXRole: AXLink} + +{AXRole: AXStaticText AXValue: apple.com} + +PASS successfullyParsed is true + +TEST COMPLETE +apple.com diff --git a/LayoutTests/accessibility/isolated-tree/animated-dropdown.html b/LayoutTests/accessibility/isolated-tree/animated-dropdown.html new file mode 100644 index 0000000000000..5e5d679602f78 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/animated-dropdown.html @@ -0,0 +1,56 @@ + + + + + + + + +
+
+ +
This text begins as hidden
+
+
+
+ + + + diff --git a/LayoutTests/accessibility/isolated-tree/announcement-notification-expected.txt b/LayoutTests/accessibility/isolated-tree/announcement-notification-expected.txt new file mode 100644 index 0000000000000..67a89dbd0f2b0 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/announcement-notification-expected.txt @@ -0,0 +1,7 @@ +Tests announcement notifications. + +Received announcement request. +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/announcement-notification.html b/LayoutTests/accessibility/isolated-tree/announcement-notification.html new file mode 100644 index 0000000000000..2f56135778c5f --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/announcement-notification.html @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/anonymous-render-block-in-continuation-causes-crash-expected.txt b/LayoutTests/accessibility/isolated-tree/anonymous-render-block-in-continuation-causes-crash-expected.txt new file mode 100644 index 0000000000000..b2052df144881 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/anonymous-render-block-in-continuation-causes-crash-expected.txt @@ -0,0 +1,13 @@ +x +y +z +End of test. +This tests that having an anonymous render block in a continuation doesn't cause a crash when walking the accessibility tree. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/anonymous-render-block-in-continuation-causes-crash.html b/LayoutTests/accessibility/isolated-tree/anonymous-render-block-in-continuation-causes-crash.html new file mode 100644 index 0000000000000..c05d22bd9ecd5 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/anonymous-render-block-in-continuation-causes-crash.html @@ -0,0 +1,40 @@ + + + + + + + + +
  • x
    • y
    z
  • + +End of test. + +

    +
    + + + diff --git a/LayoutTests/accessibility/isolated-tree/appearance-none-meter-and-progress-elements-expected.txt b/LayoutTests/accessibility/isolated-tree/appearance-none-meter-and-progress-elements-expected.txt new file mode 100644 index 0000000000000..cae3b061a3dcf --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/appearance-none-meter-and-progress-elements-expected.txt @@ -0,0 +1,11 @@ +This test ensures that and elements with appearance:none are accessible. + +PASS: progress.role === 'AXRole: AXProgressIndicator' +PASS: progress.intValue === 4 +PASS: meter.role === 'AXRole: AXLevelIndicator' +PASS: meter.valueDescription.includes('4 of 7') === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/appearance-none-meter-and-progress-elements.html b/LayoutTests/accessibility/isolated-tree/appearance-none-meter-and-progress-elements.html new file mode 100644 index 0000000000000..e92d7a28958ce --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/appearance-none-meter-and-progress-elements.html @@ -0,0 +1,39 @@ + + + + + + + + + + + 4 of 7 + + + + 4 of 7 + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/area-element-bounding-box-expected.txt b/LayoutTests/accessibility/isolated-tree/area-element-bounding-box-expected.txt new file mode 100644 index 0000000000000..9d02a2aa3a1b4 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/area-element-bounding-box-expected.txt @@ -0,0 +1,9 @@ +This test ensures we compute a non-zero size for area elements. + +PASS: link.width > 100 === true +PASS: link.height > 50 === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/area-element-bounding-box.html b/LayoutTests/accessibility/isolated-tree/area-element-bounding-box.html new file mode 100644 index 0000000000000..055e8b569f37a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/area-element-bounding-box.html @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-actions-expected.txt new file mode 100644 index 0000000000000..7659d36908d02 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-expected.txt @@ -0,0 +1,26 @@ +This test verifies the exposure of aria-actions. + +Verifying action elements are exposed in DOM order: +PASS: action1.domIdentifier === 'action1' +PASS: action2.domIdentifier === 'action2' +PASS: source.ariaActionsElementAtIndex(2) === null + +Invoking custom actions: +PASS: source.invokeCustomActionAtIndex(0) === true +PASS: clickedActions.length === 1 +PASS: clickedActions[0] === 'action1' +PASS: source.invokeCustomActionAtIndex(1) === true +PASS: clickedActions.length === 2 +PASS: clickedActions[1] === 'action2' + +Invoking out-of-bounds action returns false: +PASS: source.invokeCustomActionAtIndex(99) === false + +Testing dynamic update - removing an action: +PASS: source.ariaActionsElementAtIndex(0)?.domIdentifier === 'action2' +PASS: source.ariaActionsElementAtIndex(1) === null + +PASS successfullyParsed is true + +TEST COMPLETE +Source Delete Edit diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-focus-author-override-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-author-override-expected.txt new file mode 100644 index 0000000000000..f0d35f5b54413 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-author-override-expected.txt @@ -0,0 +1,17 @@ +This test verifies that when an aria-actions custom action's handler moves focus to some other element, that focus change is respected (not bounced back to the origin) and is surfaced to assistive technology. + +Invoking the custom action runs the handler, which moves focus elsewhere: +PASS: source.invokeCustomActionAtIndex(0) === true +PASS: actionClicked === true + +Focus lands on the element the handler chose, not the origin or the action target: +PASS: document.activeElement.id === 'elsewhere' +PASS: accessibilityController.focusedElement.domIdentifier === 'elsewhere' + +That focus change is surfaced to assistive technology: +PASS: focusNotificationCount > 0 === true + +PASS successfullyParsed is true + +TEST COMPLETE +Source Delete Elsewhere diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-focus-author-override.html b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-author-override.html new file mode 100644 index 0000000000000..59ed57537b8b2 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-author-override.html @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-focus-in-other-frame-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-in-other-frame-expected.txt new file mode 100644 index 0000000000000..782aacbbabdaa --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-in-other-frame-expected.txt @@ -0,0 +1,15 @@ +This test verifies that when page focus is in a different frame than an aria-actions host, invoking one of its actions falls back to a plain activation (focus may move to the target) rather than losing the focus that was in the other frame. + +Focus starts in a different frame: +PASS: document.activeElement.id === 'frame' +PASS: frame.contentDocument.activeElement.id === 'inner' + +Invoking the action falls back to a plain press, so focus is not lost from the other frame: +PASS: source.invokeCustomActionAtIndex(0) === true +PASS: actionClicked === true +PASS: document.activeElement.id === 'action' + +PASS successfullyParsed is true + +TEST COMPLETE +Source Delete diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-focus-in-other-frame.html b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-in-other-frame.html new file mode 100644 index 0000000000000..a2ed8385e4129 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-in-other-frame.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-focus-no-origin-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-no-origin-expected.txt new file mode 100644 index 0000000000000..1e05e0c62a0b8 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-no-origin-expected.txt @@ -0,0 +1,19 @@ +This test verifies that invoking an aria-actions custom action while nothing is focused leaves focus cleared, rather than moving it to the (focusable) action target, and that the transient focus movement is not surfaced to assistive technology. + +Nothing is focused before invoking the action: +PASS: document.activeElement === document.body === true + +Invoking the custom action clicks the focusable action target: +PASS: source.invokeCustomActionAtIndex(0) === true +PASS: actionClicked === true + +Focus is left cleared, rather than moving to the action target: +PASS: document.activeElement === document.body === true + +No accessibility focus notification was surfaced for the transient focus movement: +PASS: focusNotificationCount === 0 + +PASS successfullyParsed is true + +TEST COMPLETE +Source Delete diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-focus-no-origin.html b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-no-origin.html new file mode 100644 index 0000000000000..0d09182b9ab1a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-no-origin.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-focus-restore-fallback-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-restore-fallback-expected.txt new file mode 100644 index 0000000000000..80361cd27f560 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-restore-fallback-expected.txt @@ -0,0 +1,17 @@ +This test verifies that if an aria-actions custom action's handler removes the focused origin (so focus can't be restored to it), focus is left on the action target and surfaced to assistive technology, rather than assistive technology being left pointed at the removed origin. + +Invoking the custom action runs the handler, which removes the focused origin: +PASS: source.invokeCustomActionAtIndex(0) === true +PASS: actionClicked === true + +Focus is left on the action target, since the origin could no longer take it back: +PASS: document.activeElement.id === 'action' + +Assistive technology converges on the action target rather than the removed origin: +PASS: accessibilityController.focusedElement.domIdentifier === 'action' +PASS: focusNotificationCount > 0 === true + +PASS successfullyParsed is true + +TEST COMPLETE +Delete diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-focus-restore-fallback.html b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-restore-fallback.html new file mode 100644 index 0000000000000..e090d498fad37 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-focus-restore-fallback.html @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-ignored-target-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-actions-ignored-target-expected.txt new file mode 100644 index 0000000000000..cc0da191b0680 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-ignored-target-expected.txt @@ -0,0 +1,24 @@ +This test verifies that aria-actions targets which are themselves accessibility-ignored (here, role=button elements inside a listbox option) are still exposed with an accessible name, and thus as invokable custom actions. + +The action targets are resolved in DOM order: +PASS: deleteAction.domIdentifier === 'delete-action' +PASS: favoriteAction.domIdentifier === 'favorite-action' + +The action targets are accessibility-ignored (they are buttons inside a listbox option): +PASS: deleteAction.isIgnored === true +PASS: favoriteAction.isIgnored === true + +Even though they are ignored, the action targets expose their accessible name: +PASS: platformValueForW3CName(deleteAction) === 'Delete' +PASS: platformValueForW3CName(favoriteAction) === 'Favorite' + +The custom actions are exposed on the option and can be invoked: +PASS: option.invokeCustomActionAtIndex(0) === true +PASS: option.invokeCustomActionAtIndex(1) === true +PASS: clickedActions[0] === 'delete-action' +PASS: clickedActions[1] === 'favorite-action' + +PASS successfullyParsed is true + +TEST COMPLETE +Delete Favorite diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-ignored-target.html b/LayoutTests/accessibility/isolated-tree/aria-actions-ignored-target.html new file mode 100644 index 0000000000000..de0ead08626af --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-ignored-target.html @@ -0,0 +1,53 @@ + + + + + + + + +
      +
    • + Delete + Favorite +
    • +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-preserves-focus-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-actions-preserves-focus-expected.txt new file mode 100644 index 0000000000000..7c8a1f4bece75 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-preserves-focus-expected.txt @@ -0,0 +1,20 @@ +This test verifies that invoking an aria-actions custom action preserves focus wherever it was rather than moving it to the (focusable) action target, and that the transient focus movement is not surfaced to assistive technology. + +With the element hosting the actions focused, invoking the action preserves focus on it: +PASS: source.invokeCustomActionAtIndex(0) === true +PASS: actionClicked === true +PASS: document.activeElement.id === 'source' +PASS: accessibilityController.focusedElement.domIdentifier === 'source' +PASS: focusNotificationCount === 0 + +With an unrelated element focused, invoking the action preserves focus on that element: +PASS: source.invokeCustomActionAtIndex(0) === true +PASS: actionClicked === true +PASS: document.activeElement.id === 'other' +PASS: accessibilityController.focusedElement.domIdentifier === 'other' +PASS: focusNotificationCount === 0 + +PASS successfullyParsed is true + +TEST COMPLETE +Source Delete Other diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions-preserves-focus.html b/LayoutTests/accessibility/isolated-tree/aria-actions-preserves-focus.html new file mode 100644 index 0000000000000..18059da991bde --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions-preserves-focus.html @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-actions.html b/LayoutTests/accessibility/isolated-tree/aria-actions.html new file mode 100644 index 0000000000000..1a89be420bed3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-actions.html @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-activedescendant-crash-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-activedescendant-crash-expected.txt new file mode 100644 index 0000000000000..e2067f3e87899 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-activedescendant-crash-expected.txt @@ -0,0 +1,2 @@ +This tests that there is no crash if you set an aria-activedescendant attribute to an id of an element that has no renderer. + diff --git a/LayoutTests/accessibility/isolated-tree/aria-activedescendant-crash.html b/LayoutTests/accessibility/isolated-tree/aria-activedescendant-crash.html new file mode 100644 index 0000000000000..9942f9e115b52 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-activedescendant-crash.html @@ -0,0 +1,21 @@ + + + + + + + This tests that there is no crash if you set an aria-activedescendant attribute to an id of an element that has no renderer.
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-braillelabel-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-braillelabel-expected.txt new file mode 100644 index 0000000000000..71354c8212fb1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-braillelabel-expected.txt @@ -0,0 +1,9 @@ +This test ensures aria-braillelabel works. + +PASS: accessibilityController.accessibleElementById('button').brailleLabel === 'AXBrailleLabel: ***' +PASS: accessibilityController.accessibleElementById('button').brailleLabel === 'AXBrailleLabel: *' + +PASS successfullyParsed is true + +TEST COMPLETE +Three stars diff --git a/LayoutTests/accessibility/isolated-tree/aria-braillelabel.html b/LayoutTests/accessibility/isolated-tree/aria-braillelabel.html new file mode 100644 index 0000000000000..92111aa73b403 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-braillelabel.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-brailleroledescription-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-brailleroledescription-expected.txt new file mode 100644 index 0000000000000..009a676994e57 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-brailleroledescription-expected.txt @@ -0,0 +1,9 @@ +This test ensures aria-brailleroledescription works. + +PASS: accessibilityController.accessibleElementById('button').brailleRoleDescription === 'AXBrailleRoleDescription: btn' +PASS: accessibilityController.accessibleElementById('button').brailleRoleDescription === 'AXBrailleRoleDescription: bigbtn' + +PASS successfullyParsed is true + +TEST COMPLETE +Press diff --git a/LayoutTests/accessibility/isolated-tree/aria-brailleroledescription.html b/LayoutTests/accessibility/isolated-tree/aria-brailleroledescription.html new file mode 100644 index 0000000000000..94ec30934e10d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-brailleroledescription.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-busy-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-busy-expected.txt new file mode 100644 index 0000000000000..7da9402a112a9 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-busy-expected.txt @@ -0,0 +1,11 @@ +This tests that the 'busy' state is exposed correctly. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +listbox being populated is busy: true +listbox already populated is busy: false +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-busy.html b/LayoutTests/accessibility/isolated-tree/aria-busy.html new file mode 100644 index 0000000000000..1390a0ea911e1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-busy.html @@ -0,0 +1,23 @@ + + + + + + + +
    +
    +

    +
    + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-cellspans-with-native-cellspans-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-cellspans-with-native-cellspans-expected.txt new file mode 100644 index 0000000000000..9d2dcc83245b5 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-cellspans-with-native-cellspans-expected.txt @@ -0,0 +1,15 @@ +This verifies that ARIA cell spans are ignored when native cell spans are set. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +columnheader1 spans 3 row(s) and 2 column(s). +columnheader2 spans 4 row(s) and 10 column(s). +columnheader3 spans 4 row(s) and 3 column(s). +cell1 spans 2 row(s) and 2 column(s). +cell2 spans 3 row(s) and 10 column(s). +cell3 spans 3 row(s) and 10 column(s). +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-cellspans-with-native-cellspans.html b/LayoutTests/accessibility/isolated-tree/aria-cellspans-with-native-cellspans.html new file mode 100644 index 0000000000000..9fab9e5509dab --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-cellspans-with-native-cellspans.html @@ -0,0 +1,57 @@ + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    header 1header 2header 3
    cell 1cell 2cell 3
    cell 4
    cell 5cell 6cell 7cell 8cell 9
    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-checkbox-checked-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-checkbox-checked-expected.txt new file mode 100644 index 0000000000000..3658355b56329 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-checkbox-checked-expected.txt @@ -0,0 +1,21 @@ + +X +X + +This tests that ARIA checkboxes correctly handle the aria-checked attribute. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS checkbox.isChecked is false +PASS checkbox.isChecked is true +PASS checkbox.isChecked is false +PASS checkbox.isChecked is true +PASS checkbox.isChecked is false +PASS checkbox.isChecked is true +PASS checkbox.isChecked is false +PASS checkbox.isChecked is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-checkbox-checked.html b/LayoutTests/accessibility/isolated-tree/aria-checkbox-checked.html new file mode 100644 index 0000000000000..6f7b4609cc04a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-checkbox-checked.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + +

    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-checkbox-sends-notification.html b/LayoutTests/accessibility/isolated-tree/aria-checkbox-sends-notification.html new file mode 100644 index 0000000000000..778dc18e4056d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-checkbox-sends-notification.html @@ -0,0 +1,40 @@ + + + + + + + + + + +

    +
    + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-checkbox-text-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-checkbox-text-expected.txt new file mode 100644 index 0000000000000..e010a6c9a45c8 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-checkbox-text-expected.txt @@ -0,0 +1,15 @@ +One +Two +Three +This tests that ARIA checkboxes use accessible name computation. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +checkbox.title is AXTitle: One +checkbox.title is AXTitle: Two +checkbox.title is AXTitle: Three +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-checkbox-text.html b/LayoutTests/accessibility/isolated-tree/aria-checkbox-text.html new file mode 100644 index 0000000000000..ad2a8d8250080 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-checkbox-text.html @@ -0,0 +1,35 @@ + + + + + + + +
    + + + +
    + +

    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-checked-mixed-value-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-checked-mixed-value-expected.txt new file mode 100644 index 0000000000000..a1aaf75fe907e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-checked-mixed-value-expected.txt @@ -0,0 +1,27 @@ +This test ensures mixed values are reported correctly. + + +Role: AXRole: AXRadioButton +Mixed: false + +
    +Role: AXRole: AXMenuItem +Mixed: false + +
    +Role: AXRole: AXMenuItem +Mixed: true + + +Role: AXRole: AXCheckBox +Mixed: false + + +Role: AXRole: AXCheckBox +Mixed: true + + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-checked-mixed-value.html b/LayoutTests/accessibility/isolated-tree/aria-checked-mixed-value.html new file mode 100644 index 0000000000000..6a440b07223d8 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-checked-mixed-value.html @@ -0,0 +1,32 @@ + + + + + + + + + +
    +
    + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-controlled-table-row-visibility-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-controlled-table-row-visibility-expected.txt new file mode 100644 index 0000000000000..d89f175cd2607 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-controlled-table-row-visibility-expected.txt @@ -0,0 +1,72 @@ +This test ensures the accessibility tree is correct after table rows with an aria-controls relationship dynamically change their hidden status. + + +{#table AXRole: AXTable} + +{#r0 AXRole: AXRow} + +{#r0c0 AXRole: AXCell} + +{AXRole: AXStaticText AXValue: Author} + +{#r0c1 AXRole: AXCell} + +{AXRole: AXStaticText AXValue: Title} + +{#r1 AXRole: AXRow} + +{#r1c0 AXRole: AXCell} + +{AXRole: AXButton} + +{#r1c1 AXRole: AXCell} + +{AXRole: AXStaticText AXValue: A Brief History of Time} + +PASS: output.includes('Carl Sagan') === false +PASS: table.rowCount === 2 === true + + +Traversal after un-hiding #r2: + +{#table AXRole: AXTable} + +{#r0 AXRole: AXRow} + +{#r0c0 AXRole: AXCell} + +{AXRole: AXStaticText AXValue: Author} + +{#r0c1 AXRole: AXCell} + +{AXRole: AXStaticText AXValue: Title} + +{#r1 AXRole: AXRow} + +{#r1c0 AXRole: AXCell} + +{AXRole: AXButton} + +{#r1c1 AXRole: AXCell} + +{AXRole: AXStaticText AXValue: A Brief History of Time} + +{#r2 AXRole: AXRow} + +{#r2c0 AXRole: AXCell} + +{AXRole: AXStaticText AXValue: Carl Sagan} + +{#r2c1 AXRole: AXCell} + +{AXRole: AXStaticText AXValue: Cosmos} + +PASS: table.rowCount === 3 === true + +PASS successfullyParsed is true + +TEST COMPLETE +This is a table caption +Author Title +Toggle second row A Brief History of Time +Carl Sagan Cosmos diff --git a/LayoutTests/accessibility/isolated-tree/aria-controlled-table-row-visibility.html b/LayoutTests/accessibility/isolated-tree/aria-controlled-table-row-visibility.html new file mode 100644 index 0000000000000..81a932f94aeb1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-controlled-table-row-visibility.html @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
    This is a table caption
    AuthorTitle
    A Brief History of Time
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-controls-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-controls-expected.txt new file mode 100644 index 0000000000000..2bae886ceb979 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-controls-expected.txt @@ -0,0 +1,14 @@ +Panel 1 +Panel 2 +This tests that aria-controls returns correct element at the given index + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS: tab1.ariaControlsElementAtIndex(0).stringValue === 'AXValue: Panel 1' +PASS: tab1.ariaControlsElementAtIndex(1).stringValue === 'AXValue: Panel 2' + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-controls-with-tabs-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-controls-with-tabs-expected.txt new file mode 100644 index 0000000000000..857a915c8c2ee --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-controls-with-tabs-expected.txt @@ -0,0 +1,23 @@ +This tests that the aria tab item becomes selected if either aria-selected is used, or if aria-controls points to an item that contains KB focus. + +PASS: tab2.isSelected === true + +Removing aria-controls from #tab_2 +PASS: tab2.isSelected === false + +Resetting #tab_2 aria-controls to be '#panel_2' +PASS: tab2.isSelected === true +PASS: tab1.isSelected === false +PASS: tab2.isSelected === false +PASS: tab1.isSelected === true + +PASS successfullyParsed is true + +TEST COMPLETE +Crust +Veges +Test + +Select Crust + +Select Crust diff --git a/LayoutTests/accessibility/isolated-tree/aria-controls-with-tabs.html b/LayoutTests/accessibility/isolated-tree/aria-controls-with-tabs.html new file mode 100644 index 0000000000000..47a4be3d4376b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-controls-with-tabs.html @@ -0,0 +1,65 @@ + + + + + + + + +
      + + +
    + +

    Test

    + +
    +

    Select Crust

    +
    + +
    +

    Select Crust

    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-controls.html b/LayoutTests/accessibility/isolated-tree/aria-controls.html new file mode 100644 index 0000000000000..07f32ea0ee64c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-controls.html @@ -0,0 +1,47 @@ + + + + + + + + +
      + +
    + +
    Panel 1
    +
    Panel 2
    + +

    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-current-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-current-expected.txt new file mode 100644 index 0000000000000..4e25d186df61e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-current-expected.txt @@ -0,0 +1,38 @@ +This tests that aria-current causes the right attribute to be returned. + +PASS: AXARIACurrent is false +PASS: AXARIACurrent is false +PASS: AXARIACurrent is false +PASS: AXARIACurrent is false +PASS: AXARIACurrent is false +PASS: AXARIACurrent is false +PASS: AXARIACurrent is page +PASS: AXARIACurrent is step +PASS: AXARIACurrent is location +PASS: AXARIACurrent is date +PASS: AXARIACurrent is time +PASS: AXARIACurrent is true +PASS: AXARIACurrent is true +PASS: AXARIACurrent is true +PASS: AXARIACurrent is page +PASS: AXARIACurrent is step + +PASS successfullyParsed is true + +TEST COMPLETE +Nav1 +Nav2 +Nav3 +Nav4 +Nav5 +Nav6 +Nav7 +Nav8 +Nav9 +Nav10 +Nav11 +Nav12 +Nav13 +Nav14 +Nav15 +Nav16 diff --git a/LayoutTests/accessibility/isolated-tree/aria-current-global-attribute-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-current-global-attribute-expected.txt new file mode 100644 index 0000000000000..58e0a5528bbf0 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-current-global-attribute-expected.txt @@ -0,0 +1,12 @@ +text1 text2 +This tests that aria-current is a global attribute. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS spanAccessible('text1') is false +PASS spanAccessible('text2') is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-current-global-attribute.html b/LayoutTests/accessibility/isolated-tree/aria-current-global-attribute.html new file mode 100644 index 0000000000000..46ca3c452d782 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-current-global-attribute.html @@ -0,0 +1,37 @@ + + + + + + + + +text1 +text2 + +

    +
    + + + + + + \ No newline at end of file diff --git a/LayoutTests/accessibility/isolated-tree/aria-current-state-changed-notification-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-current-state-changed-notification-expected.txt new file mode 100644 index 0000000000000..1651c4bdc17fe --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-current-state-changed-notification-expected.txt @@ -0,0 +1,23 @@ +This tests that changing the aria-current value results in a CurrentStateChanged notification. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS item2.isAttributeSupported('AXARIACurrent') is true +PASS item2.currentStateValue is 'page' +PASS item3.isAttributeSupported('AXARIACurrent') is false +PASS item3.currentStateValue is 'false' +Setting aria-current to false on item2. +AXCurrentStateChanged notification for item2 +PASS item2.isAttributeSupported('AXARIACurrent') is false +PASS item2.currentStateValue is 'false' +PASS item3.currentStateValue is 'false' +Setting aria-current to page on item3. +AXCurrentStateChanged notification for item3 +PASS item2.currentStateValue is 'false' +PASS item3.isAttributeSupported('AXARIACurrent') is true +PASS item3.currentStateValue is 'page' +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-current-state-changed-notification.html b/LayoutTests/accessibility/isolated-tree/aria-current-state-changed-notification.html new file mode 100644 index 0000000000000..8601fee549e49 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-current-state-changed-notification.html @@ -0,0 +1,71 @@ + + + + + + + + +
    +
    1
    +
    2
    +
    3
    +
    + +

    +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-current.html b/LayoutTests/accessibility/isolated-tree/aria-current.html new file mode 100644 index 0000000000000..f9942b677aead --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-current.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-describedby-on-input-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-describedby-on-input-expected.txt new file mode 100644 index 0000000000000..070721878bbbb --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-describedby-on-input-expected.txt @@ -0,0 +1,11 @@ +This test ensures input elements properly use the aria-describedby in their accessibility description. + +The accessibility description of #time is "AXHelp: Allows you to specify the number of minutes after which the computer will self-destruct." + +Updating aria-describedby of #time to #description3. +The accessibility description of #time is "AXHelp: Foobar." + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-describedby-on-input.html b/LayoutTests/accessibility/isolated-tree/aria-describedby-on-input.html new file mode 100644 index 0000000000000..8308c008e2d58 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-describedby-on-input.html @@ -0,0 +1,51 @@ + + + + + + + + +
    + This computer will self-destruct in + + minutes. + +
    Allows you to specify the number of minutes after
    +
    which the computer will self-destruct.
    +
    Foobar.
    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-description-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-description-expected.txt new file mode 100644 index 0000000000000..8c7fd7d5df777 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-description-expected.txt @@ -0,0 +1,14 @@ +This test ensures that aria-description maps to appropriate attributes and works with aria-describedby correctly. + +Help text: +Help text: AXHelp: text +Help text: AXHelp: text + +PASS successfullyParsed is true + +TEST COMPLETE +button +button +text +button +text diff --git a/LayoutTests/accessibility/isolated-tree/aria-description.html b/LayoutTests/accessibility/isolated-tree/aria-description.html new file mode 100644 index 0000000000000..3bbf6c0d5048a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-description.html @@ -0,0 +1,50 @@ + + + + + + + + +
    button
    + +
    button
    +
    text
    + +
    button
    +
    text
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-disabled-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-disabled-expected.txt new file mode 100644 index 0000000000000..0c3b6f6a065eb --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-disabled-expected.txt @@ -0,0 +1,12 @@ + +This tests that the aria-disabled attribute works. The text field should not be enabled. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS succeeded is false +PASS succeeded is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-disabled-propagated-to-children-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-disabled-propagated-to-children-expected.txt new file mode 100644 index 0000000000000..144e86e1e0801 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-disabled-propagated-to-children-expected.txt @@ -0,0 +1,14 @@ +Foo +Bar +This tests that aria-disabled will be taken from an ancestor if available + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS item1.isEnabled is false +PASS item2.isEnabled is true +PASS tablist.isEnabled is false +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-disabled-propagated-to-children.html b/LayoutTests/accessibility/isolated-tree/aria-disabled-propagated-to-children.html new file mode 100644 index 0000000000000..fae3966cfe8e1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-disabled-propagated-to-children.html @@ -0,0 +1,43 @@ + + + + + + + +
    + + + +
    + +

    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-disabled.html b/LayoutTests/accessibility/isolated-tree/aria-disabled.html new file mode 100644 index 0000000000000..21b8588391cdc --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-disabled.html @@ -0,0 +1,41 @@ + + + + + + + + +

    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-expanded-links-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-expanded-links-expected.txt new file mode 100644 index 0000000000000..fd31d5a04e2d9 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-expanded-links-expected.txt @@ -0,0 +1,11 @@ +This tests that aria-expanded works as expected on links. + +PASS: link.isAttributeSupported('AXExpanded') === true +PASS: link.isExpanded === true +Changing link expanded status to FALSE +PASS: link.isExpanded === false + +PASS successfullyParsed is true + +TEST COMPLETE +This is an expanded link. diff --git a/LayoutTests/accessibility/isolated-tree/aria-expanded-links.html b/LayoutTests/accessibility/isolated-tree/aria-expanded-links.html new file mode 100644 index 0000000000000..f75556224e6c3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-expanded-links.html @@ -0,0 +1,34 @@ + + + + + + + + +This is an expanded link. + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-expanded-supported-roles.html b/LayoutTests/accessibility/isolated-tree/aria-expanded-supported-roles.html new file mode 100644 index 0000000000000..be5246ff5e909 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-expanded-supported-roles.html @@ -0,0 +1,109 @@ + + + + + + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + + + + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-flowto-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-flowto-expected.txt new file mode 100644 index 0000000000000..46b84428ed924 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-flowto-expected.txt @@ -0,0 +1,23 @@ +This tests that aria-flowto correctly identifies the right elements. + +PASS: item.ariaFlowToElementAtIndex(0).role === 'AXRole: AXButton' +PASS: item.ariaFlowToElementAtIndex(0).title === 'AXTitle: BUTTON' +PASS: item.ariaFlowToElementAtIndex(1).role === 'AXRole: AXRadioButton' +PASS: item.ariaFlowToElementAtIndex(1).title === 'AXTitle: RADIO BUTTON' +PASS: displayContentsImg.ariaFlowToElementAtIndex(0).role === 'AXRole: AXButton' +PASS: displayContentsImg.ariaFlowToElementAtIndex(0).title === 'AXTitle: Foo button' + +Removing id 'extra' from #item1's aria-flowto. +PASS: item.ariaFlowToElementAtIndex(0).role === 'AXRole: AXRadioButton' +PASS: item.ariaFlowToElementAtIndex(0).title === 'AXTitle: RADIO BUTTON' + +PASS successfullyParsed is true + +TEST COMPLETE +Item 1 +Item 2 +Item 3 +Foo img +BUTTON +RADIO BUTTON +Foo button diff --git a/LayoutTests/accessibility/isolated-tree/aria-flowto.html b/LayoutTests/accessibility/isolated-tree/aria-flowto.html new file mode 100644 index 0000000000000..6685085e881b9 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-flowto.html @@ -0,0 +1,48 @@ + + + + + + + + + +
    Item 2
    +
    Item 3
    + + + +
    BUTTON
    + +
    Foo button
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-grid-column-span-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-grid-column-span-expected.txt new file mode 100644 index 0000000000000..c5400600eb051 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-grid-column-span-expected.txt @@ -0,0 +1,17 @@ +Month Jan Mar +Expenses 100 130 90 +This tests that cells that span multiple columns within an ARIA grid have correct column index range + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS cell1.role is 'AXRole: AXCell' +PASS cell1.columnIndexRange() is '{1, 2}' +PASS cell2.role is 'AXRole: AXCell' +PASS cell2.columnIndexRange() is '{3, 1}' +PASS cell3.role is 'AXRole: AXCell' +PASS cell3.columnIndexRange() is '{2, 1}' +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-grid-column-span.html b/LayoutTests/accessibility/isolated-tree/aria-grid-column-span.html new file mode 100644 index 0000000000000..e9c5a54a6df05 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-grid-column-span.html @@ -0,0 +1,59 @@ + + + + + + + + +
    +
    +
    Month
    +
    Jan
    +
    Mar
    +
    +
    +
    Expenses
    +
    100
    +
    130
    +
    90
    +
    +
    + +

    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-grid-with-aria-owns-rows-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-grid-with-aria-owns-rows-expected.txt new file mode 100644 index 0000000000000..92951dc40a978 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-grid-with-aria-owns-rows-expected.txt @@ -0,0 +1,11 @@ +This tests that an ARIA table can use aria-owns for its cells. +PASS: row1.childAtIndex(0).isEqual(table.cellForColumnAndRow(0, 0)) === true +PASS: row1.childAtIndex(0).isEqual(accessibilityController.accessibleElementById('row1-cell-1')) === true +PASS: cell1.parentElement().isEqual(row1) === true +PASS: row1.parentElement().isEqual(table) === true + +PASS successfullyParsed is true + +TEST COMPLETE +Foo +Bar diff --git a/LayoutTests/accessibility/isolated-tree/aria-grid-with-aria-owns-rows.html b/LayoutTests/accessibility/isolated-tree/aria-grid-with-aria-owns-rows.html new file mode 100644 index 0000000000000..1db3b591bb8ce --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-grid-with-aria-owns-rows.html @@ -0,0 +1,35 @@ + + + + + + + + +
    +
    +
    Foo
    +
    Bar
    +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-grid-with-presentational-sections-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-grid-with-presentational-sections-expected.txt new file mode 100644 index 0000000000000..8932444cfd9c4 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-grid-with-presentational-sections-expected.txt @@ -0,0 +1,17 @@ +This test ensures an ARIA grid built from native table sections whose rows are wrapped in presentational scaffolding exposes its rows and cells. + +PASS: grid.role.toLowerCase().includes('table') === true +PASS: grid.rowCount === 2 +PASS: grid.columnCount === 3 +#grid cellForColumnAndRow(0, 0).domIdentifier is sun +#grid cellForColumnAndRow(1, 0).domIdentifier is mon +#grid cellForColumnAndRow(2, 0).domIdentifier is tue +#grid cellForColumnAndRow(0, 1).domIdentifier is day1 +#grid cellForColumnAndRow(1, 1).domIdentifier is day2 +#grid cellForColumnAndRow(2, 1).domIdentifier is day3 + +PASS successfullyParsed is true + +TEST COMPLETE +Sun Mon Tue +1 2 3 diff --git a/LayoutTests/accessibility/isolated-tree/aria-grid-with-presentational-sections.html b/LayoutTests/accessibility/isolated-tree/aria-grid-with-presentational-sections.html new file mode 100644 index 0000000000000..d711006b28ecc --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-grid-with-presentational-sections.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + +
    SunMonTue
    +
    + + + + + + + + +
    123
    +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-help-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-help-expected.txt new file mode 100644 index 0000000000000..2536021d42675 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-help-expected.txt @@ -0,0 +1,11 @@ +This tests that aria-help attribute works as expected. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS platformValueForW3CName(axButton) is "title" +PASS platformValueForW3CDescription(axButton) is "click here" +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-help.html b/LayoutTests/accessibility/isolated-tree/aria-help.html new file mode 100644 index 0000000000000..572bceeb66129 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-help.html @@ -0,0 +1,31 @@ + + + + + + + +
    +
    +button +
    +
    +

    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-crash-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-crash-expected.txt new file mode 100644 index 0000000000000..1dfed536abb2b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-crash-expected.txt @@ -0,0 +1,5 @@ +Bug 139856: Hidden aria table crash. + +This test PASSES if it does not CRASH. + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-crash.html b/LayoutTests/accessibility/isolated-tree/aria-hidden-crash.html new file mode 100644 index 0000000000000..ccd5530a5c631 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-crash.html @@ -0,0 +1,28 @@ + + + + + + +

    Bug 139856: Hidden aria table crash.

    +

    This test PASSES if it does not CRASH.

    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-deep-dom-no-crash-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-deep-dom-no-crash-expected.txt new file mode 100644 index 0000000000000..4026dd374f16f --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-deep-dom-no-crash-expected.txt @@ -0,0 +1,9 @@ +This test ensures toggling aria-hidden on a deep DOM does not crash due to unbounded recursion in enumerateDescendantsIncludingIgnored. + +PASS: webArea.childrenCount === 0 +PASS: webArea.childrenCount > 0 === true + +PASS successfullyParsed is true + +TEST COMPLETE +Deep content diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-deep-dom-no-crash.html b/LayoutTests/accessibility/isolated-tree/aria-hidden-deep-dom-no-crash.html new file mode 100644 index 0000000000000..a72f6e330e7d0 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-deep-dom-no-crash.html @@ -0,0 +1,42 @@ + + + + + + + + +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-expected.txt new file mode 100644 index 0000000000000..f0e155a5dd294 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-expected.txt @@ -0,0 +1,14 @@ +h1 test + +h2 + +This tests that the aria-hidden attribute works correctly with accessibility. The H1 element (and its children) should not appear in the AX hierarchy. The H2 element should be the first child + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +h2.title is AXTitle: h2 +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-false-ignored-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-false-ignored-expected.txt new file mode 100644 index 0000000000000..e63849b34ade3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-false-ignored-expected.txt @@ -0,0 +1,11 @@ +This test ensures that aria-hidden=false is treated like an undefined value. + +PASS: !hiddenText === true +PASS: !invisibleText === true +PASS: !ariaHiddenText === true + +PASS successfullyParsed is true + +TEST COMPLETE + +This text is hidden with aria-hidden diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-false-ignored.html b/LayoutTests/accessibility/isolated-tree/aria-hidden-false-ignored.html new file mode 100644 index 0000000000000..5fe65a337b651 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-false-ignored.html @@ -0,0 +1,38 @@ + + + + + + + + +
    +

    This text should be hidden

    +
    + +
    +

    This text should be invisible

    +
    + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-focus-not-overridden-when-caught-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-focus-not-overridden-when-caught-expected.txt new file mode 100644 index 0000000000000..9b20e98dace8f --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-focus-not-overridden-when-caught-expected.txt @@ -0,0 +1,11 @@ +This tests that aria-hidden is NOT overridden when a focus handler moves focus out before the next animation frame. + +PASS: !button1 || !button1.isValid === true +PASS: accessibilityController.focusedElement.domIdentifier === 'outside' +PASS: !button1 || !button1.isValid === true +PASS: !button2 || !button2.isValid === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-focus-not-overridden-when-caught.html b/LayoutTests/accessibility/isolated-tree/aria-hidden-focus-not-overridden-when-caught.html new file mode 100644 index 0000000000000..7a99d639b6142 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-focus-not-overridden-when-caught.html @@ -0,0 +1,61 @@ + + + + + + + + +
    + + +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-focus-override-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-focus-override-expected.txt new file mode 100644 index 0000000000000..ab584162a7a9b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-focus-override-expected.txt @@ -0,0 +1,14 @@ +This tests that focusing inside an aria-hidden region permanently overrides aria-hidden after one animation frame. + +PASS: !button1 || !button1.isValid === true +PASS: !button2 || !button2.isValid === true +PASS: button2 && button2.isValid === true +PASS: button1 && button1.isValid === true +PASS: button1 && button1.isValid === true +PASS: button2 && button2.isValid === true +PASS: !button1 || !button1.isValid === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-focus-override.html b/LayoutTests/accessibility/isolated-tree/aria-hidden-focus-override.html new file mode 100644 index 0000000000000..c6060dba08607 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-focus-override.html @@ -0,0 +1,78 @@ + + + + + + + + +
    + + +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-hides-all-elements-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-hides-all-elements-expected.txt new file mode 100644 index 0000000000000..b4549a28de520 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-hides-all-elements-expected.txt @@ -0,0 +1,10 @@ +This tests aria-hidden on a parent node will hide all these special subclass objects. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS content.childrenCount is 0 +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-hides-all-elements.html b/LayoutTests/accessibility/isolated-tree/aria-hidden-hides-all-elements.html new file mode 100644 index 0000000000000..3792588b19f21 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-hides-all-elements.html @@ -0,0 +1,44 @@ + + + + + + + +
    + +
    + + + + + +
    • item
    + + + + cake + +
    + +
    + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-modal-override-when-page-empty-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-modal-override-when-page-empty-expected.txt new file mode 100644 index 0000000000000..057ddf3b218f4 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-modal-override-when-page-empty-expected.txt @@ -0,0 +1,12 @@ +This tests that when all page content is inert and an aria-modal dialog is inside an aria-hidden container, the dialog content is still accessible. + +PASS: accessibilityController.accessibleElementById('article') === null +PASS: heading && !heading.isIgnored === true +PASS: button && !button.isIgnored === true + +PASS successfullyParsed is true + +TEST COMPLETE +Terms and Conditions + +Agree diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-modal-override-when-page-empty.html b/LayoutTests/accessibility/isolated-tree/aria-hidden-modal-override-when-page-empty.html new file mode 100644 index 0000000000000..61417348a5ef3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-modal-override-when-page-empty.html @@ -0,0 +1,48 @@ + + + + + + + + +
    +

    Main article content

    +
    + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-subtree-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-subtree-expected.txt new file mode 100644 index 0000000000000..36060223f5f15 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-subtree-expected.txt @@ -0,0 +1,19 @@ +This test ensures that the entire subtree of an aria-hidden object is hidden. + +DIV is aria-hidden=true: +PASS: !headingElement === true +PASS: !paragraphElement === true +PASS: !buttonElement === true +DIV has aria-hidden unset: +PASS: !!accessibilityController.accessibleElementById('heading') === true +PASS: !!accessibilityController.accessibleElementById('paragraph') === true +PASS: !!accessibilityController.accessibleElementById('button') === true + +PASS successfullyParsed is true + +TEST COMPLETE +Heading + +This is a nested paragraph + +Done diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-subtree.html b/LayoutTests/accessibility/isolated-tree/aria-hidden-subtree.html new file mode 100644 index 0000000000000..c6db713f38ae8 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-subtree.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-text-stitching-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-text-stitching-expected.txt new file mode 100644 index 0000000000000..b6a56766cbdac --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-text-stitching-expected.txt @@ -0,0 +1,29 @@ +This test ensures aria-hidden text is excluded from stitched accessibility text. + +PASS: platformStaticTextValue(text1).includes('$45.79') === true +PASS: platformStaticTextValue(text1).includes('/mo.') === false +PASS: platformStaticTextValue(text1).includes('per month') === true + +PASS: platformStaticTextValue(text2).includes('Hello') === true +PASS: platformStaticTextValue(text2).includes('hidden') === false +PASS: platformStaticTextValue(text2).includes('world') === true + +PASS: platformStaticTextValue(text3).includes('Start') === true +PASS: platformStaticTextValue(text3).includes('middle') === false +PASS: platformStaticTextValue(text3).includes('end') === true + +PASS: platformStaticTextValue(text3).includes('middle') === true +PASS: platformStaticTextValue(text3).includes('Start') === true +PASS: platformStaticTextValue(text3).includes('end') === true + +PASS: platformStaticTextValue(text3).includes('middle') === false +PASS: platformStaticTextValue(text3).includes('Start') === true +PASS: platformStaticTextValue(text3).includes('end') === true + + +PASS successfullyParsed is true + +TEST COMPLETE +$45.79/mo. per month +Hello hidden world +Start middle end diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-text-stitching.html b/LayoutTests/accessibility/isolated-tree/aria-hidden-text-stitching.html new file mode 100644 index 0000000000000..e01c8e8d0ed4e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-text-stitching.html @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-update-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-update-expected.txt new file mode 100644 index 0000000000000..1d548166919bc --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-update-expected.txt @@ -0,0 +1,19 @@ +This test makes sure that when aria-hidden changes, the AX hierarchy is updated. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS container.childAtIndex(0).isEqual(button1) is true +PASS container.childAtIndex(1).isEqual(button2) is true +PASS container.childAtIndex(2).isEqual(button3) is true +PASS container.childAtIndex(0).isEqual(button1) is true +PASS container.childAtIndex(1).isEqual(button3) === true +PASS container.childAtIndex(0).isEqual(button3) === true +PASS container.childAtIndex(0).isEqual(button2) === true +PASS container.childAtIndex(1).isEqual(button3) === true +PASS successfullyParsed is true + +TEST COMPLETE +Button 1 +Button 2 +Button 3 diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-update.html b/LayoutTests/accessibility/isolated-tree/aria-hidden-update.html new file mode 100644 index 0000000000000..415035f27089a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-update.html @@ -0,0 +1,51 @@ + + + + + + + + +
    +
    Button 1
    +
    Button 2
    +
    Button 3
    +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-updates-alldescendants-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-updates-alldescendants-expected.txt new file mode 100644 index 0000000000000..37ce55af24781 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-updates-alldescendants-expected.txt @@ -0,0 +1,15 @@ +This tests that if aria-hidden changes on an element, all it's existing children will update their children caches + +PASS: main.childrenCount === 1 +PASS: main.childrenCount === 2 +PASS: main.childAtIndex(1).childrenCount === 1 + +PASS successfullyParsed is true + +TEST COMPLETE +Steps + +test +Step 1: Do something +Step 2: Do another thing +Step 3: Do one last thing diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-updates-alldescendants.html b/LayoutTests/accessibility/isolated-tree/aria-hidden-updates-alldescendants.html new file mode 100644 index 0000000000000..f083831ba46ae --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-updates-alldescendants.html @@ -0,0 +1,50 @@ + + + + + + + + +
    +

    Steps

    + +
    + test + + + +
    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-with-elements-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-hidden-with-elements-expected.txt new file mode 100644 index 0000000000000..58d499e9675fd --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-with-elements-expected.txt @@ -0,0 +1,19 @@ +cell cell cell +cell cell cell +cell +test +test + +heading + +This tests that aria-hidden works as expected on elements that are subclasses of AccessibilityRenderObject + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS child.childrenCount is 0 +child.role is AXRole: AXHeading +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden-with-elements.html b/LayoutTests/accessibility/isolated-tree/aria-hidden-with-elements.html new file mode 100644 index 0000000000000..9be5e29801f4a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden-with-elements.html @@ -0,0 +1,56 @@ + + + + + + + +
    + + + + + + + + + + +
    + +
    + +

    heading

    + +
    + +

    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-hidden.html b/LayoutTests/accessibility/isolated-tree/aria-hidden.html new file mode 100644 index 0000000000000..f062e695fbbde --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-hidden.html @@ -0,0 +1,27 @@ + + + + + + +

    h1 test

    +

    h2

    +

    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-inherits-presentational.html b/LayoutTests/accessibility/isolated-tree/aria-inherits-presentational.html new file mode 100644 index 0000000000000..81733bcfebc08 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-inherits-presentational.html @@ -0,0 +1,36 @@ + + + + + + + +
    • item 0
    • item 1
    +
    item 2item 3
    +
    End of test
    +

    +
    + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-invalid-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-invalid-expected.txt new file mode 100644 index 0000000000000..19d7ac10d043e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-invalid-expected.txt @@ -0,0 +1,29 @@ +This tests that aria-invalid causes the right attribute to be returned and it ensures a notification is sent when it changes. + +PASS: AXInvalid is false. +PASS: AXInvalid is false. +PASS: AXInvalid is false. +PASS: AXInvalid is false. +PASS: AXInvalid is false. +PASS: AXInvalid is false. +PASS: AXInvalid is false. +PASS: AXInvalid is false. +PASS: AXInvalid is grammar. +PASS: AXInvalid is grammar. +PASS: AXInvalid is spelling. +PASS: AXInvalid is spelling. +PASS: AXInvalid is true. +PASS: AXInvalid is true. +PASS: AXInvalid is true. +PASS: AXInvalid is true. +PASS: AXInvalid is true. +PASS: AXInvalid is true. +PASS: AXInvalid is true. +PASS: didAddNotification === true +Notification received successfully. +PASS: AXInvalid is spelling. + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-invalid.html b/LayoutTests/accessibility/isolated-tree/aria-invalid.html new file mode 100644 index 0000000000000..a4b00e339b488 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-invalid.html @@ -0,0 +1,88 @@ + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-keyshortcuts-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-keyshortcuts-expected.txt new file mode 100644 index 0000000000000..6fae20600525d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-keyshortcuts-expected.txt @@ -0,0 +1,24 @@ +This test ensures aria-keyshortcuts is exposed to accessibility correctly. + +PASS: axItem1.isAttributeSupported('AXKeyShortcutsValue') === false +PASS: axItem2.isAttributeSupported('AXKeyShortcutsValue') === true +PASS: axItem3.isAttributeSupported('AXKeyShortcutsValue') === true +PASS: axItem1.stringAttributeValue('AXKeyShortcutsValue') === '' +PASS: axItem2.stringAttributeValue('AXKeyShortcutsValue') === 'Shift+2' +PASS: axItem3.stringAttributeValue('AXKeyShortcutsValue') === 'Shift+3 Option+4' +Update aria-keyshortcuts to Command+5 for #test1 +PASS: axItem1.isAttributeSupported('AXKeyShortcutsValue') === true +PASS: axItem1.stringAttributeValue('AXKeyShortcutsValue') === 'Command+5' +Remove aria-keyshortcuts for #test2 +PASS: axItem2.isAttributeSupported('AXKeyShortcutsValue') === false +PASS: axItem2.stringAttributeValue('AXKeyShortcutsValue') === '' +Update aria-keyshortcuts to Shift+Command+1 for #test3 +PASS: axItem3.isAttributeSupported('AXKeyShortcutsValue') === true +PASS: axItem3.stringAttributeValue('AXKeyShortcutsValue') === 'Shift+Command+1' + +PASS successfullyParsed is true + +TEST COMPLETE +X +X +X diff --git a/LayoutTests/accessibility/isolated-tree/aria-keyshortcuts.html b/LayoutTests/accessibility/isolated-tree/aria-keyshortcuts.html new file mode 100644 index 0000000000000..8e652a7e20c06 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-keyshortcuts.html @@ -0,0 +1,55 @@ + + + + + + + +
    +
    X
    +
    X
    +
    X
    +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-label-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-label-expected.txt new file mode 100644 index 0000000000000..ce6daa16701a7 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-label-expected.txt @@ -0,0 +1,11 @@ +This tests that the aria-label attribute works on both an input and an anchor. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS platformValueForW3CName(axInput) is "aria label" +PASS platformValueForW3CName(axLink) is "aria link" +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-label-on-label-element-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-label-on-label-element-expected.txt new file mode 100644 index 0000000000000..1ceaec711a8c7 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-label-on-label-element-expected.txt @@ -0,0 +1,14 @@ +This tests that the aria-label attribute works on element. + +PASS: titleUIElement1.isEqual(accessibilityController.accessibleElementById('label1')) === true +PASS: input1.title === 'AXTitle: aria label' +PASS: titleUIElement2.isEqual(accessibilityController.accessibleElementById('label2')) === true +PASS: input3.title === 'AXTitle: hidden aria label' +PASS: input4.title === 'AXTitle: aria labelledby' + +PASS successfullyParsed is true + +TEST COMPLETE + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-label-on-label-element.html b/LayoutTests/accessibility/isolated-tree/aria-label-on-label-element.html new file mode 100644 index 0000000000000..fddc17df8ecdc --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-label-on-label-element.html @@ -0,0 +1,55 @@ + + + + + + + + +
    + + + + + + + + + + +

    aria

    +

    labelledby

    + + + +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-label.html b/LayoutTests/accessibility/isolated-tree/aria-label.html new file mode 100644 index 0000000000000..ad4429ca922cd --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-label.html @@ -0,0 +1,35 @@ + + + + + + + +
    + +test +
    +

    +
    + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-added-via-innerHTML-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-added-via-innerHTML-expected.txt new file mode 100644 index 0000000000000..e3c36fbb4d4ae --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-added-via-innerHTML-expected.txt @@ -0,0 +1,8 @@ +This test checks that a relation resolves once its aria-labelledby target is inserted via innerHTML after the initial relations build. + + +PASS successfullyParsed is true + +TEST COMPLETE +fallback name +the resolved label diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-added-via-innerHTML.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-added-via-innerHTML.html new file mode 100644 index 0000000000000..90e9f08aad617 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-added-via-innerHTML.html @@ -0,0 +1,39 @@ + + + + + + + + + +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-cycle-multi-element-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-cycle-multi-element-expected.txt new file mode 100644 index 0000000000000..d2735706a9b29 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-cycle-multi-element-expected.txt @@ -0,0 +1,11 @@ +This tests that multi-element aria-labelledby cycles do not cause infinite recursion or a crash. + +PASS: typeof platformValueForW3CName(a1) === 'string' +PASS: typeof platformValueForW3CName(a2) === 'string' +PASS: typeof platformValueForW3CName(a3) === 'string' +PASS: typeof platformValueForW3CName(a4) === 'string' + +PASS successfullyParsed is true + +TEST COMPLETE +A content E content A content E content A content E content A content E content diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-cycle-multi-element.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-cycle-multi-element.html new file mode 100644 index 0000000000000..86a06ec63afb4 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-cycle-multi-element.html @@ -0,0 +1,81 @@ + + + + + + + + + + + + + A content + + + E content + + + + + A content + + + + E content + + + + + + A content + + + E content + + + + + A content + + + E content + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-display-contents-shadow-root-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-display-contents-shadow-root-expected.txt new file mode 100644 index 0000000000000..4489f1ae95a2a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-display-contents-shadow-root-expected.txt @@ -0,0 +1,11 @@ +This test ensures aria-labelledby correctly references display:contents elements with shadow roots. + +PASS: testButton.role.toLowerCase().includes('button') === true +PASS: platformValueForW3CName(testButton) === 'Shadow Label Text' +PASS: platformValueForW3CName(testButton).includes('should NOT appear') === false + +PASS successfullyParsed is true + +TEST COMPLETE +Button Content + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-display-contents-shadow-root.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-display-contents-shadow-root.html new file mode 100644 index 0000000000000..24b0ce2591c00 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-display-contents-shadow-root.html @@ -0,0 +1,36 @@ + + + + + + + + +
    + +
    + + + +
    + +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-hidden-text-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-hidden-text-expected.txt new file mode 100644 index 0000000000000..64f59a95c1879 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-hidden-text-expected.txt @@ -0,0 +1,10 @@ +Tests that labels specified via aria-lablledby and aria-label are retrieved according to ARIA specifications in cases where the label text is hidden. + +PASS: axButton.title === `AXTitle: ${button.getAttribute('data-expectedlabel')}` +PASS: axButton.title === `AXTitle: ${button.getAttribute('data-expectedlabel')}` +PASS: axButton.title === `AXTitle: ${button.getAttribute('data-expectedlabel')}` + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-hidden-text.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-hidden-text.html new file mode 100644 index 0000000000000..3be654923ae37 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-hidden-text.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-checkbox-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-checkbox-expected.txt new file mode 100644 index 0000000000000..b8a9b188d980e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-checkbox-expected.txt @@ -0,0 +1,11 @@ +This test verifies that when a button has aria-labelledby referencing a checkbox or radio, the button gets the checkbox/radio accessible name (from its label), not its value attribute. + +PASS: platformValueForW3CName(buttonReferencingCheckbox) === 'Checkbox Label Text' +PASS: platformValueForW3CName(buttonReferencingCheckboxAriaLabel) === 'Checkbox ARIA Label' +PASS: platformValueForW3CName(buttonReferencingRadio) === 'Radio Label Text' +PASS: platformValueForW3CName(buttonReferencingCheckbox) === 'Updated Checkbox Label' + +PASS successfullyParsed is true + +TEST COMPLETE +Toggle Toggle Label for checkbox with aria-label Toggle Radio Label Text Updated Checkbox Label diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-checkbox.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-checkbox.html new file mode 100644 index 0000000000000..506c82a0c9f01 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-checkbox.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-input-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-input-expected.txt new file mode 100644 index 0000000000000..9bd2649853ebe --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-input-expected.txt @@ -0,0 +1,10 @@ +This verifies the accessible name of an input with multiple aria-labelledby elements. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS platformValueForW3CName(axInput) is "This computer will self-destruct in 10 minutes." +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-input.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-input.html new file mode 100644 index 0000000000000..4cf6d1587299c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-input.html @@ -0,0 +1,25 @@ + + + + + + + +
    + This computer will self-destruct in + + minutes. +
    +
    + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-password-input-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-password-input-expected.txt new file mode 100644 index 0000000000000..1933656be1414 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-password-input-expected.txt @@ -0,0 +1,18 @@ +This test ensures a label from aria-labelledby does not include a password inputs value. + +PASS: button1.description === 'AXDescription: ' +PASS: button1.title === 'AXTitle: Button One' +PASS: button1.description === 'AXDescription: •••••' +PASS: button1.title === 'AXTitle: •••••' +Update aria-labelledby to text-1 for #button-2 +PASS: button2.description === 'AXDescription: Before password After password' +PASS: button2.title === 'AXTitle: Before password After password' +Update value for #password-2 +PASS: button2.description === 'AXDescription: Before password ••••• After password' +PASS: button2.title === 'AXTitle: Before password ••••• After password' + +PASS successfullyParsed is true + +TEST COMPLETE +Button One Button Two +Before password After password diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-password-input.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-password-input.html new file mode 100644 index 0000000000000..f5e2569be91a3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-on-password-input.html @@ -0,0 +1,57 @@ + + + + + + + + +
    + + + +

    Before password After password

    +
    + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-aria-label-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-aria-label-expected.txt new file mode 100644 index 0000000000000..50c759e321920 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-aria-label-expected.txt @@ -0,0 +1,12 @@ +This tests that if aria-labelledby is used, then aria-label attributes are not used. + +Alpha Beta Delta Eta Epsilon Theta +usingNone.title: [AXTitle: Alpha] +usingNone.description: [AXDescription: ] +usingLabel.title: [AXTitle: Beta] +usingLabel.description: [AXDescription: Gamma] +usingLabelledby.title: [AXTitle: Delta] +usingLabelledby.description: [AXDescription: Epsilon] +usingLabeledby.title: [AXTitle: Eta] +usingLabeledby.description: [AXDescription: Theta] + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-aria-label.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-aria-label.html new file mode 100644 index 0000000000000..a3fdcc1d72baf --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-aria-label.html @@ -0,0 +1,51 @@ + + + + + +

    This tests that if aria-labelledby is used, then aria-label attributes are not used.

    + + + + + +Epsilon +Theta + +
      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-aria-labeledby-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-aria-labeledby-expected.txt new file mode 100644 index 0000000000000..5a730c3fae5e9 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-aria-labeledby-expected.txt @@ -0,0 +1,10 @@ +This tests that aria-labelledby overrides aria-labeledby correctly. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS platformValueForW3CName(axLink) is "Y Z" +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-aria-labeledby.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-aria-labeledby.html new file mode 100644 index 0000000000000..8145e1362d393 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-aria-labeledby.html @@ -0,0 +1,36 @@ + + + + + +aria-labelledby Overrides aria-labeledby + + + +
      +X + +I +J + +Y +Z +
      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-label-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-label-expected.txt new file mode 100644 index 0000000000000..15b265a031a74 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-label-expected.txt @@ -0,0 +1,8 @@ +This tests that if aria-labelledby is used, then label elements are not used. + +PASS: platformValueForW3CName(text) === 'Shut down computer after 10 minutes' +Label element role is: AXRole: AXStaticText +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-label.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-label.html new file mode 100644 index 0000000000000..159c1e089dd9b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-overrides-label.html @@ -0,0 +1,32 @@ + + + + + + + + +
      + + +minutes +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-stay-within-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-stay-within-expected.txt new file mode 100644 index 0000000000000..7a14f42108097 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-stay-within-expected.txt @@ -0,0 +1,10 @@ +This tests that aria-labelledby does not append all sibling to an ARIA name + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS platformValueForW3CName(axButton) is "Reply Item Five" +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-stay-within.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-stay-within.html new file mode 100644 index 0000000000000..0bbc0e7962954 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-stay-within.html @@ -0,0 +1,41 @@ + + + + + + + +
      +

      Some focusable content before the application widgets.

      + + +
        +
      • Item Four
      • +
      • Item Five
      • +
      • Item Six
      • +
      • Item Seven
      • +
      + +
      +

      +
      + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-target-added-via-innerHTML-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-target-added-via-innerHTML-expected.txt new file mode 100644 index 0000000000000..74c23d3a599b4 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-target-added-via-innerHTML-expected.txt @@ -0,0 +1,8 @@ +This test checks that an aria-labelledby relation resolves once its target element is inserted via innerHTML after the initial relations build. + + +PASS successfullyParsed is true + +TEST COMPLETE +fallback name +the resolved label diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-target-added-via-innerHTML.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-target-added-via-innerHTML.html new file mode 100644 index 0000000000000..8b986ae6cb368 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-target-added-via-innerHTML.html @@ -0,0 +1,40 @@ + + + + + + + + + + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-targeting-slot-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-targeting-slot-expected.txt new file mode 100644 index 0000000000000..d5b7572f3ac48 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-targeting-slot-expected.txt @@ -0,0 +1,7 @@ +This test ensures we compute the correct accessible name when aria-labelledby targets a slot element. + +#button accessible name: slot with child +PASS successfullyParsed is true + +TEST COMPLETE +slotwith child diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-targeting-slot.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-targeting-slot.html new file mode 100644 index 0000000000000..a17d001230d3e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-targeting-slot.html @@ -0,0 +1,31 @@ + + + + + + + + +slotwith child + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-text-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-text-expected.txt new file mode 100644 index 0000000000000..a8d053a3db52d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-text-expected.txt @@ -0,0 +1,15 @@ +This test ensures that aria-labelledby elements return the right AXDescription when their labels are updated. + +PASS: button1.description === 'AXDescription: Label for Button One' +PASS: button1.title === 'AXTitle: Label for Button One' +PASS: button2.description === 'AXDescription: ' +PASS: button2.description === 'AXDescription: Label for Button Two' +PASS: button2.title === 'AXTitle: Label for Button Two' +PASS: button1.description === 'AXDescription: Label for Button One!' +PASS: button1.title === 'AXTitle: Label for Button One!' + +PASS successfullyParsed is true + +TEST COMPLETE + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-text.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-text.html new file mode 100644 index 0000000000000..ef7d16efec8a0 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-text.html @@ -0,0 +1,48 @@ + + + + + + + + +
      + + +

      Label for Button One

      +

      Label for Button Two

      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-updates-when-aria-label-changes-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-updates-when-aria-label-changes-expected.txt new file mode 100644 index 0000000000000..3ebfabd42815c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-updates-when-aria-label-changes-expected.txt @@ -0,0 +1,13 @@ +This test ensures an aria-labelledby reference recomputes its accessible name when the referenced element's name changes via aria-label, not just via text content. + +PASS: platformValueForW3CName(textSection) === 'Text name' +PASS: platformValueForW3CName(labelSection) === 'Aria name' +PASS: platformValueForW3CName(textSection) === 'New text name' +PASS: platformValueForW3CName(labelSection) === 'New aria name' + +PASS successfullyParsed is true + +TEST COMPLETE +New text name + +Visual content diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-updates-when-aria-label-changes.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-updates-when-aria-label-changes.html new file mode 100644 index 0000000000000..2310cc072ad1f --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-updates-when-aria-label-changes.html @@ -0,0 +1,42 @@ + + + + + + + + +
      +

      Text name

      +
      + +
      + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-with-descendants-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-labelledby-with-descendants-expected.txt new file mode 100644 index 0000000000000..0eb2bfe3c65ab --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-with-descendants-expected.txt @@ -0,0 +1,32 @@ + + +This tests that if aria-labelledby is pointing to nodes with descendants, it returns all text. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +test 1: aria-labelledby description: hello link use world test1 test2 test3 +test 1: expected description: hello link use world test1 test2 test3 + +test 2: aria-labelledby description: foo bar +test 2: expected description: foo bar + +test 3: aria-labelledby description: foo bar +test 3: expected description: foo bar + +test 4: aria-labelledby description: foo +test 4: expected description: foo + +test 5: aria-labelledby description: Delete +test 5: expected description: Delete + +test 6: aria-labelledby description: Delete product name +test 6: expected description: Delete product name + +test 7: aria-labelledby description: foo bar baz bop bap boom +test 7: expected description: foo bar baz bop bap boom + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-labelledby-with-descendants.html b/LayoutTests/accessibility/isolated-tree/aria-labelledby-with-descendants.html new file mode 100644 index 0000000000000..09df265f94c43 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-labelledby-with-descendants.html @@ -0,0 +1,62 @@ + + + + + + + + +
      + +
      group text
      +
      hello link
      skip
      skip
      world
      + +

      test2 test3

      + + + +
      + + +
      foo skip
      +
      + + +
      foo
      + + + +
      + + + +
      + + +
      foo
      + +
      bop
      + + +
      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-link-supports-press-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-link-supports-press-expected.txt new file mode 100644 index 0000000000000..398c5e52ecc42 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-link-supports-press-expected.txt @@ -0,0 +1,3 @@ +Click + +SUCCESS! This test passes because the ARIA link above supports the press action. diff --git a/LayoutTests/accessibility/isolated-tree/aria-link-supports-press.html b/LayoutTests/accessibility/isolated-tree/aria-link-supports-press.html new file mode 100644 index 0000000000000..e10b505787788 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-link-supports-press.html @@ -0,0 +1,29 @@ + + + + + + +
      + +To run this test outside of DRT, use the Accessibility Inspector to inspect the link above, and make sure AXPressed is listed as a supported action. + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-list-and-listitem-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-list-and-listitem-expected.txt new file mode 100644 index 0000000000000..a758ae36c739e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-list-and-listitem-expected.txt @@ -0,0 +1,14 @@ + + +This tests that the ARIA roles of list and listitem map correctly to Mac accessibility roles. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +obj.childAtIndex(0).role = AXRole: AXList +obj.childAtIndex(0).childAtIndex(0).role = AXRole: AXListItem +obj.childAtIndex(0).childAtIndex(1).role = AXRole: AXListItem +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-list-and-listitem.html b/LayoutTests/accessibility/isolated-tree/aria-list-and-listitem.html new file mode 100644 index 0000000000000..516748e670f58 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-list-and-listitem.html @@ -0,0 +1,34 @@ + + + + + + + +
      +
      +
      +
      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-listbox-clear-selection-crash-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-listbox-clear-selection-crash-expected.txt new file mode 100644 index 0000000000000..3e250ec85a6b0 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-listbox-clear-selection-crash-expected.txt @@ -0,0 +1,11 @@ +Option 1 +Option 2 +Tests that attempting to clear the selection in an ARIA listbox doesn't cause a crash. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + +PASS listbox.selectedChildrenCount is 0 +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-listbox-clear-selection-crash.html b/LayoutTests/accessibility/isolated-tree/aria-listbox-clear-selection-crash.html new file mode 100644 index 0000000000000..057417f53db93 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-listbox-clear-selection-crash.html @@ -0,0 +1,22 @@ + + + + + + +
        +
      • Option 1
      • +
      • Option 2
      • +
      +
      +
      + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-listbox-no-selection-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-listbox-no-selection-expected.txt new file mode 100644 index 0000000000000..4f71969d01c17 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-listbox-no-selection-expected.txt @@ -0,0 +1,14 @@ +Option 1 +Option 2 +Option 3 +Tests that native listboxes with no selected children report no selected children. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS listbox.selectedChildrenCount is 0 +PASS listbox.selectedChildAtIndex(1) == null is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-listbox-no-selection.html b/LayoutTests/accessibility/isolated-tree/aria-listbox-no-selection.html new file mode 100644 index 0000000000000..196d03b54ed01 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-listbox-no-selection.html @@ -0,0 +1,25 @@ + + + + + + +
        +
      • Option 1
      • +
      • Option 2
      • +
      • Option 3
      • +
      +

      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-liveregion-marquee-default-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-liveregion-marquee-default-expected.txt new file mode 100644 index 0000000000000..c9ab4622a0cff --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-liveregion-marquee-default-expected.txt @@ -0,0 +1,13 @@ +marquee1 +marquee2 +This tests that the marquee role has the correct default aria-live status (off). + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS accessibilityController.accessibleElementById('marquee1').stringAttributeValue('AXARIALive') is 'off' +PASS accessibilityController.accessibleElementById('marquee2').stringAttributeValue('AXARIALive') is 'assertive' +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-liveregion-marquee-default.html b/LayoutTests/accessibility/isolated-tree/aria-liveregion-marquee-default.html new file mode 100644 index 0000000000000..02d3bd4464a8b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-liveregion-marquee-default.html @@ -0,0 +1,27 @@ + + + + + + + +
      marquee1
      +
      marquee2
      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-liveregions-attributes-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-liveregions-attributes-expected.txt new file mode 100644 index 0000000000000..eebff9041e9be --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-liveregions-attributes-expected.txt @@ -0,0 +1,45 @@ +This tests that the attributes used for ARIA live regions behave correctly. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS accessibilityController.focusedElement.isAttributeSupported('AXElementBusy') is true +PASS accessibilityController.focusedElement.isAttributeSupported('AXARIARelevant') is false +PASS accessibilityController.focusedElement.isAttributeSupported('AXARIAAtomic') is false +PASS accessibilityController.focusedElement.isAttributeSupported('AXARIALive') is false +PASS accessibilityController.focusedElement.stringAttributeValue('AXARIALive') is 'assertive' +PASS accessibilityController.focusedElement.stringAttributeValue('AXARIALive') is 'assertive' +PASS accessibilityController.focusedElement.stringAttributeValue('AXARIALive') is 'polite' +PASS accessibilityController.focusedElement.stringAttributeValue('AXARIALive') is 'polite' +PASS accessibilityController.focusedElement.stringAttributeValue('AXARIALive') is 'off' +PASS accessibilityController.focusedElement.stringAttributeValue('AXARIALive') is 'polite' +PASS accessibilityController.focusedElement.stringAttributeValue('AXARIARelevant') is 'additions' +PASS accessibilityController.focusedElement.boolAttributeValue('AXElementBusy') is true +PASS accessibilityController.focusedElement.isAttributeSupported('AXARIALive') is false +PASS accessibilityController.focusedElement.isAttributeSupported('AXARIARelevant') is false +PASS accessibilityController.focusedElement.boolAttributeValue('AXARIAAtomic') is true +PASS accessibilityController.focusedElement.boolAttributeValue('AXElementBusy') is false +PASS accessibilityController.focusedElement.stringAttributeValue('AXARIARelevant') is 'additions text' +PASS accessibilityController.focusedElement.stringAttributeValue('AXARIALive') is 'assertive' +PASS accessibilityController.focusedElement.boolAttributeValue('AXARIAAtomic') is true +PASS accessibilityController.focusedElement.stringAttributeValue('AXARIALive') is 'polite' +PASS accessibilityController.focusedElement.boolAttributeValue('AXARIAAtomic') is true +PASS accessibilityController.focusedElement.stringAttributeValue('AXARIALive') is 'polite' +PASS accessibilityController.focusedElement.boolAttributeValue('AXARIAAtomic') is false +PASS accessibilityController.focusedElement.isAttributeSupported('AXARIALive') is false +PASS accessibilityController.focusedElement.boolAttributeValue('AXARIAAtomic') is false +PASS accessibilityController.focusedElement.isAttributeSupported('AXARIALive') is false +PASS accessibilityController.focusedElement.boolAttributeValue('AXARIAAtomic') is false +PASS successfullyParsed is true + +TEST COMPLETE +no live region + +test +test +test +test +test +h3 + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-liveregions-attributes.html b/LayoutTests/accessibility/isolated-tree/aria-liveregions-attributes.html new file mode 100644 index 0000000000000..45d6f9161e794 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-liveregions-attributes.html @@ -0,0 +1,104 @@ + + + + + + + + + +

      no live region

      + + + +
      test
      +
      test
      +
      test
      +
      test
      + + +
      +

      h3

      +
      + +
      + + + +
      +
      +
      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-mappings-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-mappings-expected.txt new file mode 100644 index 0000000000000..1ce09dc2b4f0e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-mappings-expected.txt @@ -0,0 +1,56 @@ +alert role +alertdialog role +article role +dialog role +document role +status role +tooltip role +tree role +treeitem role + +This tests that each of these ARIA roles have appropriate mappings. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +Role for 'body' is: AXRole: AXWebArea + + +role="alert" should give a message with important, and usually time-sensitive, information. +Role for 'alert' div is: AXRole: AXNotification + + +role="alertdialog" is a dialog which contains an alert message. +Role for 'alertdialog' div is: AXRole: AXAlert + + +role="article" is a section of a page that consists of a composition that forms an independent part of a document, page, or site +Role for 'article' div is: AXRole: AXArticle + + +role="dialog" is an application window that is designed to interrupt the current processing of an application in order to prompt the user to enter information or require a response. +Role for 'dialog' div is: AXRole: AXDialog + + +role="document" is a region containing related information that is declared as document content, as opposed to a web application. +Role for 'document' div is: AXRole: AXDocument + + +role="status" is a container whose content is advisory information for the user but is not important enough to justify an alert, often but not necessarily presented as a status bar. +Role for 'status' div is: AXRole: AXStatusBar + + +role="tooltip" is a contextual popup that displays a description for an element. +Role for 'tooltip' div is: AXRole: AXUserInterfaceTooltip + + +role="tree" is a type of list that may contain sub-level nested groups that can be collapsed and expanded. +Role for 'tree' div is: AXRole: AXTree + + +role="treeitem" is an option item of a tree. +Role for 'treeitem' div is: AXRole: AXTreeItem +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-mappings.html b/LayoutTests/accessibility/isolated-tree/aria-mappings.html new file mode 100644 index 0000000000000..1b5239566206a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-mappings.html @@ -0,0 +1,74 @@ + + + + + + + + + +
      alertdialog role
      +
      article role
      + +
      document role
      +
      status role
      + +
      tree role
      +
      treeitem role
      +
      +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-menubar-menuitems.html b/LayoutTests/accessibility/isolated-tree/aria-menubar-menuitems.html new file mode 100644 index 0000000000000..77d8d33c6e5b6 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-menubar-menuitems.html @@ -0,0 +1,48 @@ + + + + + + + + + +
      
      +

      +
      + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-modal-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-modal-expected.txt new file mode 100644 index 0000000000000..73b3af618ccb5 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-modal-expected.txt @@ -0,0 +1,38 @@ +This tests that aria-modal on dialog makes other elements inert. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +Dialog is displaying +PASS backgroundAccessible() is false +Dialog is not displaying +PASS backgroundAccessible() is true +Dialog is displaying +PASS backgroundAccessible() is false +PASS okButton.isIgnored is false +Dialog is not displaying and aria-modal=true +PASS backgroundAccessible() is true +Dialog is displaying +PASS backgroundAccessible() is false +Dialog is displaying and aria-hidden=true +PASS backgroundAccessible() is true +Dialog is displaying and removed aria-hidden +PASS backgroundAccessible() is false +Dialog is not displaying with opacity 0 +PASS backgroundAccessible() is true +Dialog is displaying with opacity 1 +PASS backgroundAccessible() is false +Dialog is not displaying with parent opacity 0 +PASS backgroundAccessible() is true +Dialog is displaying with parent opacity .5 +PASS backgroundAccessible() is false +Dialog is removed from DOM +PASS backgroundAccessible() is true +PASS successfullyParsed is true + +TEST COMPLETE +Other page content with a dummy focusable element + +Display a dialog + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-modal-in-aria-hidden-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-modal-in-aria-hidden-expected.txt new file mode 100644 index 0000000000000..3831d9ced9866 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-modal-in-aria-hidden-expected.txt @@ -0,0 +1,12 @@ +This tests that when something is aria-modal inside an aria-hidden it is ignored. + +PASS: accessibilityController.accessibleElementById('bgContent').isIgnored === false +PASS: !accessibilityController.accessibleElementById('bgContent') === true +PASS: accessibilityController.accessibleElementById('bgContent')?.isIgnored === false + +PASS successfullyParsed is true + +TEST COMPLETE +Other page content with a dummy focusable element + +Just an example. diff --git a/LayoutTests/accessibility/isolated-tree/aria-modal-in-aria-hidden.html b/LayoutTests/accessibility/isolated-tree/aria-modal-in-aria-hidden.html new file mode 100644 index 0000000000000..346f8018f0f98 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-modal-in-aria-hidden.html @@ -0,0 +1,41 @@ + + + + + + + + +
      +

      Other page content with a dummy focusable element

      +
      + +
      + +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-modal-multiple-dialogs-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-modal-multiple-dialogs-expected.txt new file mode 100644 index 0000000000000..2dd06a365a197 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-modal-multiple-dialogs-expected.txt @@ -0,0 +1,50 @@ +This tests that aria-modal works correctly on multiple dialogs + + +Verifying the background is accessible on page load. + +PASS: background accessible: true + +Clicking the display button to open #dialog1. + +PASS: background accessible: false +PASS: #dialog1 accessible: true + +Clicking the new button to open #dialog2 without closing #dialog1. + +PASS: background accessible: false +PASS: #dialog1 accessible: false +PASS: #dialog2 accessible: true + +Focusing first descendant of #dialog1. + +PASS: background accessible: false +PASS: #dialog1 accessible: true +PASS: #dialog2 accessible: false + +Focusing on background. + +PASS: background accessible: true + +Moving focus back to first descendant of #dialog2. + +PASS: background accessible: false +PASS: #dialog1 accessible: false +PASS: #dialog2 accessible: true + +Closing dialog2. + +PASS: background accessible: false +PASS: #dialog1 accessible: true + +Closing dialog1. + +PASS: background accessible: true + +PASS successfullyParsed is true + +TEST COMPLETE + +Other page content with a dummy focusable element + +Display a dialog diff --git a/LayoutTests/accessibility/isolated-tree/aria-modal-multiple-dialogs.html b/LayoutTests/accessibility/isolated-tree/aria-modal-multiple-dialogs.html new file mode 100644 index 0000000000000..8d9372e0bea88 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-modal-multiple-dialogs.html @@ -0,0 +1,140 @@ + + + + + + + + + + +

      Other page content with a dummy focusable element

      +

      Display a dialog

      + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-modal-with-text-crash-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-modal-with-text-crash-expected.txt new file mode 100644 index 0000000000000..e02b916e5189e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-modal-with-text-crash-expected.txt @@ -0,0 +1,10 @@ +This test ensures we don't crash when using search to traverse an aria-modal with text. + + +AXRole: AXStaticText +AXValue: Foo + +PASS successfullyParsed is true + +TEST COMPLETE +Foo diff --git a/LayoutTests/accessibility/isolated-tree/aria-modal-with-text-crash.html b/LayoutTests/accessibility/isolated-tree/aria-modal-with-text-crash.html new file mode 100644 index 0000000000000..67c6e056d88c4 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-modal-with-text-crash.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-modal.html b/LayoutTests/accessibility/isolated-tree/aria-modal.html new file mode 100644 index 0000000000000..af40fe1b035f0 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-modal.html @@ -0,0 +1,159 @@ + + + + + + + + + +
      +

      Other page content with a dummy focusable element

      +

      Display a dialog

      +
      + +
      + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-multiline-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-multiline-expected.txt new file mode 100644 index 0000000000000..ea46158ed8880 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-multiline-expected.txt @@ -0,0 +1,14 @@ +This tests that aria-multiline will change the role of a text control from a text field to a text area. + +#textfield role: AXRole: AXTextField +#textarea role: AXRole: AXTextArea + +Setting #textfield aria-multiline to true and #textarea aria-multiline to false. +#textfield role: AXRole: AXTextArea +#textarea role: AXRole: AXTextField + +PASS successfullyParsed is true + +TEST COMPLETE +a +b diff --git a/LayoutTests/accessibility/isolated-tree/aria-multiline.html b/LayoutTests/accessibility/isolated-tree/aria-multiline.html new file mode 100644 index 0000000000000..2298e04b34148 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-multiline.html @@ -0,0 +1,41 @@ + + + + + + + + +
      a
      +
      b
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-multiselectable-grid-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-multiselectable-grid-expected.txt new file mode 100644 index 0000000000000..64b60101a7f8b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-multiselectable-grid-expected.txt @@ -0,0 +1,13 @@ +This tests that aria-multiselectable is exposed correctly for grids. + +PASS: gridMultiselectableUnspecified.isMultiSelectable === true +PASS: gridMultiselectableTrue.isMultiSelectable === true +PASS: gridMultiselectableFalse.isMultiSelectable === false + +Updating aria-multiselectable for #grid3 from false to true. +PASS: accessibilityController.accessibleElementById('grid3').isMultiSelectable === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-multiselectable-grid.html b/LayoutTests/accessibility/isolated-tree/aria-multiselectable-grid.html new file mode 100644 index 0000000000000..5194187986d72 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-multiselectable-grid.html @@ -0,0 +1,41 @@ + + + + + + + + +
      +
      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-namefrom-author-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-namefrom-author-expected.txt new file mode 100644 index 0000000000000..e91181c1d4291 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-namefrom-author-expected.txt @@ -0,0 +1,11 @@ +This tests all the cases where nameFrom: author is used instead of nameFrom: contents. This means that if these elements are used in aria-labelledby they should not return their inner text. The button should retain its aria-label. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS buttonAccName == button.getAttribute('aria-label') is true +PASS buttonAccName != button.innerText is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-namefrom-author.html b/LayoutTests/accessibility/isolated-tree/aria-namefrom-author.html new file mode 100644 index 0000000000000..7210d66c4e00e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-namefrom-author.html @@ -0,0 +1,81 @@ + + + + + + + + +
      + + +
      alertdialog
      + +
      log
      +
      marquee
      +
      status
      +
      timer
      +
      combobox
      +
      definition
      +
      document
      +
      article
      +
      math
      +
      note
      +
      table
      +
      grid
      +
      group
      + +
      list
      +
      listbox
      +
      application
      + + + + +
      main
      + + + +
      progressbar
      +
      radiogroup
      +
      scrollbar
      +
      slider
      +
      spinbutton
      + +
      tablist
      +
      tabpanel
      +
      textbox
      + +
      treegrid
      +
      tree
      + + +
      button text
      +
      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-none-role-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-none-role-expected.txt new file mode 100644 index 0000000000000..fcc31674391bd --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-none-role-expected.txt @@ -0,0 +1,12 @@ +Link and text + +This tests that the aria 'none' role works by successfully removing the element from the AX tree. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +firstChild.role is AXRole: AXLink +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-none-role.html b/LayoutTests/accessibility/isolated-tree/aria-none-role.html new file mode 100644 index 0000000000000..85279c7a5fc51 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-none-role.html @@ -0,0 +1,31 @@ + + + + + + + +

      +Link and text +

      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-option-role-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-option-role-expected.txt new file mode 100644 index 0000000000000..b05eb5b109ea1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-option-role-expected.txt @@ -0,0 +1,17 @@ +option 1 +option 2 +This tests that the aria 'option' role works as expected. That is, it becomes a static text element with no children. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +firstChild.role is AXRole: AXListItem +firstChild.title is AXTitle: option 1 +secondChild.role is AXRole: AXListItem +secondChild.description is AXDescription: label 2 +PASS firstChild.childrenCount is 0 +PASS secondChild.childrenCount is 0 +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-option-role.html b/LayoutTests/accessibility/isolated-tree/aria-option-role.html new file mode 100644 index 0000000000000..c2e2cf7be14a4 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-option-role.html @@ -0,0 +1,37 @@ + + + + + + + +
      +
      option 1
      +
      option 2
      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-orientation-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-orientation-expected.txt new file mode 100644 index 0000000000000..30f1ce0d93b48 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-orientation-expected.txt @@ -0,0 +1,28 @@ +This test ensures that aria-orientation works correctly and the implicit defaults are defined on different roles. + +PASS: slider.orientation === 'AXOrientation: AXHorizontalOrientation' +PASS: combobox.orientation === 'AXOrientation: AXUnknownOrientation' +PASS: listbox.orientation === 'AXOrientation: AXVerticalOrientation' +PASS: menu.orientation === 'AXOrientation: AXVerticalOrientation' +PASS: menubar.orientation === 'AXOrientation: AXHorizontalOrientation' +PASS: radiogroup.orientation === 'AXOrientation: AXUnknownOrientation' +PASS: separator.orientation === 'AXOrientation: AXHorizontalOrientation' +PASS: tablist.orientation === 'AXOrientation: AXHorizontalOrientation' +PASS: toolbar.orientation === 'AXOrientation: AXHorizontalOrientation' +PASS: tree.orientation === 'AXOrientation: AXVerticalOrientation' +PASS: treegrid.orientation === 'AXOrientation: AXUnknownOrientation' +PASS: radiogroup2.orientation === 'AXOrientation: AXVerticalOrientation' +PASS: treegrid2.orientation === 'AXOrientation: AXHorizontalOrientation' +PASS: separator2.orientation === 'AXOrientation: AXUnknownOrientation' +PASS: slider2.orientation === 'AXOrientation: AXUnknownOrientation' +PASS: listbox2.orientation === 'AXOrientation: AXHorizontalOrientation' +PASS: listboxDisplayContents.orientation === 'AXOrientation: AXVerticalOrientation' + +Updating #listbox-display-contents aria-orientation to horizontal. +PASS: listboxDisplayContents.orientation === 'AXOrientation: AXHorizontalOrientation' + +PASS successfullyParsed is true + +TEST COMPLETE + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-orientation.html b/LayoutTests/accessibility/isolated-tree/aria-orientation.html new file mode 100644 index 0000000000000..4bb07dd1ea51b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-orientation.html @@ -0,0 +1,130 @@ + + + + + + + + +
      +
      Implicit defaults
      + + +
      +
      Option
      +
      + + +
      +
      radio 1
      +
      + +
      + +
        +
      • tree item
      • +
      + + + + + +
      cell
      cell2
      + +
      +
      Authored orientation
      +
      +
      radio 1
      +
      + + + + + +
      cell
      cell2
      + + X +
      +
      Option
      +
      +
      +
      Option
      +
      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-crash-after-subtree-update-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-owns-crash-after-subtree-update-expected.txt new file mode 100644 index 0000000000000..42ba4ba0476c9 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-crash-after-subtree-update-expected.txt @@ -0,0 +1,20 @@ +This tests that mixed DOM + aria-owns cycles do not cause stack-overflow recursion in updateOwnedChildrenIfNecessary(). + +PASS: typeof t1a.childrenCount === 'number' +PASS: typeof t2a.childrenCount === 'number' +PASS: typeof t3self.childrenCount === 'number' +PASS: typeof t4a.childrenCount === 'number' +PASS: did not stack overflow. + +PASS successfullyParsed is true + +TEST COMPLETE +a +b +c +child of t2-a +b +self +a-mutated +b +c diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-crash-after-subtree-update.html b/LayoutTests/accessibility/isolated-tree/aria-owns-crash-after-subtree-update.html new file mode 100644 index 0000000000000..3297081150366 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-crash-after-subtree-update.html @@ -0,0 +1,60 @@ + + + + + + + + + +
      a
      +
      b
      +
      c
      + + +
      +
      child of t2-a
      +
      +
      b
      + + +
      self
      + + +
      +
      a
      +
      b
      +
      c
      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-cycles-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-owns-cycles-expected.txt new file mode 100644 index 0000000000000..404640414e69c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-cycles-expected.txt @@ -0,0 +1,11 @@ +This tests that aria-owns cycles don't lead to crashes + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS: ax.accessibleElementById('a').childAtIndex(0).isEqual(ax.accessibleElementById('b')) === true + +PASS successfullyParsed is true + +TEST COMPLETE +text diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-cycles.html b/LayoutTests/accessibility/isolated-tree/aria-owns-cycles.html new file mode 100644 index 0000000000000..d53b391c5f279 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-cycles.html @@ -0,0 +1,25 @@ + + + + + + + + +
      +
      text
      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-owns-expected.txt new file mode 100644 index 0000000000000..d8f011efc586e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-expected.txt @@ -0,0 +1,25 @@ +This tests that aria-owns correctly exposes AXOwns and correctly returns the right elements + +PASS: group.isAttributeSupported('AXOwns') === true +PASS: group.ariaOwnsElementAtIndex(0).role === 'AXRole: AXButton' +PASS: group.ariaOwnsElementAtIndex(0).title === 'AXTitle: BUTTON' +PASS: group.ariaOwnsElementAtIndex(1).role === 'AXRole: AXRadioButton' +PASS: group.ariaOwnsElementAtIndex(1).title === 'AXTitle: RADIO BUTTON' +PASS: group.childrenCount === 5 +PASS: group.childAtIndex(4).title === 'AXTitle: RADIO BUTTON' +document.getElementById('group').removeAttribute('aria-owns') +PASS: group.childrenCount === 3 +PASS: group.isAttributeSupported('AXOwns') === false +document.getElementById('group').setAttribute('aria-owns', 'extra') +PASS: group.childrenCount === 4 +PASS: group.isAttributeSupported('AXOwns') === true +PASS: group.childAtIndex(3).title === 'AXTitle: BUTTON' + +PASS successfullyParsed is true + +TEST COMPLETE +Item 1 +Item 2 +Item 3 +BUTTON +RADIO BUTTON diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-grid-parts-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-owns-grid-parts-expected.txt new file mode 100644 index 0000000000000..d20300a5be51e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-grid-parts-expected.txt @@ -0,0 +1,52 @@ +This tests that ARIA grids correctly recognize rows and cells via aria-owns, including after dynamic attribute changes. + +Testing grid with aria-owned rows: +PASS: ownedRowsGrid.rowCount === 2 +PASS: ownedRowsGrid.columnCount === 2 +PASS: ownedRowsGrid.width >= 2 === true +PASS: ownedRowsGrid.height >= 2 === true +PASS: ownedRowsGrid.cellForColumnAndRow(0, 0).domIdentifier === 'owned-row-cell-1-1' +PASS: ownedRowsGrid.cellForColumnAndRow(1, 1).domIdentifier === 'owned-row-cell-2-2' + +Testing grid with aria-owned cells: +PASS: ownedCellsGrid.rowCount === 1 +PASS: ownedCellsGrid.columnCount === 2 +PASS: ownedCellsGrid.width >= 2 === true +PASS: ownedCellsGrid.height >= 2 === true +PASS: ownedCellsGrid.cellForColumnAndRow(0, 0).domIdentifier === 'owned-cell-1' +PASS: ownedCellsGrid.cellForColumnAndRow(1, 0).domIdentifier === 'owned-cell-2' + +Testing grid with DOM children that are also aria-owned (ordering test): +PASS: domAndOwnedGrid.rowCount === 3 +PASS: domAndOwnedGrid.width >= 2 === true +PASS: domAndOwnedGrid.height >= 2 === true +PASS: domAndOwnedGrid.cellForColumnAndRow(0, 0).domIdentifier === 'dom-row-C-cell' +PASS: domAndOwnedGrid.cellForColumnAndRow(0, 1).domIdentifier === 'dom-row-B-cell' +PASS: domAndOwnedGrid.cellForColumnAndRow(0, 2).domIdentifier === 'dom-row-A-cell' + +Adding row-3 to aria-owns... +PASS: ownedRowsGrid.rowCount === 3 +PASS: ownedRowsGrid.cellForColumnAndRow(0, 2).domIdentifier === 'owned-row-cell-3-1' + +Adding owned-cell-3 to row... +PASS: ownedCellsGrid.cellForColumnAndRow(2, 0).domIdentifier === 'owned-cell-3' + +Removing owned-row-1 from aria-owns... +PASS: ownedRowsGrid.rowCount === 2 +PASS: ownedRowsGrid.cellForColumnAndRow(0, 0).domIdentifier === 'owned-row-cell-2-1' + +PASS successfullyParsed is true + +TEST COMPLETE +Owned Row 1 Cell 1 +Owned Row 1 Cell 2 +Owned Row 2 Cell 1 +Owned Row 2 Cell 2 +Owned Row 3 Cell 1 +Owned Row 3 Cell 2 +Owned Cell 1 +Owned Cell 2 +Owned Cell 3 +Row A Cell +Row B Cell +Row C Cell diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-grid-parts.html b/LayoutTests/accessibility/isolated-tree/aria-owns-grid-parts.html new file mode 100644 index 0000000000000..bad8472a37a15 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-grid-parts.html @@ -0,0 +1,108 @@ + + + + + + + + + +
      +
      +
      Owned Row 1 Cell 1
      +
      Owned Row 1 Cell 2
      +
      +
      +
      Owned Row 2 Cell 1
      +
      Owned Row 2 Cell 2
      +
      +
      +
      Owned Row 3 Cell 1
      +
      Owned Row 3 Cell 2
      +
      + + +
      +
      +
      +
      Owned Cell 1
      +
      Owned Cell 2
      +
      Owned Cell 3
      + + +
      +
      +
      Row A Cell
      +
      +
      +
      Row B Cell
      +
      +
      +
      Row C Cell
      +
      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-hierarchy-remap-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-owns-hierarchy-remap-expected.txt new file mode 100644 index 0000000000000..1e42c58b38a23 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-hierarchy-remap-expected.txt @@ -0,0 +1,30 @@ +This tests that aria-owns can remap accessibility hierarchies. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS: list1.childrenCount === 2 +PASS: list1.childAtIndex(0).isEqual(item3) === true +PASS: list1.childAtIndex(1).isEqual(item4) === true +PASS: item3.parentElement().isEqual(list1) === true +PASS: item4.parentElement().isEqual(list1) === true +PASS: list2.childrenCount === 3 +PASS: list2.childAtIndex(0).isEqual(item1) === true +PASS: list2.childAtIndex(1).isEqual(realitem1) === true +PASS: list2.childAtIndex(2).isEqual(item2) === true +PASS: item1.parentElement().isEqual(list2) === true +PASS: realitem1.parentElement().isEqual(list2) === true +PASS: item2.parentElement().isEqual(list2) === true +PASS: list3.childrenCount === 0 +PASS: list1.childrenCount === 3 +PASS: list1.childAtIndex(0).isEqual(item3) === true +PASS: list1.childAtIndex(1).isEqual(item5) === true +PASS: list1.childAtIndex(2).isEqual(item4) === true +PASS: item3.parentElement().isEqual(list1) === true +PASS: item5.parentElement().isEqual(list1) === true +PASS: item4.parentElement().isEqual(list1) === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-hierarchy-remap.html b/LayoutTests/accessibility/isolated-tree/aria-owns-hierarchy-remap.html new file mode 100644 index 0000000000000..60e3c599c074b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-hierarchy-remap.html @@ -0,0 +1,87 @@ + + + + + + + + +
      + +
      +
      1
      +
      2
      +
      + +
        +
      • real 1
      • +
        3
        +
        4
        +
      + +
      +
      item5
      +
      + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-id-change-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-owns-id-change-expected.txt new file mode 100644 index 0000000000000..65662d5e05e57 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-id-change-expected.txt @@ -0,0 +1,34 @@ +This test ensures aria-owns relationships update when a target element's id changes. + +Initial state - owner should have 2 children (Item 1 + owned button), other-owner should have 1 child: +PASS: owner.childrenCount === 2 +PASS: owner.childAtIndex(1).role.toLowerCase().includes('button') === true +PASS: otherOwner.childrenCount === 1 + +Changing the owned element's id from 'owned' to 'not-owned': +document.getElementById('owned').id = 'not-owned' + +Owner should now only have 1 child (Item 1): +PASS: owner.childrenCount === 1 + +Changing the element's id back to 'owned': +document.getElementById('not-owned').id = 'owned' + +Owner should now have 2 children again: +PASS: owner.childrenCount === 2 +PASS: owner.childAtIndex(1).role.toLowerCase().includes('button') === true + +Testing ownership transfer - changing id to match other-owner's aria-owns: +document.getElementById('owned').id = 'different-id' + +Owner should have 1 child, other-owner should have 2 children: +PASS: owner.childrenCount === 1 +PASS: otherOwner.childrenCount === 2 +PASS: otherOwner.childAtIndex(1).role.toLowerCase().includes('button') === true + +PASS successfullyParsed is true + +TEST COMPLETE +Item 1 +Item 2 +Owned button diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-id-change.html b/LayoutTests/accessibility/isolated-tree/aria-owns-id-change.html new file mode 100644 index 0000000000000..a229f308b35cc --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-id-change.html @@ -0,0 +1,60 @@ + + + + + + + + +
      +
      Item 1
      +
      + +
      +
      Item 2
      +
      + +
      Owned button
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-text-stitching-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-owns-text-stitching-expected.txt new file mode 100644 index 0000000000000..54dae8ee6d29c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-text-stitching-expected.txt @@ -0,0 +1,18 @@ +This test ensures we handle text stitching appropriately for text within aria-owns. + + +{AXRole: AXStaticText AXValue: Foo } + +{AXRole: AXStaticText AXValue: Baz} + +{AXRole: AXStaticText AXValue: Bar} + +Second traversal: + +{AXRole: AXStaticText AXValue: Foo Bar Baz} + +PASS successfullyParsed is true + +TEST COMPLETE +Foo Bar Baz + diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns-text-stitching.html b/LayoutTests/accessibility/isolated-tree/aria-owns-text-stitching.html new file mode 100644 index 0000000000000..9ada24a0ca1c1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns-text-stitching.html @@ -0,0 +1,44 @@ + + + + + + + + +
      + + Foo Bar Baz +
      + +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-owns.html b/LayoutTests/accessibility/isolated-tree/aria-owns.html new file mode 100644 index 0000000000000..cb6559bf2ed4c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-owns.html @@ -0,0 +1,55 @@ + + + + + + + + +
      +
      Item 1
      +
      Item 2
      +
      Item 3
      +
      + +
      BUTTON
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-presentational-role-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-presentational-role-expected.txt new file mode 100644 index 0000000000000..d09e6631d87c0 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-presentational-role-expected.txt @@ -0,0 +1,12 @@ +Link and text + +This tests that the aria 'presentation' role works by successfully removing the element from the AX tree. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +firstChild.role is AXRole: AXLink +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-presentational-role.html b/LayoutTests/accessibility/isolated-tree/aria-presentational-role.html new file mode 100644 index 0000000000000..e686c00b992d1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-presentational-role.html @@ -0,0 +1,31 @@ + + + + + + + +

      +Link and text +

      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-readonly-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-readonly-expected.txt new file mode 100644 index 0000000000000..e1f93c4de2237 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-readonly-expected.txt @@ -0,0 +1,58 @@ +This tests that the readonly state of the AXValue property is correctly reported for native and non-native elements. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +Elements to test: 47 + +PASS htmlEditableDivIsWritable is true +PASS htmlEditableDiv2IsWritable is false +PASS htmlEditableDiv3IsWritable is true +PASS htmlEditableDiv4IsWritable is true +PASS htmlEditableDiv5IsWritable is false +PASS htmlEditableDiv6IsWritable is true +PASS htmlNonEditableDivIsWritable is false +PASS htmlNonEditableDiv2IsWritable is false +PASS htmlNonEditableDiv3IsWritable is true +PASS ariaTextBoxIsWritable is true +PASS ariaReadOnlyAriaTextBoxIsWritable is false +PASS htmlReadOnlyTextFieldIsWritable is false +PASS htmlReadOnlyTextField2IsWritable is false +PASS htmlReadOnlyTextField3IsWritable is false +PASS htmlReadOnlyTextField4IsWritable is false +PASS htmlReadOnlyTextAreaIsWritable is false +PASS htmlReadOnlyTextArea2IsWritable is false +PASS htmlReadOnlyTextArea3IsWritable is false +PASS htmlReadOnlyTextArea3IsWritable is false +PASS textFieldIsWritable is true +PASS ariaReadOnlyTextFieldIsWritable is true +PASS ariaNonReadOnlyTextFieldIsWritable is true +PASS textAreaIsWritable is true +PASS textArea2IsWritable is true +PASS textArea3IsWritable is true +PASS ariaGridCellIsWritable is false +PASS ariaGridCell2IsWritable is true +PASS ariaColumnHeaderIsWritable is false +PASS ariaColumnHeader2IsWritable is true +PASS ariaRowHeaderIsWritable is false +PASS ariaRowHeader2IsWritable is true +PASS ariaGridIsWritable is false +PASS ariaGrid2IsWritable is true +PASS ariaTreeGridIsWritable is false +PASS ariaTreeGrid2IsWritable is true +PASS ariaGridCell3IsWritable is false +PASS ariaGridCell4IsWritable is true +PASS ariaGridCell5IsWritable is false +PASS ariaGridCell6IsWritable is true +PASS ariaColumnHeader3IsWritable is false +PASS ariaColumnHeader4IsWritable is true +PASS ariaColumnHeader5IsWritable is false +PASS ariaColumnHeader6IsWritable is true +PASS ariaRowHeader3IsWritable is false +PASS ariaRowHeader4IsWritable is true +PASS ariaRowHeader5IsWritable is false +PASS ariaRowHeader6IsWritable is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-readonly-updates-after-dynamic-change-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-readonly-updates-after-dynamic-change-expected.txt new file mode 100644 index 0000000000000..c7fec5ee0bbca --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-readonly-updates-after-dynamic-change-expected.txt @@ -0,0 +1,12 @@ +This test ensures that objects readonly status is updated after dynamically changing aria-readonly. + +PASS: axElement.isAttributeSettable('AXValue') === true +Updating aria-readonly to true. +PASS: axElement.isAttributeSettable('AXValue') === false +Updating aria-readonly to false. +PASS: axElement.isAttributeSettable('AXValue') === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-readonly-updates-after-dynamic-change.html b/LayoutTests/accessibility/isolated-tree/aria-readonly-updates-after-dynamic-change.html new file mode 100644 index 0000000000000..b1a6b957be1b7 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-readonly-updates-after-dynamic-change.html @@ -0,0 +1,38 @@ + + + + + + + + +
      + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-readonly.html b/LayoutTests/accessibility/isolated-tree/aria-readonly.html new file mode 100644 index 0000000000000..8c5619fe705d8 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-readonly.html @@ -0,0 +1,102 @@ + + + + + + +
      + + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + + + +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-relations-disconnected-subtree-no-timeout-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-relations-disconnected-subtree-no-timeout-expected.txt new file mode 100644 index 0000000000000..7237e3b997b06 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-relations-disconnected-subtree-no-timeout-expected.txt @@ -0,0 +1,10 @@ +This test ensures a large disconnected subtree whose elements carry ARIA relation attributes does not trigger a quadratic getElementByIdIncludingDisconnected() scan when accessibility relations are rebuilt. + +PASS: controlLabel.isEqual(control.titleUIElement()) === true +PASS: Did not timeout. + +PASS successfullyParsed is true + +TEST COMPLETE +fallback name +the resolved label diff --git a/LayoutTests/accessibility/isolated-tree/aria-relations-disconnected-subtree-no-timeout.html b/LayoutTests/accessibility/isolated-tree/aria-relations-disconnected-subtree-no-timeout.html new file mode 100644 index 0000000000000..8c1aaef1ae14a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-relations-disconnected-subtree-no-timeout.html @@ -0,0 +1,60 @@ + + + + + + + + + +

      the resolved label

      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-required-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-required-expected.txt new file mode 100644 index 0000000000000..9eba752423b79 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-required-expected.txt @@ -0,0 +1,50 @@ +This tests that aria-required is a usable attribute. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +Elements to test: 39 + +PASS textfield isRequired is false +PASS textfield_required isRequired is true +PASS textfield_required_ariarequired isRequired is true +PASS textfield_ariarequiredfalse isRequired is false +PASS textfield_required_ariarequiredfalse isRequired is true +PASS textfield_requiredfalse_ariarequiredtrue isRequired is true +PASS checkbox isRequired is false +PASS checkbox_required isRequired is true +PASS checkbox_required_ariarequired isRequired is true +PASS checkbox_ariarequiredfalse isRequired is false +PASS checkbox_required_ariarequiredfalse isRequired is true +PASS checkbox_requiredfalse_ariarequiredtrue isRequired is true +PASS select isRequired is false +PASS select_required isRequired is true +PASS select_required_ariarequired isRequired is true +PASS select_ariarequiredfalse isRequired is false +PASS select_required_ariarequiredfalse isRequired is true +PASS select_requiredfalse_ariarequiredtrue isRequired is true +PASS textarea isRequired is false +PASS textarea_required isRequired is true +PASS textarea_required_ariarequired isRequired is true +PASS textarea_ariarequiredfalse isRequired is false +PASS textarea_required_ariarequiredfalse isRequired is true +PASS textarea_requiredfalse_ariarequiredtrue isRequired is true +PASS listbox_ariarequiredtrue isRequired is true +PASS listbox_ariarequiredfalse isRequired is false +PASS listbox isRequired is false +PASS radiogroup_ariarequiredtrue isRequired is true +PASS radiogroup_ariarequiredfalse isRequired is false +PASS radiogroup isRequired is false +PASS spinbutton_ariarequiredtrue isRequired is true +PASS spinbutton_ariarequiredfalse isRequired is false +PASS spinbutton isRequired is false +PASS tree_ariarequiredtrue isRequired is true +PASS tree_ariarequiredfalse isRequired is false +PASS tree isRequired is false +PASS switch_ariarequiredtrue isRequired is true +PASS switch_ariarequiredfalse isRequired is false +PASS switch isRequired is false +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-required-updates-after-dynamic-change-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-required-updates-after-dynamic-change-expected.txt new file mode 100644 index 0000000000000..50c71234ec6ad --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-required-updates-after-dynamic-change-expected.txt @@ -0,0 +1,12 @@ +This tests that changing the aria-required attribute is properly reflected in an AX object's required status. + +#textbox is required: false +Setting aria-required to true on #textbox. +#textbox is required: true +Setting aria-required to false on #textbox. +#textbox is required: false + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-required-updates-after-dynamic-change.html b/LayoutTests/accessibility/isolated-tree/aria-required-updates-after-dynamic-change.html new file mode 100644 index 0000000000000..e26b2a8fa90f3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-required-updates-after-dynamic-change.html @@ -0,0 +1,44 @@ + + + + + + + + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-required.html b/LayoutTests/accessibility/isolated-tree/aria-required.html new file mode 100644 index 0000000000000..df92e782e72a9 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-required.html @@ -0,0 +1,108 @@ + + + + + + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      text
      +
      text
      +
      text
      + +
      text
      +
      text
      +
      text
      + +
      text
      +
      text
      +
      text
      + +
      text
      +
      text
      +
      text
      + +
      text
      +
      text
      +
      text
      + +
      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-role-on-label.html b/LayoutTests/accessibility/isolated-tree/aria-role-on-label.html new file mode 100644 index 0000000000000..37f4b73768efc --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-role-on-label.html @@ -0,0 +1,26 @@ + + + + + + + + +

      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-roledescription-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-roledescription-expected.txt new file mode 100644 index 0000000000000..ebc16a728e9ac --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-roledescription-expected.txt @@ -0,0 +1,15 @@ +This tests that aria-roledescription works. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS axButton.roleDescription is 'AXRoleDescription: Super Button' +PASS Got default role description: AXRoleDescription: button +PASS axButton.roleDescription === 'AXRoleDescription: Super Button' +PASS Got default role description: AXRoleDescription: button +PASS axButton.roleDescription === 'AXRoleDescription: Super Button' +PASS Got default role description: AXRoleDescription: button +PASS successfullyParsed is true + +TEST COMPLETE +text diff --git a/LayoutTests/accessibility/isolated-tree/aria-roledescription.html b/LayoutTests/accessibility/isolated-tree/aria-roledescription.html new file mode 100644 index 0000000000000..d15f1f7a1bc14 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-roledescription.html @@ -0,0 +1,62 @@ + + + + + + + + +
      text
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-roles-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-roles-expected.txt new file mode 100644 index 0000000000000..339a78d9b5d85 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-roles-expected.txt @@ -0,0 +1,43 @@ +Basic test of some aria roles to ensure they match their corresponding html-based roles. + +checkbox: +PASS: ariaRole === realRole +button: +PASS: ariaRole === realRole +heading: +PASS: ariaRole === realRole +link: +PASS: ariaRole === realRole +radio: +PASS: ariaRole === realRole +textbox: +PASS: ariaRole === realRole +image: +PASS: ariaRole === realRole +list: +PASS: ariaRole === realRole + +PASS successfullyParsed is true + +TEST COMPLETE + + + + + + + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-roles-unignored-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-roles-unignored-expected.txt new file mode 100644 index 0000000000000..30e502ad92f05 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-roles-unignored-expected.txt @@ -0,0 +1,31 @@ +This tests that ARIA roles are not ignored for 'p', 'label', 'form' and 'div' elements. + +AXRole: AXWebArea + AXRole: AXGroup + AXRole: AXStaticText + AXValue: Simple paragraph + AXRole: AXTable + AXRole: AXGroup + AXRole: AXStaticText + AXValue: A label + AXRole: AXStaticText + AXValue: A label + AXRole: AXHeading + AXRole: AXStaticText + AXValue: Who said label? It's a heading! + AXRole: AXGroup + AXRole: AXStaticText + AXValue: A form with a button + AXRole: AXButton + AXRole: AXButton + AXRole: AXGroup + AXRole: AXStaticText + AXValue: Just some text inside a div + AXRole: AXTextField + +PASS successfullyParsed is true + +TEST COMPLETE + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-roles-unignored.html b/LayoutTests/accessibility/isolated-tree/aria-roles-unignored.html new file mode 100644 index 0000000000000..27eb87687aa21 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-roles-unignored.html @@ -0,0 +1,54 @@ + + + + + + + + +
      +

      Simple paragraph

      +

      A paragraph pretending to be a table

      + + + + +
      A form with a button
      +
      Just a button
      + +
      Just some text inside a div
      +
      This div is contains a textbox (an entry)
      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-roles.html b/LayoutTests/accessibility/isolated-tree/aria-roles.html new file mode 100644 index 0000000000000..294d9a539cf5b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-roles.html @@ -0,0 +1,106 @@ + + + + + + + + + +
      + +
      +

      X

      +
      + Broccoli
      + Asparagus
      +
      +
      +
      + + +
      +

      X

      + +
      +
      + + +
      +

      X

      +

      Hello

      +
      +
      + + +
      +

      X

      + Hello +
      +
      + + +
      +

      X

      +
      + Broccoli
      + Asparagus
      +
      +
      +
      + + +
      +

      X

      + +
      +
      + + +
      +

      X

      + Giant cupcake +
      +
      + + +
      +

      X

      +
        +
      • Broccoli
      • +
      • Beets
      • +
      +
      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-scrollbar-role-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-scrollbar-role-expected.txt new file mode 100644 index 0000000000000..9e429184815ad --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-scrollbar-role-expected.txt @@ -0,0 +1,15 @@ +This tests that the ARIA scrollbar role works correctly + +PASS: scroller.role === 'AXRole: AXScrollBar' +PASS: scroller.intValue === 55 +PASS: scroller.orientation === 'AXOrientation: AXVerticalOrientation' +PASS: scroller.orientation === 'AXOrientation: AXHorizontalOrientation' +PASS: scroller.role === 'AXRole: AXScrollBar' +PASS: scroller.intValue === 55 +PASS: scroller.orientation === 'AXOrientation: AXVerticalOrientation' + +PASS successfullyParsed is true + +TEST COMPLETE +scrollbar +scrollbar diff --git a/LayoutTests/accessibility/isolated-tree/aria-scrollbar-role.html b/LayoutTests/accessibility/isolated-tree/aria-scrollbar-role.html new file mode 100644 index 0000000000000..b97b232a0cc32 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-scrollbar-role.html @@ -0,0 +1,41 @@ + + + + + + + + +
      scrollbar
      +
      scrollbar
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-selected-menu-items.html b/LayoutTests/accessibility/isolated-tree/aria-selected-menu-items.html new file mode 100644 index 0000000000000..8cb8351a65a3c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-selected-menu-items.html @@ -0,0 +1,75 @@ + + + + + + + + +
      + + + + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-selected.html b/LayoutTests/accessibility/isolated-tree/aria-selected.html new file mode 100644 index 0000000000000..240850ad2e57e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-selected.html @@ -0,0 +1,76 @@ + + + + + + + +
      +
      +
      1
      +
      2
      +
      3
      +
      +
      +
      1
      +
      2
      +
      3
      +
      +
      +
      1
      +
      2
      +
      3
      +
      +
      +
      1
      +
      2
      +
      3
      +
      +
      +
      +
      - Expanded
      +
      +
      +
      Data 1
      Data 2
      +
      +
      +
      Data 3
      Data 4
      +
      +
      +
      Data 4
      Data 5
      +
      + +
      +
      +

      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-setsize-posinset-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-setsize-posinset-expected.txt new file mode 100644 index 0000000000000..3f7de2c9eb736 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-setsize-posinset-expected.txt @@ -0,0 +1,31 @@ +This test verifies that aria-posinset and aria-setsize are exposed to accessibility correctly. + +Verify that the list supports setsize. +PASS: axList.isAttributeSupported('AXARIASetSize') === true +Verify that the list returns the correct value for setsize. +PASS: axList.numberAttributeValue('AXARIASetSize') === 100 +Verify that the first item in the list exposes posinset attributes. +PASS: axItem1.isAttributeSupported('AXARIAPosInSet') === true +PASS: axItem1.numberAttributeValue('AXARIAPosInSet') === 3 +Verify that the second item in the list does not support setsize and posinset. +PASS: axItem2.isAttributeSupported('AXARIASetSize') === false +PASS: axItem2.isAttributeSupported('AXARIAPosInSet') === false +Update aria-posinset to 4 for the item1. +PASS: axItem1.numberAttributeValue('AXARIAPosInSet') === 4 +Set aria-posinset to foo and verify that posinset = 1 for invalid value fallback. +PASS: axItem1.numberAttributeValue('AXARIAPosInSet') === 1 +Set aria-posinset to -50 and verify that posinset = 1 for invalid value fallback. +PASS: axItem1.numberAttributeValue('AXARIAPosInSet') === 1 +Update aria-setsize to 101. +PASS: axList.numberAttributeValue('AXARIASetSize') === 101 +Set aria-setsize to -1 and verify that the list still exposes the number of items. +PASS: axList.numberAttributeValue('AXARIASetSize') === -1 +PASS: axList.isAttributeSupported('AXARIASetSize') === true +Set aria-setsize to foo and verify that the list still exposes the number of items. +PASS: axList.numberAttributeValue('AXARIASetSize') === -1 +PASS: axList.isAttributeSupported('AXARIASetSize') === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-setsize-posinset.html b/LayoutTests/accessibility/isolated-tree/aria-setsize-posinset.html new file mode 100644 index 0000000000000..dc4e2deb0e7f3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-setsize-posinset.html @@ -0,0 +1,72 @@ + + + + + + + + +
        +
      • 3
      • +
      • 4
      • +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-slider-value-change-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-slider-value-change-expected.txt new file mode 100644 index 0000000000000..fa0240548688a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-slider-value-change-expected.txt @@ -0,0 +1,13 @@ +slider +This tests that its possible to increment and decrement an aria slider and have the value updated correctly. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS slider.intValue is 50 +PASS slider.intValue is 50 +PASS slider.intValue is 50 +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-slider-value-change.html b/LayoutTests/accessibility/isolated-tree/aria-slider-value-change.html new file mode 100644 index 0000000000000..adcef8aa8c393 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-slider-value-change.html @@ -0,0 +1,39 @@ + + + + + + + +
      slider
      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-slider-value-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-slider-value-expected.txt new file mode 100644 index 0000000000000..19fabad30e877 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-slider-value-expected.txt @@ -0,0 +1,14 @@ +This test ensures we properly expose the current, minimum, and maximum values of ARIA sliders. + +PASS: slider.intValue === 5 +PASS: slider.minValue === 0 +PASS: slider.maxValue === 10 + +For #slider element, updating aria-valuemin to 2, aria-valuemax to 8. +PASS: slider.minValue === 2 +PASS: slider.maxValue === 8 + +PASS successfullyParsed is true + +TEST COMPLETE +X diff --git a/LayoutTests/accessibility/isolated-tree/aria-slider-value.html b/LayoutTests/accessibility/isolated-tree/aria-slider-value.html new file mode 100644 index 0000000000000..9aa4f924a4b94 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-slider-value.html @@ -0,0 +1,41 @@ + + + + + + + + +X + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-sort-changed-notification-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-sort-changed-notification-expected.txt new file mode 100644 index 0000000000000..1e56872212572 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-sort-changed-notification-expected.txt @@ -0,0 +1,20 @@ +This tests that changing the aria-sort value results in a SortDirectionChanged notification. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS axColumnHeader.sortDirection is 'AXAscendingSortDirection' +Toggling aria-sort +AXSortDirectionChanged notification for Account +PASS axColumnHeader.sortDirection is 'AXDescendingSortDirection' +Setting aria-sort to a random value +AXSortDirectionChanged notification for Account +PASS axColumnHeader.sortDirection is 'AXUnknownSortDirection' +Toggling aria-sort +AXSortDirectionChanged notification for Account +PASS axColumnHeader.sortDirection is 'AXAscendingSortDirection' +PASS notificationCount is 3 +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-sort-changed-notification.html b/LayoutTests/accessibility/isolated-tree/aria-sort-changed-notification.html new file mode 100644 index 0000000000000..d7075fe854389 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-sort-changed-notification.html @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + +
      Account Name
      + +

      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-sort-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-sort-expected.txt new file mode 100644 index 0000000000000..2f644b2b71549 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-sort-expected.txt @@ -0,0 +1,20 @@ +column column column column +row +This tests that aria-sort is exposed correctly to the Mac accessibility API. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS col1.isAttributeSupported('AXSortDirection') is true +PASS col1.sortDirection is 'AXAscendingSortDirection' +PASS col2.isAttributeSupported('AXSortDirection') is true +PASS col2.sortDirection is 'AXDescendingSortDirection' +PASS col3.isAttributeSupported('AXSortDirection') is true +PASS col3.sortDirection is 'AXUnknownSortDirection' +PASS link1.isAttributeSupported('AXSortDirection') is true +PASS link1.sortDirection is 'AXAscendingSortDirection' +PASS row1.isAttributeSupported('AXSortDirection') is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-sort.html b/LayoutTests/accessibility/isolated-tree/aria-sort.html new file mode 100644 index 0000000000000..7a0ea8ced6846 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-sort.html @@ -0,0 +1,59 @@ + + + + +aria-sort + + + +
      +
      + + +column + +column + +column + +column +
      +
      + +row + + + + +
      +
      + +

      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-switch-checked-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-switch-checked-expected.txt new file mode 100644 index 0000000000000..9183307d19367 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-switch-checked-expected.txt @@ -0,0 +1,14 @@ +This tests that ARIA switches correctly handle the aria-checked attribute. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS switch1.isChecked is false +PASS switch2.isChecked is true +PASS switch3.isChecked is false +PASS switch3.isChecked === true +PASS switch3.isChecked === false +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-switch-checked.html b/LayoutTests/accessibility/isolated-tree/aria-switch-checked.html new file mode 100644 index 0000000000000..9809f04048d88 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-switch-checked.html @@ -0,0 +1,52 @@ + + + + + + + + +
      +
      X
      +
      X
      +
      X
      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-switch-sends-notification-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-switch-sends-notification-expected.txt new file mode 100644 index 0000000000000..4f390dec9bd8a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-switch-sends-notification-expected.txt @@ -0,0 +1,12 @@ +Test Switch +This tests that toggling an aria switch sends a notification. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS successfullyParsed is true + +TEST COMPLETE +Got notification: CheckedStateChanged +Got notification: CheckedStateChanged + diff --git a/LayoutTests/accessibility/isolated-tree/aria-switch-sends-notification.html b/LayoutTests/accessibility/isolated-tree/aria-switch-sends-notification.html new file mode 100644 index 0000000000000..984f393e9e405 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-switch-sends-notification.html @@ -0,0 +1,39 @@ + + + + + + + + +
      Test Switch
      + +

      +
      + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-switch-text.html b/LayoutTests/accessibility/isolated-tree/aria-switch-text.html new file mode 100644 index 0000000000000..faabeadf79ea8 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-switch-text.html @@ -0,0 +1,40 @@ + + + + + + +
      +
      One
      +
      Two
      +
      Three
      +
      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-tab-role-on-buttons-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-tab-role-on-buttons-expected.txt new file mode 100644 index 0000000000000..199d03ae75eb1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-tab-role-on-buttons-expected.txt @@ -0,0 +1,16 @@ +This tests that the aria roles for tab and tablist work as expected for buttons. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +tabList.role = AXRole: AXTabGroup +tab1.role = AXRole: AXTab +tab1.title = AXTitle: Tab A +tab1.childrenCount = 0 +tab2.role = AXRole: AXTab +tab2.title = AXTitle: Tab B + +PASS successfullyParsed is true + +TEST COMPLETE +Tab A Tab B Tab C diff --git a/LayoutTests/accessibility/isolated-tree/aria-tab-role-on-buttons.html b/LayoutTests/accessibility/isolated-tree/aria-tab-role-on-buttons.html new file mode 100644 index 0000000000000..187ceef3084db --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-tab-role-on-buttons.html @@ -0,0 +1,35 @@ + + + + + + + +
      + + + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-tab-roles-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-tab-roles-expected.txt new file mode 100644 index 0000000000000..91cd5977dfa33 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-tab-roles-expected.txt @@ -0,0 +1,21 @@ +Crust +Veges +Select Crust + +This tests that the aria roles for tab, tabpanel and tablist work as expected correctly. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +tabList.role = AXRole: AXTabGroup +tab1.role = AXRole: AXTab +tab1.title = AXTitle: Crust +PASS tab1.childrenCount is 0 +tab2.role = AXRole: AXTab +tab2.title = AXTitle: Veges +tabPanel.role = AXRole: AXGroup +tabPanel.subrole = +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-tab-roles.html b/LayoutTests/accessibility/isolated-tree/aria-tab-roles.html new file mode 100644 index 0000000000000..b19a69f399c33 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-tab-roles.html @@ -0,0 +1,41 @@ + + + + + + + +
        + + +
      + +
      +

      Select Crust

      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-table-attributes-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-table-attributes-expected.txt new file mode 100644 index 0000000000000..b8c9f4ed2e530 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-table-attributes-expected.txt @@ -0,0 +1,39 @@ +This tests that attributes related to aria table/grid are working correctly. + +PASS: grid.numberAttributeValue('AXARIAColumnCount') === 16 +PASS: grid.numberAttributeValue('AXARIARowCount') === 30 +PASS: grid.rowCount === 4 +PASS: grid.columnCount === 4 +PASS: cell1.numberAttributeValue('AXARIAColumnIndex') === 2 +PASS: cell1.numberAttributeValue('AXARIARowIndex') === 7 +PASS: cell2.numberAttributeValue('AXARIAColumnIndex') === 4 +PASS: cell2.numberAttributeValue('AXARIARowIndex') === 8 +PASS: cell4.numberAttributeValue('AXARIAColumnIndex') === 3 +PASS: cell2.rowIndexRange() === '{1, 2}' +PASS: cell5.columnIndexRange() === '{2, 3}' +PASS: cell3.rowIndexRange() === '{1, 2}' +PASS: cell8.rowIndexRange() === '{2, 2}' +PASS: cell6.rowIndexRange() === '{0, 2}' +PASS: cell7.rowIndexRange() === '{0, 2}' +PASS: #grid AXARIARowCount dynamically changed to 60. +PASS: #grid AXARIAColumnCount dynamically changed to 30. +PASS: #cell1 AXARIAColumnIndex dynamically changed to 4. +PASS: #cell1 AXARIARowIndex dynamically changed to 10. +PASS: After dynamic aria-rowspan change, #cell2 row range changed to {1, 1}. +PASS: After dynamic aria-colspan change, #cell5 column range changed to {2, 1}. + +PASS successfullyParsed is true + +TEST COMPLETE +First Name Last Name Company Address +Fred Jackson Acme, Inc. 123 Broad St. +Sara James +Footer 1 Footer 2 Footer 3 +Name Company Address +Cell Span Cell +Cell +Cell Cell +Cell +Cell +January $100 +February diff --git a/LayoutTests/accessibility/isolated-tree/aria-table-attributes.html b/LayoutTests/accessibility/isolated-tree/aria-table-attributes.html new file mode 100644 index 0000000000000..0687d1a09fada --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-table-attributes.html @@ -0,0 +1,154 @@ + + + + + + + + +
      +
      +
      + First Name + Last Name + Company + Address +
      +
      +
      +
      + Fred + Jackson + Acme, Inc. + 123 Broad St. +
      +
      + Sara + James +
      +
      +
      +
      + Footer 1 + Footer 2 + Footer 3 +
      +
      +
      + +
      +
      +
      + Name + Company + Address +
      +
      +
      + +
      +
      + Cell + + Span Cell +
      +
      + Cell +
      +
      +
      + Cell + + Cell +
      +
      + Cell +
      +
      +
      + Cell +
      +
      + + + + + + + +
      January$100
      February
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-table-content-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-table-content-expected.txt new file mode 100644 index 0000000000000..1eb91dd54e2b6 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-table-content-expected.txt @@ -0,0 +1,16 @@ +Header +Item 1 + +This tests that in an aria table with CSS that makes a row anonymous, the cells can be accessed. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +cell00.role is AXRole: AXCell +cell01.role is AXRole: AXCell +PASS cell00.isEqual(table.rowAtIndex(0).childAtIndex(0)) is true +PASS cell01.isEqual(table.rowAtIndex(1).childAtIndex(0)) is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-table-content.html b/LayoutTests/accessibility/isolated-tree/aria-table-content.html new file mode 100644 index 0000000000000..a2948c978adc8 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-table-content.html @@ -0,0 +1,37 @@ + + + + + +Table Anonymous Row + + +
      + + + +
      Header

      Item 1

      +
      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-table-hierarchy.html b/LayoutTests/accessibility/isolated-tree/aria-table-hierarchy.html new file mode 100644 index 0000000000000..cb8df175dbc5c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-table-hierarchy.html @@ -0,0 +1,50 @@ + + + + + + + +
      +
      foo
      bar
      +
      +
      +
      Odd
      Even
      +
      1
      2
      +
      3
      4
      +
      +
      +
      hello
      world
      +
      +
      +
      Odd
      Even
      +

      1

      2

      +

      3

      4

      +
      +
      End of test
      +

      +
      + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-table-indextext-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-table-indextext-expected.txt new file mode 100644 index 0000000000000..88053c79c55e1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-table-indextext-expected.txt @@ -0,0 +1,22 @@ +This tests that aria-{row,col}indextext attributes in aria table/grid are working correctly. + +PASS: cell1.stringAttributeValue('AXColumnIndexDescription') === 'B' +PASS: cell1.stringAttributeValue('AXRowIndexDescription') === 'Seven' +PASS: cell2.stringAttributeValue('AXColumnIndexDescription') === 'D' +PASS: cell2.stringAttributeValue('AXRowIndexDescription') === 'Eight' +PASS: cell3.stringAttributeValue('AXColumnIndexDescription') === 'E' +PASS: cell3.stringAttributeValue('AXRowIndexDescription') === 'Eight' +PASS: cell4.stringAttributeValue('AXColumnIndexDescription') === null +PASS: cell5.stringAttributeValue('AXColumnIndexDescription') === null +PASS: cell6.stringAttributeValue('AXColumnIndexDescription') === null +PASS: cell1.stringAttributeValue('AXColumnIndexDescription') === 'D' +PASS: cell1.stringAttributeValue('AXRowIndexDescription') === 'Ten' + +PASS successfullyParsed is true + +TEST COMPLETE +First Name Last Name Company Address +Fred Jackson Acme, Inc. 123 Broad St. +Sara James +Footer 1 Footer 2 Footer 3 +Name Company Address diff --git a/LayoutTests/accessibility/isolated-tree/aria-table-indextext.html b/LayoutTests/accessibility/isolated-tree/aria-table-indextext.html new file mode 100644 index 0000000000000..ec81cce8f68b3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-table-indextext.html @@ -0,0 +1,88 @@ + + + + + + + + +
      +
      +
      + First Name + Last Name + Company + Address +
      +
      +
      +
      + Fred + Jackson + Acme, Inc. + 123 Broad St. +
      +
      + Sara + James +
      +
      +
      +
      + Footer 1 + Footer 2 + Footer 3 +
      +
      +
      + +
      +
      +
      + Name + Company + Address +
      +
      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-table-selection-support-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-table-selection-support-expected.txt new file mode 100644 index 0000000000000..31ca914a4d853 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-table-selection-support-expected.txt @@ -0,0 +1,11 @@ +This test ensures that role='table' elements do not support selection or aria-multiselectable. + +PASS: accessibilityController.accessibleElementById('table').isAttributeSupported('AXSelectedRows') === false +PASS: accessibilityController.accessibleElementById('table').isMultiSelectable === false + +PASS successfullyParsed is true + +TEST COMPLETE +Foo Bar +header h1 +header h6 diff --git a/LayoutTests/accessibility/isolated-tree/aria-table-selection-support.html b/LayoutTests/accessibility/isolated-tree/aria-table-selection-support.html new file mode 100644 index 0000000000000..b5886cb8a2607 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-table-selection-support.html @@ -0,0 +1,45 @@ + + + + + + + + +
      +
      +
      + Foo + Bar +
      +
      +
      +
      + header + h1 +
      +
      + header + h6 +
      +
      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-table-with-presentational-elements-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-table-with-presentational-elements-expected.txt new file mode 100644 index 0000000000000..7ba85008a504d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-table-with-presentational-elements-expected.txt @@ -0,0 +1,11 @@ +This tests that in an aria table a row will report its parent as the table. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS row.role is 'AXRole: AXRow' +PASS row.parentElement().role is 'AXRole: AXTable' +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-table-with-presentational-elements.html b/LayoutTests/accessibility/isolated-tree/aria-table-with-presentational-elements.html new file mode 100644 index 0000000000000..c5a43f71f79bb --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-table-with-presentational-elements.html @@ -0,0 +1,42 @@ + + + + + + + + +
      + +
      +
      +
      +
      +
      hello
      +
      hello
      +
      hello
      +
      +
      +
      +
      + +
      + +

      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-tables-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-tables-expected.txt new file mode 100644 index 0000000000000..4aa4abf812713 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-tables-expected.txt @@ -0,0 +1,20 @@ +header 1 +header 2 +header 3 +cell +cell +cell +cell +cell +cell +header 1 header 2 header 2 +cell cell cell +cell +AXRole: AXTable +AXRole: AXTable +AXRole: AXColumnHeader +AXRole: AXColumnHeader +AXRole: AXRowHeader +AXRole: AXCell +Test passed + diff --git a/LayoutTests/accessibility/isolated-tree/aria-tables.html b/LayoutTests/accessibility/isolated-tree/aria-tables.html new file mode 100644 index 0000000000000..5fff8f31ef75f --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-tables.html @@ -0,0 +1,88 @@ + + + + + + + +
      +
      +
      header 1
      +
      header 2
      +
      header 3
      +
      +
      +
      cell
      +
      cell
      +
      cell
      +
      +
      +
      cell
      +
      cell
      +
      cell
      +
      +
      + + + + + + + + + + + + + + + +
      header 1header 2header 2
      cellcellcell
      cell
      + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-text-role-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-text-role-expected.txt new file mode 100644 index 0000000000000..3f7bc2cf24f60 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-text-role-expected.txt @@ -0,0 +1,13 @@ +hello world this is a test more test +This tests that you can set an ARIA text role and that it will not have children through hit testing + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS textrole.role is platformRoleForStaticText(textrole) +PASS textrole.stringValue is 'AXValue: all at once' +PASS textrole.elementAtPoint(x, y).isEqual(textrole) is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-text-role.html b/LayoutTests/accessibility/isolated-tree/aria-text-role.html new file mode 100644 index 0000000000000..a074964564264 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-text-role.html @@ -0,0 +1,38 @@ + + + + + + + + +
      +hello world +this is a test +more test +
      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-toggle-button-with-title-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-toggle-button-with-title-expected.txt new file mode 100644 index 0000000000000..cd5996f2e8dec --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-toggle-button-with-title-expected.txt @@ -0,0 +1,11 @@ +This tests that a toggle button properly exposes the title when there isn't a direct relation and textUnderElement is required to be used. +PASS: tbutton1.role === 'AXRole: AXCheckBox' +PASS: tbutton1.title === 'AXTitle: Toggle button' +PASS: button.role === 'AXRole: AXButton' +PASS: button.title === 'AXTitle: Button title' + +PASS successfullyParsed is true + +TEST COMPLETE +Toggle button +Button title diff --git a/LayoutTests/accessibility/isolated-tree/aria-toggle-button-with-title.html b/LayoutTests/accessibility/isolated-tree/aria-toggle-button-with-title.html new file mode 100644 index 0000000000000..c32f29a364aa7 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-toggle-button-with-title.html @@ -0,0 +1,35 @@ + + + + + + + + +Toggle button + +
      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/aria-used-on-image-maps-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-used-on-image-maps-expected.txt new file mode 100644 index 0000000000000..ae1b33eb63443 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-used-on-image-maps-expected.txt @@ -0,0 +1,12 @@ + +This tests that you can set an ARIA role on image map elements. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS group.childAtIndex(0).role is 'AXRole: AXButton' +PASS group.childAtIndex(1).role is 'AXRole: AXButton' +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/aria-used-on-image-maps.html b/LayoutTests/accessibility/isolated-tree/aria-used-on-image-maps.html new file mode 100644 index 0000000000000..8a128a789b1a1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-used-on-image-maps.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/attachment-element-expected.txt b/LayoutTests/accessibility/isolated-tree/attachment-element-expected.txt new file mode 100644 index 0000000000000..ead57f63e4de6 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/attachment-element-expected.txt @@ -0,0 +1,13 @@ + +This tests that attachment elements are accessible. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +Attachment description: AXDescription: action, title, subtitle +Attachment value: AXValue: 0.5 +Attachment role: AXRoleDescription: attachment +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/attachment-element.html b/LayoutTests/accessibility/isolated-tree/attachment-element.html new file mode 100644 index 0000000000000..a08b606e0f188 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/attachment-element.html @@ -0,0 +1,29 @@ + + + + + + +

      +
      + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/attributed-string-for-line-in-table-expected.txt b/LayoutTests/accessibility/isolated-tree/attributed-string-for-line-in-table-expected.txt new file mode 100644 index 0000000000000..9cf702bd1cf0e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/attributed-string-for-line-in-table-expected.txt @@ -0,0 +1,12 @@ +This tests that attributedStringForTextMarkerRange returns the correct text for line ranges inside nested tables where the range crosses table boundaries. + +PASS: lineString === 'foo/bar/174211843' +PASS: attributedString.includes('foo/bar/174211843') === true + +PASS successfullyParsed is true + +TEST COMPLETE +BRANCHES + +foo/bar/174211843 +Second row content diff --git a/LayoutTests/accessibility/isolated-tree/attributed-string-for-line-in-table.html b/LayoutTests/accessibility/isolated-tree/attributed-string-for-line-in-table.html new file mode 100644 index 0000000000000..85822b67e1751 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/attributed-string-for-line-in-table.html @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + +
      + + + + + + + + + +
      +

      BRANCHES

      +
      + + + + + + +
      foo/bar/174211843
      +
      +
      Second row content
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/attributed-string-text-stitching-expected.txt b/LayoutTests/accessibility/isolated-tree/attributed-string-text-stitching-expected.txt new file mode 100644 index 0000000000000..bfb96822c2ae6 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/attributed-string-text-stitching-expected.txt @@ -0,0 +1,66 @@ +This test ensures we return the right attributed string from a stitched-text's text marker range. + +Attributes in range {0, 13}: +AXFont: { + AXFontFamily = Times; + AXFontName = "Times-Roman"; + AXFontSize = 16; +} +AXForegroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 1 ) +AXBackgroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 0 ) +Attributes in range {13, 3}: +AXFont: { + AXFontFamily = Times; + AXFontItalic = 1; + AXFontName = "Times-Italic"; + AXFontSize = 16; +} +AXForegroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 1 ) +AXBackgroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 0 ) +Attributes in range {16, 1}: +AXFont: { + AXFontFamily = Times; + AXFontName = "Times-Roman"; + AXFontSize = 16; +} +AXForegroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 1 ) +AXBackgroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 0 ) +Attributes in range {17, 3}: +AXFont: { + AXFontBold = 1; + AXFontFamily = Times; + AXFontName = "Times-Bold"; + AXFontSize = 16; +} +AXForegroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 1 ) +AXBackgroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 0 ) +Attributes in range {20, 13}: +AXFont: { + AXFontFamily = Times; + AXFontName = "Times-Roman"; + AXFontSize = 16; +} +AXForegroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 1 ) +AXBackgroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 0 ) +Attributes in range {33, 14}: +AXFont: { + AXFontBold = 1; + AXFontFamily = Times; + AXFontName = "Times-Bold"; + AXFontSize = 16; +} +AXForegroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 1 ) +AXBackgroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 0 ) +Attributes in range {47, 1}: +AXFont: { + AXFontFamily = Times; + AXFontName = "Times-Roman"; + AXFontSize = 16; +} +AXForegroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 1 ) +AXBackgroundColor: (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB IEC61966-2.1) ( 0 0 0 0 ) +Hello world! How are you doing on this fine day? +PASS successfullyParsed is true + +TEST COMPLETE +Hello world! How are you doing on this fine day? diff --git a/LayoutTests/accessibility/isolated-tree/attributed-string-text-stitching.html b/LayoutTests/accessibility/isolated-tree/attributed-string-text-stitching.html new file mode 100644 index 0000000000000..60af005fab9cc --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/attributed-string-text-stitching.html @@ -0,0 +1,33 @@ + + + + + + + + + +
      + Hello world! + How + are + you doing on this fine day? +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/auto-fill-crash-expected.txt b/LayoutTests/accessibility/isolated-tree/auto-fill-crash-expected.txt new file mode 100644 index 0000000000000..98a0be4d0a3f5 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/auto-fill-crash-expected.txt @@ -0,0 +1,12 @@ +This tests that when an auto fill element is removed we won't crash accessing an old value. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS axTextField.childrenCount === 1 +PASS axTextField.childAtIndex(0).description === 'AXDescription: contact info AutoFill' +PASS axTextField.childrenCount === 0 +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/auto-fill-crash.html b/LayoutTests/accessibility/isolated-tree/auto-fill-crash.html new file mode 100644 index 0000000000000..284a55015f3b2 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/auto-fill-crash.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/auto-fill-types-expected.txt b/LayoutTests/accessibility/isolated-tree/auto-fill-types-expected.txt new file mode 100644 index 0000000000000..93171321b6232 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/auto-fill-types-expected.txt @@ -0,0 +1,15 @@ +This tests that the auto-filled buttons show up. +Initial auto-fill available: false +Auto-fill type: none +Contact button role: AXRole: AXButton +Contact button label: AXDescription: contact info AutoFill +Auto-fill type: contacts +Credentials button role: AXRole: AXButton +Credentials button label: AXDescription: password AutoFill +Auto-fill type: credentials +Post auto-fill available: true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/auto-fill-types.html b/LayoutTests/accessibility/isolated-tree/auto-fill-types.html new file mode 100644 index 0000000000000..3ee08bf72888e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/auto-fill-types.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/ax-object-destroyed-on-reload-expected.txt b/LayoutTests/accessibility/isolated-tree/ax-object-destroyed-on-reload-expected.txt new file mode 100644 index 0000000000000..bfd2cf0420b16 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/ax-object-destroyed-on-reload-expected.txt @@ -0,0 +1,10 @@ +This test ensures that when a page is reloaded, we properly clean up the AX objects from the previous iteration of the page. + +Role of #button element retained from first page load: AXRole: + +Role of #button element from second page load: AXRole: AXButton + +PASS successfullyParsed is true + +TEST COMPLETE +Click me diff --git a/LayoutTests/accessibility/isolated-tree/ax-object-destroyed-on-reload.html b/LayoutTests/accessibility/isolated-tree/ax-object-destroyed-on-reload.html new file mode 100644 index 0000000000000..597c1db41bf9c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/ax-object-destroyed-on-reload.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/ax-value-with-search-expected.txt b/LayoutTests/accessibility/isolated-tree/ax-value-with-search-expected.txt new file mode 100644 index 0000000000000..2d97287543ed7 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/ax-value-with-search-expected.txt @@ -0,0 +1,8 @@ +This tests that a search field returns its value correctly. +PASS: searchInput.stringValue === 'AXValue: hello' +PASS: searchInput.stringValue === 'AXValue: test' + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/ax-value-with-search.html b/LayoutTests/accessibility/isolated-tree/ax-value-with-search.html new file mode 100644 index 0000000000000..610679f7bcf6e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/ax-value-with-search.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/axpress-on-aria-button-expected.txt b/LayoutTests/accessibility/isolated-tree/axpress-on-aria-button-expected.txt new file mode 100644 index 0000000000000..857233718ec1b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/axpress-on-aria-button-expected.txt @@ -0,0 +1,19 @@ +OPEN FILE PANEL +Upload + +Upload + +Upload + +This tests that if a non-native action type is exposed as a control, then we will look for descendants to call press() on. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +Press performed on fileupload +Press performed on button +Press performed on checkbox +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/axpress-on-aria-button.html b/LayoutTests/accessibility/isolated-tree/axpress-on-aria-button.html new file mode 100644 index 0000000000000..cfb0aa83f7989 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/axpress-on-aria-button.html @@ -0,0 +1,43 @@ + + + + + + + + +
      Upload
      +
      +
      + + +
      Upload
      +
      +
      + + +
      Upload
      +
      +
      + +

      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-basic-expected.txt b/LayoutTests/accessibility/isolated-tree/base-select-basic-expected.txt new file mode 100644 index 0000000000000..d46a321e2b660 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-basic-expected.txt @@ -0,0 +1,40 @@ +This test verifies basic accessibility properties of base-appearance selects (appearance: base-select). + +--- Select role and value --- +PASS: select.role.toLowerCase().includes('popup') === true +PASS: select.stringValue.includes('Banana') === true +PASS: select.isExpanded === false +PASS: select.isExpanded === true +PASS: menu != null === true +PASS: menu.role.toLowerCase().includes('menu') === true +PASS: menu.childrenCount === 3 +PASS: (menu.childAtIndex(0)).role.toLowerCase().includes('menuitem') === true + AXTitle: Apple + AXDescription: + AXHelp: +PASS: _menuItemText.includes('Apple') === true +PASS: (menu.childAtIndex(1)).role.toLowerCase().includes('menuitem') === true + AXTitle: Banana + AXDescription: + AXHelp: +PASS: _menuItemText.includes('Banana') === true +PASS: (menu.childAtIndex(2)).role.toLowerCase().includes('menuitem') === true + AXTitle: Cherry + AXDescription: + AXHelp: +PASS: _menuItemText.includes('Cherry') === true +PASS: menu.childAtIndex(0).isSelected === false +PASS: menu.childAtIndex(1).isSelected === true +PASS: menu.childAtIndex(2).isSelected === false +PASS: menu.selectedChildrenCount === 1 +PASS: (menu.selectedChildAtIndex(0)).role.toLowerCase().includes('menuitem') === true + AXTitle: Banana + AXDescription: + AXHelp: +PASS: _menuItemText.includes('Banana') === true +PASS: select.isExpanded === false + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-basic.html b/LayoutTests/accessibility/isolated-tree/base-select-basic.html new file mode 100644 index 0000000000000..cfd67bfeb5dc6 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-basic.html @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-complex-options-expected.txt b/LayoutTests/accessibility/isolated-tree/base-select-complex-options-expected.txt new file mode 100644 index 0000000000000..b5cb4c5884dba --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-complex-options-expected.txt @@ -0,0 +1,40 @@ +This test verifies that text nodes inside complex base-appearance select options are exposed as accessible elements (e.g. for VoiceOver navigation), but only when the option has non-text descendants. + +PASS: select.isExpanded === true +PASS: menu.role.toLowerCase().includes('menu') === true + +--- Simple option: text is NOT exposed as a StaticText child --- +PASS: (menu.childAtIndex(0)).role.toLowerCase().includes('menuitem') === true + AXTitle: Apple + AXDescription: + AXHelp: +PASS: _menuItemText.includes('Apple') === true +PASS: appleItem.childrenCount === 0 + +--- Complex option text includes all descendant text --- +PASS: bananaItem.role.toLowerCase().includes('menuitem') === true + AXTitle: Banana Foo Bar + AXDescription: + AXHelp: +PASS: bananaText.includes('Banana') === true +PASS: bananaText.includes('Foo') === true +PASS: bananaText.includes('Bar') === true +PASS: bananaHasStaticText === true + +--- Option with only a span wrapper: text is NOT exposed --- +PASS: (menu.childAtIndex(2)).role.toLowerCase().includes('menuitem') === true + AXTitle: Cherry + AXDescription: + AXHelp: +PASS: _menuItemText.includes('Cherry') === true +PASS: cherryItem.childrenCount === 0 +PASS: cherryHasStaticTextAfterAdd === true + +--- Dynamic: remove the button from the option --- +PASS: cherryHasStaticTextAfterRemove === false +PASS: select.isExpanded === false + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-complex-options.html b/LayoutTests/accessibility/isolated-tree/base-select-complex-options.html new file mode 100644 index 0000000000000..fc630ba99ba20 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-complex-options.html @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-focus-on-open-expected.txt b/LayoutTests/accessibility/isolated-tree/base-select-focus-on-open-expected.txt new file mode 100644 index 0000000000000..064506bff4944 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-focus-on-open-expected.txt @@ -0,0 +1,20 @@ +This test verifies that when a base-appearance select opens, VoiceOver's focused element is the selected option. + +PASS: select.isExpanded === true +PASS: window.focusedRoleAtNotificationTime != null === true + +--- Focused element at notification time should be the selected menu item --- +PASS: window.focusedRoleAtNotificationTime.toLowerCase().includes('menuitem') === true + AXTitle: Bravo + AXDescription: + AXHelp: +PASS: window.focusedTextAtNotificationTime.includes('Bravo') === true + +PASS successfullyParsed is true + +TEST COMPLETE + +Alpha +Bravo +Charlie + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-focus-on-open.html b/LayoutTests/accessibility/isolated-tree/base-select-focus-on-open.html new file mode 100644 index 0000000000000..2d5eae5b2553f --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-focus-on-open.html @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-notifications-expected.txt b/LayoutTests/accessibility/isolated-tree/base-select-notifications-expected.txt new file mode 100644 index 0000000000000..c4191b86af244 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-notifications-expected.txt @@ -0,0 +1,12 @@ +This test verifies accessibility notifications for base-appearance selects (appearance: base-select). + +PASS: select.isExpanded === true +PASS: select.isExpanded === false +PASS: select.stringValue.includes('Bravo') === true +PASS: window.receivedNotification === true +PASS: select.stringValue.includes('Charlie') === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-notifications.html b/LayoutTests/accessibility/isolated-tree/base-select-notifications.html new file mode 100644 index 0000000000000..a320b47d8898c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-notifications.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-option-border-radius-path-expected.txt b/LayoutTests/accessibility/isolated-tree/base-select-option-border-radius-path-expected.txt new file mode 100644 index 0000000000000..cb36fdcdad565 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-option-border-radius-path-expected.txt @@ -0,0 +1,12 @@ +This test verifies that base-appearance select options with border-radius correctly support AXPath. + +PASS: select.isExpanded === true +PASS: pathSegmentCountOfID('opt1', 'Curve to') > 0 === true +PASS: pathSegmentCountOfID('opt2', 'Curve to') > 0 === true +PASS: pathSegmentCountOfID('opt3', 'Curve to') > 0 === true +PASS: select.isExpanded === false + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-option-border-radius-path.html b/LayoutTests/accessibility/isolated-tree/base-select-option-border-radius-path.html new file mode 100644 index 0000000000000..ce8d35fd85939 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-option-border-radius-path.html @@ -0,0 +1,61 @@ + + + + + + + + + +
      + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-option-press-closes-popover-expected.txt b/LayoutTests/accessibility/isolated-tree/base-select-option-press-closes-popover-expected.txt new file mode 100644 index 0000000000000..0c9e6da5fb0d3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-option-press-closes-popover-expected.txt @@ -0,0 +1,14 @@ +This test verifies that pressing an option via accessibility (e.g. VoiceOver) in a base-appearance select closes the popover and updates the selection. + +PASS: select.stringValue.includes('Banana') === true +PASS: select.isExpanded === false +PASS: select.isExpanded === true +PASS: menu.role.toLowerCase().includes('menu') === true +PASS: option.role.toLowerCase().includes('menuitem') === true +PASS: select.isExpanded === false +PASS: select.stringValue.includes('Apple') === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-search-predicate-expected.txt b/LayoutTests/accessibility/isolated-tree/base-select-search-predicate-expected.txt new file mode 100644 index 0000000000000..99a31e4fb8a50 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-search-predicate-expected.txt @@ -0,0 +1,27 @@ +This test verifies that base-appearance select menu items are discoverable via the accessibility search predicate API (used by Voice Control to generate numbered overlays). + +PASS: select.isExpanded === true + +--- Search for controls should find the select and its menu items --- +PASS: foundMenuItems.length === 3 +PASS: (foundMenuItems[0]).role.toLowerCase().includes('menuitem') === true + AXTitle: Alpha + AXDescription: + AXHelp: +PASS: _menuItemText.includes('Alpha') === true +PASS: (foundMenuItems[1]).role.toLowerCase().includes('menuitem') === true + AXTitle: Bravo + AXDescription: + AXHelp: +PASS: _menuItemText.includes('Bravo') === true +PASS: (foundMenuItems[2]).role.toLowerCase().includes('menuitem') === true + AXTitle: Charlie + AXDescription: + AXHelp: +PASS: _menuItemText.includes('Charlie') === true +PASS: select.isExpanded === false + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/base-select-search-predicate.html b/LayoutTests/accessibility/isolated-tree/base-select-search-predicate.html new file mode 100644 index 0000000000000..0c8cf7b47f1fd --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/base-select-search-predicate.html @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/basic-focusability-expected.txt b/LayoutTests/accessibility/isolated-tree/basic-focusability-expected.txt new file mode 100644 index 0000000000000..e6bf16bb5c572 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/basic-focusability-expected.txt @@ -0,0 +1,18 @@ +This test ensures you can set focus to AX elements via the AX API (not the DOM). + +Checking initial button focus state. +#button isFocused: false +#canvas isFocused: false + +Focusing #button. +#button isFocused: true +#canvas isFocused: false + +Focusing #canvas. +#button isFocused: false +#canvas isFocused: true + +PASS successfullyParsed is true + +TEST COMPLETE +Click me diff --git a/LayoutTests/accessibility/isolated-tree/basic-focusability.html b/LayoutTests/accessibility/isolated-tree/basic-focusability.html new file mode 100644 index 0000000000000..423278d0378ec --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/basic-focusability.html @@ -0,0 +1,57 @@ + + + + + + + + + + + + + +
      + Foo +
      +
      + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/body-element-aria-hidden-expected.txt b/LayoutTests/accessibility/isolated-tree/body-element-aria-hidden-expected.txt new file mode 100644 index 0000000000000..bb6433dc4b948 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/body-element-aria-hidden-expected.txt @@ -0,0 +1,8 @@ +This test ensures that aria-hidden='true' is not respected on the body element. + +PASS: accessibilityController.accessibleElementById('button').role.toLowerCase().includes('button') === true + +PASS successfullyParsed is true + +TEST COMPLETE +Submit diff --git a/LayoutTests/accessibility/isolated-tree/body-element-aria-hidden.html b/LayoutTests/accessibility/isolated-tree/body-element-aria-hidden.html new file mode 100644 index 0000000000000..bcdf45e3852d0 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/body-element-aria-hidden.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/box-styled-lists-expected.txt b/LayoutTests/accessibility/isolated-tree/box-styled-lists-expected.txt new file mode 100644 index 0000000000000..34fd902dc4834 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/box-styled-lists-expected.txt @@ -0,0 +1,13 @@ +test 1 +test 2 +test a +test b + + + + +il in ol role: AXRole: AXListItem + +il in ul role: AXRole: AXListItem + + diff --git a/LayoutTests/accessibility/isolated-tree/box-styled-lists.html b/LayoutTests/accessibility/isolated-tree/box-styled-lists.html new file mode 100644 index 0000000000000..fde345373d28d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/box-styled-lists.html @@ -0,0 +1,54 @@ + + + + + + + +
      +
      +
        +
      • test 1
      • +
      • test 2
      • +
      +
      +
      +
      +
      +
        +
      1. test a
      2. +
      3. test b
      4. +
      +
      +
      + +




      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/braille-label-role-expected.txt b/LayoutTests/accessibility/isolated-tree/braille-label-role-expected.txt new file mode 100644 index 0000000000000..02f07ba98dae2 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/braille-label-role-expected.txt @@ -0,0 +1,12 @@ + +Validate aria-braillelabel and aria-brailleroledescription + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS label.stringAttributeValue('AXBrailleLabel') is 'braille' +PASS label.stringAttributeValue('AXBrailleRoleDescription') is 'braille role' +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/braille-label-role.html b/LayoutTests/accessibility/isolated-tree/braille-label-role.html new file mode 100644 index 0000000000000..2990c696e21a5 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/braille-label-role.html @@ -0,0 +1,23 @@ + + + + + + + + + +

      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/button-hidden-and-unhidden-text-expected.txt b/LayoutTests/accessibility/isolated-tree/button-hidden-and-unhidden-text-expected.txt new file mode 100644 index 0000000000000..cd8dc2271ddc5 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-hidden-and-unhidden-text-expected.txt @@ -0,0 +1,17 @@ +This test ensures we compute the correct accessibility label when visibility:hidden and visibility:visible are nested. + + AXTitle: visible to all users, un-hidden for all users + AXDescription: + AXHelp: +PASS: text.includes('un-hidden for all users') === true +PASS: !text.includes('hidden-foo-bar') === true + + AXTitle: visible to all users, hidden-foo-bar, un-hidden for all users + AXDescription: + AXHelp: +PASS: text.includes('hidden-foo-bar') === true + +PASS successfullyParsed is true + +TEST COMPLETE +visible to all users, hidden-foo-bar, un-hidden for all users diff --git a/LayoutTests/accessibility/isolated-tree/button-hidden-and-unhidden-text.html b/LayoutTests/accessibility/isolated-tree/button-hidden-and-unhidden-text.html new file mode 100644 index 0000000000000..03dc8714b743a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-hidden-and-unhidden-text.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/button-hit-test-expected.txt b/LayoutTests/accessibility/isolated-tree/button-hit-test-expected.txt new file mode 100644 index 0000000000000..dc62c0cb1c25a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-hit-test-expected.txt @@ -0,0 +1,9 @@ +This test ensures that hit testing a button works. + +PASS: hitTestResult.role.toLowerCase().includes('button') === true +PASS: hitTestResult.role.toLowerCase().includes('button') === true + +PASS successfullyParsed is true + +TEST COMPLETE +Press diff --git a/LayoutTests/accessibility/isolated-tree/button-hit-test.html b/LayoutTests/accessibility/isolated-tree/button-hit-test.html new file mode 100644 index 0000000000000..512ae7738e839 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-hit-test.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/button-in-deep-dom-expected.txt b/LayoutTests/accessibility/isolated-tree/button-in-deep-dom-expected.txt new file mode 100644 index 0000000000000..c8855d9230c88 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-in-deep-dom-expected.txt @@ -0,0 +1,8 @@ +This test ensures we can access content inside a deep DOM without crashing. + +PASS: webArea.childAtIndex(0)?.role.toLowerCase().includes('button') === true + +PASS successfullyParsed is true + +TEST COMPLETE +Press diff --git a/LayoutTests/accessibility/isolated-tree/button-in-deep-dom.html b/LayoutTests/accessibility/isolated-tree/button-in-deep-dom.html new file mode 100644 index 0000000000000..69b816f87a8f0 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-in-deep-dom.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/button-inside-label-ax-text-expected.txt b/LayoutTests/accessibility/isolated-tree/button-inside-label-ax-text-expected.txt new file mode 100644 index 0000000000000..a2b422a5c60e0 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-inside-label-ax-text-expected.txt @@ -0,0 +1,46 @@ +This test ensures that buttons within a label that has a for='' don't use the label's text over their own. + + +AXRole: AXGroup + AXTitle: + AXDescription: + AXHelp: + +AXRole: AXGroup + AXTitle: + AXDescription: + AXHelp: + +AXRole: AXHeading + AXTitle: Enter the characters in the image below. + AXDescription: + AXHelp: + +AXRole: AXStaticText + AXTitle: + AXDescription: + AXHelp: + AXValue: Enter the characters in the image below. + +AXRole: AXButton + AXTitle: Switch to audio + AXDescription: + AXHelp: + +AXRole: AXButton + AXTitle: Try another + AXDescription: + AXHelp: + +AXRole: AXTextField + AXTitle: Enter the characters in the image below. Switch to audio Try another + AXDescription: + AXHelp: + +PASS successfullyParsed is true + +TEST COMPLETE + +Enter the characters in the image below. + +Switch to audio Try another diff --git a/LayoutTests/accessibility/isolated-tree/button-inside-label-ax-text.html b/LayoutTests/accessibility/isolated-tree/button-inside-label-ax-text.html new file mode 100644 index 0000000000000..5b69634f2621f --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-inside-label-ax-text.html @@ -0,0 +1,47 @@ + + + + + + + + +
      + +
      + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/button-press-action-expected.txt b/LayoutTests/accessibility/isolated-tree/button-press-action-expected.txt new file mode 100644 index 0000000000000..467a81c2f7620 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-press-action-expected.txt @@ -0,0 +1,3 @@ +Click me +Test passed + diff --git a/LayoutTests/accessibility/isolated-tree/button-press-action.html b/LayoutTests/accessibility/isolated-tree/button-press-action.html new file mode 100644 index 0000000000000..e52ccef8fb98d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-press-action.html @@ -0,0 +1,32 @@ + + + + + + + + + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/button-title-change-expected.txt b/LayoutTests/accessibility/isolated-tree/button-title-change-expected.txt new file mode 100644 index 0000000000000..c0e1d33657911 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-title-change-expected.txt @@ -0,0 +1,8 @@ +Tests that the title of the button is properly updated when the text of its descendant changes. +PASS: button.title === 'AXTitle: 0' +PASS: button.title === 'AXTitle: 3' + +PASS successfullyParsed is true + +TEST COMPLETE +3 diff --git a/LayoutTests/accessibility/isolated-tree/button-title-change.html b/LayoutTests/accessibility/isolated-tree/button-title-change.html new file mode 100644 index 0000000000000..d7f7aa6ab64e9 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-title-change.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/button-title-uses-inner-img-alt-expected.txt b/LayoutTests/accessibility/isolated-tree/button-title-uses-inner-img-alt-expected.txt new file mode 100644 index 0000000000000..9eaeb3e26cf16 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-title-uses-inner-img-alt-expected.txt @@ -0,0 +1,12 @@ +Button with image of +This test makes sure that a generic focusable div can get accessibility focus and gets its accessible text from contents.. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS document.activeElement == button is true +PASS axButton.title.indexOf('Button with image of cake') >= 0 is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/button-title-uses-inner-img-alt.html b/LayoutTests/accessibility/isolated-tree/button-title-uses-inner-img-alt.html new file mode 100644 index 0000000000000..7328c8cc42bbc --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-title-uses-inner-img-alt.html @@ -0,0 +1,31 @@ + + + + + + + + + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/button-with-aria-haspopup-role-expected.txt b/LayoutTests/accessibility/isolated-tree/button-with-aria-haspopup-role-expected.txt new file mode 100644 index 0000000000000..ee799f2f500b5 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-with-aria-haspopup-role-expected.txt @@ -0,0 +1,29 @@ +This tests the platform role exposed for buttons with aria-haspopup + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +test1 AXRole: AXButton for aria-haspopup = (null) +AX popupValue = 'false' +test2 AXRole: AXPopUpButton for aria-haspopup = 'true' +AX popupValue = 'menu' +test3 AXRole: AXButton for aria-haspopup = 'false' +AX popupValue = 'false' +test4 AXRole: AXPopUpButton for aria-haspopup = 'dialog' +AX popupValue = 'dialog' +test5 AXRole: AXPopUpButton for aria-haspopup = 'grid' +AX popupValue = 'grid' +test6 AXRole: AXPopUpButton for aria-haspopup = 'listbox' +AX popupValue = 'listbox' +test7 AXRole: AXPopUpButton for aria-haspopup = 'menu' +AX popupValue = 'menu' +test8 AXRole: AXPopUpButton for aria-haspopup = 'tree' +AX popupValue = 'tree' +test9 AXRole: AXButton for aria-haspopup = 'foo' +AX popupValue = 'false' +test10 AXRole: AXButton for aria-haspopup = '' +AX popupValue = 'false' +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/button-with-aria-haspopup-role.html b/LayoutTests/accessibility/isolated-tree/button-with-aria-haspopup-role.html new file mode 100644 index 0000000000000..ff156e698b68f --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/button-with-aria-haspopup-role.html @@ -0,0 +1,38 @@ + + + + + + +
      +
      X
      +
      X
      +
      X
      +
      X
      +
      X
      +
      X
      +
      X
      +
      X
      +
      X
      +
      X
      +
      +

      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/canvas-accessibilitynodeobject-expected.txt b/LayoutTests/accessibility/isolated-tree/canvas-accessibilitynodeobject-expected.txt new file mode 100644 index 0000000000000..07532f703ffbd --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/canvas-accessibilitynodeobject-expected.txt @@ -0,0 +1,20 @@ +Link Button ARIA button ARIA link +This test makes sure that AccessibilityNodeObjects are created for elements in a canvas subtree. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS axRenderObjects.length is axNodeObjects.length +PASS i == 0; axRenderObject.role == axNodeObject.role is true +PASS i == 1; axRenderObject.role == axNodeObject.role is true +PASS i == 2; axRenderObject.role == axNodeObject.role is true +PASS i == 3; axRenderObject.role == axNodeObject.role is true +PASS i == 4; axRenderObject.role == axNodeObject.role is true +PASS i == 5; axRenderObject.role == axNodeObject.role is true +PASS i == 6; axRenderObject.role == axNodeObject.role is true +PASS i == 7; axRenderObject.role == axNodeObject.role is true +PASS i == 8; axRenderObject.role == axNodeObject.role is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/canvas-accessibilitynodeobject.html b/LayoutTests/accessibility/isolated-tree/canvas-accessibilitynodeobject.html new file mode 100644 index 0000000000000..19c70203d5a29 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/canvas-accessibilitynodeobject.html @@ -0,0 +1,72 @@ + + + + + +
      + Link + + + + + + + ARIA button + ARIA link +
      + + + Link + + + + + + + ARIA button + ARIA link + + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/canvas-description-and-role-expected.txt b/LayoutTests/accessibility/isolated-tree/canvas-description-and-role-expected.txt new file mode 100644 index 0000000000000..10ed264f40165 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/canvas-description-and-role-expected.txt @@ -0,0 +1,14 @@ +This test makes sure that a canvas with and without fallback content each has the right role and description. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS axContainer.childrenCount is 2 +PASS platformValueForW3CName(axCanvas1) is "Canvas label" +Canvas 1 role: AXRole: AXCanvas +PASS platformValueForW3CName(axCanvas2) is "" +Canvas 2 role: AXRole: AXCanvas +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/canvas-description-and-role.html b/LayoutTests/accessibility/isolated-tree/canvas-description-and-role.html new file mode 100644 index 0000000000000..b6446532e1f5e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/canvas-description-and-role.html @@ -0,0 +1,36 @@ + + + + + + + + +
      Fallback text
      + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/canvas-drawFocusIfNeeded-bounds-expected.txt b/LayoutTests/accessibility/isolated-tree/canvas-drawFocusIfNeeded-bounds-expected.txt new file mode 100644 index 0000000000000..985a9d2090596 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/canvas-drawFocusIfNeeded-bounds-expected.txt @@ -0,0 +1,18 @@ +This test ensures that drawFocusIfNeeded() correctly sets accessibility bounds for canvas fallback elements. + +PASS: pageX was equal to 108. +PASS: pageY was equal to 58. +PASS: width was equal to 80. +PASS: height was equal to 40. + +Dynamic update: move the focus path to a new position. + +PASS: pageX was equal to 208. +PASS: pageY was equal to 158. +PASS: width was equal to 80. +PASS: height was equal to 40. + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/canvas-drawFocusIfNeeded-bounds-with-object-fit-expected.txt b/LayoutTests/accessibility/isolated-tree/canvas-drawFocusIfNeeded-bounds-with-object-fit-expected.txt new file mode 100644 index 0000000000000..9ad6bb356a8f7 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/canvas-drawFocusIfNeeded-bounds-with-object-fit-expected.txt @@ -0,0 +1,11 @@ +This test ensures that drawFocusIfNeeded() accessibility bounds account for object-fit on the canvas. + +PASS: pageX was equal or approximately equal to 108. +PASS: pageY was equal or approximately equal to 158. +PASS: width was equal or approximately equal to 80. +PASS: height was equal or approximately equal to 40. + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/canvas-drawFocusIfNeeded-bounds-with-transform-expected.txt b/LayoutTests/accessibility/isolated-tree/canvas-drawFocusIfNeeded-bounds-with-transform-expected.txt new file mode 100644 index 0000000000000..e7f38ba269d6c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/canvas-drawFocusIfNeeded-bounds-with-transform-expected.txt @@ -0,0 +1,11 @@ +This test ensures that drawFocusIfNeeded() accessibility bounds account for CSS transforms on the canvas. + +PASS: pageX was equal or approximately equal to 208. +PASS: pageY was equal or approximately equal to 108. +PASS: width was equal or approximately equal to 160. +PASS: height was equal or approximately equal to 80. + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/canvas-fallback-content-2-expected.txt b/LayoutTests/accessibility/isolated-tree/canvas-fallback-content-2-expected.txt new file mode 100644 index 0000000000000..d65364696e0f2 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/canvas-fallback-content-2-expected.txt @@ -0,0 +1,334 @@ +Link Button Button Button +Focusable +Heading + +ARIA button +ARIA disabled button +ARIA enabled button +ARIA required button +ARIA toggle button +ARIA link +This tests a number of different elements in canvas fallback content to make sure their accessible attributes are essentially identical to the corresponding elements outside of canvas fallback content. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +link1 +link2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +button1 +button2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +labeled-button1 +labeled-button2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +button-with-title1 +button-with-title2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +text1 +text2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +checkbox1 +checkbox2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +number1 +number2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +radio1 +radio2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +slider1 +slider2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +submit1 +submit2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +combobox1 +combobox2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +listbox1 +listbox2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +textarea1 +textarea2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +focusable1 +focusable2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +heading1 +heading2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +aria-button1 +aria-button2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +aria-disabledbutton1 +aria-disabledbutton2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +aria-enabledbutton1 +aria-enabledbutton2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +aria-requiredbutton1 +aria-requiredbutton2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +aria-togglebutton1 +aria-togglebutton2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +aria-link1 +aria-link2 +PASS axElement2.role is axElement1.role +PASS axElement2.roleDescription is axElement1.roleDescription +PASS axElement2.title is axElement1.title +PASS axElement2.description is axElement1.description +PASS axElement2.helpText is axElement1.helpText +PASS axElement2.stringValue is axElement1.stringValue +PASS axElement2.isEnabled is axElement1.isEnabled +PASS axElement2.isRequired is axElement1.isRequired +PASS axElement2.isChecked is axElement1.isChecked +PASS axElement2.intValue is axElement1.intValue +PASS axElement2.minValue is axElement1.minValue +PASS axElement2.maxValue is axElement1.maxValue + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/canvas-fallback-content-2.html b/LayoutTests/accessibility/isolated-tree/canvas-fallback-content-2.html new file mode 100644 index 0000000000000..23ade6388603c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/canvas-fallback-content-2.html @@ -0,0 +1,109 @@ + + + + + + + +
      + Link + + + + + + + + + + + + +
      Focusable
      +
      Heading
      +
      ARIA button
      +
      ARIA disabled button
      +
      ARIA enabled button
      +
      ARIA required button
      +
      ARIA toggle button
      +
      ARIA link
      +
      + + + Link + + + + + + + + + + + + +
      Focusable
      +
      Heading
      +
      ARIA button
      +
      ARIA disabled button
      +
      ARIA enabled button
      +
      ARIA required button
      +
      ARIA toggle button
      +
      ARIA link
      +
      + +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/canvas-fallback-content-expected.txt b/LayoutTests/accessibility/isolated-tree/canvas-fallback-content-expected.txt new file mode 100644 index 0000000000000..187ebb05e178e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/canvas-fallback-content-expected.txt @@ -0,0 +1,99 @@ +This test makes sure that focusable elements in canvas fallback content are accessible. + +link1 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXLink" + +button1 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXButton" + +text1 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXTextField" + +checkbox1 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXCheckBox" + +radio1 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXRadioButton" + +submit1 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXButton" + +combobox1 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXComboBox" + +focusable1 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXGroup" + +aria-button1 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXButton" + +aria-link1 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXLink" + +link2 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXLink" + +button2 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXButton" + +text2 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXTextField" + +checkbox2 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXCheckBox" + +radio2 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXRadioButton" + +submit2 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXButton" + +combobox2 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXComboBox" + +focusable2 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXGroup" + +aria-button2 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXButton" + +aria-link2 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXLink" + +focusable1 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXButton" + +focusable2 +PASS: document.activeElement == element === true +PASS: axElement.role === "AXRole: AXButton" + + +PASS successfullyParsed is true + +TEST COMPLETE +Link Button +Focusable +ARIA button +ARIA link + diff --git a/LayoutTests/accessibility/isolated-tree/canvas-fallback-content.html b/LayoutTests/accessibility/isolated-tree/canvas-fallback-content.html new file mode 100644 index 0000000000000..52db990cb1fe5 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/canvas-fallback-content.html @@ -0,0 +1,167 @@ + + + + + + + + + + +
      + Link + + + + + + + Focusable +
      ARIA button
      +
      ARIA link
      +
      + + + Link + + + + + + + Focusable +
      ARIA button
      +
      ARIA link
      +
      + + + + diff --git a/LayoutTests/accessibility/isolated-tree/cells-inside-contenteditable-expected.txt b/LayoutTests/accessibility/isolated-tree/cells-inside-contenteditable-expected.txt new file mode 100644 index 0000000000000..c3b1ea67ca13d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/cells-inside-contenteditable-expected.txt @@ -0,0 +1,10 @@ +This test verifies that cells expose their static text when inside contenteditable + textbox containers. + +AXValue: Head 1 +AXValue: Cell 1 + +PASS successfullyParsed is true + +TEST COMPLETE +Head 1 Head 2 +Cell 1 cell 2 diff --git a/LayoutTests/accessibility/isolated-tree/cells-inside-contenteditable.html b/LayoutTests/accessibility/isolated-tree/cells-inside-contenteditable.html new file mode 100644 index 0000000000000..50db182e7a79c --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/cells-inside-contenteditable.html @@ -0,0 +1,40 @@ + + + + + + + +
      + + + + + + + + + +
      Head 1Head 2
      Cell 1cell 2
      +
      + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/changing-aria-hidden-with-display-none-parent-expected.txt b/LayoutTests/accessibility/isolated-tree/changing-aria-hidden-with-display-none-parent-expected.txt new file mode 100644 index 0000000000000..5bcf423b2b03b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/changing-aria-hidden-with-display-none-parent-expected.txt @@ -0,0 +1,38 @@ +This test ensures that we don't crash when removing an object from the AX tree whose child recently changed aria-hidden status. + + +{#body AXRole: AXGroup} + +{#ul AXRole: AXList} + +{#li1 AXRole: AXGroup} + +{AXRole: AXListMarker} + +{AXRole: AXStaticText AXValue: One} + +{AXRole: AXGroup} + +{AXRole: AXListMarker} + +{AXRole: AXStaticText AXValue: Two} + +{AXRole: AXGroup} + +{AXRole: AXListMarker} + +{AXRole: AXStaticText AXValue: Three} + +Making #li1 aria-hidden and #ul display:none. + +Re-dumping AX tree. + + +{#body AXRole: AXGroup} + +PASS: There was no crash. + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/changing-aria-hidden-with-display-none-parent.html b/LayoutTests/accessibility/isolated-tree/changing-aria-hidden-with-display-none-parent.html new file mode 100644 index 0000000000000..b81755de41bd4 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/changing-aria-hidden-with-display-none-parent.html @@ -0,0 +1,48 @@ + + + + + + + + +
        +
      • One
      • +
      • Two
      • +
      • Three
      • +
      + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/checkbox-mixed-value-expected.txt b/LayoutTests/accessibility/isolated-tree/checkbox-mixed-value-expected.txt new file mode 100644 index 0000000000000..62125a4f4af72 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/checkbox-mixed-value-expected.txt @@ -0,0 +1,12 @@ +This test ensures mixed values are reported properly on native checkboxes. + +PASS: accessibilityController.accessibleElementById('checkbox').isIndeterminate === false +document.getElementById('checkbox').indeterminate = true +PASS: accessibilityController.accessibleElementById('checkbox').isIndeterminate === true +document.getElementById('checkbox').indeterminate = false +PASS: accessibilityController.accessibleElementById('checkbox').isIndeterminate === false + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/checkbox-mixed-value.html b/LayoutTests/accessibility/isolated-tree/checkbox-mixed-value.html new file mode 100644 index 0000000000000..ea0f07b4483a3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/checkbox-mixed-value.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/checkbox-radio-element-rect-expected.txt b/LayoutTests/accessibility/isolated-tree/checkbox-radio-element-rect-expected.txt new file mode 100644 index 0000000000000..3278ad2b53a41 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/checkbox-radio-element-rect-expected.txt @@ -0,0 +1,22 @@ +This test ensures we calculate the frame correctly for checkboxes (inclusive of switches) and radio buttons. + +#test-checkbox-without-label: {width: 14, height: 16} +#test-radio-without-label: {width: 14, height: 15} +#test-switch-without-label: {width: 36, height: 21} +#test-checkbox: {width: 784, height: 19} +#test-checkbox: {width: 784, height: 19} +#test-checkbox-switch: {width: 784, height: 22} +#test-role-checkbox: {width: 784, height: 18} +#test-role-radio: {width: 784, height: 18} +#test-role-switch: {width: 784, height: 18} + +PASS successfullyParsed is true + +TEST COMPLETE + + + + +Fake control. +Fake control. +Fake control. diff --git a/LayoutTests/accessibility/isolated-tree/checkbox-radio-element-rect.html b/LayoutTests/accessibility/isolated-tree/checkbox-radio-element-rect.html new file mode 100644 index 0000000000000..5bf4666e764f2 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/checkbox-radio-element-rect.html @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/clickable-text-stitching-expected.txt b/LayoutTests/accessibility/isolated-tree/clickable-text-stitching-expected.txt new file mode 100644 index 0000000000000..21f765f54d1a6 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/clickable-text-stitching-expected.txt @@ -0,0 +1,33 @@ +Tests text stitching around clickable elements. + +Scenario 1 (handler on
        , .btn signals the target): + +{AXRole: AXStaticText AXValue: before stitched-with-before} + +{AXRole: AXStaticText AXValue: clickable-not-stitched-with-anything} + +{AXRole: AXStaticText AXValue: afterstitched-with-after} + +Scenario 2 (handler on via delegation, cursor:pointer signals the target): + +{AXRole: AXStaticText AXValue: Foo stitched-with-foo } + +{AXRole: AXStaticText AXValue: button-not-stitched-with-anything} + +{AXRole: AXStaticText AXValue: barstitched-with-bar} + +Scenario 3 (handler directly on an inline element wrapping multiple texts): + +{AXRole: AXStaticText AXValue: Foo stitched-with-foo } + +{AXRole: AXStaticText AXValue: btn should-be-stitched-with-btn} + +{AXRole: AXStaticText AXValue: barstitched-with-bar} + + +PASS successfullyParsed is true + +TEST COMPLETE +before stitched-with-beforeclickable-not-stitched-with-anything afterstitched-with-after +Foo stitched-with-foo button-not-stitched-with-anything barstitched-with-bar +Foo stitched-with-foo btn should-be-stitched-with-btn barstitched-with-bar diff --git a/LayoutTests/accessibility/isolated-tree/clickable-text-stitching.html b/LayoutTests/accessibility/isolated-tree/clickable-text-stitching.html new file mode 100644 index 0000000000000..702a0a788090b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/clickable-text-stitching.html @@ -0,0 +1,59 @@ + + + + + + + + + + +
          +
        • before stitched-with-beforeclickable-not-stitched-with-anything afterstitched-with-after
        • +
        + + +
        +
        +
        Foo stitched-with-foo button-not-stitched-with-anything barstitched-with-bar
        +
        +
        + + +

        + Foo stitched-with-foo btn should-be-stitched-with-btn barstitched-with-bar +

        + + + + diff --git a/LayoutTests/accessibility/isolated-tree/clip-path-bounding-box-expected.txt b/LayoutTests/accessibility/isolated-tree/clip-path-bounding-box-expected.txt new file mode 100644 index 0000000000000..de5ab6147de0d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/clip-path-bounding-box-expected.txt @@ -0,0 +1,39 @@ +This test ensures we compute the right frame for elements affected by clip-path. + +PASS: current.role.toLowerCase().includes("statictext") === true +x: 8 +y: 150 +width: 25 +height: 18 + +x is valid: true +y is valid: true +width is valid: true +height is valid: true + +PASS: current.role.toLowerCase().includes("statictext") === true +x: 8 +y: 186 +width: 109 +height: 18 + +PASS: current.role.toLowerCase().includes("group") === true +x: 8 +y: 204 +width: 300 +height: 300 + +PASS: current.role.toLowerCase().includes("statictext") === true +x: 8 +y: 204 +width: 288 +height: 54 + + +PASS successfullyParsed is true + +TEST COMPLETE +Foo + +This is some text +This text is clipped and unpainted but still should have a valid and reasonable bounding box. diff --git a/LayoutTests/accessibility/isolated-tree/clip-path-bounding-box.html b/LayoutTests/accessibility/isolated-tree/clip-path-bounding-box.html new file mode 100644 index 0000000000000..eb14fbc6a7e37 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/clip-path-bounding-box.html @@ -0,0 +1,97 @@ + + + + + + + + + + +
        +
        Foo
        +
        + +
        + This is some text +
        +
        + This text is clipped and unpainted but still should have a valid and reasonable bounding box. +
        +
        + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/color-input-value-changes-expected.txt b/LayoutTests/accessibility/isolated-tree/color-input-value-changes-expected.txt new file mode 100644 index 0000000000000..a2c26d13889ee --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/color-input-value-changes-expected.txt @@ -0,0 +1,14 @@ +This test ensures accessibility properly responds to dynamic changes in a color input's value. + +#color AXValue: rgb 0.00000 0.00000 0.00000 1 + +Updating #color value to '#ff0f00'. +#color AXValue: rgb 1.00000 0.0588235 0.00000 1 + +Updating #color value to '#000000'. +#color AXValue: rgb 0.00000 0.00000 0.00000 1 + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/color-input-value-changes.html b/LayoutTests/accessibility/isolated-tree/color-input-value-changes.html new file mode 100644 index 0000000000000..592f02fc3a68d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/color-input-value-changes.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/color-picker-press-expected.txt b/LayoutTests/accessibility/isolated-tree/color-picker-press-expected.txt new file mode 100644 index 0000000000000..056180c9ac789 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/color-picker-press-expected.txt @@ -0,0 +1,7 @@ +This test ensures that pressing a color picker via accessibility fires a click event. + +Click event received +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/color-picker-press.html b/LayoutTests/accessibility/isolated-tree/color-picker-press.html new file mode 100644 index 0000000000000..8dcc5168b0aab --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/color-picker-press.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/color-well-expected.txt b/LayoutTests/accessibility/isolated-tree/color-well-expected.txt new file mode 100644 index 0000000000000..a9f10fdabc8b1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/color-well-expected.txt @@ -0,0 +1,13 @@ +This test checks the role of ColorWellRole on an input with type=color + +Role of input type=color is: AXRole: AXColorWell +Value of empty color well: AXValue: rgb 0.00000 0.00000 0.00000 1 +Value of good color well: AXValue: rgb 1.00000 0.00000 0.00000 1 +Value of bad color well: AXValue: rgb 0.00000 0.00000 0.00000 1 +Value of alpha color well: AXValue: rgb 0.231373 0.235294 0.231373 0.400000 +Value of p3 color well: AXValue: rgb 1.00000 0.266667 0.600000 0.258824 + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/color-well-legacy-expected.txt b/LayoutTests/accessibility/isolated-tree/color-well-legacy-expected.txt new file mode 100644 index 0000000000000..4620c1b1bfb86 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/color-well-legacy-expected.txt @@ -0,0 +1,13 @@ +This test checks the role of ColorWellRole on an input with type=color + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +Role of input type=color is: AXRole: AXColorWell +Value of empty color well: AXValue: rgb 0.00000 0.00000 0.00000 1 +Value of good color well: AXValue: rgb 1.00000 0.00000 0.00000 1 +Value of bad color well: AXValue: rgb 0.00000 0.00000 0.00000 1 +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/color-well-legacy.html b/LayoutTests/accessibility/isolated-tree/color-well-legacy.html new file mode 100644 index 0000000000000..dfd24fda0a6e0 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/color-well-legacy.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/color-well.html b/LayoutTests/accessibility/isolated-tree/color-well.html new file mode 100644 index 0000000000000..ba78cda4c58fc --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/color-well.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/column-header-scope-expected.txt b/LayoutTests/accessibility/isolated-tree/column-header-scope-expected.txt new file mode 100644 index 0000000000000..ac71f4ece0e32 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/column-header-scope-expected.txt @@ -0,0 +1,20 @@ +This test ensures that the right header is returned when AX clients request the table header. + +The table cell at (0, 1) should have exactly 1 column header, currently it has 1 column header(s). +The table cell at (2, 0) should have exactly 0 row headers, currently it has 0 row header(s). +The table cell at (1, 2) should have exactly 0 row headers, currently it has 0 row header(s). + +Changing scope of table header at (0, 0) to 'row': +The table cell at (0, 1) should have exactly 0 column headers, currently it has 0 column header(s). +The table cell at (2, 0) should have exactly 1 row header, currently it has 1 row header(s). +The table cell at (1, 2) should have exactly 0 row headers, currently it has 0 row header(s). + +Changing scope of cell at (0, 1) to 'rowgroup': +The table cell at (1, 2) should have exactly 1 row header, currently it has 1 row header(s). + +PASS successfullyParsed is true + +TEST COMPLETE +Title Col 1 Col 2 Col 3 +Data abc Data def Data ghi Data jkl +Data abc Data def Data ghi diff --git a/LayoutTests/accessibility/isolated-tree/column-header-scope.html b/LayoutTests/accessibility/isolated-tree/column-header-scope.html new file mode 100644 index 0000000000000..4f8aa86268a30 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/column-header-scope.html @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +
        TitleCol 1Col 2Col 3
        Data abc + Data defData ghiData jkl
        Data abcData defData ghi
        + + + \ No newline at end of file diff --git a/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-control-owns-elements-expected.txt b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-control-owns-elements-expected.txt new file mode 100644 index 0000000000000..044667eead2b2 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-control-owns-elements-expected.txt @@ -0,0 +1,25 @@ +This tests variations of the comboboxes and elements it can control and own. Then verifies the active-descendant is reflected correctly. + +Received notification for Combobox1 +PASS: combobox.activeElement.isEqual(listitem1) === true +Received notification for Combobox2 +PASS: combobox.activeElement.isEqual(option2_1) === true +Received notification for Combobox3 +PASS: combobox.activeElement.isEqual(row3_1) === true +Received notification for Combobox4 +PASS: combobox.activeElement.isEqual(treeitem4_1) === true + +PASS successfullyParsed is true + +TEST COMPLETE + +item1 +item2 + +item1 +item2 + +cell1 + +treeitem1 +treeitem2 diff --git a/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-control-owns-elements.html b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-control-owns-elements.html new file mode 100644 index 0000000000000..d84feae2ec7a1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-control-owns-elements.html @@ -0,0 +1,101 @@ + + + + + + + + + + +
        +
        item1
        +
        item2
        +
        + + + +
        +
        item1
        +
        item2
        +
        + + + +
        +
        +
        cell1
        +
        +
        + + + +
        +
        treeitem1
        +
        treeitem2
        +
        + + + + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-expected.txt b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-expected.txt new file mode 100644 index 0000000000000..8af0356604cd3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-expected.txt @@ -0,0 +1,15 @@ +option 1 +option 2 +This tests that the aria roles for combobox and aria-expanded work correctly in conjunction. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +Role: AXRole: AXComboBox +PASS combobox.isExpanded is false +PASS combobox.isExpanded is false +Role: AXRole: AXList +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-hierarchy-expected.txt b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-hierarchy-expected.txt new file mode 100644 index 0000000000000..be935108336a4 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-hierarchy-expected.txt @@ -0,0 +1,14 @@ +This verifies the accessibility tree of ARIA comboboxes. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +AXRole: AXComboBox AXValue: option 1 +option 2 + AXRole: AXList AXValue: + AXRole: AXStaticText AXValue: + AXRole: AXStaticText AXValue: +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-hierarchy.html b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-hierarchy.html new file mode 100644 index 0000000000000..beb06a4511e13 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-hierarchy.html @@ -0,0 +1,29 @@ + + + + + + + +
        +
        +
        +
        option 1
        +
        option 2
        +
        +
        +
        +

        +
        
        +
        + + + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-no-owns-expected.txt b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-no-owns-expected.txt new file mode 100644 index 0000000000000..dd8b8e8b93f3b --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-no-owns-expected.txt @@ -0,0 +1,9 @@ +A combobox should still support aria-activedescendant even if it doesn't use aria-owns. +Received AXActiveElementChanged for Combobox +PASS: combobox.activeElement.isEqual(listitem1) === true + +PASS successfullyParsed is true + +TEST COMPLETE + +item1 diff --git a/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-no-owns.html b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-no-owns.html new file mode 100644 index 0000000000000..393a5ae3329ec --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox-no-owns.html @@ -0,0 +1,46 @@ + + + + + + + + + +
        +
        item1
        +
        + + + + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox.html b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox.html new file mode 100644 index 0000000000000..9a23df6e0284e --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/aria-combobox.html @@ -0,0 +1,44 @@ + + + + + + + + + + +

        +
        + + + + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/combobox-active-element-selected-children-expected.txt b/LayoutTests/accessibility/isolated-tree/combobox/combobox-active-element-selected-children-expected.txt new file mode 100644 index 0000000000000..807404e8fbe89 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/combobox-active-element-selected-children-expected.txt @@ -0,0 +1,23 @@ +Tests that for a combobox, ActiveElement and SelectedChildren return the same object. + +PASS: axCombobox.activeElement === null +PASS: axCombobox.selectedChildren().length === 0 +Setting activedescendant to 1 and selected to 2: +notification: AXActiveElementChanged + activeElement: item1 +notification: AXSelectedChildrenChanged + selectedChildren: [ item1 ] +Setting activedescendant to 2: +notification: AXActiveElementChanged + activeElement: item2 +Setting activedescendant to 3: +notification: AXActiveElementChanged + activeElement: item3 +Selecting 3: +notification: AXSelectedChildrenChanged + selectedChildren: [ item3 ] + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/combobox-active-element-selected-children.html b/LayoutTests/accessibility/isolated-tree/combobox/combobox-active-element-selected-children.html new file mode 100644 index 0000000000000..7c36274acd6db --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/combobox-active-element-selected-children.html @@ -0,0 +1,81 @@ + + + + + + + + +
        + +
        + +
        +
        1 +
        2
        +
        3
        +
        + +
        + + + + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/combobox-linked-listbox-destroyed-expected.txt b/LayoutTests/accessibility/isolated-tree/combobox/combobox-linked-listbox-destroyed-expected.txt new file mode 100644 index 0000000000000..dc7bffb6b665d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/combobox-linked-listbox-destroyed-expected.txt @@ -0,0 +1,24 @@ +This verifies that when a listbox ax object is destroyed, the new object maintains the aria-controls/aria-owns relationship. + +PASS: linkedListbox1.role === 'AXRole: AXList' + +Hiding List Box #1: +PASS: !linkedListbox1 === true +Showing List Box #1: +PASS: linkedListbox1.role === 'AXRole: AXList' +Showing List Box #2: +PASS: linkedListbox2.role === 'AXRole: AXList' + +Hiding List Box #2: +PASS: !linkedListbox2 === true +Showing List Box #2: +PASS: linkedListbox2.role === 'AXRole: AXList' + +PASS successfullyParsed is true + +TEST COMPLETE + +Apple +Banana +Carrot + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/combobox-linked-listbox-destroyed.html b/LayoutTests/accessibility/isolated-tree/combobox/combobox-linked-listbox-destroyed.html new file mode 100644 index 0000000000000..6c9a4cbefa213 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/combobox-linked-listbox-destroyed.html @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/mac/aria-combobox-activedescendant-expected.txt b/LayoutTests/accessibility/isolated-tree/combobox/mac/aria-combobox-activedescendant-expected.txt new file mode 100644 index 0000000000000..bddb538541b77 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/mac/aria-combobox-activedescendant-expected.txt @@ -0,0 +1,12 @@ +On macOS, a combobox should map aria-activedescendant to AXSelectedChildren. +PASS: combobox.selectedChildrenCount === 1 +PASS: activeDescendant.role === 'AXRole: AXGroup' +PASS: activeDescendant.title === 'AXTitle: item2' +PASS: combobox.selectedChildAtIndex(0).title === 'AXTitle: item1' + +PASS successfullyParsed is true + +TEST COMPLETE + +item1 +item2 diff --git a/LayoutTests/accessibility/isolated-tree/combobox/mac/aria-combobox-activedescendant.html b/LayoutTests/accessibility/isolated-tree/combobox/mac/aria-combobox-activedescendant.html new file mode 100644 index 0000000000000..c57f0f8275f28 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/mac/aria-combobox-activedescendant.html @@ -0,0 +1,37 @@ + + + + + + + + + +
        +
        item1
        +
        item2
        +
        + + + + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-activedescendant-notifications-expected.txt b/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-activedescendant-notifications-expected.txt new file mode 100644 index 0000000000000..87f3889e09eff --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-activedescendant-notifications-expected.txt @@ -0,0 +1,13 @@ +This test ensures that changing aria-activedescendant on a combobox posts AXFocusedUIElementChanged for the active descendant, and that the focused element becomes the active descendant. + +The ComboBox should start out as the focused element. +PASS: axCombo.isEqual(accessibilityController.focusedElement) === true +After setting aria-activedescendant, the focused element should be the active descendant, not the combobox. +PASS: axCombo.isEqual(accessibilityController.focusedElement) === false +PASS: focusedElement.role.toLowerCase().includes('statictext') === true +PASS: platformStaticTextValue(focusedElement).includes('item1') === true + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-activedescendant-notifications.html b/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-activedescendant-notifications.html new file mode 100644 index 0000000000000..5bc9abdbaac7a --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-activedescendant-notifications.html @@ -0,0 +1,57 @@ + + + + + + + + +
        + +
          +
        • item1
        • +
        +
        + + + + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-inherited-role-expected.txt b/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-inherited-role-expected.txt new file mode 100644 index 0000000000000..7c197aa4304b6 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-inherited-role-expected.txt @@ -0,0 +1,8 @@ +Tests that input text elements contained in a combobox inherit the combobox role. + +PASS: input.role === 'AXRole: AXComboBox' + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-inherited-role.html b/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-inherited-role.html new file mode 100644 index 0000000000000..42aafb90912a7 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-inherited-role.html @@ -0,0 +1,34 @@ + + + + + + + + +
        +
        + +
        +
        + +
        +
        + + + + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-value-expected.txt b/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-value-expected.txt new file mode 100644 index 0000000000000..a54eff00e5426 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-value-expected.txt @@ -0,0 +1,12 @@ + +This tests that a combobox element used on a native text control will return the correct AXValue and placeholder. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS combobox.stringValue is 'AXValue: text' +PASS combobox.stringAttributeValue('AXPlaceholderValue') is 'Placeholder' +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-value.html b/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-value.html new file mode 100644 index 0000000000000..04c7e8326ac52 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/combobox/mac/combobox-value.html @@ -0,0 +1,26 @@ + + + + + + + + + +

        +
        + + + + diff --git a/LayoutTests/accessibility/isolated-tree/container-node-delete-causes-crash-expected.txt b/LayoutTests/accessibility/isolated-tree/container-node-delete-causes-crash-expected.txt new file mode 100644 index 0000000000000..f1866803daa29 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/container-node-delete-causes-crash-expected.txt @@ -0,0 +1,10 @@ +Checks to make sure a heap-use-after-free crash doesn't occur when a container node with an associated accessibility object is deleted from the tree. The heap-use-after free was occuring when the AccessibilityObject corresponding to the child of the text node walked up its parent chain in AccessibilityObject::supportsARIALiveRegion but its parent was already deleted. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS successfullyParsed is true + +TEST COMPLETE +Text + diff --git a/LayoutTests/accessibility/isolated-tree/container-node-delete-causes-crash.html b/LayoutTests/accessibility/isolated-tree/container-node-delete-causes-crash.html new file mode 100644 index 0000000000000..7c071c6e147f3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/container-node-delete-causes-crash.html @@ -0,0 +1,27 @@ + + + + + +
        + + + Text + + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/content-changed-notification-causes-crash-expected.txt b/LayoutTests/accessibility/isolated-tree/content-changed-notification-causes-crash-expected.txt new file mode 100644 index 0000000000000..e956edbd01b12 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/content-changed-notification-causes-crash-expected.txt @@ -0,0 +1,11 @@ +>> +Ensures that this snippet does not lead to a crash. Bug 86029. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS. WebKit did not crash. +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/content-changed-notification-causes-crash.html b/LayoutTests/accessibility/isolated-tree/content-changed-notification-causes-crash.html new file mode 100644 index 0000000000000..3fd21064b03d8 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/content-changed-notification-causes-crash.html @@ -0,0 +1,37 @@ + + + + + + + +
        + +
          >> + +
        + +

        +
        + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/content-editable-expected.txt b/LayoutTests/accessibility/isolated-tree/content-editable-expected.txt new file mode 100644 index 0000000000000..5182cd4f2adf9 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/content-editable-expected.txt @@ -0,0 +1,8 @@ +This test ensures that the body element has a writable AXValue because it has a contenteditable attribute. + +PASS: body.isAttributeSettable('AXValue') === true + +PASS successfullyParsed is true + +TEST COMPLETE +Some Text. diff --git a/LayoutTests/accessibility/isolated-tree/content-editable-set-inner-text-generates-axvalue-notification-expected.txt b/LayoutTests/accessibility/isolated-tree/content-editable-set-inner-text-generates-axvalue-notification-expected.txt new file mode 100644 index 0000000000000..92c4d7f583934 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/content-editable-set-inner-text-generates-axvalue-notification-expected.txt @@ -0,0 +1,13 @@ +This tests that a contenteditable region will send an AXValueChange notification when JS methods for changing children are used. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +Updated value: AXValue: Test1 +Updated value: AXValue: Test2 +Updated value: AXValue: Test3 +Updated value: AXValue: Test3Test4 +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/content-editable-set-inner-text-generates-axvalue-notification.html b/LayoutTests/accessibility/isolated-tree/content-editable-set-inner-text-generates-axvalue-notification.html new file mode 100644 index 0000000000000..e97cb9c180065 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/content-editable-set-inner-text-generates-axvalue-notification.html @@ -0,0 +1,58 @@ + + + + + + + +
        +hello
        +world +
        + +

        +
        + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/content-editable.html b/LayoutTests/accessibility/isolated-tree/content-editable.html new file mode 100644 index 0000000000000..84883bcd2d2e5 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/content-editable.html @@ -0,0 +1,23 @@ + + + + + + + + +Some Text. + + + + diff --git a/LayoutTests/accessibility/isolated-tree/contenteditable-hidden-div-expected.txt b/LayoutTests/accessibility/isolated-tree/contenteditable-hidden-div-expected.txt new file mode 100644 index 0000000000000..15ecbcf72b713 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/contenteditable-hidden-div-expected.txt @@ -0,0 +1,12 @@ +test + +This tests that a contenteditable element will not be ignored by accessibility. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS accessibilityController.focusedElement.isEqual(editableDiv) is false +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/contenteditable-hidden-div.html b/LayoutTests/accessibility/isolated-tree/contenteditable-hidden-div.html new file mode 100644 index 0000000000000..9ee96e85f0dba --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/contenteditable-hidden-div.html @@ -0,0 +1,33 @@ + + + + + + + +
        + +

        test

        + +

        +
        + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/contenteditable-spinbutton-uses-arrow-keys-expected.txt b/LayoutTests/accessibility/isolated-tree/contenteditable-spinbutton-uses-arrow-keys-expected.txt new file mode 100644 index 0000000000000..c5ad0e7e40a72 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/contenteditable-spinbutton-uses-arrow-keys-expected.txt @@ -0,0 +1,13 @@ +This test ensures that increment and decrement simulate up and down keypresses for ARIA spinbuttons when they have the contenteditable attribute set. + +#spinbutton initial value: AXValue: 2022 +Key event received: {keyIdentifier: Up, key: ArrowUp, keyCode: 38} +#spinbutton value after increment: AXValue: 2023 + +Key event received: {keyIdentifier: Down, key: ArrowDown, keyCode: 40} +#spinbutton value after decrement: AXValue: 2022 + +PASS successfullyParsed is true + +TEST COMPLETE +2022 diff --git a/LayoutTests/accessibility/isolated-tree/contenteditable-spinbutton-uses-arrow-keys.html b/LayoutTests/accessibility/isolated-tree/contenteditable-spinbutton-uses-arrow-keys.html new file mode 100644 index 0000000000000..1b5297f7e3f80 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/contenteditable-spinbutton-uses-arrow-keys.html @@ -0,0 +1,66 @@ + + + + + + + + + + + + 2022 + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/contenteditable-table-check-causes-crash-expected.txt b/LayoutTests/accessibility/isolated-tree/contenteditable-table-check-causes-crash-expected.txt new file mode 100644 index 0000000000000..b9ee65a8220d8 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/contenteditable-table-check-causes-crash-expected.txt @@ -0,0 +1,9 @@ +Ensures that this snippet does not lead to a crash in the code that detects if a table is contenteditable. Bug 87409. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/contenteditable-table-check-causes-crash.html b/LayoutTests/accessibility/isolated-tree/contenteditable-table-check-causes-crash.html new file mode 100644 index 0000000000000..8ab7272360ae3 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/contenteditable-table-check-causes-crash.html @@ -0,0 +1,28 @@ + + + + + + + + + + + + +

        +
        + + + diff --git a/LayoutTests/accessibility/isolated-tree/contenteditable-values-expected.txt b/LayoutTests/accessibility/isolated-tree/contenteditable-values-expected.txt new file mode 100644 index 0000000000000..4301e3559a64f --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/contenteditable-values-expected.txt @@ -0,0 +1,15 @@ +This test ensures we parse contenteditable correctly. + +PASS: element.role === 'AXRole: AXTextArea' +PASS: element.role === 'AXRole: AXGroup' +PASS: element.role === 'AXRole: AXTextArea' +PASS: element.role === 'AXRole: AXGroup' +PASS: element.role === 'AXRole: AXTextArea' +PASS: element.role === 'AXRole: AXGroup' +PASS: element.role === 'AXRole: AXTextArea' +PASS: element.role === 'AXRole: AXGroup' + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/contenteditable-values.html b/LayoutTests/accessibility/isolated-tree/contenteditable-values.html new file mode 100644 index 0000000000000..2f37c1a577eb1 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/contenteditable-values.html @@ -0,0 +1,43 @@ + + + + + + + + +
        +
        True
        +
        False
        +
        Plaintext-Only
        +
        None
        +
        Empty
        +
        Other
        +
        TRUE
        +
         true 
        +
        + + + + diff --git a/LayoutTests/accessibility/isolated-tree/crash-deleting-dynamically-updated-text-input-expected.txt b/LayoutTests/accessibility/isolated-tree/crash-deleting-dynamically-updated-text-input-expected.txt new file mode 100644 index 0000000000000..01bb56f634d18 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/crash-deleting-dynamically-updated-text-input-expected.txt @@ -0,0 +1,11 @@ +This test ensures we don't crash when deleting the parent of a text input that has had at least one value change. + +PASS: input.role === 'AXRole: AXTextField' +PASS: accessibilityController.accessibleElementById('form').childAtIndex(0).role === 'AXRole: AXTextField' +PASS: input.stringValue === 'AXValue: abc' +PASS: input.role === 'AXRole: ' + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/crash-deleting-dynamically-updated-text-input.html b/LayoutTests/accessibility/isolated-tree/crash-deleting-dynamically-updated-text-input.html new file mode 100644 index 0000000000000..161697857fd21 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/crash-deleting-dynamically-updated-text-input.html @@ -0,0 +1,47 @@ + + + + + + + + +
        + +
        + + + + + + diff --git a/LayoutTests/accessibility/isolated-tree/crash-determining-aria-role-when-label-present-expected.txt b/LayoutTests/accessibility/isolated-tree/crash-determining-aria-role-when-label-present-expected.txt new file mode 100644 index 0000000000000..2fb251fc03741 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/crash-determining-aria-role-when-label-present-expected.txt @@ -0,0 +1,11 @@ + +This tests a crashing scenario where an element with a role attribute is a child of a label that also has a corresponding control. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS input.childrenCount is 0 +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/crash-determining-aria-role-when-label-present.html b/LayoutTests/accessibility/isolated-tree/crash-determining-aria-role-when-label-present.html new file mode 100644 index 0000000000000..99c6a2034b05d --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/crash-determining-aria-role-when-label-present.html @@ -0,0 +1,29 @@ + + + + + + + +