diff --git a/CMakePresets.json b/CMakePresets.json index a0edbc3e3f20..3645ec1f5d4d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -180,6 +180,10 @@ "CMAKE_OBJCXX_FLAGS_DEBUG": { "type": "STRING", "value": "-g -O3" + }, + "USE_PCH_CODEGEN": { + "type": "BOOL", + "value": "OFF" } } }, diff --git a/Configurations/CommonBase.xcconfig b/Configurations/CommonBase.xcconfig index fdfbb303b0e4..3aac28a5e664 100644 --- a/Configurations/CommonBase.xcconfig +++ b/Configurations/CommonBase.xcconfig @@ -93,7 +93,7 @@ OTHER_LDFLAGS = $(inherited) $(WK_COMMON_OTHER_LDFLAGS); WK_COMMON_OTHER_TAPI_FLAGS = -x objective-c++ -std=c++2b -fno-rtti $(WK_SANITIZER_OTHER_TAPI_FLAGS) $(WK_AVAILABILITY_OVERLAY_FLAGS); OTHER_TAPI_FLAGS = $(inherited) $(WK_COMMON_OTHER_TAPI_FLAGS); -WK_DEFAULT_WK_AUDIT_SPI[sdk=iphoneos*] = $(WK_AND_$(WK_NOT_$(WK_ANY_SANITIZER_ENABLED))_$(SUPPORTS_TEXT_BASED_API)); +WK_DEFAULT_WK_AUDIT_SPI[sdk=iphoneos*] = $(WK_NOT_$(WK_ANY_SANITIZER_ENABLED)); // Explicitly disable auditing on other embedded platforms, because some SDKs // fall back to reading settings for iOS. WK_DEFAULT_WK_AUDIT_SPI[sdk=appletvos*] = ; @@ -102,7 +102,9 @@ WK_DEFAULT_WK_AUDIT_SPI[sdk=xros*] = ; // Disable auditing on internal builds of already-shipped releases. WK_DEFAULT_WK_AUDIT_SPI[sdk=iphoneos26*.internal] = ; -WK_AUDIT_SPI = $(WK_DEFAULT_WK_AUDIT_SPI); +// Overridden in projects which do not have an InstallAPI requirement to +// support audit-spi (e.g. WebCore). +WK_AUDIT_SPI = $(WK_AND_$(WK_DEFAULT_WK_AUDIT_SPI)_$(SUPPORTS_TEXT_BASED_API)); WK_OTHER_AUDIT_SPI_FLAGS = $(WK_OTHER_AUDIT_SPI_FLAGS_148943382) $(WK_OTHER_AUDIT_SPI_FLAGS_164901718) $(WK_OTHER_AUDIT_SPI_FLAGS_$(CONFIGURATION)); WK_OTHER_AUDIT_SPI_FLAGS_Release = -DNDEBUG; diff --git a/Introduction.md b/Introduction.md index 0ce1818526a2..da41acbcca7c 100644 --- a/Introduction.md +++ b/Introduction.md @@ -1389,9 +1389,12 @@ mirroring the same directory structure as `LayoutTests`. For example, the actual output produced for `LayoutTests/editing/inserting/typing-001.html`, if failed, will appear in `WebKitBuild/Debug/layout-test-results/editing/inserting/typing-001-actual.txt`. run-webkit-tests also generates a web page with the summary of results in -`WebKitBuild/Debug/layout-test-results/results.html` and automatically tries to open it in Safari using the local build of WebKit. +`WebKitBuild/Debug/layout-test-results/results.html`, and prints the path to it at the end of +a run with unexpected results so that you can open it manually. -> If Safari fails to launch, specify `--no-show-results` and open results.html file manually. +> Specify `--show-results` to have run-webkit-tests automatically open results.html in a browser +> using the local build of WebKit once the tests are done. If the browser fails to launch, drop +> the option and open results.html yourself. ### Updating Expected Results diff --git a/JSTests/microbenchmarks/float32array-sort-large-array.js b/JSTests/microbenchmarks/float32array-sort-large-array.js new file mode 100644 index 000000000000..3225ce92054e --- /dev/null +++ b/JSTests/microbenchmarks/float32array-sort-large-array.js @@ -0,0 +1,22 @@ +var length = 4096; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +// Two draws per element so the mantissa has entropy in every byte. A single 32-bit draw leaves +// the low mantissa bytes zero, which lets the sort skip digit passes that real data would not. +var source = new Float32Array(length); +for (var i = 0; i < length; ++i) + source[i] = nextRandom() + nextRandom() * 2.3283064365386963e-10; + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Float32Array(length); +for (var i = 0; i < 4000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/float64array-sort-large-array.js b/JSTests/microbenchmarks/float64array-sort-large-array.js new file mode 100644 index 000000000000..ecbf81cae20c --- /dev/null +++ b/JSTests/microbenchmarks/float64array-sort-large-array.js @@ -0,0 +1,22 @@ +var length = 16384; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +// Two draws per element so the mantissa has entropy in every byte. A single 32-bit draw leaves +// the low mantissa bytes zero, which lets the sort skip digit passes that real data would not. +var source = new Float64Array(length); +for (var i = 0; i < length; ++i) + source[i] = nextRandom() + nextRandom() * 2.3283064365386963e-10; + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Float64Array(length); +for (var i = 0; i < 300; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/float64array-sort-low-entropy.js b/JSTests/microbenchmarks/float64array-sort-low-entropy.js new file mode 100644 index 000000000000..a1785e32792e --- /dev/null +++ b/JSTests/microbenchmarks/float64array-sort-low-entropy.js @@ -0,0 +1,20 @@ +var length = 16384; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +var source = new Float64Array(length); +for (var i = 0; i < length; ++i) + source[i] = (nextRandom() & 1) ? 1e10 : 7.5; + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Float64Array(length); +for (var i = 0; i < 5000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/float64array-sort-medium-array.js b/JSTests/microbenchmarks/float64array-sort-medium-array.js new file mode 100644 index 000000000000..9c25b4419dd1 --- /dev/null +++ b/JSTests/microbenchmarks/float64array-sort-medium-array.js @@ -0,0 +1,22 @@ +var length = 8192; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +// Two draws per element so the mantissa has entropy in every byte. A single 32-bit draw leaves +// the low mantissa bytes zero, which lets the sort skip digit passes that real data would not. +var source = new Float64Array(length); +for (var i = 0; i < length; ++i) + source[i] = nextRandom() + nextRandom() * 2.3283064365386963e-10; + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Float64Array(length); +for (var i = 0; i < 1000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/float64array-sort-presorted.js b/JSTests/microbenchmarks/float64array-sort-presorted.js new file mode 100644 index 000000000000..13e593deeeb9 --- /dev/null +++ b/JSTests/microbenchmarks/float64array-sort-presorted.js @@ -0,0 +1,12 @@ +var length = 16384; + +var source = new Float64Array(length); +for (var i = 0; i < length; ++i) + source[i] = i * 0.5; + +// Keep the input sorted every iteration; sorting it again must stay cheap. +var array = new Float64Array(length); +for (var i = 0; i < 7500; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/float64array-sort-small-array.js b/JSTests/microbenchmarks/float64array-sort-small-array.js new file mode 100644 index 000000000000..477e1180195f --- /dev/null +++ b/JSTests/microbenchmarks/float64array-sort-small-array.js @@ -0,0 +1,22 @@ +var length = 512; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +// Two draws per element so the mantissa has entropy in every byte. A single 32-bit draw leaves +// the low mantissa bytes zero, which lets the sort skip digit passes that real data would not. +var source = new Float64Array(length); +for (var i = 0; i < length; ++i) + source[i] = nextRandom() + nextRandom() * 2.3283064365386963e-10; + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Float64Array(length); +for (var i = 0; i < 50000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/int16array-sort-large-array.js b/JSTests/microbenchmarks/int16array-sort-large-array.js new file mode 100644 index 000000000000..7cb7af92500a --- /dev/null +++ b/JSTests/microbenchmarks/int16array-sort-large-array.js @@ -0,0 +1,20 @@ +var length = 4096; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +var source = new Int16Array(length); +for (var i = 0; i < length; ++i) + source[i] = nextRandom(); + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Int16Array(length); +for (var i = 0; i < 6000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/int16array-sort-low-entropy.js b/JSTests/microbenchmarks/int16array-sort-low-entropy.js new file mode 100644 index 000000000000..b294775451af --- /dev/null +++ b/JSTests/microbenchmarks/int16array-sort-low-entropy.js @@ -0,0 +1,20 @@ +var length = 4096; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +var source = new Int16Array(length); +for (var i = 0; i < length; ++i) + source[i] = (nextRandom() & 1) ? 30000 : 7; + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Int16Array(length); +for (var i = 0; i < 13000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/int32array-sort-large-array.js b/JSTests/microbenchmarks/int32array-sort-large-array.js new file mode 100644 index 000000000000..823387b6dc67 --- /dev/null +++ b/JSTests/microbenchmarks/int32array-sort-large-array.js @@ -0,0 +1,20 @@ +var length = 4096; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +var source = new Int32Array(length); +for (var i = 0; i < length; ++i) + source[i] = nextRandom(); + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Int32Array(length); +for (var i = 0; i < 6000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/int32array-sort-low-entropy.js b/JSTests/microbenchmarks/int32array-sort-low-entropy.js new file mode 100644 index 000000000000..74061ff6fe97 --- /dev/null +++ b/JSTests/microbenchmarks/int32array-sort-low-entropy.js @@ -0,0 +1,20 @@ +var length = 4096; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +var source = new Int32Array(length); +for (var i = 0; i < length; ++i) + source[i] = (nextRandom() & 1) ? 200000 : 7; + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Int32Array(length); +for (var i = 0; i < 12000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/int32array-sort-medium-array.js b/JSTests/microbenchmarks/int32array-sort-medium-array.js new file mode 100644 index 000000000000..858bf6494b7e --- /dev/null +++ b/JSTests/microbenchmarks/int32array-sort-medium-array.js @@ -0,0 +1,20 @@ +var length = 512; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +var source = new Int32Array(length); +for (var i = 0; i < length; ++i) + source[i] = nextRandom(); + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Int32Array(length); +for (var i = 0; i < 60000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/int32array-sort-presorted.js b/JSTests/microbenchmarks/int32array-sort-presorted.js new file mode 100644 index 000000000000..92ab96c9142c --- /dev/null +++ b/JSTests/microbenchmarks/int32array-sort-presorted.js @@ -0,0 +1,12 @@ +var length = 4096; + +var source = new Int32Array(length); +for (var i = 0; i < length; ++i) + source[i] = i * 1024; + +// Keep the input sorted every iteration; sorting it again must stay cheap. +var array = new Int32Array(length); +for (var i = 0; i < 40000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/int32array-sort-small-array.js b/JSTests/microbenchmarks/int32array-sort-small-array.js new file mode 100644 index 000000000000..3add4c7fc85c --- /dev/null +++ b/JSTests/microbenchmarks/int32array-sort-small-array.js @@ -0,0 +1,20 @@ +var length = 192; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +var source = new Int32Array(length); +for (var i = 0; i < length; ++i) + source[i] = nextRandom(); + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Int32Array(length); +for (var i = 0; i < 190000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/map-for-each-deleted-entries.js b/JSTests/microbenchmarks/map-for-each-deleted-entries.js new file mode 100644 index 000000000000..5332b84897f9 --- /dev/null +++ b/JSTests/microbenchmarks/map-for-each-deleted-entries.js @@ -0,0 +1,17 @@ +function sumEntries(map) { + var sum = 0; + map.forEach(function (value, key) { + sum += value - key; + }); + return sum; +} +noInline(sumEntries); + +var map = new Map; +for (var i = 0; i < 1500; ++i) + map.set(i, i * 2); +for (var i = 0; i < 1500; i += 3) + map.delete(i); + +for (var i = 0; i < 1e4; ++i) + sumEntries(map); diff --git a/JSTests/microbenchmarks/map-for-each-key-value.js b/JSTests/microbenchmarks/map-for-each-key-value.js new file mode 100644 index 000000000000..30cdcf02d13c --- /dev/null +++ b/JSTests/microbenchmarks/map-for-each-key-value.js @@ -0,0 +1,15 @@ +function sumEntries(map) { + var sum = 0; + map.forEach(function (value, key) { + sum += value + key; + }); + return sum; +} +noInline(sumEntries); + +var map = new Map; +for (var i = 0; i < 1000; ++i) + map.set(i, i * 2); + +for (var i = 0; i < 1e4; ++i) + sumEntries(map); diff --git a/JSTests/microbenchmarks/object-assign-clone-multiple-sources.js b/JSTests/microbenchmarks/object-assign-clone-multiple-sources.js new file mode 100644 index 000000000000..1736280a3a72 --- /dev/null +++ b/JSTests/microbenchmarks/object-assign-clone-multiple-sources.js @@ -0,0 +1,4 @@ +var cities = ["a", "b", "c", "d", "e", "f", "g", "h"]; +var state = { id: 1, name: "x", age: 3, city: "z", zip: 0, flag: true, score: 1.5, tag: "t" }; +for (var i = 0; i < 1e4; ++i) + state = Object.assign({}, state, { city: cities[i & 7], zip: i }); diff --git a/JSTests/microbenchmarks/regexp-lookbehind-backreference.js b/JSTests/microbenchmarks/regexp-lookbehind-backreference.js new file mode 100644 index 000000000000..15debffa57f1 --- /dev/null +++ b/JSTests/microbenchmarks/regexp-lookbehind-backreference.js @@ -0,0 +1,10 @@ +let text = "abab-abcabc-xyxy-abcabd-aa-a-".repeat(40); +let re = /(?<=\1(\w+))-/g; +let result = 0; +for (let i = 0; i < 1e4; ++i) { + re.lastIndex = 0; + while (re.exec(text)) + result++; +} +if (result !== 160 * 1e4) + throw new Error("Bad result: " + result); diff --git a/JSTests/microbenchmarks/set-for-each-value.js b/JSTests/microbenchmarks/set-for-each-value.js new file mode 100644 index 000000000000..8ca352288f7f --- /dev/null +++ b/JSTests/microbenchmarks/set-for-each-value.js @@ -0,0 +1,15 @@ +function sumValues(set) { + var sum = 0; + set.forEach(function (value) { + sum += value; + }); + return sum; +} +noInline(sumValues); + +var set = new Set; +for (var i = 0; i < 1000; ++i) + set.add(i); + +for (var i = 0; i < 1e4; ++i) + sumValues(set); diff --git a/JSTests/microbenchmarks/uint16array-sort-large-array.js b/JSTests/microbenchmarks/uint16array-sort-large-array.js new file mode 100644 index 000000000000..f0688aa3ae74 --- /dev/null +++ b/JSTests/microbenchmarks/uint16array-sort-large-array.js @@ -0,0 +1,20 @@ +var length = 4096; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +var source = new Uint16Array(length); +for (var i = 0; i < length; ++i) + source[i] = nextRandom(); + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Uint16Array(length); +for (var i = 0; i < 6000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/microbenchmarks/uint32array-sort-large-array.js b/JSTests/microbenchmarks/uint32array-sort-large-array.js new file mode 100644 index 000000000000..dd72fc020dbc --- /dev/null +++ b/JSTests/microbenchmarks/uint32array-sort-large-array.js @@ -0,0 +1,20 @@ +var length = 4096; + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed; +} + +var source = new Uint32Array(length); +for (var i = 0; i < length; ++i) + source[i] = nextRandom() >>> 0; + +// Restore the unsorted input every iteration so the sort never sees an already sorted array. +var array = new Uint32Array(length); +for (var i = 0; i < 6000; ++i) { + array.set(source); + array.sort(); +} diff --git a/JSTests/stress/async-function-return-non-thenable-folding.js b/JSTests/stress/async-function-return-non-thenable-folding.js index b4ce4021dcba..5293e314e1e5 100644 --- a/JSTests/stress/async-function-return-non-thenable-folding.js +++ b/JSTests/stress/async-function-return-non-thenable-folding.js @@ -231,5 +231,3 @@ const log = []; shouldBe(m, (1e5 - 1) * 3, "phase11 method"); log.push("phase11 ok"); } - -print(log.join("\n")); diff --git a/JSTests/stress/date-get-day-full-range.js b/JSTests/stress/date-get-day-full-range.js new file mode 100644 index 000000000000..a374b3258c1b --- /dev/null +++ b/JSTests/stress/date-get-day-full-range.js @@ -0,0 +1,42 @@ +function shouldBe(actual, expected, message) { + if (actual !== expected) + throw new Error(`bad value: ${actual}, expected ${expected} (${message})`); +} + +function referenceDay(days) { + let r = (days + 4) % 7; + return r < 0 ? r + 7 : r; +} + +const msPerDay = 86400000; +const maxDays = 100000000; + +// 0: Sunday ... 6: Saturday +shouldBe(new Date(Date.UTC(1970, 0, 1)).getUTCDay(), 4, "1970-01-01 is Thursday"); +shouldBe(new Date(Date.UTC(1969, 11, 31)).getUTCDay(), 3, "1969-12-31 is Wednesday"); +shouldBe(new Date(Date.UTC(2000, 0, 1)).getUTCDay(), 6, "2000-01-01 is Saturday"); +shouldBe(new Date(Date.UTC(1900, 0, 1)).getUTCDay(), 1, "1900-01-01 is Monday"); +shouldBe(new Date(Date.UTC(1600, 0, 1)).getUTCDay(), 6, "1600-01-01 is Saturday"); +shouldBe(new Date(0).getUTCDay(), 4); + +// Extremes of the ECMAScript time range. +shouldBe(new Date(maxDays * msPerDay).getUTCDay(), referenceDay(maxDays), "max time value"); +shouldBe(new Date(-maxDays * msPerDay).getUTCDay(), referenceDay(-maxDays), "min time value"); +shouldBe(new Date(maxDays * msPerDay).getUTCDay(), 6, "+275760-09-13 is Saturday"); +shouldBe(new Date(-maxDays * msPerDay).getUTCDay(), 2, "-271821-04-20 is Tuesday"); + +// Days around zero and around each boundary. +for (let days = -20; days <= 20; ++days) + shouldBe(new Date(days * msPerDay).getUTCDay(), referenceDay(days), `days=${days}`); +for (let days = maxDays - 20; days <= maxDays; ++days) { + shouldBe(new Date(days * msPerDay).getUTCDay(), referenceDay(days), `days=${days}`); + shouldBe(new Date(-days * msPerDay).getUTCDay(), referenceDay(-days), `days=${-days}`); +} + +// Strided sweep over the whole range. +for (let days = -maxDays; days <= maxDays; days += 4093) + shouldBe(new Date(days * msPerDay).getUTCDay(), referenceDay(days), `days=${days}`); + +// Last millisecond of a day still belongs to that day. +for (let days = -1000; days <= 1000; days += 97) + shouldBe(new Date(days * msPerDay + msPerDay - 1).getUTCDay(), referenceDay(days), `days=${days} end`); diff --git a/JSTests/stress/duplicate-parameter-names-last-wins.js b/JSTests/stress/duplicate-parameter-names-last-wins.js new file mode 100644 index 000000000000..7e916027341c --- /dev/null +++ b/JSTests/stress/duplicate-parameter-names-last-wins.js @@ -0,0 +1,21 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`bad value: ${actual}, expected ${expected}`); +} + +function direct(x, x) { return x; } +function captured(x, x) { return function() { return x; }; } +function withEval(x, x) { return eval("x"); } +function withArguments(x, x) { arguments; return x; } +function fullActivation(x, y, x) { return function() { return x + y; }; } +function mixed(a, b, b, b, b, b, b) { return function() { return a; }; } + +for (var i = 0; i < 1e4; ++i) { + shouldBe(direct(1, 2), 2); + shouldBe(direct(1), undefined); + shouldBe(captured(1, 2)(), 2); + shouldBe(withEval(1, 2), 2); + shouldBe(withArguments(1, 2), 2); + shouldBe(fullActivation(1, 10, 2)(), 12); + shouldBe(mixed("success")(), "success"); +} diff --git a/JSTests/stress/generator-and-async-body-without-parameters-hoisting.js b/JSTests/stress/generator-and-async-body-without-parameters-hoisting.js new file mode 100644 index 000000000000..046922b11140 --- /dev/null +++ b/JSTests/stress/generator-and-async-body-without-parameters-hoisting.js @@ -0,0 +1,55 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error('bad value: ' + actual); +} + +function* generatorNoParams() { + { function a() { } } + var b = 1; + yield typeof a; + yield b; +} + +function* generatorWithParams(a, b) { + { function a() { } } + var b; + yield typeof a; + yield b; +} + +async function asyncNoParams() { + { function a() { } } + var b = 1; + await 1; + return [typeof a, b]; +} + +async function asyncWithParams(a, b) { + { function a() { } } + var b; + await 1; + return [typeof a, b]; +} + +var asyncArrowNoParams = async () => { + { function a() { } } + var b = 1; + await 1; + return [typeof a, b]; +}; + +for (var i = 0; i < testLoopCount; ++i) { + var g = generatorNoParams(); + shouldBe(g.next().value, 'function'); + shouldBe(g.next().value, 1); + g = generatorWithParams(42, 43); + shouldBe(g.next().value, 'number'); + shouldBe(g.next().value, 43); +} + +var results = []; +asyncNoParams().then(v => results.push(v)); +asyncWithParams(42, 43).then(v => results.push(v)); +asyncArrowNoParams().then(v => results.push(v)); +drainMicrotasks(); +shouldBe(JSON.stringify(results), '[["function",1],["number",43],["function",1]]'); diff --git a/JSTests/stress/inc-dec-int32-overflow-dce.js b/JSTests/stress/inc-dec-int32-overflow-dce.js new file mode 100644 index 000000000000..4a33a90b1832 --- /dev/null +++ b/JSTests/stress/inc-dec-int32-overflow-dce.js @@ -0,0 +1,28 @@ +//@ requireOptions("--useConcurrentJIT=0", "--thresholdForFTLOptimizeAfterWarmUp=1000") + +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error("FAIL: got " + actual + ", expected " + expected); +} + +function inc(k) { + let y = k; + ++y; + return (y | 0) === y; +} +noInline(inc); + +function dec(k) { + let y = k; + --y; + return (y | 0) === y; +} +noInline(dec); + +for (let i = 0; i < 1e6; ++i) { + inc(1); + dec(1); +} + +shouldBe(inc(2147483647), false); +shouldBe(dec(-2147483648), false); diff --git a/JSTests/stress/json-parse-array-materialization.js b/JSTests/stress/json-parse-array-materialization.js new file mode 100644 index 000000000000..6fe187ea0544 --- /dev/null +++ b/JSTests/stress/json-parse-array-materialization.js @@ -0,0 +1,129 @@ +function shouldBe(actual, expected) { + if (!Object.is(actual, expected)) + throw new Error("bad value: " + actual + " expected: " + expected); +} + +function shouldThrow(source) { + var threw = false; + try { + JSON.parse(source); + } catch (error) { + threw = true; + } + shouldBe(threw, true); +} + +// An array is allocated at its final length and indexing type, so every element type combination +// has to land in the same indexing shape a growing butterfly would have reached, and the values +// have to survive a garbage collection that happens while the elements are still being collected. +var cases = [ + ["[]", []], + ["[0]", [0]], + ["[1,2,3]", [1, 2, 3]], + ["[-2147483648,2147483647]", [-2147483648, 2147483647]], + ["[1.5,2.5]", [1.5, 2.5]], + ["[1,2.5]", [1, 2.5]], + ["[2.5,1]", [2.5, 1]], + ["[1e400,1]", [Infinity, 1]], + ["[null]", [null]], + ["[true,false]", [true, false]], + ["[\"a\",\"b\"]", ["a", "b"]], + ["[1,\"a\"]", [1, "a"]], + ["[\"a\",1]", ["a", 1]], + ["[[1],[2]]", [[1], [2]]], + ["[{\"a\":1},{\"a\":2}]", [{ a: 1 }, { a: 2 }]], + ["[1,[2,[3,[4]]]]", [1, [2, [3, [4]]]]], +]; + +for (var i = 0; i < 1e3; ++i) { + for (var [source, expected] of cases) { + var parsed = JSON.parse(source); + shouldBe(JSON.stringify(parsed), JSON.stringify(expected)); + shouldBe(parsed.length, expected.length); + shouldBe(Array.isArray(parsed), true); + } +} + +// -0 must stay -0 rather than being flattened to 0 by the double indexing shape. +shouldBe(JSON.parse("[-0]")[0], -0); +shouldBe(JSON.parse("[-0,1.5]")[0], -0); +shouldBe(JSON.parse("[1.5,-0]")[1], -0); + +// Lengths that cross the growth thresholds of the butterfly the old path grew element by element. +for (var length of [1, 2, 3, 4, 5, 8, 9, 16, 17, 100, 1024, 100000]) { + var ints = JSON.parse("[" + Array.from({ length }, (_, i) => i).join(",") + "]"); + shouldBe(ints.length, length); + shouldBe(ints[0], 0); + shouldBe(ints[length - 1], length - 1); + + var doubles = JSON.parse("[" + Array.from({ length }, (_, i) => i + 0.5).join(",") + "]"); + shouldBe(doubles.length, length); + shouldBe(doubles[length - 1], length - 1 + 0.5); + + var strings = JSON.parse("[" + Array.from({ length }, (_, i) => '"' + i + '"').join(",") + "]"); + shouldBe(strings.length, length); + shouldBe(strings[length - 1], String(length - 1)); +} + +// A malformed array must still report the same error, and must not leave collected elements behind. +shouldThrow("[1,]"); +shouldThrow("[,1]"); +shouldThrow("[1 2]"); +shouldThrow("["); +shouldThrow("[1"); +shouldThrow("[}"); +shouldThrow("[[1],]"); +shouldThrow("[1,[2,]]"); +shouldThrow('[{"a":1},]'); +for (var i = 0; i < 1e3; ++i) + shouldThrow("[1,2,3,]"); +shouldBe(JSON.stringify(JSON.parse("[1,2,3]")), "[1,2,3]"); + +// Nesting past the recursive parser's stack limit hands the value to the iterative parser. +var depth = 20000; +var deep = JSON.parse("[".repeat(depth) + "1" + "]".repeat(depth)); +var levels = 0; +while (Array.isArray(deep)) { + shouldBe(deep.length, 1); + deep = deep[0]; + ++levels; +} +shouldBe(levels, depth); +shouldBe(deep, 1); + +// An array index as an object key stays an indexed property, and a duplicate key keeps the last +// value, both of which the fast property path has to decline. +var indexed = JSON.parse('{"0":1,"b":2}'); +shouldBe(JSON.stringify(Object.keys(indexed)), '["0","b"]'); +shouldBe(indexed[0], 1); +shouldBe(indexed.b, 2); + +var duplicate = JSON.parse('{"a":1,"a":2}'); +shouldBe(JSON.stringify(Object.keys(duplicate)), '["a"]'); +shouldBe(duplicate.a, 2); + +// Objects wide enough to need out-of-line storage exercise the butterfly growth check. +for (var count of [1, 5, 6, 7, 8, 20, 64, 65, 200]) { + var source = "{" + Array.from({ length: count }, (_, i) => '"p' + i + '":' + i).join(",") + "}"; + var wide = JSON.parse(source); + shouldBe(Object.keys(wide).length, count); + shouldBe(wide["p0"], 0); + shouldBe(wide["p" + (count - 1)], count - 1); +} + +// A reviver goes through the general parser, which must agree with the fast one. +shouldBe(JSON.stringify(JSON.parse("[1,2,3]", (key, value) => typeof value === "number" ? value * 2 : value)), "[2,4,6]"); +shouldBe(JSON.stringify(JSON.parse('{"a":[1,2]}', (key, value) => value)), '{"a":[1,2]}'); + +// An indexed accessor on Array.prototype makes the global object have a bad time, after which arrays +// are allocated with array storage instead of a contiguous vector. +Object.defineProperty(Array.prototype, "1", { get() { return "bad"; }, configurable: true }); +for (var [source, expected] of cases) { + var parsed = JSON.parse(source); + shouldBe(JSON.stringify(parsed), JSON.stringify(expected)); + shouldBe(parsed.length, expected.length); +} +var afterBadTime = JSON.parse("[" + Array.from({ length: 1000 }, (_, i) => i).join(",") + "]"); +shouldBe(afterBadTime.length, 1000); +shouldBe(afterBadTime[999], 999); +shouldBe(JSON.parse("[7,8]")[1], 8); diff --git a/JSTests/stress/json-stringify-space-fast-path.js b/JSTests/stress/json-stringify-space-fast-path.js index 42e0895ce019..c4019f7e3063 100644 --- a/JSTests/stress/json-stringify-space-fast-path.js +++ b/JSTests/stress/json-stringify-space-fast-path.js @@ -106,4 +106,3 @@ for (const space of spaces) { if (failures) throw new Error('FAILED: ' + failures + ' mismatches'); -print('all ok'); diff --git a/JSTests/stress/map-for-each-mutation-during-iteration.js b/JSTests/stress/map-for-each-mutation-during-iteration.js new file mode 100644 index 000000000000..6780549983cd --- /dev/null +++ b/JSTests/stress/map-for-each-mutation-during-iteration.js @@ -0,0 +1,95 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error('bad value: ' + actual); +} + +function makeMap(count) { + var map = new Map; + for (var i = 0; i < count; ++i) + map.set(i, i * 10); + return map; +} + +function keys(map) { + var result = []; + map.forEach(function (value, key) { result.push(key + ':' + value); }); + return result.join(','); +} +noInline(keys); + +function deleteAhead(map) { + var result = []; + map.forEach(function (value, key) { + result.push(key); + if (key % 2 == 0) + map.delete(key + 1); + }); + return result.join(','); +} +noInline(deleteAhead); + +function deleteAllAhead(map) { + var result = []; + map.forEach(function (value, key) { + result.push(key); + if (key == 2) { + for (var i = 3; i < 10; ++i) + map.delete(i); + } + }); + return result.join(','); +} +noInline(deleteAllAhead); + +function addDuring(map) { + var result = []; + var added = 0; + map.forEach(function (value, key) { + result.push(key); + if (added < 40) + map.set('x' + added++, added); + }); + return result.length; +} +noInline(addDuring); + +function clearDuring(map) { + var result = []; + map.forEach(function (value, key) { + result.push(key); + if (key == 3) + map.clear(); + }); + return result.join(','); +} +noInline(clearDuring); + +function clearAndReadd(map) { + var result = []; + map.forEach(function (value, key) { + result.push(key); + if (key == 3) { + map.clear(); + map.set('a', 1); + map.set('b', 2); + } + }); + return result.join(','); +} +noInline(clearAndReadd); + +for (var i = 0; i < testLoopCount; ++i) { + shouldBe(keys(new Map), ''); + var map = makeMap(5); + map.delete(0); + map.delete(4); + shouldBe(keys(map), '1:10,2:20,3:30'); + map = new Map([[1, 1]]); + map.delete(1); + shouldBe(keys(map), ''); + shouldBe(deleteAhead(makeMap(10)), '0,2,4,6,8'); + shouldBe(deleteAllAhead(makeMap(10)), '0,1,2'); + shouldBe(addDuring(makeMap(3)), 43); + shouldBe(clearDuring(makeMap(10)), '0,1,2,3'); + shouldBe(clearAndReadd(makeMap(10)), '0,1,2,3,a,b'); +} diff --git a/JSTests/stress/microtask-call-cache-delete-all-code.js b/JSTests/stress/microtask-call-cache-delete-all-code.js new file mode 100644 index 000000000000..ebbf067f7052 --- /dev/null +++ b/JSTests/stress/microtask-call-cache-delete-all-code.js @@ -0,0 +1,31 @@ +function shouldBe(actual, expected) +{ + if (actual !== expected) + throw new Error(`bad value: expected ${expected} but got ${actual}`); +} + +const count = 3000; + +async function* generator() +{ + for (let index = 0; index < count; ++index) + yield index; +} + +async function sum() +{ + let result = 0; + for await (const value of generator()) + result += value; + return result; +} + +asyncTestStart(1); +sum().then((result) => { + shouldBe(result, count * (count - 1) / 2); + asyncTestPassed(); +}); + +// Deleting all code detaches every CodeBlock from its executable, and it runs once this script returns, +// so the resumptions above happen afterwards and must not reuse the entry points cached for them here. +$vm.deleteAllCodeWhenIdle(); diff --git a/JSTests/stress/object-assign-clone-multiple-sources.js b/JSTests/stress/object-assign-clone-multiple-sources.js new file mode 100644 index 000000000000..ed99cdb19c63 --- /dev/null +++ b/JSTests/stress/object-assign-clone-multiple-sources.js @@ -0,0 +1,58 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error('bad value: ' + actual); +} + +function shouldBeArray(actual, expected) { + shouldBe(actual.length, expected.length); + for (var i = 0; i < expected.length; ++i) + shouldBe(actual[i], expected[i]); +} + +function makeState() { + var state = {}; + for (var i = 0; i < 8; ++i) + state["k" + i] = i; + return state; +} + +for (var i = 0; i < testLoopCount; ++i) { + var state = makeState(); + var result = Object.assign({}, state, { k3: -1, extra: i }); + shouldBeArray(Object.keys(result), ["k0", "k1", "k2", "k3", "k4", "k5", "k6", "k7", "extra"]); + shouldBe(result.k3, -1); + shouldBe(result.k7, 7); + shouldBe(result.extra, i); + shouldBe(state.k3, 3); + result.k0 = 42; + shouldBe(state.k0, 0); + + var empty = Object.assign({}, state, {}); + shouldBeArray(Object.keys(empty), Object.keys(state)); + shouldBe(Object.getPrototypeOf(empty), Object.prototype); + shouldBe(Object.isFrozen(empty), false); + + var three = Object.assign({}, { a: 1 }, { b: 2 }, { a: 3 }); + shouldBeArray(Object.keys(three), ["a", "b"]); + shouldBe(three.a, 3); + + var frozen = Object.freeze(makeState()); + var fromFrozen = Object.assign({}, frozen, { k1: "x" }); + shouldBe(Object.isFrozen(fromFrozen), false); + fromFrozen.k0 = "y"; + shouldBe(fromFrozen.k0, "y"); + shouldBe(fromFrozen.k1, "x"); + shouldBe(frozen.k1, 1); + + var sym = Symbol("s"); + var withSymbol = makeState(); + withSymbol[sym] = "sym"; + Object.defineProperty(withSymbol, "hidden", { value: 1, enumerable: false }); + var copied = Object.assign({}, withSymbol, { k2: 2 }); + shouldBe(copied[sym], "sym"); + shouldBe(Object.getOwnPropertyDescriptor(copied, "hidden"), undefined); + + var nonEmptyTarget = Object.assign({ first: 0 }, state, {}); + shouldBe(Object.keys(nonEmptyTarget)[0], "first"); + shouldBe(Object.keys(nonEmptyTarget).length, 9); +} diff --git a/JSTests/stress/regexp-backreference-greedy-non-bmp-capture-restore-pos.js b/JSTests/stress/regexp-backreference-greedy-non-bmp-capture-restore-pos.js new file mode 100644 index 000000000000..e7f4740d5cd5 --- /dev/null +++ b/JSTests/stress/regexp-backreference-greedy-non-bmp-capture-restore-pos.js @@ -0,0 +1,25 @@ +//@ runDefault("--useRegExpJIT=false") + +function shouldBe(actual, expected) +{ + actual = JSON.stringify(actual); + expected = JSON.stringify(expected); + if (actual !== expected) + throw new Error("bad value: " + actual + " (expected " + expected + ")"); +} + +// E is a non-BMP code point, so a back reference to a capture containing it +// reads the input as a surrogate pair. When that read fails mid-way through a +// greedy back reference iteration, the input position must be restored. +var E = "\u{1F601}"; + +shouldBe(new RegExp("(" + E + ")(\\1*)?", "u").exec(E + "\n" + E), [E, E, undefined]); +shouldBe(new RegExp("(" + E + ")\\1*$", "u").exec(E + "ab"), null); +shouldBe(new RegExp("(" + E + ")\\1*(a)", "u").exec(E + "xya"), null); +shouldBe(new RegExp("(" + E + ")\\1+$", "u").exec(E + E + "ab"), null); +shouldBe(new RegExp("(a" + E + ")\\1*X", "u").exec("a" + E + "abcX"), null); +shouldBe(new RegExp("(?" + E + ")\\k*$", "u").exec(E + "zz"), null); +shouldBe(new RegExp("(" + E + ")\\1*$", "v").exec(E + "ab"), null); + +shouldBe(new RegExp("(" + E + ")\\1*$", "u").exec(E + E + E), [E + E + E, E]); +shouldBe(new RegExp("(" + E + ")\\1*(a)", "u").exec(E + E + "a"), [E + E + "a", E, "a"]); diff --git a/JSTests/stress/regexp-lookbehind-jit-backreferences-unicode.js b/JSTests/stress/regexp-lookbehind-jit-backreferences-unicode.js new file mode 100644 index 000000000000..1db65646340b --- /dev/null +++ b/JSTests/stress/regexp-lookbehind-jit-backreferences-unicode.js @@ -0,0 +1,205 @@ +//@ skip if not $jitTests +//@ runDefault + +// The interpreter reads several unicode lookbehinds incorrectly (bug 317275), so this only runs on the JIT. + +function shouldBe(actual, expected) { + actual = JSON.stringify(actual, (key, value) => value === undefined ? "" : value); + expected = JSON.stringify(expected, (key, value) => value === undefined ? "" : value); + if (actual !== expected) + throw new Error("bad value: " + actual + " expected: " + expected); +} + +function matchOf(re, string) { + let match = re.exec(string); + return match ? [match.index, ...match] : null; +} + +function indicesOf(re, string) { + let match = re.exec(string); + return match ? [match.index, ...match, ...match.indices] : null; +} + +function matchAllOf(re, string) { + return [...string.matchAll(re)].map((match) => [match.index, ...match]); +} + +function execAt(re, string, lastIndex) { + re.lastIndex = lastIndex; + let match = re.exec(string); + return [match ? [match.index, ...match] : null, re.lastIndex]; +} + +shouldBe(matchOf(/(?<=\1(a))b/u, "aab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=\1(a))b/u, "xab"), null); +shouldBe(matchOf(/(?<=\1(a))b/v, "aab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=\1(\u{1F600}))b/u, "\u{1f600}\u{1f600}b"), [4, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=\1(\u{1F600}))b/u, "\u{1f600}\u{1f601}b"), null); +shouldBe(matchOf(/(?<=\1(\u{1F600}))b/u, "\u{1f600}b"), null); +shouldBe(matchOf(/(?<=\1(\u{1F600}))b/u, "a\u{1f600}b"), null); +shouldBe(matchOf(/(?<=\1(\u{1F600}))b/u, "\ude00\u{1f600}b"), null); +shouldBe(matchOf(/(?<=\1(\u{1F600}a))b/u, "\u{1f600}a\u{1f600}ab"), [6, "b", "\u{1f600}a"]); +shouldBe(matchOf(/(?<=\1(\u{1F600}a))b/u, "\u{1f600}b\u{1f600}ab"), null); +shouldBe(matchOf(/(?<=\1(a\u{1F600}))b/u, "a\u{1f600}a\u{1f600}b"), [6, "b", "a\u{1f600}"]); +shouldBe(matchOf(/(?<=\1(a\u{1F600}))b/u, "a\u{1f601}a\u{1f600}b"), null); +shouldBe(matchOf(/(?<=\1(.))b/su, "\u{1f600}\u{1f600}b"), [4, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=\1(.))b/su, "\u{1f600}\u{1f601}b"), null); +shouldBe(matchOf(/(?<=\1(.))b/su, "\u{1f600}\ude00b"), null); +shouldBe(matchOf(/(?<=\1(.))b/su, "\ud83d\ud83db"), [2, "b", "\ud83d"]); +shouldBe(matchOf(/(?<=\1(.))b/su, "\ude00\ude00b"), [2, "b", "\ude00"]); +shouldBe(matchOf(/(?<=\1(.))b/su, "\ud83d\u{1f600}b"), null); +shouldBe(matchOf(/(?<=\1(.))b/su, "aab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=\1(.))b/su, "a\u{1f600}b"), null); +shouldBe(matchOf(/(?<=\1(.))b/su, "\u{1f600}ab"), null); +shouldBe(matchOf(/(?<=\1(.))b/su, "\u{1f600}b"), null); +shouldBe(matchOf(/(?<=\1(.))b/su, "\ude00b"), null); +shouldBe(matchOf(/(?<=\1(.))b/su, "b"), null); +shouldBe(matchOf(/(?<=\1(.))b/sv, "\u{1f600}\u{1f600}b"), [4, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=\1(.))b/sv, "\u{1f600}\u{1f601}b"), null); +shouldBe(matchOf(/(?<=\1(.+))b/su, "\u{1f600}a\u{1f600}ab"), [6, "b", "\u{1f600}a"]); +shouldBe(matchOf(/(?<=\1(.+))b/su, "a\u{1f600}a\u{1f600}b"), [6, "b", "a\u{1f600}"]); +shouldBe(matchOf(/(?<=\1(.+))b/su, "a\u{1f600}a\u{1f601}b"), null); +shouldBe(matchOf(/(?<=\1(.+))b/su, "\u{1f600}\u{1f600}\u{1f600}\u{1f600}b"), [8, "b", "\u{1f600}\u{1f600}"]); +shouldBe(matchOf(/(?<=\1(.+))b/su, "\u{1f600}\u{1f600}\u{1f600}b"), [6, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=\1(.+?))b/su, "\u{1f600}\u{1f600}\u{1f600}\u{1f600}b"), [8, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=\1(.+?))b/su, "\u{1f600}\u{1f601}\u{1f600}\u{1f601}b"), [8, "b", "\u{1f600}\u{1f601}"]); +shouldBe(matchOf(/(?<=\1(.{2}))b/su, "\u{1f600}a\u{1f600}ab"), [6, "b", "\u{1f600}a"]); +shouldBe(matchOf(/(?<=\1(.{2}))b/su, "\u{1f600}a\u{1f600}bb"), null); +shouldBe(matchOf(/(?<=\1(.{2}))b/su, "\u{1f600}\u{1f601}\u{1f600}\u{1f601}b"), [8, "b", "\u{1f600}\u{1f601}"]); +shouldBe(matchOf(/(?<=\1(.{2}))b/su, "\u{1f600}\u{1f601}\u{1f601}\u{1f601}b"), null); +shouldBe(matchOf(/(?<=\1(\p{Emoji_Presentation}))b/u, "\u{1f600}\u{1f600}b"), [4, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=\1(\p{Emoji_Presentation}))b/u, "\u{1f600}\u{1f601}b"), null); +shouldBe(matchOf(/(?<=\1([\u{1F600}-\u{1F64F}]))b/u, "\u{1f600}\u{1f600}b"), [4, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=\1([\u{1F600}-\u{1F64F}]))b/u, "\u{1f601}\u{1f600}b"), null); +shouldBe(matchOf(/(?<=\1([\u{1F600}-\u{1F64F}]+))b/u, "\u{1f600}\u{1f601}\u{1f600}\u{1f601}b"), [8, "b", "\u{1f600}\u{1f601}"]); +shouldBe(matchOf(/(?<=\1([\u{1F600}-\u{1F64F}]+))b/u, "\u{1f600}\u{1f601}\u{1f601}\u{1f601}b"), [8, "b", "\u{1f601}"]); +shouldBe(matchOf(/(?<=\1([\u{1F600}-\u{1F64F}]+))b/u, "\u{1f600}\u{1f601}\u{1f601}\u{1f601}\u{1f601}b"), [10, "b", "\u{1f601}\u{1f601}"]); +shouldBe(matchOf(/(?<=\1{2}(\u{1F600}))b/u, "\u{1f600}\u{1f600}\u{1f600}b"), [6, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=\1{2}(\u{1F600}))b/u, "\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(?<=\1{2}(\u{1F600}))b/u, "a\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(?<=\1{2}(.))b/su, "\u{1f600}\u{1f600}\u{1f600}b"), [6, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=\1{2}(.))b/su, "\u{1f600}\u{1f600}\u{1f601}b"), null); +shouldBe(matchOf(/(?<=\1{2}(.))b/su, "a\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(?<=\1{2}(.))b/su, "\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(?<=x\1*(\u{1F600}))b/u, "x\u{1f600}\u{1f600}\u{1f600}b"), [7, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=x\1*(\u{1F600}))b/u, "x\u{1f600}b"), [3, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=x\1*(\u{1F600}))b/u, "y\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(?<=x\1*(\u{1F600}))b/u, "x\u{1f601}\u{1f600}b"), null); +shouldBe(matchOf(/(?<=x\1*(\u{1F600}))b/u, "xa\u{1f600}b"), null); +shouldBe(matchOf(/(?<=x\1*?(\u{1F600}))b/u, "x\u{1f600}\u{1f600}\u{1f600}b"), [7, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=x\1*?(\u{1F600}))b/u, "y\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(?<=x\1?(\u{1F600}))b/u, "x\u{1f600}\u{1f600}b"), [5, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=x\1?(\u{1F600}))b/u, "x\u{1f600}b"), [3, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=x\1?(\u{1F600}))b/u, "x\u{1f600}\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(?<=x\1{0,2}(.))b/su, "x\u{1f600}\u{1f600}\u{1f600}b"), [7, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=x\1{0,2}(.))b/su, "x\u{1f600}\u{1f600}\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(?<=x\1{0,2}(.))b/su, "x\u{1f600}b"), [3, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=x\1{0,2}(.))b/su, "x\u{1f601}\u{1f600}b"), null); +shouldBe(matchOf(/(?<=x\1{0,2}?(.))b/su, "x\u{1f600}\u{1f600}\u{1f600}b"), [7, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=x\1{0,2}?(.))b/su, "x\u{1f600}\u{1f600}\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(\u{1F600})b(?<=\1b)/u, "\u{1f600}b"), [0, "\u{1f600}b", "\u{1f600}"]); +shouldBe(matchOf(/(\u{1F600})b(?<=x\1+b)/u, "x\u{1f600}\u{1f600}b"), [3, "\u{1f600}b", "\u{1f600}"]); +shouldBe(matchOf(/(\u{1F600})b(?<=x\1+b)/u, "y\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(\u{1F600})b(?<=x\1+b)/u, "x\u{1f601}\u{1f600}b"), null); +shouldBe(matchOf(/(\u{1F600})b(?<=x\1+?b)/u, "x\u{1f600}\u{1f600}b"), [3, "\u{1f600}b", "\u{1f600}"]); +shouldBe(matchOf(/(\u{1F600})b(?<=x\1{2}b)/u, "x\u{1f600}\u{1f600}b"), [3, "\u{1f600}b", "\u{1f600}"]); +shouldBe(matchOf(/(\u{1F600})b(?<=x\1{2}b)/u, "x\u{1f600}b"), null); +shouldBe(matchOf(/(\u{1F600})b(?<=x\1{2}b)/u, "x\u{1f600}\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(\u{1F600})b(?<=x\1{1,2}b)/u, "x\u{1f600}\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(\u{1F600})b(?<=x\1{1,2}?b)/u, "x\u{1f600}\u{1f600}\u{1f600}b"), null); +shouldBe(matchOf(/(\u{1F600})b(?<=x\1{1,2}?b)/u, "x\u{1f600}b"), [1, "\u{1f600}b", "\u{1f600}"]); +shouldBe(matchOf(/(.)b(?<=x\1+b)/su, "x\u{1f600}\u{1f600}b"), [3, "\u{1f600}b", "\u{1f600}"]); +shouldBe(matchOf(/(.)b(?<=x\1+b)/su, "x\u{1f600}\u{1f601}b"), null); +shouldBe(matchOf(/(.)b(?<=x\1+b)/su, "xaab"), [2, "ab", "a"]); +shouldBe(matchOf(/(.+)b(?<=x\1\1b)/su, "x\u{1f600}a\u{1f600}ab"), [4, "\u{1f600}ab", "\u{1f600}a"]); +shouldBe(matchOf(/(.+)b(?<=x\1\1b)/su, "x\u{1f600}a\u{1f600}bb"), null); +shouldBe(matchOf(/(.+)b(?<=x\1\1b)/su, "xa\u{1f600}a\u{1f600}b"), [4, "a\u{1f600}b", "a\u{1f600}"]); +shouldBe(matchOf(/(?<=\k(?\u{1F600}))b/u, "\u{1f600}\u{1f600}b"), [4, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=\k(?\u{1F600}))b/u, "\u{1f600}\u{1f601}b"), null); +shouldBe(matchOf(/(?<=\k(?\u{1F600})|\k(?\u{1F601}))b/u, "\u{1f600}\u{1f600}b"), [4, "b", "\u{1f600}", undefined]); +shouldBe(matchOf(/(?<=\k(?\u{1F600})|\k(?\u{1F601}))b/u, "\u{1f601}\u{1f601}b"), [4, "b", undefined, "\u{1f601}"]); +shouldBe(matchOf(/(?<=\k(?\u{1F600})|\k(?\u{1F601}))b/u, "\u{1f600}\u{1f601}b"), null); +shouldBe(matchOf(/(?<=\k(?\u{1F600})|\k(?\u{1F601}))b/u, "\u{1f601}\u{1f600}b"), null); +shouldBe(matchOf(/(?<=\k(?.)|\k(?a))b/su, "\u{1f600}\u{1f600}b"), [4, "b", "\u{1f600}", undefined]); +shouldBe(matchOf(/(?<=\k(?.)|\k(?a))b/su, "\u{1f600}\u{1f601}b"), null); +shouldBe(matchOf(/(?<=\k(?.)|\k(?a))b/su, "aab"), [2, "b", "a", undefined]); +shouldBe(matchOf(/(?<=x\k*(?\u{1F600})|x\k*(?\u{1F601}))b/u, "x\u{1f601}\u{1f601}\u{1f601}b"), [7, "b", undefined, "\u{1f601}"]); +shouldBe(matchOf(/(?<=x\k*(?\u{1F600})|x\k*(?\u{1F601}))b/u, "x\u{1f600}\u{1f601}\u{1f601}b"), null); +shouldBe(matchOf(/(?<=x\k*(?\u{1F600})|x\k*(?\u{1F601}))b/u, "y\u{1f601}\u{1f601}b"), null); +shouldBe(matchOf(/(?<=\1(a))b/iu, "aAb"), [2, "b", "A"]); +shouldBe(matchOf(/(?<=\1(a))b/iu, "Aab\u{1f600}"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=\1(a))b/iu, "xAb\u{1f600}"), null); +shouldBe(matchOf(/(?<=\1(\u{1F600}))b/iu, "\u{1f600}\u{1f600}b"), [4, "b", "\u{1f600}"]); +shouldBe(matchOf(/(?<=\1(\u{1F600}))b/iu, "\u{1f600}\u{1f601}b"), null); +shouldBe(matchOf(/(?<=\1(\u{10400}))b/iu, "\u{10400}\u{10428}b"), [4, "b", "\u{10428}"]); +shouldBe(matchOf(/(?<=\1(\u{10400}))b/iu, "\u{10428}\u{10400}b"), [4, "b", "\u{10400}"]); +shouldBe(matchOf(/(?<=\1(\u{10400}))b/iu, "\u{10428}\u{10428}b"), [4, "b", "\u{10428}"]); +shouldBe(matchOf(/(?<=\1(\u{10400}))b/iu, "\u{10429}\u{10400}b"), null); +shouldBe(matchOf(/(?<=\1(\u{10400}))b/u, "\u{10428}\u{10400}b"), null); +shouldBe(matchOf(/(?<=\1(\u{10400}))b/iv, "\u{10428}\u{10400}b"), [4, "b", "\u{10400}"]); +shouldBe(matchOf(/(?<=\1(.))b/isu, "\u{10400}\u{10428}b"), [4, "b", "\u{10428}"]); +shouldBe(matchOf(/(?<=\1(.))b/isu, "\u{10429}\u{10428}b"), null); +shouldBe(matchOf(/(?<=\1(.+))b/isu, "\u{10400}a\u{10428}Ab"), [6, "b", "\u{10428}A"]); +shouldBe(matchOf(/(?<=\1(.+))b/isu, "\u{10400}a\u{10429}Ab"), null); +shouldBe(matchOf(/(?<=\1(k))x/iu, "k\u212ax"), [2, "x", "\u212a"]); +shouldBe(matchOf(/(?<=\1(k))x/iu, "\u212akx"), [2, "x", "k"]); +shouldBe(matchOf(/(?<=\1(k))x/iu, "\u212a\u212ax"), [2, "x", "\u212a"]); +shouldBe(matchOf(/(?<=\1(k))x/iu, "K\u212ax\u{1f600}"), [2, "x", "\u212a"]); +shouldBe(matchOf(/(?<=\1(s))x/iu, "s\u017fx"), [2, "x", "\u017f"]); +shouldBe(matchOf(/(?<=\1(σ))x/iu, "\u03c3\u03c2x"), [2, "x", "\u03c2"]); +shouldBe(matchOf(/(?<=\1(σ))x/iu, "\u03a3\u03c2x\u{1f600}"), [2, "x", "\u03c2"]); +shouldBe(matchOf(/(?<=\1{2}(\u{10400}))b/iu, "\u{10400}\u{10428}\u{10400}b"), [6, "b", "\u{10400}"]); +shouldBe(matchOf(/(?<=\1{2}(\u{10400}))b/iu, "\u{10400}\u{10428}\u{10401}b"), null); +shouldBe(matchOf(/(?<=x\1*(\u{10400}))b/iu, "x\u{10428}\u{10400}\u{10428}b"), [7, "b", "\u{10428}"]); +shouldBe(matchOf(/(?<=x\1*(\u{10400}))b/iu, "y\u{10428}\u{10400}\u{10428}b"), null); +shouldBe(matchOf(/(?<=x\1*?(\u{10400}))b/iu, "x\u{10428}\u{10400}\u{10428}b"), [7, "b", "\u{10428}"]); +shouldBe(matchOf(/(\u{10400})b(?<=x\1+b)/iu, "x\u{10428}\u{10400}\u{10428}b"), [5, "\u{10428}b", "\u{10428}"]); +shouldBe(matchOf(/(\u{10400})b(?<=x\1+b)/iu, "x\u{10428}\u{10401}\u{10428}b"), null); +shouldBe(matchOf(/(\u{10400})b(?<=x\1{2}b)/iu, "x\u{10428}\u{10400}b"), [3, "\u{10400}b", "\u{10400}"]); +shouldBe(matchOf(/(\u{10400})b(?<=x\1{1,2}?b)/iu, "x\u{10428}\u{10428}\u{10400}b"), null); +shouldBe(matchOf(/(?<=\k(?\u{10400})|\k(?\u{10401}))b/iu, "\u{10428}\u{10400}b"), [4, "b", "\u{10400}", undefined]); +shouldBe(matchOf(/(?<=\k(?\u{10400})|\k(?\u{10401}))b/iu, "\u{10429}\u{10401}b"), [4, "b", undefined, "\u{10401}"]); +shouldBe(matchOf(/(?<=\k(?\u{10400})|\k(?\u{10401}))b/iu, "\u{10429}\u{10400}b"), null); +shouldBe(matchOf(/(? value === undefined ? "" : value); + expected = JSON.stringify(expected, (key, value) => value === undefined ? "" : value); + if (actual !== expected) + throw new Error("bad value: " + actual + " expected: " + expected); +} + +function matchOf(re, string) { + let match = re.exec(string); + return match ? [match.index, ...match] : null; +} + +function indicesOf(re, string) { + let match = re.exec(string); + return match ? [match.index, ...match, ...match.indices] : null; +} + +function matchAllOf(re, string) { + return [...string.matchAll(re)].map((match) => [match.index, ...match]); +} + +function execAt(re, string, lastIndex) { + re.lastIndex = lastIndex; + let match = re.exec(string); + return [match ? [match.index, ...match] : null, re.lastIndex]; +} + + +// Repeated content: forward references converted to backreferences. +shouldBe(matchOf(/(?<=\1(a))b/, "aab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=\1(a))b/, "xab"), null); +shouldBe(matchOf(/(?<=\1(a))b/, "ab"), null); +shouldBe(matchOf(/(?<=\1(a))b/, "b"), null); +shouldBe(matchOf(/(?<=\1(ab))c/, "ababc"), [4, "c", "ab"]); +shouldBe(matchOf(/(?<=\1(ab))c/, "abc"), null); +shouldBe(matchOf(/(?<=\1(ab))c/, "xabc"), null); +shouldBe(matchOf(/(?<=\1(ab))c/, "babc"), null); +shouldBe(matchOf(/(?<=\1(ab))/, "abab"), [4, "", "ab"]); +shouldBe(matchOf(/(?<=\1(ab))/, "xabab"), [5, "", "ab"]); +shouldBe(matchOf(/(?<=\1(ab))/, "bab"), null); +shouldBe(matchOf(/(?<=\1(ab))/, "ab"), null); +shouldBe(matchOf(/(?<=\1(\w+))c/, "ababc"), [4, "c", "ab"]); +shouldBe(matchOf(/(?<=\1(\w+))c/, "ababbc"), [5, "c", "b"]); +shouldBe(matchOf(/(?<=\1(\w+))c/, "ababdc"), null); +shouldBe(matchOf(/(?<=\1(\w+))c/, "abcabcabc"), [8, "c", "cab"]); +shouldBe(matchOf(/(?<=\1(\w+?))c/, "ababc"), [4, "c", "ab"]); +shouldBe(matchOf(/(?<=\1(\w+?))c/, "aaaac"), [4, "c", "a"]); +shouldBe(matchOf(/(?<=\1(a+))b/, "aaaab"), [4, "b", "aa"]); +shouldBe(matchOf(/(?<=\1(a+))b/, "aaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=\1(a+))b/, "ab"), null); +shouldBe(matchOf(/(?<=\1(a*))b/, "b"), [0, "b", ""]); +shouldBe(matchOf(/(?<=\1(a*))b/, "aab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=\1(a{2}))b/, "aaaab"), [4, "b", "aa"]); +shouldBe(matchOf(/(?<=\1(a{2}))b/, "aaab"), null); +shouldBe(matchOf(/(?<=\1\1(a))b/, "aaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=\1\1(a))b/, "aab"), null); +shouldBe(matchOf(/(?<=\1\1(ab))c/, "abababc"), [6, "c", "ab"]); +shouldBe(matchOf(/(?<=\1\1(ab))c/, "ababc"), null); +shouldBe(matchOf(/(?<=\2\1(\d{2})(\d{2}))X/, "34121234X"), [8, "X", "12", "34"]); +shouldBe(matchOf(/(?<=\2\1(\d{2})(\d{2}))X/, "12341234X"), null); +shouldBe(matchOf(/(?<=\1\2(a)(b))c/, "ababc"), [4, "c", "a", "b"]); +shouldBe(matchOf(/(?<=\1\2(a)(b))c/, "abbac"), null); +shouldBe(matchOf(/(?<=\2\1(a)(b))c/, "abbac"), null); +shouldBe(matchOf(/(?<=\2\1(a)(b))c/, "ababc"), null); +shouldBe(matchOf(/(?<=\1(a)b)c/, "abc"), null); +shouldBe(matchOf(/(?<=\1(a)b)c/, "aabc"), [3, "c", "a"]); +shouldBe(matchOf(/(?<=\1x(a))b/, "axab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=\1x(a))b/, "xab"), null); +shouldBe(matchOf(/(?<=\1x(a))b/, "bxab"), null); +shouldBe(matchOf(/(?<=(a)\1)b/, "aab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=(a)\1)b/, "ab"), [1, "b", "a"]); +shouldBe(matchOf(/(?<=(a)\1)b/, "xb"), null); +shouldBe(matchOf(/(?<=(ab)\1)/, "abab"), [2, "", "ab"]); +shouldBe(matchOf(/(?<=(a)\1x)b/, "axb"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=(a)\1x)b/, "aaxb"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=(a)(?:\1b))c/, "abc"), [2, "c", "a"]); +shouldBe(matchOf(/(?<=(a)(?:\1b))c/, "aabc"), [3, "c", "a"]); +shouldBe(matchOf(/(?<=(?:\1b)(a))c/, "abac"), [3, "c", "a"]); +shouldBe(matchOf(/(?<=(?:\1b)(a))c/, "xbac"), null); +shouldBe(matchOf(/(?<=(?:\1b)(a))c/, "bac"), null); +shouldBe(matchOf(/(?<=(\1b)(a))c/, "abac"), [3, "c", "b", "a"]); +shouldBe(matchOf(/(?<=(x\1)(ab))c/, "xababc"), null); +shouldBe(matchOf(/(?<=(x\1)(ab))c/, "ababc"), null); +shouldBe(matchOf(/(?<=(x\1)(ab))c/, "xabc"), [3, "c", "x", "ab"]); +shouldBe(matchOf(/(?<=(x\1)(ab))c/, "xbabc"), null); +shouldBe(matchOf(/(?<=(x\1)?(ab))c/, "xababc"), [5, "c", undefined, "ab"]); +shouldBe(matchOf(/(?<=(x\1)?(ab))c/, "abc"), [2, "c", undefined, "ab"]); +shouldBe(matchOf(/(?<=(x\1)?(ab))c/, "ababc"), [4, "c", undefined, "ab"]); +shouldBe(matchOf(/(?<=(x\1)??(ab))c/, "xababc"), [5, "c", undefined, "ab"]); +shouldBe(matchOf(/(?<=(x\1)??(ab))c/, "ababc"), [4, "c", undefined, "ab"]); + +// Quantified converted references. +shouldBe(matchOf(/(?<=\1{2}(a))b/, "aaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=\1{2}(a))b/, "aab"), null); +shouldBe(matchOf(/(?<=\1{2}(a))b/, "xaab"), null); +shouldBe(matchOf(/(?<=\1{2}(ab))c/, "abababc"), [6, "c", "ab"]); +shouldBe(matchOf(/(?<=\1{2}(ab))c/, "xababc"), null); +shouldBe(matchOf(/(?<=\1{2}(ab))c/, "ababc"), null); +shouldBe(matchOf(/(?<=\1{2}(ab))c/, "aabababc"), [7, "c", "ab"]); +shouldBe(matchOf(/(?<=\1{2}(a+))b/, "aaaaab"), [5, "b", "a"]); +shouldBe(matchOf(/(?<=\1{2}(a+))b/, "aaaab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=\1{2}(a+))b/, "aab"), null); +shouldBe(matchOf(/(?<=\1{2}(a+))b/, "ab"), null); +shouldBe(matchOf(/(?<=\1{2}(a+))b/, "aaaaaab"), [6, "b", "aa"]); +shouldBe(matchOf(/(?<=\1{3}(ab))c/, "ababababc"), [8, "c", "ab"]); +shouldBe(matchOf(/(?<=\1{3}(ab))c/, "abababc"), null); +shouldBe(matchOf(/(?<=\1*(a))b/, "ab"), [1, "b", "a"]); +shouldBe(matchOf(/(?<=\1*(a))b/, "aab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=\1*(a))b/, "xab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=\1*(a))b/, "b"), null); +shouldBe(matchOf(/(?<=x\1*(a))b/, "xaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=x\1*(a))b/, "xaaab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=x\1*(a))b/, "xab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=x\1*(a))b/, "yaaab"), null); +shouldBe(matchOf(/(?<=x\1*(ab))c/, "xabababc"), [7, "c", "ab"]); +shouldBe(matchOf(/(?<=x\1*(ab))c/, "xabc"), [3, "c", "ab"]); +shouldBe(matchOf(/(?<=x\1*(ab))c/, "ababc"), null); +shouldBe(matchOf(/(?<=x\1*(ab))c/, "xbababc"), null); +shouldBe(matchOf(/(?<=x\1*?(a))b/, "xaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=x\1*?(a))b/, "xaaab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=x\1*?(a))b/, "yaaab"), null); +shouldBe(matchOf(/(?<=x\1*?(ab))c/, "xabababc"), [7, "c", "ab"]); +shouldBe(matchOf(/(?<=x\1*?(ab))c/, "xbababc"), null); +shouldBe(matchOf(/(?<=x\1?(a))b/, "xaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=x\1?(a))b/, "xab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=x\1?(a))b/, "xaaab"), null); +shouldBe(matchOf(/(?<=x\1??(a))b/, "xaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=x\1??(a))b/, "xab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=x\1??(a))b/, "xaaab"), null); +shouldBe(matchOf(/(?<=x\1{0,2}(a))b/, "xaaab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=x\1{0,2}(a))b/, "xaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=x\1{0,2}(a))b/, "xab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=x\1{0,2}(a))b/, "xaaaab"), null); +shouldBe(matchOf(/(?<=x\1{0,2}?(a))b/, "xaaab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=x\1{0,2}?(a))b/, "xaaaab"), null); +shouldBe(matchOf(/(?<=x\1{0,2}(ab))c/, "xabababc"), [7, "c", "ab"]); +shouldBe(matchOf(/(?<=x\1{0,2}(ab))c/, "xababc"), [5, "c", "ab"]); +shouldBe(matchOf(/(?<=x\1{0,2}(ab))c/, "xabababababc"), null); +shouldBe(matchOf(/(?<=\1{0}(a))b/, "ab"), [1, "b", "a"]); +shouldBe(matchOf(/(?<=\1{0}(a))b/, "xb"), null); +shouldBe(matchOf(/(?<=^\1*(a))b/, "aaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=^\1*(a))b/, "xaab"), null); +shouldBe(matchOf(/(?<=^\1*(ab))c/, "abababc"), [6, "c", "ab"]); +shouldBe(matchOf(/(?<=^\1*(ab))c/, "aabababc"), null); +shouldBe(matchOf(/(?<=^\1{2}(ab))c/, "abababc"), [6, "c", "ab"]); +shouldBe(matchOf(/(?<=^\1{2}(ab))c/, "ababababc"), null); +shouldBe(matchOf(/(?<=\b\1*(a))b/, "aaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=\b\1*(a))b/, " aaab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=\b\1*(a))b/, "xaaab"), null); +shouldBe(matchOf(/(?<=\b\1{2}(a))b/, "aaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=\b\1{2}(a))b/, "xaaab"), null); + +// References to groups captured before the lookbehind. +shouldBe(matchOf(/(a)(?<=\1)/, "a"), [0, "a", "a"]); +shouldBe(matchOf(/(a)b(?<=\1b)/, "ab"), [0, "ab", "a"]); +shouldBe(matchOf(/(a)b(?<=\1b)/, "xab"), [1, "ab", "a"]); +shouldBe(matchOf(/(a)b(?<=x\1b)/, "xab"), [1, "ab", "a"]); +shouldBe(matchOf(/(a)b(?<=x\1b)/, "yab"), null); +shouldBe(matchOf(/(a)b(?<=\1\1b)/, "aab"), [1, "ab", "a"]); +shouldBe(matchOf(/(a)b(?<=\1\1b)/, "ab"), null); +shouldBe(matchOf(/(a+)b(?<=\1b)/, "aaab"), [0, "aaab", "aaa"]); +shouldBe(matchOf(/(a+)b(?<=\1\1b)/, "aaab"), [2, "ab", "a"]); +shouldBe(matchOf(/(a+)b(?<=\1\1b)/, "aaaab"), [2, "aab", "aa"]); +shouldBe(matchOf(/(a+)b(?<=\1\1b)/, "aab"), [1, "ab", "a"]); +shouldBe(matchOf(/(a+)b(?<=\1\1\1b)/, "aaab"), [2, "ab", "a"]); +shouldBe(matchOf(/(a+)b(?<=\1\1\1b)/, "aaaaaab"), [4, "aab", "aa"]); +shouldBe(matchOf(/(a+)b(?<=\1\1\1b)/, "aaaab"), [3, "ab", "a"]); +shouldBe(matchOf(/(\w+)-(?<=\1-)/, "abc-"), [0, "abc-", "abc"]); +shouldBe(matchOf(/(\w+)-\1(?<=\1\1)/, "abc-abc"), null); +shouldBe(matchOf(/(\w+)-\1(?<=-\1)/, "abc-abc"), [0, "abc-abc", "abc"]); +shouldBe(matchOf(/(\w+)-\1(?<=\1-\1)/, "abc-abc"), [0, "abc-abc", "abc"]); +shouldBe(matchOf(/(\w+)-\1(?<=\1-\1)/, "abc-abd"), null); +shouldBe(matchOf(/(\w+)-\1(?<=c\1)/, "abc-abc"), null); +shouldBe(matchOf(/(ab)(?<=\1)/, "ab"), [0, "ab", "ab"]); +shouldBe(matchOf(/(ab)(?<=(\1))/, "ab"), [0, "ab", "ab", "ab"]); +shouldBe(matchOf(/(ab)(?<=a(\1))/, "ab"), null); +shouldBe(matchOf(/(ab)(?<=(?:\1))/, "ab"), [0, "ab", "ab"]); +shouldBe(matchOf(/(a)b(?<=(?:\1)b)/, "ab"), [0, "ab", "a"]); +shouldBe(matchOf(/(a)b(?<=(\1)b)/, "ab"), [0, "ab", "a", "a"]); +shouldBe(matchOf(/(a)b(?<=(\1)+b)/, "ab"), [0, "ab", "a", "a"]); +shouldBe(matchOf(/(a)b(?<=(\1)?b)/, "ab"), [0, "ab", "a", "a"]); +shouldBe(matchOf(/(a)b(?<=(\1)?b)/, "xb"), null); +shouldBe(matchOf(/(a)b(?<=(\1|x)b)/, "ab"), [0, "ab", "a", "a"]); +shouldBe(matchOf(/(a)b(?<=(\1|x)b)/, "xb"), null); +shouldBe(matchOf(/(a)b(?<=(x|\1)b)/, "ab"), [0, "ab", "a", "a"]); +shouldBe(matchOf(/(a)b(?<=(x|\1)b)/, "axb"), null); +shouldBe(matchOf(/(a)b(?<=(x|\1)b)/, "ayb"), null); +shouldBe(matchOf(/(a)|b(?<=\1b)/, "b"), [0, "b", undefined]); +shouldBe(matchOf(/(a)|b(?<=\1b)/, "ab"), [0, "a", "a"]); +shouldBe(matchOf(/(a)|b(?<=\1b)/, "bb"), [0, "b", undefined]); +shouldBe(matchOf(/(a)?b(?<=\1b)/, "ab"), [0, "ab", "a"]); +shouldBe(matchOf(/(a)?b(?<=\1b)/, "b"), [0, "b", undefined]); +shouldBe(matchOf(/(a)?b(?<=\1b)/, "xb"), [1, "b", undefined]); +shouldBe(matchOf(/(a)?b(?<=x\1b)/, "xb"), [1, "b", undefined]); +shouldBe(matchOf(/(a)?b(?<=x\1b)/, "xab"), [1, "ab", "a"]); +shouldBe(matchOf(/(a)?b(?<=x\1b)/, "axb"), [2, "b", undefined]); +shouldBe(matchOf(/(a)?b(?<=x\1b)/, "ab"), null); +shouldBe(matchOf(/(a)?xb(?<=x\1b)/, "axb"), [1, "xb", undefined]); +shouldBe(matchOf(/(a)?xb(?<=\1xb)/, "axb"), [0, "axb", "a"]); +shouldBe(matchOf(/(a)?xb(?<=\1xb)/, "xb"), [0, "xb", undefined]); +shouldBe(matchOf(/(a)?xb(?<=\1xb)/, "bxb"), [1, "xb", undefined]); +shouldBe(matchOf(/(?:(a)|b)c(?<=\1c)/, "ac"), [0, "ac", "a"]); +shouldBe(matchOf(/(?:(a)|b)c(?<=\1c)/, "bc"), [0, "bc", undefined]); +shouldBe(matchOf(/(?:(a)|b)c(?<=\1c)/, "abc"), [1, "bc", undefined]); +shouldBe(matchOf(/(?:(a)|(b))c(?<=\2c)/, "bc"), [0, "bc", undefined, "b"]); +shouldBe(matchOf(/(?:(a)|(b))c(?<=\2c)/, "ac"), [0, "ac", "a", undefined]); +shouldBe(matchOf(/(?:(a)|(b))c(?<=\2c)/, "bbc"), [1, "bc", undefined, "b"]); +shouldBe(matchOf(/(?:(a)|(b))c(?<=\1\2c)/, "bc"), [0, "bc", undefined, "b"]); +shouldBe(matchOf(/(?:(a)|(b))c(?<=\1\2c)/, "ac"), [0, "ac", "a", undefined]); +shouldBe(matchOf(/(?:(a)|(b))c(?<=\1\2c)/, "abc"), [1, "bc", undefined, "b"]); +shouldBe(matchOf(/(?:(a)|(b))c(?<=\1\2c)/, "bac"), [1, "ac", "a", undefined]); +shouldBe(matchOf(/(a)b(?<=\1(?<=\1)b)/, "ab"), [0, "ab", "a"]); +shouldBe(matchOf(/(a)b(?<=(?<=\1)\1b)/, "ab"), null); +shouldBe(matchOf(/(a)b(?<=(?<=\1)\1b)/, "aab"), [1, "ab", "a"]); +shouldBe(matchOf(/(a)b(?(?a))b/, "aab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=\k(?a))b/, "xab"), null); +shouldBe(matchOf(/(?<=\k(?ab))c/, "ababc"), [4, "c", "ab"]); +shouldBe(matchOf(/(?<=\k(?ab))c/, "xabc"), null); +shouldBe(matchOf(/(?<=\k(?\w+))c/, "ababc"), [4, "c", "ab"]); +shouldBe(matchOf(/(?<=\k(?\w+))c/, "ababbc"), [5, "c", "b"]); +shouldBe(matchOf(/(?<=\k(?\w+))c/, "ababdc"), null); +shouldBe(matchOf(/(?<=\k{2}(?a))b/, "aaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=\k{2}(?a))b/, "xaab"), null); +shouldBe(matchOf(/(?<=x\k*(?a))b/, "xaaab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=x\k*(?a))b/, "yaaab"), null); +shouldBe(matchOf(/(?<=x\k*?(?a))b/, "xaaab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=x\k?(?a))b/, "xaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=x\k?(?a))b/, "xab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=x\k?(?a))b/, "xaaab"), null); +shouldBe(matchOf(/(?<=(?a)\k)b/, "aab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=(?a)\k)b/, "ab"), [1, "b", "a"]); +shouldBe(matchOf(/(?<=(?a)\k)b/, "xb"), null); +shouldBe(matchOf(/(?<=\k(?a)|\k(?b))c/, "aac"), [2, "c", "a", undefined]); +shouldBe(matchOf(/(?<=\k(?a)|\k(?b))c/, "bbc"), [2, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=\k(?a)|\k(?b))c/, "abc"), null); +shouldBe(matchOf(/(?<=\k(?a)|\k(?b))c/, "bac"), null); +shouldBe(matchOf(/(?<=\k(?a)|\k(?b))c/, "xac"), null); +shouldBe(matchOf(/(?<=\k(?a)|\k(?b))c/, "xbc"), null); +shouldBe(matchOf(/(?<=\k(?ab)|\k(?cd))x/, "ababx"), [4, "x", "ab", undefined]); +shouldBe(matchOf(/(?<=\k(?ab)|\k(?cd))x/, "cdcdx"), [4, "x", undefined, "cd"]); +shouldBe(matchOf(/(?<=\k(?ab)|\k(?cd))x/, "abcdx"), null); +shouldBe(matchOf(/(?<=\k(?ab)|\k(?cd))x/, "cdabx"), null); +shouldBe(matchOf(/(?<=\k(?a+)|\k(?b+))c/, "aaaac"), [4, "c", "aa", undefined]); +shouldBe(matchOf(/(?<=\k(?a+)|\k(?b+))c/, "bbbc"), [3, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=\k(?a+)|\k(?b+))c/, "abbc"), [3, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=\k(?a+)|\k(?b+))c/, "bbac"), null); +shouldBe(matchOf(/(?<=\k{2}(?a)|\k{2}(?b))c/, "aaac"), [3, "c", "a", undefined]); +shouldBe(matchOf(/(?<=\k{2}(?a)|\k{2}(?b))c/, "bbbc"), [3, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=\k{2}(?a)|\k{2}(?b))c/, "aabc"), null); +shouldBe(matchOf(/(?<=\k{2}(?a)|\k{2}(?b))c/, "abbc"), null); +shouldBe(matchOf(/(?<=x\k*(?a)|x\k*(?b))c/, "xaaac"), [4, "c", "a", undefined]); +shouldBe(matchOf(/(?<=x\k*(?a)|x\k*(?b))c/, "xbbbc"), [4, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=x\k*(?a)|x\k*(?b))c/, "xbc"), [2, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=x\k*(?a)|x\k*(?b))c/, "ybbbc"), null); +shouldBe(matchOf(/(?<=x\k*?(?a)|x\k*?(?b))c/, "xbbbc"), [4, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=x\k?(?a)|x\k?(?b))c/, "xbbc"), [3, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=x\k?(?a)|x\k?(?b))c/, "xbc"), [2, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=x\k?(?a)|x\k?(?b))c/, "xbbbc"), null); +shouldBe(matchOf(/(?<=\k(?a)|\k(?b))c/i, "aAc"), [2, "c", "A", undefined]); +shouldBe(matchOf(/(?<=\k(?a)|\k(?b))c/i, "Bbc"), [2, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=\k(?a)|\k(?b))c/i, "aAc\u0100"), [2, "c", "A", undefined]); +shouldBe(matchOf(/(?<=\k(?a)|\k(?b))c/i, "Bbc\u0100"), [2, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=\k(?\u0100)|\k(?\u0102))c/i, "\u0101\u0100c"), [2, "c", "\u0100", undefined]); +shouldBe(matchOf(/(?<=\k(?\u0100)|\k(?\u0102))c/i, "\u0103\u0102c"), [2, "c", undefined, "\u0102"]); +shouldBe(matchOf(/(?<=\k(?\u0100)|\k(?\u0102))c/i, "\u0101\u0102c"), null); +shouldBe(matchOf(/(?<=\k{2}(?a)|\k{2}(?b))c/i, "aAac"), [3, "c", "a", undefined]); +shouldBe(matchOf(/(?<=x\k*(?a)|x\k*(?b))c/i, "XbBbc"), [4, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=x\k*(?a)|x\k*(?b))c/i, "XbBbc\u0100"), [4, "c", undefined, "b"]); +shouldBe(matchOf(/(?<=(?a)|(?b))\k/, "aa"), [1, "a", "a", undefined]); +shouldBe(matchOf(/(?<=(?a)|(?b))\k/, "bb"), [1, "b", undefined, "b"]); +shouldBe(matchOf(/(?<=(?a)|(?b))\k/, "ab"), null); +shouldBe(matchOf(/(?<=(?a)|(?b))\kx/, "aax"), [1, "ax", "a", undefined]); +shouldBe(matchOf(/(?<=(?a)|(?b))\kx/, "bbx"), [1, "bx", undefined, "b"]); +shouldBe(matchOf(/(?<=(?a)|(?b))\kx/, "abx"), null); +shouldBe(matchOf(/(?<=(?a)|(?b))\kx/, "bax"), null); + +// Empty and undefined captures. +shouldBe(matchOf(/(?<=()\1)a/, "a"), [0, "a", ""]); +shouldBe(matchOf(/(?<=\1())a/, "a"), [0, "a", ""]); +shouldBe(matchOf(/(?<=(a*)\1)b/, "b"), [0, "b", ""]); +shouldBe(matchOf(/(?<=\1(a*))b/, "b"), [0, "b", ""]); +shouldBe(matchOf(/(?<=\1(a*))b/, "ab"), [1, "b", ""]); +shouldBe(matchOf(/(?<=\1(a*))b/, "aab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=\1(a*))b/, "aaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=x\1(a*))b/, "xb"), [1, "b", ""]); +shouldBe(matchOf(/(?<=x\1(a*))b/, "xaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=x\1(a*))b/, "xaaab"), null); +shouldBe(matchOf(/(?<=x\1(a*))b/, "xaaaab"), [5, "b", "aa"]); +shouldBe(matchOf(/(?<=\1(a*)a)b/, "aab"), [2, "b", ""]); +shouldBe(matchOf(/(?<=x\1(a*)a)b/, "xaab"), null); +shouldBe(matchOf(/(?<=x\1(a*)a)b/, "xaaab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=x\1(a*)a)b/, "xab"), [2, "b", ""]); +shouldBe(matchOf(/(?<=x\1(a*?)a)b/, "xaab"), null); +shouldBe(matchOf(/(?<=x\1(a*?)a)b/, "xaaab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=x\1(a*?)a)b/, "xab"), [2, "b", ""]); +shouldBe(matchOf(/(?<=\1{2}())a/, "a"), [0, "a", ""]); +shouldBe(matchOf(/(?<=\1*())a/, "a"), [0, "a", ""]); +shouldBe(matchOf(/(?<=\1(a)?)b/, "b"), [0, "b", undefined]); +shouldBe(matchOf(/(?<=\1(a)?)b/, "ab"), [1, "b", undefined]); +shouldBe(matchOf(/(?<=\1(a)?)b/, "aab"), [2, "b", "a"]); +shouldBe(matchOf(/(?<=x\1(a)?)b/, "xab"), null); +shouldBe(matchOf(/(?<=x\1(a)?)b/, "xaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=x\1(a)?)b/, "xb"), [1, "b", undefined]); +shouldBe(matchOf(/(?<=x\1(a)?)b/, "yb"), null); +shouldBe(matchOf(/(?<=x\1(a)??)b/, "xab"), null); +shouldBe(matchOf(/(?<=x\1(a)??)b/, "xaab"), [3, "b", "a"]); +shouldBe(matchOf(/(?<=x\1(a)??)b/, "xb"), [1, "b", undefined]); +shouldBe(matchOf(/(?<=x\1{2}(a)?)b/, "xb"), [1, "b", undefined]); +shouldBe(matchOf(/(?<=x\1{2}(a)?)b/, "xab"), null); +shouldBe(matchOf(/(?<=x\1{2}(a)?)b/, "xaab"), null); +shouldBe(matchOf(/(?<=x\1{2}(a)?)b/, "xaaab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=x\1{2}(a)?)b/, "yaaab"), null); +shouldBe(matchOf(/(?<=x\1{2}(a)?)b/, "yb"), null); +shouldBe(matchOf(/(a)?b(?<=\1\1b)/, "b"), [0, "b", undefined]); +shouldBe(matchOf(/(a)?b(?<=\1\1b)/, "ab"), [1, "b", undefined]); +shouldBe(matchOf(/(a)?b(?<=\1\1b)/, "aab"), [1, "ab", "a"]); +shouldBe(matchOf(/(?<=(a)|\1{0})b/, "ab"), [1, "b", "a"]); +shouldBe(matchOf(/(?<=(a)|\1{0})b/, "xb"), [1, "b", undefined]); +shouldBe(matchOf(/(?(?a)|\k(?b))c/.test("aac"), true); +shouldBe(/(?<=\k(?a)|\k(?b))c/.test("bbc"), true); +shouldBe(/(?<=\k(?a)|\k(?b))c/.test("abc"), false); +shouldBe(/(?<=\1(a))b/i.test("aAb"), true); +shouldBe(/(?<=\1(a))b/i.test("xAb"), false); +shouldBe(/(?<=\1(a))b/i.test("aAb\u0100"), true); +shouldBe(/(?<=\1(a))b/i.test("xAb\u0100"), false); +shouldBe(/(?<=\1(\u0100))x/i.test("\u0100\u0101x"), true); +shouldBe(/(?<=\1(\u0100))x/i.test("\u0102\u0101x"), false); +shouldBe(/(?(?a)|\k(?b))c/g, "[$&|$1]"), "aa[c|a] bb[c|] abc bac"); +shouldBe("aAb".replace(/(?<=\1(a))b/i, "[$&|$1]"), "aA[b|A]"); +shouldBe("aAb Aab xAb aab\u0100".replace(/(?<=\1(a))b/gi, "[$&|$1]"), "aA[b|A] Aa[b|a] xAb aa[b|a]\u0100"); +shouldBe("aabxabaab".split(/(?<=\1(a))b/), ["aa", "a", "xabaa", "a", ""]); +shouldBe("ababcabcababc".split(/(?<=\1(ab))c/), ["abab", "ab", "abcabab", "ab", ""]); +shouldBe(matchAllOf(/(?<=\1(a))b/g, "aabxabaab"), [[2, "b", "a"], [8, "b", "a"]]); +shouldBe(matchAllOf(/(?<=\1(ab))c/g, "ababcabcababc"), [[4, "c", "ab"], [12, "c", "ab"]]); +shouldBe(matchAllOf(/(?<=\1(\w+))c/g, "ababc abc aac"), [[4, "c", "ab"], [12, "c", "a"]]); +shouldBe(execAt(/(?<=\1(a))b/y, "aab", 2), [[2, "b", "a"], 3]); +shouldBe(execAt(/(?<=\1(a))b/y, "aabb", 2), [[2, "b", "a"], 3]); +shouldBe(execAt(/(?<=\1(a))b/y, "xab", 2), [null, 0]); +shouldBe(execAt(/(?<=\1(a))b/y, "aab", 1), [null, 0]); +shouldBe(execAt(/(?<=\1(ab))c/y, "ababc", 4), [[4, "c", "ab"], 5]); +shouldBe(execAt(/(?<=\1(ab))c/y, "ababc", 3), [null, 0]); +shouldBe(execAt(/(?<=\1(ab))c/g, "ababc", 3), [[4, "c", "ab"], 5]); +shouldBe(execAt(/(?<=\1(ab))c/g, "ababcababc", 5), [[9, "c", "ab"], 10]); +shouldBe(indicesOf(/(?<=\1(a))b/d, "aab"), [2, "b", "a", [2, 3], [1, 2]]); +shouldBe(indicesOf(/(?<=\1(ab))c/d, "ababc"), [4, "c", "ab", [4, 5], [2, 4]]); +shouldBe(indicesOf(/(?<=\1(\w+))c/d, "ababc"), [4, "c", "ab", [4, 5], [2, 4]]); +shouldBe(indicesOf(/(?<=\1{2}(a+))b/d, "aaaaaab"), [6, "b", "aa", [6, 7], [4, 6]]); +shouldBe(indicesOf(/(?<=x\1*(ab))c/d, "xabababc"), [7, "c", "ab", [7, 8], [5, 7]]); +shouldBe(indicesOf(/(?<=\k(?a)|\k(?b))c/d, "bbc"), [2, "c", undefined, "b", [2, 3], undefined, [1, 2]]); +shouldBe(indicesOf(/(?<=\k(?a)|\k(?b))c/d, "aac"), [2, "c", "a", undefined, [2, 3], [1, 2], undefined]); +shouldBe(indicesOf(/(a)b(?<=x\1+b)/d, "xaaab"), [3, "ab", "a", [3, 5], [3, 4]]); + +// Long captures and many repetitions. +shouldBe(matchOf(/(?<=\1(\w{20}))x/, "abcdefghijklmnopqrstabcdefghijklmnopqrstx"), [40, "x", "abcdefghijklmnopqrst"]); +shouldBe(matchOf(/(?<=\1(\w{20}))x/, "abcdefghijklmnopqrstabcdefghijklmnopqrsux"), null); +shouldBe(matchOf(/(?<=\1(\w{20}))x/, "abcdefghijklmnopqrsabcdefghijklmnopqrstx"), null); +shouldBe(matchOf(/(?<=\1(\w{20}))x/i, "ABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrstx"), [40, "x", "abcdefghijklmnopqrst"]); +shouldBe(matchOf(/(?<=\1(\w{20}))x/i, "ABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrstx\u0100"), [40, "x", "abcdefghijklmnopqrst"]); +shouldBe(matchOf(/(?<=\1(\w{20}))x/i, "ABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrsux\u0100"), null); +shouldBe(matchOf(/(?<=\1{9}(ab))c/, "ababababababababababc"), [20, "c", "ab"]); +shouldBe(matchOf(/(?<=\1{9}(ab))c/, "abababababababababc"), null); +shouldBe(matchOf(/(?<=x\1*(ab))c/, "xababababababababababababababababababababababababababababababc"), [61, "c", "ab"]); +shouldBe(matchOf(/(?<=x\1*(ab))c/, "yababababababababababababababababababababababababababababababc"), null); +shouldBe(matchOf(/(?<=x\1*(ab))c/, "xaababababababababababababababababababababababababababababababc"), null); +shouldBe(matchOf(/(?<=x\1*?(ab))c/, "xababababababababababababababababababababababababababababababc"), [61, "c", "ab"]); +shouldBe(matchOf(/(?<=x\1*?(ab))c/, "yababababababababababababababababababababababababababababababc"), null); +shouldBe(matchOf(/(?<=x\1*?(ab))c/, "xaababababababababababababababababababababababababababababababc"), null); +shouldBe(matchOf(/(?<=x\1{0,5}(ab))c/, "xababababababc"), [13, "c", "ab"]); +shouldBe(matchOf(/(?<=x\1{0,5}(ab))c/, "xabababababababc"), null); +shouldBe(matchOf(/(?<=x\1{0,5}(ab))c/, "yababababababc"), null); +shouldBe(matchOf(/(?<=x\1{0,5}?(ab))c/, "xababababababc"), [13, "c", "ab"]); +shouldBe(matchOf(/(?<=x\1{0,5}?(ab))c/, "xabababababababc"), null); +shouldBe(matchOf(/(ab)c(?<=x\1+c)/, "xababababababababababababababababababababababababababababababc"), [59, "abc", "ab"]); +shouldBe(matchOf(/(ab)c(?<=x\1+c)/, "yababababababababababababababababababababababababababababababc"), null); +shouldBe(matchOf(/(ab)c(?<=x\1+?c)/, "xababababababababababababababababababababababababababababababc"), [59, "abc", "ab"]); +shouldBe(matchOf(/(ab)c(?<=x\1{29}c)/, "xababababababababababababababababababababababababababababababc"), null); +shouldBe(matchOf(/(ab)c(?<=x\1{29}c)/, "xabababababababababababababababababababababababababababababababc"), null); +shouldBe(matchOf(/(ab)c(?<=x\1{29}c)/, "xabababababababababababababababababababababababababababababc"), [57, "abc", "ab"]); +shouldBe(matchOf(/(ab)c(?<=x\1{2,29}c)/, "xababababababababababababababababababababababababababababababc"), null); +shouldBe(matchOf(/(ab)c(?<=x\1{2,29}c)/, "xabababababababababababababababababababababababababababababababc"), null); +shouldBe(matchOf(/(ab)c(?<=x\1{2,29}?c)/, "xababababababababababababababababababababababababababababababc"), null); +shouldBe(matchOf(/(ab)c(?<=x\1{2,29}?c)/, "xabababababababababababababababababababababababababababababababc"), null); +shouldBe(matchOf(/(?<=\1(a))b/, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxaab"), [102, "b", "a"]); +shouldBe(matchOf(/(?<=\1(a))b/, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxab"), null); +shouldBe("aabxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxaabxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxaabxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".replace(/(?<=\1(a))b/g, "[$&|$1]"), "aa[b|a]xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxaa[b|a]xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxaa[b|a]xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); +shouldBe(matchAllOf(/(?<=\1(a))b/g, "aabxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxaabxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxaabxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"), [[2, "b", "a"], [55, "b", "a"], [108, "b", "a"]]); +shouldBe(matchAllOf(/(?<=x\1*(a))b/g, "xaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaab"), [[4, "b", "a"], [9, "b", "a"], [14, "b", "a"], [19, "b", "a"], [24, "b", "a"], [29, "b", "a"], [34, "b", "a"], [39, "b", "a"], [44, "b", "a"], [49, "b", "a"], [54, "b", "a"], [59, "b", "a"], [64, "b", "a"], [69, "b", "a"], [74, "b", "a"], [79, "b", "a"], [84, "b", "a"], [89, "b", "a"], [94, "b", "a"], [99, "b", "a"]]); +shouldBe("xaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaab".replace(/(?<=x\1*(a))b/g, "[$&|$1]"), "xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]xaaa[b|a]"); +shouldBe(matchAllOf(/(?<=x\1*(a))b/gy, "xaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaab"), []); +shouldBe("xaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaab".replace(/(?<=x\1*(a))b/gy, "[$&|$1]"), "xaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaab"); +shouldBe(execAt(/(?<=x\1*(a))b/gy, "xaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaab", 4), [[4, "b", "a"], 5]); +shouldBe(execAt(/(?<=x\1*(a))b/gy, "xaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaab", 3), [null, 0]); +shouldBe(execAt(/(?<=x\1*(a))b/gy, "xaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaabxaaab", 9), [[9, "b", "a"], 10]); + +// Modifiers and other flags. +shouldBe(matchOf(/(?<=(?i:\1(a)))b/, "aAb"), [2, "b", "A"]); +shouldBe(matchOf(/(?<=(?i:\1(a)))b/, "xAb"), null); +shouldBe(matchOf(/(?<=(?i:\1(a)))b/, "aAB"), null); +shouldBe(matchOf(/(?<=(?i:\1(a)))b/, "aAb\u0100"), [2, "b", "A"]); +shouldBe(matchOf(/(?<=(?i:\1(a)))B/, "aAb"), null); +shouldBe(matchOf(/(?<=(?i:\1(a))b)c/, "aAbc"), [3, "c", "A"]); +shouldBe(matchOf(/(?<=(?i:\1(a))b)c/, "aABc"), null); +shouldBe(matchOf(/(?<=(?i:\1(a))b)c/, "aAbc\u0100"), [3, "c", "A"]); +shouldBe(matchOf(/(?<=(?i:\1(a))b)c/, "aABc\u0100"), null); +shouldBe(matchOf(/(?<=(?-i:\1(a))b)c/i, "aAbc"), null); +shouldBe(matchOf(/(?<=(?-i:\1(a))b)c/i, "aaBc"), [3, "c", "a"]); +shouldBe(matchOf(/(?<=(?-i:\1(a))b)c/i, "aABc\u0100"), null); +shouldBe(matchOf(/(?<=(?-i:\1(a))b)c/i, "aaBc\u0100"), [3, "c", "a"]); +shouldBe(/(?<=(?i:\1(a)))b/.test("aAb"), true); +shouldBe(/(?<=(?i:\1(a)))b/.test("xAb"), false); +shouldBe(/(?<=(?i:\1(a)))b/.test("aAb\u0100"), true); +shouldBe(/(?<=(?i:\1(a)))b/.test("xAb\u0100"), false); +shouldBe("aAb Aab xAb aab\u0100".replace(/(?<=(?i:\1(a)))b/g, "[$&|$1]"), "aA[b|A] Aa[b|a] xAb aa[b|a]\u0100"); +shouldBe(matchOf(/(?<=\1(a))b/m, "a\naab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=\1(a))b/m, "a\nab"), null); +shouldBe(matchOf(/(?<=^\1(a))b/m, "a\naab"), [4, "b", "a"]); +shouldBe(matchOf(/(?<=^\1(a))b/m, "a\nxaab"), null); +shouldBe(matchOf(/(?<=\1(.))b/s, "\n\nb"), [2, "b", "\n"]); +shouldBe(matchOf(/(?<=\1(.))b/, "\n\nb"), null); +shouldBe(matchOf(/(?<=\1(.))b/s, "\nxb"), null); +shouldBe(matchOf(/(?<=\1(.))b/i, "aAb"), [2, "b", "A"]); +shouldBe(matchOf(/(?<=\1(.))b/is, "\n\nb"), [2, "b", "\n"]); +shouldBe(matchOf(/(?<=\1(.))b/is, "aAb\u0100"), [2, "b", "A"]); +shouldBe(matchOf(/(?<=\1(.))b/is, "\n\nb\u0100"), [2, "b", "\n"]); +shouldBe(matchOf(/(?<=\1(.))b/is, "\u0100\u0101b"), [2, "b", "\u0101"]); +shouldBe(matchOf(/(?<=\1(.))b/s, "\u0100\u0101b"), null); + +// References to groups after the lookbehind stay forward references. +shouldBe(matchOf(/(?<=\1)(a)/, "a"), [0, "a", "a"]); +shouldBe(matchOf(/(?<=\1)(a)/, "aa"), [0, "a", "a"]); +shouldBe(matchOf(/(?<=x\1)(a)/, "xa"), [1, "a", "a"]); +shouldBe(matchOf(/(?<=x\1)(a)/, "xaa"), [1, "a", "a"]); +shouldBe(matchOf(/(?<=x\1)(a)/, "ya"), null); +shouldBe(matchOf(/(?<=\1x)(a)/, "xa"), [1, "a", "a"]); +shouldBe(matchOf(/(?<=\1x)(a)/, "axa"), [2, "a", "a"]); +shouldBe(matchOf(/(?<=\1x)(a)/, "ya"), null); +shouldBe(matchOf(/(?<=\2(a))(b)/, "ab"), [1, "b", "a", "b"]); +shouldBe(matchOf(/(?<=\2(a))(b)/, "bab"), [2, "b", "a", "b"]); +shouldBe(matchOf(/(?<=\2(a))(b)/, "xb"), null); +shouldBe(matchOf(/(?<=\1(a))(b)/, "aab"), [2, "b", "a", "b"]); +shouldBe(matchOf(/(?<=\1(a))(b)/, "ab"), null); +shouldBe(matchOf(/(?<=\1(a))(b)/, "xab"), null); +shouldBe(matchOf(/(?<=(a)\2)(b)/, "ab"), [1, "b", "a", "b"]); +shouldBe(matchOf(/(?<=(a)\2)(b)/, "bab"), [2, "b", "a", "b"]); +shouldBe(matchOf(/(?<=(a)\2)(b)/, "xb"), null); +shouldBe(matchOf(/(?<=(a)\1)(b)/, "aab"), [2, "b", "a", "b"]); +shouldBe(matchOf(/(?<=(a)\1)(b)/, "ab"), [1, "b", "a", "b"]); +shouldBe(matchOf(/(?<=(a)\1)(b)/, "xb"), null); diff --git a/JSTests/stress/set-for-each-mutation-during-iteration.js b/JSTests/stress/set-for-each-mutation-during-iteration.js new file mode 100644 index 000000000000..27492864d5c0 --- /dev/null +++ b/JSTests/stress/set-for-each-mutation-during-iteration.js @@ -0,0 +1,95 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error('bad value: ' + actual); +} + +function makeSet(count) { + var set = new Set; + for (var i = 0; i < count; ++i) + set.add(i); + return set; +} + +function values(set) { + var result = []; + set.forEach(function (value, key) { result.push(key + ':' + value); }); + return result.join(','); +} +noInline(values); + +function deleteAhead(set) { + var result = []; + set.forEach(function (value) { + result.push(value); + if (value % 2 == 0) + set.delete(value + 1); + }); + return result.join(','); +} +noInline(deleteAhead); + +function deleteAllAhead(set) { + var result = []; + set.forEach(function (value) { + result.push(value); + if (value == 2) { + for (var i = 3; i < 10; ++i) + set.delete(i); + } + }); + return result.join(','); +} +noInline(deleteAllAhead); + +function addDuring(set) { + var result = []; + var added = 0; + set.forEach(function (value) { + result.push(value); + if (added < 40) + set.add('x' + added++); + }); + return result.length; +} +noInline(addDuring); + +function clearDuring(set) { + var result = []; + set.forEach(function (value) { + result.push(value); + if (value == 3) + set.clear(); + }); + return result.join(','); +} +noInline(clearDuring); + +function clearAndReadd(set) { + var result = []; + set.forEach(function (value) { + result.push(value); + if (value == 3) { + set.clear(); + set.add('a'); + set.add('b'); + } + }); + return result.join(','); +} +noInline(clearAndReadd); + +for (var i = 0; i < testLoopCount; ++i) { + shouldBe(values(new Set), ''); + var set = makeSet(5); + set.delete(0); + set.delete(4); + shouldBe(values(set), '1:1,2:2,3:3'); + set = new Set([1]); + set.delete(1); + shouldBe(values(set), ''); + shouldBe(deleteAhead(makeSet(10)), '0,2,4,6,8'); + shouldBe(deleteAllAhead(makeSet(10)), '0,1,2'); + shouldBe(addDuring(makeSet(3)), 43); + shouldBe(clearDuring(makeSet(10)), '0,1,2,3'); + shouldBe(clearAndReadd(makeSet(10)), '0,1,2,3,a,b'); +} diff --git a/JSTests/stress/switch-char-scrutinee-live-at-osr-exit.js b/JSTests/stress/switch-char-scrutinee-live-at-osr-exit.js new file mode 100644 index 000000000000..c2ade6b0db23 --- /dev/null +++ b/JSTests/stress/switch-char-scrutinee-live-at-osr-exit.js @@ -0,0 +1,13 @@ +//@ requireOptions("--useLLInt=0", "--forceEagerCompilation=1", "--poisonDeadOSRExitVariables=1") + +function opt(a4) { + switch (a4) { + case 'a': + function f() { a4 } + case 'b': + case 'c': + } +} + +opt(''); +opt([]); diff --git a/JSTests/stress/switch-string-substring-rope.js b/JSTests/stress/switch-string-substring-rope.js new file mode 100644 index 000000000000..ecb281d74f14 --- /dev/null +++ b/JSTests/stress/switch-string-substring-rope.js @@ -0,0 +1,75 @@ +// Coverage for the DFG and FTL binary string switch reading a substring rope in place, which +// depends on decoding the rope's base pointer and character offset correctly. + +function shouldBe(actual, expected, tag, detail) { + if (actual !== expected) { + let where = detail === undefined ? tag : `${tag} [${String(detail)}]`; + throw new Error(`${where}: expected ${String(expected)} but got ${String(actual)}`); + } +} + +function switchOnKeys(scrutinee) { + switch (scrutinee) { + case "a": return "a"; + case "no": return "no"; + case "for": return "for"; + case "fort": return "fort"; + case "forte": return "forte"; + case "with": return "with"; + case "abcdefghij": return "abcdefghij"; + default: return "default"; + } +} +noInline(switchOnKeys); + +// Keys appear at several offsets, adjacent to and overlapping one another, so a base pointer or +// character offset that is off by any amount decodes into a different case. +const base8Bit = "xanoforteforwithabcdefghijnofortex"; +const base16Bit = $vm.make16BitStringIfPossible(base8Bit); + +// Oracle: the same dispatch over a string built character by character, which is resolved rather +// than a rope and so never takes the path under test. +const keyToResult = new Map([ + ["a", "a"], ["no", "no"], ["for", "for"], ["fort", "fort"], + ["forte", "forte"], ["with", "with"], ["abcdefghij", "abcdefghij"], +]); +function expectedFor(offset, length) { + let characters = []; + for (let i = offset; i < Math.min(offset + length, base8Bit.length); ++i) + characters.push(base8Bit.charCodeAt(i)); + const key = String.fromCharCode(...characters); + return keyToResult.has(key) ? keyToResult.get(key) : "default"; +} + +const substringCases = []; +for (let offset = 0; offset <= base8Bit.length; ++offset) { + for (let length = 0; length <= 11; ++length) + substringCases.push([offset, length, expectedFor(offset, length)]); +} + +// A substring of a substring collapses onto the original base, exercising a decode of a rope whose +// offset is the sum of two slices. +function substringOfSubstring(string) { + return string.substring(2, 12).substring(2, 5); +} + +function check([offset, length, expected]) { + const detail = `${offset},${length}`; + shouldBe(switchOnKeys(base8Bit.substring(offset, offset + length)), expected, "8-bit base", detail); + shouldBe(switchOnKeys(base16Bit.substring(offset, offset + length)), expected, "16-bit base", detail); + // A concat rope of the same characters must reach the same case. + shouldBe(switchOnKeys(base8Bit.substring(offset, offset + length) + ""), expected, "concat rope", detail); +} + +// Sweep every offset and length once, then keep one rotating case hot so the switch tiers up with +// substring ropes flowing through it. +for (const substringCase of substringCases) + check(substringCase); + +for (let i = 0; i < testLoopCount; ++i) { + check(substringCases[i % substringCases.length]); + shouldBe(switchOnKeys(substringOfSubstring(base8Bit)), "for", "substring of substring"); + shouldBe(switchOnKeys(substringOfSubstring(base16Bit)), "for", "substring of substring, 16-bit base"); + // An unsliced substring yields the base itself rather than a rope. + shouldBe(switchOnKeys("for".substring(0, 3)), "for", "whole-string substring"); +} diff --git a/JSTests/stress/temporal-plaindate-dayofweek-full-range.js b/JSTests/stress/temporal-plaindate-dayofweek-full-range.js new file mode 100644 index 000000000000..550d5f90a3fc --- /dev/null +++ b/JSTests/stress/temporal-plaindate-dayofweek-full-range.js @@ -0,0 +1,40 @@ +//@ requireOptions("--useTemporal=1") + +function shouldBe(actual, expected, message) { + if (actual !== expected) + throw new Error(`bad value: ${actual}, expected ${expected} (${message})`); +} + +// Temporal.PlainDate.dayOfWeek is 1 (Monday) ... 7 (Sunday). +function dayOfWeekFromLegacy(isoString) { + let day = new Date(isoString + "T00:00:00Z").getUTCDay(); + return day === 0 ? 7 : day; +} + +const cases = [ + ["1970-01-01", 4], + ["1969-12-31", 3], + ["1970-01-04", 7], + ["1970-01-05", 1], + ["2000-01-01", 6], + ["1600-01-01", 6], + ["0001-01-01", 1], + ["0000-12-31", 7], + ["-000001-12-31", 5], + ["+275760-09-13", 6], + ["-271821-04-20", 2], +]; + +for (let [iso, expected] of cases) { + let plainDate = Temporal.PlainDate.from(iso); + shouldBe(plainDate.dayOfWeek, expected, iso); + shouldBe(plainDate.dayOfWeek, dayOfWeekFromLegacy(iso), `${iso} vs Date`); +} + +for (let year = -271820; year <= 275759; year += 3001) { + for (let month = 1; month <= 12; month += 5) { + let plainDate = new Temporal.PlainDate(year, month, 1); + let iso = plainDate.toString(); + shouldBe(plainDate.dayOfWeek, dayOfWeekFromLegacy(iso), iso); + } +} diff --git a/JSTests/stress/typed-array-sort-radix-sort.js b/JSTests/stress/typed-array-sort-radix-sort.js new file mode 100644 index 000000000000..015a8c1203fd --- /dev/null +++ b/JSTests/stress/typed-array-sort-radix-sort.js @@ -0,0 +1,492 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error('bad value: ' + actual + ' (expected ' + expected + ')'); +} + +var seed = 1; +function nextRandom() { + seed ^= seed << 13; seed |= 0; + seed ^= seed >>> 17; + seed ^= seed << 5; seed |= 0; + return seed >>> 8; +} + +function isBigIntArray(ctor) { + return ctor === BigInt64Array || ctor === BigUint64Array; +} + +function compareNumbers(a, b) { + return a - b; +} + +function compareBigInts(a, b) { + if (a < b) + return -1; + if (a > b) + return 1; + return 0; +} + +function sortedReference(array) { + return Array.from(array).sort(isBigIntArray(array.constructor) ? compareBigInts : compareNumbers); +} + +function shouldMatchReference(array) { + var expected = sortedReference(array); + shouldBe(array.sort(), array); + for (var i = 0; i < expected.length; ++i) + shouldBe(array[i], expected[i]); +} + +function coerce(ctor, value) { + return isBigIntArray(ctor) ? BigInt(value) : value; +} + +var integerConstructors = [Int16Array, Uint16Array, Int32Array, Uint32Array, BigInt64Array, BigUint64Array]; + +// Straddle every length at which the engine switches to radix sort, from well below the lowest to +// above the highest. +var lengths = [0, 1, 2, 8, 64, 255, 256, 257, 511, 512, 513, 1023, 1024, 1025, 1100]; + +var integerPatterns = { + random: function (i, length) { return nextRandom(); }, + identical: function (i, length) { return 42; }, + ascending: function (i, length) { return i; }, + descending: function (i, length) { return length - i; }, + // Only the low byte varies, so every higher digit holds one value and is skipped. + lowDigitOnly: function (i, length) { return nextRandom() & 0xff; }, + // Only the second byte varies, so the lowest digit is skipped instead. + secondDigitOnly: function (i, length) { return (nextRandom() & 0xff) * 0x100; }, + twoValues: function (i, length) { return (i % 2) ? 30000 : 7; }, + alternatingSign: function (i, length) { return (i % 2) ? -nextRandom() : nextRandom(); }, +}; + +for (var ctor of integerConstructors) { + for (var length of lengths) { + for (var name in integerPatterns) { + var array = new ctor(length); + for (var i = 0; i < length; ++i) + array[i] = coerce(ctor, integerPatterns[name](i, length)); + shouldMatchReference(array); + } + } +} + +// Extreme values must land at the ends, which is where a wrong sign bit flip shows up. +var extremes = new Map([ + [Int16Array, [-32768, 32767]], + [Uint16Array, [0, 65535]], + [Int32Array, [-2147483648, 2147483647]], + [Uint32Array, [0, 4294967295]], + [BigInt64Array, [-9223372036854775808n, 9223372036854775807n]], + [BigUint64Array, [0n, 18446744073709551615n]], +]); + +for (var ctor of integerConstructors) { + var bounds = extremes.get(ctor); + var low = bounds[0]; + var high = bounds[1]; + var array = new ctor(2048); + for (var i = 0; i < array.length; ++i) + array[i] = (i % 3 === 0) ? low : ((i % 3 === 1) ? high : coerce(ctor, 0)); + shouldMatchReference(array); + shouldBe(array[0], low); + shouldBe(array[array.length - 1], high); +} + +// toSorted takes the same no-comparator path, and must not disturb the receiver. +for (var ctor of integerConstructors) { + var array = new ctor(2048); + for (var i = 0; i < array.length; ++i) + array[i] = coerce(ctor, nextRandom()); + var expectedReceiver = Array.from(array); + var expected = sortedReference(array); + + var sorted = array.toSorted(); + shouldBe(sorted instanceof ctor, true); + shouldBe(sorted === array, false); + shouldBe(sorted.length, array.length); + for (var i = 0; i < expected.length; ++i) { + shouldBe(sorted[i], expected[i]); + shouldBe(array[i], expectedReceiver[i]); + } +} + +// A view over part of a buffer must not write outside its own range. The scatter passes derive write +// cursors from a histogram, so a mismatch between the two escapes the view. +for (var ctor of integerConstructors) { + var elementCount = 2048; + var guardBytes = 16 * ctor.BYTES_PER_ELEMENT; + var dataBytes = elementCount * ctor.BYTES_PER_ELEMENT; + var buffer = new ArrayBuffer(dataBytes + guardBytes * 2); + var whole = new Uint8Array(buffer); + whole.fill(0xab); + + var view = new ctor(buffer, guardBytes, elementCount); + for (var i = 0; i < elementCount; ++i) + view[i] = coerce(ctor, nextRandom()); + var expected = sortedReference(view); + + shouldBe(view.sort(), view); + for (var i = 0; i < elementCount; ++i) + shouldBe(view[i], expected[i]); + for (var i = 0; i < guardBytes; ++i) { + shouldBe(whole[i], 0xab); + shouldBe(whole[guardBytes + dataBytes + i], 0xab); + } +} + +// Auto-length views over a resizable buffer read their length through a different path. +for (var ctor of integerConstructors) { + var elementCount = 2048; + var dataBytes = elementCount * ctor.BYTES_PER_ELEMENT; + var buffer = new ArrayBuffer(dataBytes, { maxByteLength: dataBytes * 2 }); + var array = new ctor(buffer); + for (var i = 0; i < array.length; ++i) + array[i] = coerce(ctor, nextRandom()); + shouldMatchReference(array); + + buffer.resize(dataBytes * 2); + shouldBe(array.length, elementCount * 2); + for (var i = 0; i < array.length; ++i) + array[i] = coerce(ctor, nextRandom()); + shouldMatchReference(array); + + buffer.resize(dataBytes / 4); + shouldBe(array.length, elementCount / 4); + for (var i = 0; i < array.length; ++i) + array[i] = coerce(ctor, nextRandom()); + shouldMatchReference(array); +} + +// An array that is sorted apart from one adjacent pair. This pins the block-at-a-time presortedness +// scan, where an inversion straddling a block boundary is the case most likely to be missed. The +// loop's special cases are its opening blocks and its separately handled trailing block, so every +// position in those is covered, plus a few in between where every block is treated alike. +function inversionPositions(length, stride) { + var positions = []; + var head = Math.min(length - 1, 4 * stride); + for (var i = 0; i < head; ++i) + positions.push(i); + for (var i = head; i + 1 < length; i += length >> 3) + positions.push(i); + for (var i = Math.max(head, length - 1 - 4 * stride); i + 1 < length; ++i) + positions.push(i); + return positions; +} + +// Comparing the raw storage keeps this cheap enough to sweep, and is exact: every value here is an +// ordinary finite number, so the sorted result must match the ascending base bit for bit. The widest +// view that divides the storage evenly costs the fewest reads. +function storageView(buffer) { + if (!(buffer.byteLength % 4)) + return new Uint32Array(buffer); + return new Uint16Array(buffer); +} + +function sweepInversions(ctor, base) { + var length = base.length; + var array = new ctor(length); + var expectedStorage = storageView(base.buffer); + var actualStorage = storageView(array.buffer); + + for (var position of inversionPositions(length, 16 / ctor.BYTES_PER_ELEMENT)) { + array.set(base); + // Swapping neighbours leaves this as the only descending pair. + var lower = array[position]; + array[position] = array[position + 1]; + array[position + 1] = lower; + + array.sort(); + for (var i = 0; i < actualStorage.length; ++i) { + if (actualStorage[i] !== expectedStorage[i]) + throw new Error('unsorted for inversion at ' + position + ', length ' + length + ', ' + ctor.name); + } + } +} + +// At or above the radix sort thresholds for 2 and 4 byte elements, so the scan under test runs for +// those. 8 byte elements have a much higher threshold and get their own lengths further down. One +// length is a multiple of every block size and one is not, covering a trailing partial block. +var sweepLengths = [1024, 1031]; + +for (var ctor of integerConstructors) { + for (var length of sweepLengths) { + var base = new ctor(length); + for (var i = 0; i < length; ++i) + base[i] = coerce(ctor, i); + sweepInversions(ctor, base); + } +} + +if (typeof SharedArrayBuffer !== 'undefined') { + for (var ctor of integerConstructors) { + var elementCount = 2048; + var dataBytes = elementCount * ctor.BYTES_PER_ELEMENT; + + var buffer = new SharedArrayBuffer(dataBytes); + var array = new ctor(buffer); + for (var i = 0; i < array.length; ++i) + array[i] = coerce(ctor, nextRandom()); + shouldMatchReference(array); + + var growable = new SharedArrayBuffer(dataBytes, { maxByteLength: dataBytes * 2 }); + var growableArray = new ctor(growable); + for (var i = 0; i < growableArray.length; ++i) + growableArray[i] = coerce(ctor, nextRandom()); + shouldMatchReference(growableArray); + + growable.grow(dataBytes * 2); + shouldBe(growableArray.length, elementCount * 2); + for (var i = 0; i < growableArray.length; ++i) + growableArray[i] = coerce(ctor, nextRandom()); + shouldMatchReference(growableArray); + } +} + +// -Infinity < negative finite < -0.0 < +0.0 < positive finite < +Infinity < NaN, and NaN bit patterns +// are canonicalized rather than ordered among themselves. +function compareFloats(a, b) { + var aIsNaN = Number.isNaN(a); + var bIsNaN = Number.isNaN(b); + if (aIsNaN) + return bIsNaN ? 0 : 1; + if (bIsNaN) + return -1; + if (a < b) + return -1; + if (a > b) + return 1; + if (a === 0 && b === 0) { + var aIsNegativeZero = Object.is(a, -0); + if (aIsNegativeZero === Object.is(b, -0)) + return 0; + return aIsNegativeZero ? -1 : 1; + } + return 0; +} + +function shouldMatchFloatReference(array) { + var expected = Array.from(array).sort(compareFloats); + shouldBe(array.sort(), array); + for (var i = 0; i < expected.length; ++i) { + // NaN !== NaN, so compare NaN-ness rather than value. + if (Number.isNaN(expected[i])) { + if (!Number.isNaN(array[i])) + throw new Error('expected NaN at index ' + i + ', got ' + array[i] + ' in ' + array.constructor.name); + } else + shouldBe(array[i], expected[i]); + } +} + +var floatConstructors = [Float16Array, Float32Array, Float64Array]; + +var floatPatterns = { + random: function (i, length) { return (nextRandom() - 8388608) / 1024; }, + identical: function (i, length) { return 1.5; }, + ascending: function (i, length) { return i * 0.5; }, + descending: function (i, length) { return (length - i) * 0.5; }, + subnormalAndTiny: function (i, length) { return (i % 2) ? 5e-8 : 6e-8; }, + largeMagnitude: function (i, length) { return (i % 2) ? -1e30 : 1e30; }, + withZeros: function (i, length) { return (i % 3 === 0) ? -0 : ((i % 3 === 1) ? 0 : (nextRandom() / 65536)); }, + withInfinities: function (i, length) { return (i % 4 === 0) ? -Infinity : ((i % 4 === 1) ? Infinity : (nextRandom() / 65536 - 64)); }, + withNaN: function (i, length) { return (i % 5 === 0) ? NaN : (nextRandom() / 65536 - 64); }, + allNaN: function (i, length) { return NaN; }, + negativeNaN: function (i, length) { return (i % 2) ? -NaN : 1; }, +}; + +for (var ctor of floatConstructors) { + for (var length of lengths) { + for (var name in floatPatterns) { + var array = new ctor(length); + for (var i = 0; i < length; ++i) + array[i] = floatPatterns[name](i, length); + shouldMatchFloatReference(array); + } + } +} + +// -0.0 sorts before +0.0, which === cannot see. This is the case a plain float comparison in the +// presortedness scan would miss, so check the sign of each zero directly. +for (var ctor of floatConstructors) { + for (var length of [256, 512, 1024, 2048]) { + var array = new ctor(length); + var negativeZeroCount = 0; + for (var i = 0; i < length; ++i) { + // Start in the wrong order, so the sort has to move the zeros. + array[i] = (i % 2) ? -0 : 0; + if (i % 2) + ++negativeZeroCount; + } + array.sort(); + for (var i = 0; i < length; ++i) { + if (Object.is(array[i], -0) !== (i < negativeZeroCount)) + throw new Error('zero sign wrong at index ' + i + ' of ' + length + ' in ' + ctor.name); + } + } +} + +// A NaN written through another view over the same buffer keeps whatever bit pattern it was given, +// including a set sign bit. It must still sort last, which is what canonicalizing NaN buys. +var rawNaNs = new Map([ + [Float16Array, [Uint16Array, 0xfe01, 0x7e55]], + [Float32Array, [Uint32Array, 0xffc00001, 0x7fc00123]], + [Float64Array, [BigUint64Array, 0xfff8000000000001n, 0x7ff8000000000123n]], +]); + +for (var ctor of floatConstructors) { + var length = 1024; + var array = new ctor(length); + for (var i = 0; i < length; ++i) + array[i] = i - 512; + + var recipe = rawNaNs.get(ctor); + var raw = new recipe[0](array.buffer); + raw[0] = recipe[1]; + raw[length - 1] = recipe[2]; + + var nanCount = 0; + for (var i = 0; i < length; ++i) { + if (Number.isNaN(array[i])) + ++nanCount; + } + shouldBe(nanCount, 2); + + array.sort(); + shouldBe(Number.isNaN(array[length - 1]), true); + shouldBe(Number.isNaN(array[length - 2]), true); + for (var i = 0; i < length - 2; ++i) { + if (Number.isNaN(array[i])) + throw new Error('NaN did not sort last, found at index ' + i + ' in ' + ctor.name); + if (i && array[i] < array[i - 1]) + throw new Error('unsorted at index ' + i + ' in ' + ctor.name); + } +} + +// A view over part of a buffer must not write outside its own range, for floats too. +for (var ctor of floatConstructors) { + var elementCount = 2048; + var guardBytes = 16 * ctor.BYTES_PER_ELEMENT; + var dataBytes = elementCount * ctor.BYTES_PER_ELEMENT; + var buffer = new ArrayBuffer(dataBytes + guardBytes * 2); + var whole = new Uint8Array(buffer); + whole.fill(0xab); + + var view = new ctor(buffer, guardBytes, elementCount); + for (var i = 0; i < elementCount; ++i) + view[i] = (nextRandom() - 8388608) / 1024; + var expected = Array.from(view).sort(compareFloats); + + shouldBe(view.sort(), view); + for (var i = 0; i < elementCount; ++i) + shouldBe(view[i], expected[i]); + for (var i = 0; i < guardBytes; ++i) { + shouldBe(whole[i], 0xab); + shouldBe(whole[guardBytes + dataBytes + i], 0xab); + } +} + +// The same inversion sweep over the float presortedness scan, which additionally has to compute the +// key transform per lane. +for (var ctor of floatConstructors) { + for (var length of sweepLengths) { + var base = new ctor(length); + for (var i = 0; i < length; ++i) + base[i] = (i - length / 2) * 0.5; + sweepInversions(ctor, base); + } +} + +if (typeof SharedArrayBuffer !== 'undefined') { + for (var ctor of floatConstructors) { + var elementCount = 2048; + var buffer = new SharedArrayBuffer(elementCount * ctor.BYTES_PER_ELEMENT); + var array = new ctor(buffer); + for (var i = 0; i < array.length; ++i) + array[i] = (i % 7 === 0) ? NaN : (nextRandom() - 8388608) / 1024; + shouldMatchFloatReference(array); + } +} + +// The radix sort threshold for 8-byte elements sits far above every length used above, so 8-byte +// types need their own cases to reach the radix path at all. Sorting a shuffled permutation of a +// strictly ascending base has to reproduce that base exactly, which verifies the result without a +// reference sort of BigInt values. +var wideConstructors = [BigInt64Array, BigUint64Array, Float64Array]; + +// Strictly ascending, and spread so that no digit holds a single value: an all-integral double would +// leave its low mantissa bytes zero, and consecutive integers would leave the high bytes constant. +function ascendingWideBase(ctor, length) { + var base = new ctor(length); + if (isBigIntArray(ctor)) { + for (var i = 0; i < length; ++i) + base[i] = BigInt(i) * 2654435761n + 12345n; + } else { + for (var i = 0; i < length; ++i) + base[i] = i * 3 + 1 + i * 1e-9; + } + return base; +} + +function shouldRestoreBase(ctor, base, array, description) { + var expected = storageView(base.buffer); + var actual = storageView(array.buffer); + array.sort(); + for (var i = 0; i < actual.length; ++i) { + if (actual[i] !== expected[i]) + throw new Error(description + ' at storage index ' + i + ', length ' + base.length + ', ' + ctor.name); + } +} + +var wideLengths = [8192, 8193, 8199]; + +for (var ctor of wideConstructors) { + for (var length of wideLengths) { + var base = ascendingWideBase(ctor, length); + var array = new ctor(length); + array.set(base); + // Deterministic Fisher-Yates, so the sort sees a thoroughly unsorted permutation. + for (var i = length - 1; i > 0; --i) { + var j = nextRandom() % (i + 1); + var swapped = array[i]; + array[i] = array[j]; + array[j] = swapped; + } + shouldRestoreBase(ctor, base, array, 'shuffled permutation not restored'); + } +} + +// Reversed input reaches the radix path with every digit maximally out of order. +for (var ctor of wideConstructors) { + var base = ascendingWideBase(ctor, 8192); + var array = new ctor(8192); + for (var i = 0; i < 8192; ++i) + array[i] = base[8191 - i]; + shouldRestoreBase(ctor, base, array, 'reversed input not sorted'); +} + +// Two distinct values over a long array: few enough that the sort declines radix sort and hands off, +// so this covers the handoff rather than the radix passes. +for (var ctor of wideConstructors) { + var length = 8192; + var low = coerce(ctor, 7); + var high = coerce(ctor, 30000); + var array = new ctor(length); + var highCount = 0; + for (var i = 0; i < length; ++i) { + var useHigh = !!(nextRandom() & 1); + array[i] = useHigh ? high : low; + if (useHigh) + ++highCount; + } + array.sort(); + for (var i = 0; i < length; ++i) { + var expected = (i < length - highCount) ? low : high; + if (array[i] !== expected) + throw new Error('two value sort wrong at ' + i + ' of ' + length + ', ' + ctor.name); + } +} + +// The inversion sweep again, at a length that reaches the radix path for 8-byte elements. +for (var ctor of wideConstructors) + sweepInversions(ctor, ascendingWideBase(ctor, 8192)); diff --git a/JSTests/stress/typedarray-sort-out-of-memory.js b/JSTests/stress/typedarray-sort-out-of-memory.js index c43e0df55a32..f215f7d08542 100644 --- a/JSTests/stress/typedarray-sort-out-of-memory.js +++ b/JSTests/stress/typedarray-sort-out-of-memory.js @@ -2,7 +2,8 @@ let ar = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 1073741824)); -// No comparator version. +// A freshly allocated buffer is all zeros, so it is already sorted. Detecting that needs no scratch +// allocation, so this succeeds rather than running out of memory. var exception; try { ar.sort(); @@ -10,6 +11,21 @@ try { exception = e; } +if (exception !== undefined) + throw "FAILED: expected no exception for an already sorted array, got " + exception; + +// Swapping neighbours leaves one descending pair, so sorting now has to allocate. +ar[0] = 1; +ar[1] = 0; + +// No comparator version. +exception = undefined; +try { + ar.sort(); +} catch (e) { + exception = e; +} + if (exception != "RangeError: Out of memory") throw "FAILED: " + exception; @@ -23,4 +39,3 @@ try { if (exception != "RangeError: Out of memory") throw "FAILED: " + exception; - diff --git a/JSTests/stress/uint8array-setFromBase64-zero-length-reads-nothing.js b/JSTests/stress/uint8array-setFromBase64-zero-length-reads-nothing.js new file mode 100644 index 000000000000..c1505bf39850 --- /dev/null +++ b/JSTests/stress/uint8array-setFromBase64-zero-length-reads-nothing.js @@ -0,0 +1,46 @@ +// FromBase64 returns before examining any character when maxLength is 0, so a zero length destination +// accepts input that is not valid base64 at all. +// https://tc39.es/proposal-arraybuffer-base64/spec/#sec-frombase64 + +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`FAIL: expected '${expected}' actual '${actual}'`); +} + +function shouldThrow(callback, errorConstructor) { + try { + callback(); + } catch (e) { + shouldBe(e instanceof errorConstructor, true); + return; + } + throw new Error('FAIL: should have thrown'); +} + +var lastChunkHandlings = ["loose", "strict", "stop-before-partial"]; +// "aa==" is deliberately absent: it is valid base64 under loose and stop-before-partial. +var invalidStrings = ["#", "a#", "aa#", "aaa#", "aaaa#", "=", "a=", "aa=a", "===="]; + +for (var lastChunkHandling of lastChunkHandlings) { + for (var string of invalidStrings) { + var empty = new Uint8Array(0); + var result = empty.setFromBase64(string, { lastChunkHandling }); + shouldBe(result.read, 0); + shouldBe(result.written, 0); + } + + // A zero length view onto a larger buffer behaves the same way. + for (var string of invalidStrings) { + var view = new Uint8Array(new ArrayBuffer(8), 4, 0); + var result = view.setFromBase64(string, { lastChunkHandling }); + shouldBe(result.read, 0); + shouldBe(result.written, 0); + } + + // Uint8Array.fromBase64 has no maxLength, so it still rejects the same input. + for (var string of invalidStrings) { + shouldThrow(() => { + Uint8Array.fromBase64(string, { lastChunkHandling }); + }, SyntaxError); + } +} diff --git a/JSTests/test262/expectations-linux.yaml b/JSTests/test262/expectations-linux.yaml index 3ce3fd0ef29f..ce95d9260303 100644 --- a/JSTests/test262/expectations-linux.yaml +++ b/JSTests/test262/expectations-linux.yaml @@ -58,24 +58,39 @@ test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-of test/built-ins/TypedArrayConstructors/ctors/object-arg/iterated-array-changed-by-tonumber.js: default: 3 strict mode: 3 -test/built-ins/Uint8Array/prototype/setFromBase64/trailing-garbage-empty.js: +test/built-ins/Uint8Array/prototype/setFromBase64/trailing-garbage.js: default: 3 strict mode: 3 -test/built-ins/Uint8Array/prototype/setFromBase64/trailing-garbage.js: +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/intl402/Temporal/PlainDate/prototype/daysInYear/basic-chinese.js: default: 3 strict mode: 3 test/intl402/Temporal/PlainDate/prototype/monthCode/chinese-calendar-dates.js: default: 3 strict mode: 3 +test/intl402/Temporal/PlainDateTime/prototype/daysInYear/basic-chinese.js: + default: 3 + strict mode: 3 test/intl402/Temporal/PlainDateTime/prototype/monthCode/chinese-calendar-dates.js: default: 3 strict mode: 3 test/intl402/Temporal/PlainMonthDay/prototype/monthCode/chinese-calendar-dates.js: default: 3 strict mode: 3 +test/intl402/Temporal/PlainYearMonth/prototype/daysInYear/basic-chinese.js: + default: 3 + strict mode: 3 test/intl402/Temporal/PlainYearMonth/prototype/monthCode/chinese-calendar-dates.js: default: 3 strict mode: 3 +test/intl402/Temporal/ZonedDateTime/prototype/daysInYear/basic-chinese.js: + default: 3 + strict mode: 3 test/intl402/Temporal/ZonedDateTime/prototype/monthCode/chinese-calendar-dates.js: default: 3 strict mode: 3 @@ -111,9 +126,6 @@ test/language/expressions/call/tco-non-eval-with.js: test/language/expressions/delete/super-property-uninitialized-this.js: default: 3 strict mode: 3 -test/language/expressions/dynamic-import/import-attributes/2nd-param-with-type-text.js: - default: 3 - strict mode: 3 test/language/expressions/new/non-ctor-err-realm.js: default: 3 strict mode: 3 @@ -143,16 +155,6 @@ test/language/expressions/yield/star-rhs-iter-thrw-res-done-no-value.js: strict mode: 3 test/language/identifier-resolution/assign-to-global-undefined.js: strict mode: 3 -test/language/import/import-attributes/text-empty.js: - module: 3 -test/language/import/import-attributes/text-javascript.js: - module: 3 -test/language/import/import-attributes/text-self.js: - module: 3 -test/language/import/import-attributes/text-string.js: - module: 3 -test/language/import/import-attributes/text-via-namespace.js: - module: 3 test/language/statements/class/elements/private-class-field-on-nonextensible-objects.js: strict mode: 3 test/language/statements/class/subclass/private-class-field-on-nonextensible-return-override.js: diff --git a/JSTests/test262/expectations.yaml b/JSTests/test262/expectations.yaml index 29975636a55c..d254885d55ef 100644 --- a/JSTests/test262/expectations.yaml +++ b/JSTests/test262/expectations.yaml @@ -58,9 +58,6 @@ test/built-ins/TypedArray/prototype/slice/speciesctor-return-same-buffer-with-of test/built-ins/TypedArrayConstructors/ctors/object-arg/iterated-array-changed-by-tonumber.js: default: 3 strict mode: 3 -test/built-ins/Uint8Array/prototype/setFromBase64/trailing-garbage-empty.js: - default: 3 - strict mode: 3 test/built-ins/Uint8Array/prototype/setFromBase64/trailing-garbage.js: default: 3 strict mode: 3 diff --git a/JSTests/test262/latest-changes-summary.txt b/JSTests/test262/latest-changes-summary.txt index 8635df7d42c8..9246104df70f 100644 --- a/JSTests/test262/latest-changes-summary.txt +++ b/JSTests/test262/latest-changes-summary.txt @@ -1,243 +1,3 @@ -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 +A test/built-ins/Promise/try/avoids-wrap-for-subclass.js +A test/built-ins/Promise/try/avoids-wrap.js +A test/built-ins/Promise/try/ctx-ctor-for-error.js \ No newline at end of file diff --git a/JSTests/test262/test/built-ins/Promise/try/avoids-wrap-for-subclass.js b/JSTests/test262/test/built-ins/Promise/try/avoids-wrap-for-subclass.js new file mode 100644 index 000000000000..188f6ddbbd01 --- /dev/null +++ b/JSTests/test262/test/built-ins/Promise/try/avoids-wrap-for-subclass.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. + +/*--- +description: When the callback passed to PromiseSubclass.try returns a PromiseSubclass, it is not wrapped +esid: sec-promise.try +features: [promise-try, class] +---*/ + +class SubPromise extends Promise { + constructor(executor) { + super(executor); + } +} + +var sentinel = SubPromise.resolve(); +assert(sentinel instanceof SubPromise); + +var returnValue = SubPromise.try(function () { + return sentinel; +}); +assert.sameValue(returnValue, sentinel); diff --git a/JSTests/test262/test/built-ins/Promise/try/avoids-wrap.js b/JSTests/test262/test/built-ins/Promise/try/avoids-wrap.js new file mode 100644 index 000000000000..daf378ea7259 --- /dev/null +++ b/JSTests/test262/test/built-ins/Promise/try/avoids-wrap.js @@ -0,0 +1,15 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: When the callback passed to Promise.try returns a Promise, it is not wrapped +esid: sec-promise.try +features: [promise-try] +---*/ + +var sentinel = Promise.resolve(); + +var returnValue = Promise.try(function () { + return sentinel; +}); +assert.sameValue(returnValue, sentinel); diff --git a/JSTests/test262/test/built-ins/Promise/try/ctx-ctor-for-error.js b/JSTests/test262/test/built-ins/Promise/try/ctx-ctor-for-error.js new file mode 100644 index 000000000000..2a17583463e3 --- /dev/null +++ b/JSTests/test262/test/built-ins/Promise/try/ctx-ctor-for-error.js @@ -0,0 +1,30 @@ +// Copyright (C) 2026 Kevin Gibbons. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +description: Promise.try produces instances of the receiver for errors +esid: sec-promise.try +features: [promise-try, class] +flags: [async] +includes: [asyncHelpers.js] +---*/ + +class SubPromise extends Promise { + constructor(executor) { + super(executor); + } +} + +var error = {}; +var instance = SubPromise.try(function () { + throw error; +}); +assert(instance instanceof SubPromise); + +asyncTest(function() { + return instance.then(function () { + throw new Test262Error('Promise.try given a throwing function should throw'); + }, function (observedError) { + assert.sameValue(observedError, error); + }); +}); diff --git a/JSTests/test262/test262-Revision.txt b/JSTests/test262/test262-Revision.txt index bc9d1a37ece2..e44e33c6b14e 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: be13516fb6441b950ba8a3df97eb34062c186972 +test262 revision: 3655e7464de3d52643ecddd4b5f9f4f3e7f62398 diff --git a/JSTests/wasm/WASM.js b/JSTests/wasm/WASM.js index 2867cab5a39d..e0d078cab3c6 100644 --- a/JSTests/wasm/WASM.js +++ b/JSTests/wasm/WASM.js @@ -34,7 +34,8 @@ export const description = JSON.parse(read("wasm.json", "caller relative")); // export const type = Object.keys(description.type); const _typeSet = new Set(type); export const isValidType = v => _typeSet.has(v); -export const typeValue = _mapValues(description.type); +export const definedTypeValue = _mapValues(description.defined_type); +export const typeValue = { ..._mapValues(description.type), ...definedTypeValue }; const _valueTypeSet = new Set(description.value_type); export const isValidValueType = v => _valueTypeSet.has(v); const _blockTypeSet = new Set(description.block_type); diff --git a/JSTests/wasm/gc/bulk-array-element-types.js b/JSTests/wasm/gc/bulk-array-element-types.js new file mode 100644 index 000000000000..4db5a70fbaef --- /dev/null +++ b/JSTests/wasm/gc/bulk-array-element-types.js @@ -0,0 +1,312 @@ +import * as assert from "../assert.js"; +import { instantiate } from "./wast-wrapper.js"; + +// array.copy and array.fill are lowered per element type, and the length is only known to be +// non-zero on one side of a branch, so exercise every element type against a constant length, a +// runtime length and a zero length. + +// A packed element is written and read as an i32, so its operand type differs from its storage type. +const cases = [ + { type: "i8", valueType: "i32", get: "array.get_u", zero: 0, fill: 0x41 }, + { type: "i16", valueType: "i32", get: "array.get_u", zero: 0, fill: 0x4142 }, + { type: "i32", valueType: "i32", get: "array.get", zero: 0, fill: 0x41424344 }, + { type: "i64", valueType: "i64", get: "array.get", zero: 0n, fill: 0x4142434445464748n }, + { type: "f32", valueType: "f32", get: "array.get", zero: 0, fill: 1.5 }, + { type: "f64", valueType: "f64", get: "array.get", zero: 0, fill: 1.5 }, +]; + +function module({ type, valueType, get }, length) { + return instantiate(` + (module + (type $arr (array (mut ${type}))) + (global $a (mut (ref null $arr)) (ref.null $arr)) + (global $b (mut (ref null $arr)) (ref.null $arr)) + (func (export "reset") + (global.set $a (array.new_default $arr (i32.const 16))) + (global.set $b (array.new_default $arr (i32.const 16)))) + (func (export "fillConst") (param ${valueType}) + (array.fill $arr (global.get $a) (i32.const 2) (local.get 0) (i32.const ${length}))) + (func (export "fill") (param ${valueType}) (param i32) + (array.fill $arr (global.get $a) (i32.const 2) (local.get 0) (local.get 1))) + (func (export "copyConst") + (array.copy $arr $arr (global.get $b) (i32.const 3) (global.get $a) (i32.const 2) (i32.const ${length}))) + (func (export "copy") (param i32) + (array.copy $arr $arr (global.get $b) (i32.const 3) (global.get $a) (i32.const 2) (local.get 0))) + (func (export "copyWithin") (param i32 i32 i32) + (array.copy $arr $arr (global.get $a) (local.get 0) (global.get $a) (local.get 1) (local.get 2))) + (func (export "getA") (param i32) (result ${valueType}) (${get} $arr (global.get $a) (local.get 0))) + (func (export "getB") (param i32) (result ${valueType}) (${get} $arr (global.get $b) (local.get 0))) + (func (export "setA") (param i32 ${valueType}) (array.set $arr (global.get $a) (local.get 0) (local.get 1)))) + `).exports; +} + +for (const c of cases) { + const length = 5; + // reset() installs fresh default-initialized arrays, so one module covers every scenario without + // depending on the instructions under test to clear them. + const m = module(c, length); + + // A constant length lets the fill and the copy be emitted without a call at all. + m.reset(); + m.fillConst(c.fill); + for (let i = 0; i < 16; ++i) + assert.eq(m.getA(i), i >= 2 && i < 2 + length ? c.fill : c.zero); + + m.copyConst(); + for (let i = 0; i < 16; ++i) + assert.eq(m.getB(i), i >= 3 && i < 3 + length ? c.fill : c.zero); + + // The same work with the length only known at runtime. + m.reset(); + m.fill(c.fill, length); + for (let i = 0; i < 16; ++i) + assert.eq(m.getA(i), i >= 2 && i < 2 + length ? c.fill : c.zero); + + m.copy(length); + for (let i = 0; i < 16; ++i) + assert.eq(m.getB(i), i >= 3 && i < 3 + length ? c.fill : c.zero); + + // A zero length is in bounds as long as neither offset is past the end, and must not write. + m.reset(); + m.fill(c.fill, 0); + m.copy(0); + for (let i = 0; i < 16; ++i) { + assert.eq(m.getA(i), c.zero); + assert.eq(m.getB(i), c.zero); + } + + // Overlapping ranges within one array move in both directions. A distinct value per element is + // what makes a copy that walks in the wrong direction observable. + for (const [dstOffset, srcOffset] of [[0, 4], [4, 0]]) { + m.reset(); + const before = []; + for (let i = 0; i < 16; ++i) { + m.setA(i, c.valueType === "i64" ? BigInt(i + 1) : i + 1); + before.push(m.getA(i)); + } + + m.copyWithin(dstOffset, srcOffset, 8); + for (let i = 0; i < 16; ++i) { + const expected = i >= dstOffset && i < dstOffset + 8 ? before[srcOffset + (i - dstOffset)] : before[i]; + assert.eq(m.getA(i), expected); + } + } +} + +// Reference elements go through a copy that stores whole references, and need a write barrier. +{ + const m = instantiate(` + (module + (type $arr (array (mut anyref))) + (type $box (struct (field i32))) + (global $a (ref $arr) (array.new_default $arr (i32.const 8))) + (global $b (ref $arr) (array.new_default $arr (i32.const 8))) + (func (export "fill") (param i32) (param i32) + (array.fill $arr (global.get $a) (i32.const 1) (struct.new $box (local.get 0)) (local.get 1))) + (func (export "copy") (param i32) + (array.copy $arr $arr (global.get $b) (i32.const 2) (global.get $a) (i32.const 1) (local.get 0))) + (func (export "copyWithin") (param i32 i32 i32) + (array.copy $arr $arr (global.get $a) (local.get 0) (global.get $a) (local.get 1) (local.get 2))) + (func (export "setA") (param i32 i32) + (array.set $arr (global.get $a) (local.get 0) (struct.new $box (local.get 1)))) + (func (export "getA") (param i32) (result i32) + (struct.get $box 0 (ref.cast (ref $box) (array.get $arr (global.get $a) (local.get 0))))) + (func (export "getB") (param i32) (result i32) + (struct.get $box 0 (ref.cast (ref $box) (array.get $arr (global.get $b) (local.get 0))))) + (func (export "isNullA") (param i32) (result i32) + (ref.is_null (array.get $arr (global.get $a) (local.get 0)))) + (func (export "isNullB") (param i32) (result i32) + (ref.is_null (array.get $arr (global.get $b) (local.get 0))))) + `).exports; + + m.fill(7, 4); + for (let i = 0; i < 8; ++i) { + if (i >= 1 && i < 5) { + assert.eq(m.isNullA(i), 0); + assert.eq(m.getA(i), 7); + } else + assert.eq(m.isNullA(i), 1); + } + + m.copy(4); + for (let i = 0; i < 8; ++i) { + if (i >= 2 && i < 6) { + assert.eq(m.isNullB(i), 0); + assert.eq(m.getB(i), 7); + } else + assert.eq(m.isNullB(i), 1); + } + + // A zero-length reference copy must not disturb the destination. + m.copy(0); + for (let i = 0; i < 8; ++i) + assert.eq(m.isNullB(i), i >= 2 && i < 6 ? 0 : 1); + + // References move through a GC-safe memmove, so overlapping ranges have to walk in the direction + // that preserves the source. + for (const [dstOffset, srcOffset] of [[0, 3], [3, 0]]) { + const before = []; + for (let i = 0; i < 8; ++i) { + m.setA(i, i + 1); + before.push(i + 1); + } + + m.copyWithin(dstOffset, srcOffset, 5); + for (let i = 0; i < 8; ++i) { + const expected = i >= dstOffset && i < dstOffset + 5 ? before[srcOffset + (i - dstOffset)] : before[i]; + assert.eq(m.getA(i), expected); + } + } +} + +// A v128 fill passes its two lanes separately, and a v128 payload is found by rounding the address +// up at runtime rather than by a fixed offset. +{ + const m = instantiate(` + (module + (type $arr (array (mut v128))) + (global $a (ref $arr) (array.new_default $arr (i32.const 8))) + (global $b (ref $arr) (array.new_default $arr (i32.const 8))) + (func (export "fill") (param i32) + (array.fill $arr (global.get $a) (i32.const 1) + (v128.const i64x2 0x0102030405060708 0x1112131415161718) + (local.get 0))) + (func (export "copy") (param i32) + (array.copy $arr $arr (global.get $b) (i32.const 2) (global.get $a) (i32.const 1) (local.get 0))) + (func (export "getA0") (param i32) (result i64) + (i64x2.extract_lane 0 (array.get $arr (global.get $a) (local.get 0)))) + (func (export "getA1") (param i32) (result i64) + (i64x2.extract_lane 1 (array.get $arr (global.get $a) (local.get 0)))) + (func (export "getB0") (param i32) (result i64) + (i64x2.extract_lane 0 (array.get $arr (global.get $b) (local.get 0)))) + (func (export "getB1") (param i32) (result i64) + (i64x2.extract_lane 1 (array.get $arr (global.get $b) (local.get 0))))) + `).exports; + + const lane0 = 0x0102030405060708n; + const lane1 = 0x1112131415161718n; + + m.fill(4); + for (let i = 0; i < 8; ++i) { + const filled = i >= 1 && i < 5; + assert.eq(m.getA0(i), filled ? lane0 : 0n); + assert.eq(m.getA1(i), filled ? lane1 : 0n); + } + + m.copy(4); + for (let i = 0; i < 8; ++i) { + const copied = i >= 2 && i < 6; + assert.eq(m.getB0(i), copied ? lane0 : 0n); + assert.eq(m.getB1(i), copied ? lane1 : 0n); + } + + // A zero length must leave both arrays alone. + m.fill(0); + m.copy(0); + for (let i = 0; i < 8; ++i) { + assert.eq(m.getA0(i), i >= 1 && i < 5 ? lane0 : 0n); + assert.eq(m.getB0(i), i >= 2 && i < 6 ? lane0 : 0n); + } +} + +// offset + size is checked without wrapping, so a size that would overflow a 32-bit sum still traps +// rather than being treated as in bounds. +{ + const m = instantiate(` + (module + (type $arr (array (mut i32))) + (global $a (ref $arr) (array.new_default $arr (i32.const 8))) + (func (export "fill") (param i32 i32) + (array.fill $arr (global.get $a) (local.get 0) (i32.const 1) (local.get 1))) + (func (export "copy") (param i32 i32 i32) + (array.copy $arr $arr (global.get $a) (local.get 0) (global.get $a) (local.get 1) (local.get 2)))) + `).exports; + + for (const [offset, size] of [[1, -1], [-1, 1], [-1, -1], [0, 9], [8, 1], [9, 0]]) { + assert.throws(() => m.fill(offset, size), WebAssembly.RuntimeError, "Out of bounds array.fill"); + assert.throws(() => m.copy(offset, 0, size), WebAssembly.RuntimeError, "Out of bounds array.copy"); + assert.throws(() => m.copy(0, offset, size), WebAssembly.RuntimeError, "Out of bounds array.copy"); + } + + // A zero-length range at the very end of the array is in bounds. + m.fill(8, 0); + m.copy(8, 8, 0); +} + +// A constant offset and a constant size are extended to 64 bits by hand, so repeat the range check +// with every operand constant. +for (const [offset, size] of [[1, -1], [-1, 1], [-1, -1], [0, 9], [8, 1], [9, 0]]) { + const m = instantiate(` + (module + (type $arr (array (mut i32))) + (global $a (ref $arr) (array.new_default $arr (i32.const 8))) + (func (export "fill") + (array.fill $arr (global.get $a) (i32.const ${offset}) (i32.const 1) (i32.const ${size}))) + (func (export "copy") + (array.copy $arr $arr (global.get $a) (i32.const ${offset}) (global.get $a) (i32.const 0) (i32.const ${size})))) + `).exports; + + assert.throws(() => m.fill(), WebAssembly.RuntimeError, "Out of bounds array.fill"); + assert.throws(() => m.copy(), WebAssembly.RuntimeError, "Out of bounds array.copy"); +} + +// A constant fill value whose bytes all repeat is lowered to a byte-wise fill, which needs the +// element count scaled to a byte count. A float constant reaches the same path through its bits. +{ + const m = instantiate(` + (module + (type $i32arr (array (mut i32))) + (type $i64arr (array (mut i64))) + (type $f64arr (array (mut f64))) + (global $i32 (ref $i32arr) (array.new_default $i32arr (i32.const 8))) + (global $i64 (ref $i64arr) (array.new_default $i64arr (i32.const 8))) + (global $f64 (ref $f64arr) (array.new_default $f64arr (i32.const 8))) + (func (export "fillI32") + (array.fill $i32arr (global.get $i32) (i32.const 1) (i32.const 0x41414141) (i32.const 5))) + (func (export "fillI64") + (array.fill $i64arr (global.get $i64) (i32.const 1) (i64.const -1) (i32.const 5))) + (func (export "setF64") (param i32 f64) + (array.set $f64arr (global.get $f64) (local.get 0) (local.get 1))) + (func (export "fillF64") + (array.fill $f64arr (global.get $f64) (i32.const 1) (f64.const 0) (i32.const 5))) + (func (export "getI32") (param i32) (result i32) (array.get $i32arr (global.get $i32) (local.get 0))) + (func (export "getI64") (param i32) (result i64) (array.get $i64arr (global.get $i64) (local.get 0))) + (func (export "getF64") (param i32) (result f64) (array.get $f64arr (global.get $f64) (local.get 0)))) + `).exports; + + for (let i = 0; i < 8; ++i) + m.setF64(i, 1.5); + + m.fillI32(); + m.fillI64(); + m.fillF64(); + + for (let i = 0; i < 8; ++i) { + const filled = i >= 1 && i < 6; + assert.eq(m.getI32(i), filled ? 0x41414141 : 0); + assert.eq(m.getI64(i), filled ? -1n : 0n); + assert.eq(m.getF64(i), filled ? 0 : 1.5); + } +} + +// A null array traps on the length load that the bounds check needs, so it reports a plain null +// access rather than a message naming the instruction. +{ + const m = instantiate(` + (module + (type $arr (array (mut i32))) + (global $null (ref null $arr) (ref.null $arr)) + (func (export "fill") (param i32) + (array.fill $arr (global.get $null) (i32.const 0) (i32.const 1) (local.get 0))) + (func (export "copyDst") (param i32) + (array.copy $arr $arr (global.get $null) (i32.const 0) (array.new_default $arr (i32.const 4)) (i32.const 0) (local.get 0))) + (func (export "copySrc") (param i32) + (array.copy $arr $arr (array.new_default $arr (i32.const 4)) (i32.const 0) (global.get $null) (i32.const 0) (local.get 0)))) + `).exports; + + for (const size of [0, 1]) { + assert.throws(() => m.fill(size), WebAssembly.RuntimeError, "access to a null reference"); + assert.throws(() => m.copyDst(size), WebAssembly.RuntimeError, "access to a null reference"); + assert.throws(() => m.copySrc(size), WebAssembly.RuntimeError, "access to a null reference"); + } +} diff --git a/JSTests/wasm/gc/bulk-array.js b/JSTests/wasm/gc/bulk-array.js index 75d38c501b78..5366d6d597f8 100644 --- a/JSTests/wasm/gc/bulk-array.js +++ b/JSTests/wasm/gc/bulk-array.js @@ -85,7 +85,7 @@ function testArrayFill() { (start 0)) `), WebAssembly.RuntimeError, - "array.fill to a null reference" + "access to a null reference" ); assert.throws( @@ -98,7 +98,7 @@ function testArrayFill() { (start 0)) `), WebAssembly.RuntimeError, - "array.fill to a null reference" + "access to a null reference" ); assert.throws( @@ -303,7 +303,7 @@ function testArrayCopy() { (start 0)) `), WebAssembly.RuntimeError, - "array.copy to a null reference" + "access to a null reference" ); assert.throws( @@ -316,7 +316,7 @@ function testArrayCopy() { (start 0)) `), WebAssembly.RuntimeError, - "array.copy to a null reference" + "access to a null reference" ); assert.throws( @@ -328,7 +328,7 @@ function testArrayCopy() { (start 0)) `), WebAssembly.RuntimeError, - "array.copy to a null reference" + "access to a null reference" ); assert.throws( @@ -341,7 +341,7 @@ function testArrayCopy() { (start 0)) `), WebAssembly.RuntimeError, - "array.copy to a null reference" + "access to a null reference" ); assert.throws( diff --git a/JSTests/wasm/js-api/Module.exports.js b/JSTests/wasm/js-api/Module.exports.js index ba88ec08ab16..777fd06197b3 100644 --- a/JSTests/wasm/js-api/Module.exports.js +++ b/JSTests/wasm/js-api/Module.exports.js @@ -34,20 +34,10 @@ assert.eq(WebAssembly.Module.exports.length, 1); assert.eq(WebAssembly.Module.exports(m).length, 4); assert.eq(WebAssembly.Module.exports(m)[0].name, "func"); assert.eq(WebAssembly.Module.exports(m)[0].kind, "function"); - assert.eq(WebAssembly.Module.exports(m)[0].type.parameters, []); - assert.eq(WebAssembly.Module.exports(m)[0].type.results, []); assert.eq(WebAssembly.Module.exports(m)[1].name, "tab"); assert.eq(WebAssembly.Module.exports(m)[1].kind, "table"); - assert.eq(WebAssembly.Module.exports(m)[1].type.minimum, 20); - assert.eq(WebAssembly.Module.exports(m)[1].type.maximum, 30); - assert.eq(WebAssembly.Module.exports(m)[1].type.element, "funcref"); assert.eq(WebAssembly.Module.exports(m)[2].name, "mem"); assert.eq(WebAssembly.Module.exports(m)[2].kind, "memory"); - assert.eq(WebAssembly.Module.exports(m)[2].type.minimum, 1); - assert.eq(WebAssembly.Module.exports(m)[2].type.maximum, 1); - assert.eq(WebAssembly.Module.exports(m)[2].type.shared, false); assert.eq(WebAssembly.Module.exports(m)[3].name, "glob"); assert.eq(WebAssembly.Module.exports(m)[3].kind, "global"); - assert.eq(WebAssembly.Module.exports(m)[3].type.value, "i32"); - assert.eq(WebAssembly.Module.exports(m)[3].type.mutable, false); } diff --git a/JSTests/wasm/js-api/Module.imports-exports-no-type.js b/JSTests/wasm/js-api/Module.imports-exports-no-type.js new file mode 100644 index 000000000000..5e21c7275c57 --- /dev/null +++ b/JSTests/wasm/js-api/Module.imports-exports-no-type.js @@ -0,0 +1,68 @@ +import Builder from '../Builder.js'; +import * as assert from '../assert.js'; + +{ + const m = new WebAssembly.Module( + (new Builder()) + .Type().End() + .Import() + .Function("fooFunction", "barFunction", { params: [] }) + .Table("fooTable", "barTable", { initial: 20, element: "funcref" }) + .Memory("fooMemory", "barMemory", { initial: 20 }) + .Global().I32("fooGlobal", "barGlobal", "immutable").End() + .End() + .WebAssembly().get()); + const imports = WebAssembly.Module.imports(m); + assert.eq(imports.length, 4); + for (const imp of imports) + assert.isUndef(imp.type); +} + +{ + const m = new WebAssembly.Module( + (new Builder()) + .Type().End() + .Function().End() + .Table() + .Table({ initial: 20, maximum: 30, element: "funcref" }) + .End() + .Memory().InitialMaxPages(1, 1).End() + .Global().I32(42, "immutable").End() + .Export() + .Function("func") + .Table("tab", 0) + .Memory("mem", 0) + .Global("glob", 0) + .End() + .Code() + .Function("func", { params: [] }).Return().End() + .End() + .WebAssembly().get()); + const exports = WebAssembly.Module.exports(m); + assert.eq(exports.length, 4); + for (const exp of exports) + assert.isUndef(exp.type); +} + +{ + // (module (global (export "g") (ref func) (ref.func 0)) (func)) + const bytes = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 6, 7, 1, 100, 112, 0, 210, 0, 11, 7, 5, 1, 1, 103, 3, 0, 10, 4, 1, 2, 0, 11]); + const module = new WebAssembly.Module(bytes); + const exports = WebAssembly.Module.exports(module); + assert.eq(exports.length, 1); + assert.eq(exports[0].name, "g"); + assert.eq(exports[0].kind, "global"); + assert.isUndef(exports[0].type); +} + +{ + // (module (import "m" "g" (global (ref func)))) + const bytes = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 2, 9, 1, 1, 109, 1, 103, 3, 100, 112, 0]); + const module = new WebAssembly.Module(bytes); + const imports = WebAssembly.Module.imports(module); + assert.eq(imports.length, 1); + assert.eq(imports[0].module, "m"); + assert.eq(imports[0].name, "g"); + assert.eq(imports[0].kind, "global"); + assert.isUndef(imports[0].type); +} diff --git a/JSTests/wasm/js-api/Module.imports.js b/JSTests/wasm/js-api/Module.imports.js index 07c65ab4c0c5..b991c626502b 100644 --- a/JSTests/wasm/js-api/Module.imports.js +++ b/JSTests/wasm/js-api/Module.imports.js @@ -26,23 +26,13 @@ assert.eq(WebAssembly.Module.imports.length, 1); assert.eq(WebAssembly.Module.imports(m)[0].module, "fooFunction"); assert.eq(WebAssembly.Module.imports(m)[0].name, "barFunction"); assert.eq(WebAssembly.Module.imports(m)[0].kind, "function"); - assert.eq(WebAssembly.Module.imports(m)[0].type.parameters, []); - assert.eq(WebAssembly.Module.imports(m)[0].type.results, []); assert.eq(WebAssembly.Module.imports(m)[1].module, "fooTable"); assert.eq(WebAssembly.Module.imports(m)[1].name, "barTable"); assert.eq(WebAssembly.Module.imports(m)[1].kind, "table"); - assert.eq(WebAssembly.Module.imports(m)[1].type.minimum, 20); - assert.eq(WebAssembly.Module.imports(m)[1].type.maximum, undefined); - assert.eq(WebAssembly.Module.imports(m)[1].type.element, "funcref"); assert.eq(WebAssembly.Module.imports(m)[2].module, "fooMemory"); assert.eq(WebAssembly.Module.imports(m)[2].name, "barMemory"); assert.eq(WebAssembly.Module.imports(m)[2].kind, "memory"); - assert.eq(WebAssembly.Module.imports(m)[2].type.minimum, 20); - assert.eq(WebAssembly.Module.imports(m)[2].type.maximum, undefined); - assert.eq(WebAssembly.Module.imports(m)[2].type.shared, false); assert.eq(WebAssembly.Module.imports(m)[3].module, "fooGlobal"); assert.eq(WebAssembly.Module.imports(m)[3].name, "barGlobal"); assert.eq(WebAssembly.Module.imports(m)[3].kind, "global"); - assert.eq(WebAssembly.Module.imports(m)[3].type.value, "i32"); - assert.eq(WebAssembly.Module.imports(m)[3].type.mutable, false); } diff --git a/JSTests/wasm/js-api/global-import-export-identity.js b/JSTests/wasm/js-api/global-import-export-identity.js new file mode 100644 index 000000000000..dcf9411c69e5 --- /dev/null +++ b/JSTests/wasm/js-api/global-import-export-identity.js @@ -0,0 +1,46 @@ +import * as assert from '../assert.js'; +import Builder from '../Builder.js'; + +function moduleImportingAndExportingI32() { + const builder = new Builder(); + builder.Type().End() + .Import() + .Global().I32("imp", "global", "immutable").End() + .End() + .Export() + .Global("global", 0) + .End(); + const bin = builder.WebAssembly(); + bin.trim(); + return new WebAssembly.Module(bin.get()); +} + +{ + const module = moduleImportingAndExportingI32(); + const imported = new WebAssembly.Global({ value: "i32", mutable: false }, 7); + const instance = new WebAssembly.Instance(module, { imp: { global: imported } }); + assert.eq(instance.exports.global, imported); + assert.eq(instance.exports.global.value, 7); +} + +{ + const module = moduleImportingAndExportingI32(); + const instance = new WebAssembly.Instance(module, { imp: { global: 11 } }); + assert.eq(instance.exports.global.value, 11); +} + +{ + const producer = new Builder(); + producer.Type().End() + .Global().I32(13, "immutable").End() + .Export() + .Global("global", 0) + .End(); + const producedBin = producer.WebAssembly(); + producedBin.trim(); + const produced = new WebAssembly.Instance(new WebAssembly.Module(producedBin.get())); + const module = moduleImportingAndExportingI32(); + const instance = new WebAssembly.Instance(module, { imp: { global: produced.exports.global } }); + assert.eq(instance.exports.global, produced.exports.global); + assert.eq(instance.exports.global.value, 13); +} diff --git a/JSTests/wasm/js-api/tag-import-export-identity.js b/JSTests/wasm/js-api/tag-import-export-identity.js new file mode 100644 index 000000000000..6bec35ef1912 --- /dev/null +++ b/JSTests/wasm/js-api/tag-import-export-identity.js @@ -0,0 +1,54 @@ +import * as assert from '../assert.js'; +import Builder from '../Builder.js'; + +function moduleImportingAndExportingTag() { + const builder = new Builder(); + builder.Type().End() + .Import() + .Exception("imp", "tag", { params: ["i32"], ret: "void" }) + .End() + .Export() + .Exception("tag", 0) + .End(); + const bin = builder.WebAssembly(); + bin.trim(); + return new WebAssembly.Module(bin.get()); +} + +{ + const module = moduleImportingAndExportingTag(); + const imported = new WebAssembly.Tag({ parameters: ["i32"] }); + const instance = new WebAssembly.Instance(module, { imp: { tag: imported } }); + assert.eq(instance.exports.tag, imported); + const exception = new WebAssembly.Exception(imported, [1]); + assert.eq(exception.is(instance.exports.tag), true); +} + +{ + const producer = new Builder(); + producer.Type().End() + .Exception().Signature({ params: ["i32"] }).End() + .Export() + .Exception("tag", 0) + .End(); + const producedBin = producer.WebAssembly(); + producedBin.trim(); + const produced = new WebAssembly.Instance(new WebAssembly.Module(producedBin.get())); + const module = moduleImportingAndExportingTag(); + const instance = new WebAssembly.Instance(module, { imp: { tag: produced.exports.tag } }); + assert.eq(instance.exports.tag, produced.exports.tag); +} + +{ + const builder = new Builder(); + builder.Type().End() + .Exception().Signature({ params: ["i32"] }).End() + .Export() + .Exception("a", 0) + .Exception("b", 0) + .End(); + const bin = builder.WebAssembly(); + bin.trim(); + const instance = new WebAssembly.Instance(new WebAssembly.Module(bin.get())); + assert.eq(instance.exports.a, instance.exports.b); +} diff --git a/JSTests/wasm/js-api/type-reflection-concrete-types.js b/JSTests/wasm/js-api/type-reflection-concrete-types.js index 1e3fe129faf8..924ddfb3e087 100644 --- a/JSTests/wasm/js-api/type-reflection-concrete-types.js +++ b/JSTests/wasm/js-api/type-reflection-concrete-types.js @@ -1,3 +1,4 @@ +//@ requireOptions("--useWasmJSTypes=true") import * as assert from "../assert.js" // https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md#type-reflection diff --git a/JSTests/wasm/js-api/type-reflection-exports.js b/JSTests/wasm/js-api/type-reflection-exports.js index de8d8dde246b..de2378806b44 100644 --- a/JSTests/wasm/js-api/type-reflection-exports.js +++ b/JSTests/wasm/js-api/type-reflection-exports.js @@ -1,3 +1,4 @@ +//@ requireOptions("--useWasmJSTypes=true") import { compile } from "../wabt-wrapper.js"; import * as assert from "../assert.js" diff --git a/JSTests/wasm/js-api/type-reflection-imports.js b/JSTests/wasm/js-api/type-reflection-imports.js index 755f32ccbb84..c3d732b86a4c 100644 --- a/JSTests/wasm/js-api/type-reflection-imports.js +++ b/JSTests/wasm/js-api/type-reflection-imports.js @@ -1,3 +1,4 @@ +//@ requireOptions("--useWasmJSTypes=true") import { compile } from "../wabt-wrapper.js"; import * as assert from "../assert.js" diff --git a/JSTests/wasm/modules/js-wasm-cycle.js b/JSTests/wasm/modules/js-wasm-cycle.js index 2369fd1220c8..1b2fb793e527 100644 --- a/JSTests/wasm/modules/js-wasm-cycle.js +++ b/JSTests/wasm/modules/js-wasm-cycle.js @@ -25,8 +25,8 @@ import("./js-wasm-cycle/entry-memory.js").then($vm.abort, function (error) { // Test Wasm exports. import { g } from "./js-wasm-cycle/entry-wasm-global.js"; -assert.instanceof(g, WebAssembly.Global); -assert.eq(g.valueOf(), 42); +assert.isNumber(g); +assert.eq(g, 42); import { m } from "./js-wasm-cycle/entry-wasm-memory.js"; assert.instanceof(m, WebAssembly.Memory); diff --git a/JSTests/wasm/modules/js-wasm-global-namespace.js b/JSTests/wasm/modules/js-wasm-global-namespace.js index 01505061be65..52a4dfabd863 100644 --- a/JSTests/wasm/modules/js-wasm-global-namespace.js +++ b/JSTests/wasm/modules/js-wasm-global-namespace.js @@ -1,8 +1,8 @@ import * as constant from "./constant.wasm" import * as assert from '../assert.js'; -assert.isNumber(constant.constant.value); -assert.eq(constant.constant.value, 42); +assert.isNumber(constant.constant); +assert.eq(constant.constant, 42); assert.throws(() => { - constant.constant.value = 200; -}, TypeError, `WebAssembly.Global.prototype.value attempts to modify immutable global value`); + constant.constant = 200; +}, TypeError, `Attempted to assign to readonly property.`); diff --git a/JSTests/wasm/modules/js-wasm-global.js b/JSTests/wasm/modules/js-wasm-global.js index e884f47cd347..709fbf29b874 100644 --- a/JSTests/wasm/modules/js-wasm-global.js +++ b/JSTests/wasm/modules/js-wasm-global.js @@ -1,8 +1,13 @@ import { constant } from "./constant.wasm" import * as assert from '../assert.js'; -assert.isNumber(constant.value); -assert.eq(constant.value, 42); -assert.throws(() => { - constant.value = 200; -}, TypeError, `WebAssembly.Global.prototype.value attempts to modify immutable global value`); +assert.isNumber(constant); +assert.eq(constant, 42); + +const instanceExports = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, + 0x06, 0x06, 0x01, 0x7f, 0x00, 0x41, 0x2a, 0x0b, + 0x07, 0x05, 0x01, 0x01, 0x63, 0x03, 0x00, +]))).exports; +assert.instanceof(instanceExports.c, WebAssembly.Global); +assert.eq(instanceExports.c.value, 42); diff --git a/JSTests/wasm/modules/js-wasm-v128-global.js b/JSTests/wasm/modules/js-wasm-v128-global.js new file mode 100644 index 000000000000..fda2725a36b4 --- /dev/null +++ b/JSTests/wasm/modules/js-wasm-v128-global.js @@ -0,0 +1,8 @@ +//@ requireOptions("--useWasmSIMD=1") +//@ skip if !$isSIMDPlatform +import * as ns from "./v128-global.wasm" +import * as assert from '../assert.js'; + +assert.throws(() => { + ns.v; +}, ReferenceError, `Cannot access 'v' before initialization.`); diff --git a/JSTests/wasm/modules/js-wasm-v128-import.js b/JSTests/wasm/modules/js-wasm-v128-import.js new file mode 100644 index 000000000000..93a6783a0a4a --- /dev/null +++ b/JSTests/wasm/modules/js-wasm-v128-import.js @@ -0,0 +1,6 @@ +//@ requireOptions("--useWasmSIMD=1") +//@ skip if !$isSIMDPlatform +import { ok } from "./v128-import.wasm" +import * as assert from '../assert.js'; + +assert.eq(ok(), 1); diff --git a/JSTests/wasm/modules/v128-global.wasm b/JSTests/wasm/modules/v128-global.wasm new file mode 100644 index 000000000000..8e8c76d057f2 Binary files /dev/null and b/JSTests/wasm/modules/v128-global.wasm differ diff --git a/JSTests/wasm/modules/v128-global.wat b/JSTests/wasm/modules/v128-global.wat new file mode 100644 index 000000000000..c5c8adf10f12 --- /dev/null +++ b/JSTests/wasm/modules/v128-global.wat @@ -0,0 +1,2 @@ +(module + (global (export "v") v128 (v128.const i32x4 0 0 0 0))) diff --git a/JSTests/wasm/modules/v128-import.wasm b/JSTests/wasm/modules/v128-import.wasm new file mode 100644 index 000000000000..66764ea7ac7f Binary files /dev/null and b/JSTests/wasm/modules/v128-import.wasm differ diff --git a/JSTests/wasm/modules/v128-import.wat b/JSTests/wasm/modules/v128-import.wat new file mode 100644 index 000000000000..5f353c2e28b8 --- /dev/null +++ b/JSTests/wasm/modules/v128-import.wat @@ -0,0 +1,4 @@ +(module + (import "./v128-global.wasm" "v" (global v128)) + (func (export "ok") (result i32) + i32.const 1)) diff --git a/JSTests/wasm/stress/bbq-mul-wide-register-pressure.js b/JSTests/wasm/stress/bbq-mul-wide-register-pressure.js new file mode 100644 index 000000000000..3f534ef120fb --- /dev/null +++ b/JSTests/wasm/stress/bbq-mul-wide-register-pressure.js @@ -0,0 +1,77 @@ +//@ requireOptions("--useWasmWideArithmetic=1", "--useBBQJIT=1", "--useOMGJIT=0", "--thresholdForBBQOptimizeAfterWarmUp=0", "--thresholdForBBQOptimizeSoon=0") +import * as assert from '../assert.js'; + +// Test that i64.mul_wide_u and i64.mul_wide_s produce correct results in the +// BBQ JIT under register pressure. On x86_64, the mul instruction produces +// results in rdx:rax. If the register allocator assigns resultLo to rdx and +// resultHi to rax, the move sequence must not destroy the low half. +// This function sets 6 live locals before mul_wide to force the allocator +// toward using eax/edx for the results. + +const bytes = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, + + // type section: (i64, i64) -> (i64, i64) + 0x01, 0x08, 0x01, + 0x60, 0x02, 0x7e, 0x7e, 0x02, 0x7e, 0x7e, + + // function section + 0x03, 0x02, 0x01, 0x00, + + // export section: "mul_wide_u_pressure" -> func 0 + 0x07, 0x17, 0x01, + 0x13, 0x6d, 0x75, 0x6c, 0x5f, 0x77, 0x69, 0x64, 0x65, 0x5f, 0x75, 0x5f, 0x70, 0x72, 0x65, 0x73, 0x73, 0x75, 0x72, 0x65, + 0x00, 0x00, + + // code section + 0x0a, 0x48, 0x01, + 0x46, // body size = 70 + 0x01, 0x06, 0x7e, // 1 local decl group: 6 i64 locals (indices 2-7) + 0x20, 0x00, 0x42, 0x01, 0x7c, 0x21, 0x02, // l2 = a + 1 + 0x20, 0x00, 0x42, 0x02, 0x7c, 0x21, 0x03, // l3 = a + 2 + 0x20, 0x00, 0x42, 0x03, 0x7c, 0x21, 0x04, // l4 = a + 3 + 0x20, 0x01, 0x42, 0x04, 0x7c, 0x21, 0x05, // l5 = b + 4 + 0x20, 0x01, 0x42, 0x05, 0x7c, 0x21, 0x06, // l6 = b + 5 + 0x20, 0x01, 0x42, 0x06, 0x7c, 0x21, 0x07, // l7 = b + 6 + 0x20, 0x00, // local.get a + 0x20, 0x01, // local.get b + 0xfc, 0x16, // i64.mul_wide_u + 0x20, 0x02, 0x20, 0x03, 0x7c, 0x1a, // drop(l2 + l3) + 0x20, 0x04, 0x20, 0x05, 0x7c, 0x1a, // drop(l4 + l5) + 0x20, 0x06, 0x20, 0x07, 0x7c, 0x1a, // drop(l6 + l7) + 0x0b, +]); + +const module = new WebAssembly.Module(bytes); +const instance = new WebAssembly.Instance(module); +const mul_wide_u_pressure = instance.exports["mul_wide_u_pressure"]; + +// Cases where the low and high halves differ, so a swap would be detected. +function computeExpected(a, b) { + const ua = BigInt.asUintN(64, a); + const ub = BigInt.asUintN(64, b); + const product = ua * ub; + return [product & 0xFFFFFFFFFFFFFFFFn, (product >> 64n) & 0xFFFFFFFFFFFFFFFFn]; +} + +const cases = [ + [0xFFFFFFFFFFFFFFFFn, 0xFFFFFFFFFFFFFFFFn], + [0x8000000000000000n, 0x8000000000000000n], + [0x7FFFFFFFFFFFFFFFn, 0x7FFFFFFFFFFFFFFFn], + [1234567890123456789n, 9876543210987654321n], + [1n, 1n], + [0n, 0n], + [-1n, -1n], + [-1n, 1n], + [42n, 27n], + [0xDEADBEEFCAFEBABEn, 0x0123456789ABCDEFn], +]; + +for (let i = 0; i < wasmTestLoopCount; ++i) { + for (const [a, b] of cases) { + const r = mul_wide_u_pressure(a, b); + const [expectedLo, expectedHi] = computeExpected(a, b); + assert.eq(BigInt.asUintN(64, r[0]), expectedLo); + assert.eq(BigInt.asUintN(64, r[1]), expectedHi); + } +} diff --git a/JSTests/wasm/stress/consistent-compile-error-function-index.js b/JSTests/wasm/stress/consistent-compile-error-function-index.js new file mode 100644 index 000000000000..0ce949bdb0fc --- /dev/null +++ b/JSTests/wasm/stress/consistent-compile-error-function-index.js @@ -0,0 +1,75 @@ +// Concurrent function validation should report the CompileError for the lowest +// failing function index, not whichever worker finishes first. +// https://bugs.webkit.org/show_bug.cgi?id=283476 +//@ requireOptions("--useConcurrentJIT=true") + +import * as assert from "../assert.js"; + +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]; +} + +// Many invalid functions. Function 0 is large (slow to validate) so a higher-index +// worker is likely to finish first under concurrent compilation. The reported +// error must still be for function 0. +function invalidModuleBytes() { + const types = [0x01, 0x60, 0x00, 0x00]; + const functionCount = 16; + const functions = [functionCount, ...Array(functionCount).fill(0x00)]; + + // Function 0: many nops then empty-stack i32.add (fails late). + const body0 = [0x00]; + for (let i = 0; i < 8000; ++i) + body0.push(0x01); // nop + body0.push(0x6a, 0x0b); // i32.add end + + // Functions 1..N: immediate empty-stack f32.add (fails quickly). + const bodyFast = [0x00, 0x92, 0x0b]; + + const codePayload = [functionCount, ...leb(body0.length), ...body0]; + for (let i = 1; i < functionCount; ++i) + codePayload.push(...leb(bodyFast.length), ...bodyFast); + + return new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, + ...section(1, types), + ...section(3, functions), + ...section(10, codePayload), + ]); +} + +const bytes = invalidModuleBytes(); +const iterations = 80; + +for (let i = 0; i < iterations; ++i) { + let message; + try { + new WebAssembly.Module(bytes); + throw new Error("expected CompileError"); + } catch (error) { + assert.truthy(error instanceof WebAssembly.CompileError, `expected CompileError, got ${error}`); + message = String(error); + } + assert.truthy( + message.includes("function at index 0"), + `iteration ${i}: expected error for function 0, got: ${message}` + ); + for (let j = 1; j < 16; ++j) { + assert.truthy( + !message.includes(`function at index ${j}`), + `iteration ${i}: should not report function ${j} when function 0 also fails: ${message}` + ); + } +} diff --git a/JSTests/wasm/stress/js-callee-stack-overflow.js b/JSTests/wasm/stress/js-callee-stack-overflow.js new file mode 100644 index 000000000000..54f2602ff57f --- /dev/null +++ b/JSTests/wasm/stress/js-callee-stack-overflow.js @@ -0,0 +1,56 @@ +//@ requireOptions("--maxPerThreadStackUsage=524288") + +import Builder from "../Builder.js"; + +const builder = (new Builder()) + .Type().End() + .Import().Function("env", "reenter", { params: [], ret: "void" }).End() + .Function().End() + .Export() + .Function("run") + .End() + .Code() + .Function("run", { params: [], ret: "void" }) + .Call(0) + .End() + .End(); + +let instance; +function reenter() +{ + instance.exports.run(); +} + +instance = new WebAssembly.Instance(new WebAssembly.Module(builder.WebAssembly().get()), { env: { reenter } }); + +try { + instance.exports.run(); +} catch (e) { + if (!(e instanceof RangeError)) + throw e; +} + +const identity = (new Builder()) + .Type().End() + .Function().End() + .Export() + .Function("f") + .End() + .Code() + .Function("f", { params: ["f64"], ret: "f64" }) + .GetLocal(0) + .End() + .End(); + +const numberInstance = new WebAssembly.Instance(new WebAssembly.Module(identity.WebAssembly().get())); +const rec = { + valueOf() { + return numberInstance.exports.f(rec); + } +}; +try { + numberInstance.exports.f(rec); +} catch (e) { + if (!(e instanceof RangeError)) + throw e; +} diff --git a/JSTests/wasm/stress/licm-trapping-load-in-try.js b/JSTests/wasm/stress/licm-trapping-load-in-try.js new file mode 100644 index 000000000000..1cb52dacbc51 --- /dev/null +++ b/JSTests/wasm/stress/licm-trapping-load-in-try.js @@ -0,0 +1,137 @@ +import { instantiate } from "../wabt-wrapper.js"; +import * as assert from "../assert.js"; + +// LICM may hoist a trapping load out of a loop, but only to a point where the load was going to +// run anyway and where nothing observable happens first. These check the cases where hoisting +// would be wrong. + +// The load is guarded by a condition that is never true, so it must never run. Hoisting it to the +// pre-header unconditionally would turn a clean return into a trap. +let watNeverTaken = ` +(module + (memory 1) + (func (export "test") (param $iterations i32) (param $addr i32) (result i32) + (local $i i32) + (loop $loop + local.get $i + i32.const -1 + i32.eq + if + local.get $addr + i32.load + drop + end + local.get $i + i32.const 1 + i32.add + local.set $i + local.get $i + local.get $iterations + i32.lt_s + br_if $loop + ) + i32.const 42 + ) +) +`; + +// A wasm trap tears down to the entry frame, so stores the loop already made stay visible to the +// embedder. The load may only trap after the iteration that stored, never before. +let watStoresStayVisible = ` +(module + (memory (export "mem") 1) + (func (export "test") (param $trapAt i32) (param $addr i32) + (local $i i32) + (loop $loop + i32.const 0 + i32.const 0 + i32.load + i32.const 1 + i32.add + i32.store + + local.get $i + local.get $trapAt + i32.eq + if + local.get $addr + i32.load + drop + end + + local.get $i + i32.const 1 + i32.add + local.set $i + local.get $i + local.get $trapAt + i32.le_s + br_if $loop + ) + ) +) +`; + +// A loop-invariant load sharing a try with a call that throws every iteration. The catch edge is a +// side exit inside the loop, which is the case the pass used to reject outright. +let watLoadInTry = ` +(module + (memory 1) + (tag $e) + (func $thrower (throw $e)) + (func (export "test") (param $iterations i32) (param $addr i32) (result i32) + (local $i i32) + (local $caught i32) + (loop $loop + try + local.get $addr + i32.load + drop + call $thrower + catch_all + local.get $caught + i32.const 1 + i32.add + local.set $caught + end + local.get $i + i32.const 1 + i32.add + local.set $i + local.get $i + local.get $iterations + i32.lt_s + br_if $loop + ) + local.get $caught + ) +) +`; + +const iterations = 2000; +const unmappedAddress = 0xFFFFFF0; + +async function test() { + { + const { test } = (await instantiate(watNeverTaken, {}, {})).exports; + for (let i = 0; i < 10; ++i) + assert.eq(test(iterations, unmappedAddress), 42); + } + + { + const instance = await instantiate(watStoresStayVisible, {}, {}); + const { test, mem } = instance.exports; + const view = new Int32Array(mem.buffer); + const trapAt = 500; + assert.throws(() => test(trapAt, unmappedAddress), WebAssembly.RuntimeError, "Out of bounds memory access"); + assert.eq(view[0], trapAt + 1); + } + + { + const { test } = (await instantiate(watLoadInTry, {}, { exceptions: true })).exports; + for (let i = 0; i < 10; ++i) + assert.eq(test(iterations, 0), iterations); + } +} + +await assert.asyncTest(test()); diff --git a/JSTests/wasm/stress/memory64-multi-memory-rejected.js b/JSTests/wasm/stress/memory64-multi-memory-rejected.js deleted file mode 100644 index 7d9cb1d52c4d..000000000000 --- a/JSTests/wasm/stress/memory64-multi-memory-rejected.js +++ /dev/null @@ -1,30 +0,0 @@ -//@ 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-multi-memory.js b/JSTests/wasm/stress/memory64-multi-memory.js new file mode 100644 index 000000000000..51556e1df856 --- /dev/null +++ b/JSTests/wasm/stress/memory64-multi-memory.js @@ -0,0 +1,218 @@ +//@ requireOptions("--useWasmMultiMemory=1", "--useWasmMemory64=1") +//@ skip if $addressBits <= 32 + +import * as assert from "../assert.js"; +import { instantiate } from "../wabt-wrapper.js"; + +// A module may mix memory32 and memory64. The address width of an access follows the memory it +// targets, so a module whose memory 0 is 64-bit must still narrow i32 addresses for its memory32s, +// and vice versa. + +async function test(wat, fn, imports = {}, options = {}) { + const instance = await instantiate(wat, imports, { multi_memory: true, memory64: true, ...options }); + fn(instance.exports); +} + +// Memory 0 is 64-bit, memory 1 is 32-bit. +await test(` +(module + (memory i64 1) + (memory 1) + (func (export "store64") (param i64 i32) (local.get 0) (local.get 1) (i32.store 0)) + (func (export "load64") (param i64) (result i32) (local.get 0) (i32.load 0)) + (func (export "store32") (param i32 i32) (local.get 0) (local.get 1) (i32.store 1)) + (func (export "load32") (param i32) (result i32) (local.get 0) (i32.load 1)) +)`, (e) => { + e.store64(8n, 0xcafe); + e.store32(8, 0xbeef); + assert.eq(e.load64(8n), 0xcafe); + assert.eq(e.load32(8), 0xbeef); +}); + +// Memory 0 is 32-bit, memory 1 is 64-bit. A memory64 address must keep its full width even when +// memory 0 is 32-bit, rather than being truncated to the width of memory 0. +await test(` +(module + (memory 1) + (memory i64 1) + (func (export "store32") (param i32 i32) (local.get 0) (local.get 1) (i32.store 0)) + (func (export "load32") (param i32) (result i32) (local.get 0) (i32.load 0)) + (func (export "store64") (param i64 i32) (local.get 0) (local.get 1) (i32.store 1)) + (func (export "load64") (param i64) (result i32) (local.get 0) (i32.load 1)) +)`, (e) => { + e.store32(16, 0x1234); + e.store64(16n, 0x5678); + assert.eq(e.load32(16), 0x1234); + assert.eq(e.load64(16n), 0x5678); +}); + +// An i32 address on the IPInt stack may hold garbage in its upper half, because i32.wrap_i64 is a +// no-op there. Accessing a memory32 in a module whose memory 0 is memory64 must still ignore those +// bits rather than folding them into the address. +await test(` +(module + (memory i64 1) + (memory 1) + (func (export "storeWrapped") (param i64 i32) + (i32.wrap_i64 (local.get 0)) (local.get 1) (i32.store 1)) + (func (export "loadWrapped") (param i64) (result i32) + (i32.wrap_i64 (local.get 0)) (i32.load 1)) + ;; Force the multi-memory slow path with a large offset as well. + (func (export "storeWrappedOffset") (param i64 i32) + (i32.wrap_i64 (local.get 0)) (local.get 1) (i32.store 1 offset=256)) + (func (export "loadWrappedOffset") (param i64) (result i32) + (i32.wrap_i64 (local.get 0)) (i32.load 1 offset=256)) +)`, (e) => { + for (const garbage of [0n, 1n, 0xffffffffn, 0x123456789n]) { + const addr = (garbage << 32n) | 32n; + e.storeWrapped(addr, 0xaaaa); + assert.eq(e.loadWrapped(addr), 0xaaaa); + e.storeWrappedOffset(addr, 0xbbbb); + assert.eq(e.loadWrappedOffset(addr), 0xbbbb); + } + // The two accesses target distinct addresses, so neither clobbered the other. + assert.eq(e.loadWrapped((0x123456789n << 32n) | 32n), 0xaaaa); +}); + +// Bulk memory across mixed widths. +await test(` +(module + (memory i64 1) + (memory 1) + (data "wxyz") + (func (export "init64") (i64.const 0) (i32.const 0) (i32.const 4) (memory.init 0 0)) + (func (export "init32") (i32.const 0) (i32.const 0) (i32.const 4) (memory.init 1 0)) + (func (export "copyTo32") (i32.const 64) (i64.const 0) (i32.const 4) (memory.copy 1 0)) + (func (export "copyTo64") (i64.const 64) (i32.const 0) (i32.const 4) (memory.copy 0 1)) + (func (export "fill64") (i64.const 128) (i32.const 7) (i64.const 4) (memory.fill 0)) + (func (export "fill32") (i32.const 128) (i32.const 9) (i32.const 4) (memory.fill 1)) + (func (export "load64") (param i64) (result i32) (local.get 0) (i32.load 0)) + (func (export "load32") (param i32) (result i32) (local.get 0) (i32.load 1)) +)`, (e) => { + e.init64(); + e.init32(); + const wxyz = 0x7a797877; + assert.eq(e.load64(0n), wxyz); + assert.eq(e.load32(0), wxyz); + + e.copyTo32(); + assert.eq(e.load32(64), wxyz); + e.copyTo64(); + assert.eq(e.load64(64n), wxyz); + + e.fill64(); + e.fill32(); + assert.eq(e.load64(128n), 0x07070707); + assert.eq(e.load32(128), 0x09090909); +}); + +// memory.size and memory.grow keep their per-memory result types. +await test(` +(module + (memory i64 1) + (memory 1) + (func (export "size64") (result i64) (memory.size 0)) + (func (export "size32") (result i32) (memory.size 1)) + (func (export "grow64") (param i64) (result i64) (local.get 0) (memory.grow 0)) + (func (export "grow32") (param i32) (result i32) (local.get 0) (memory.grow 1)) +)`, (e) => { + assert.eq(e.size64(), 1n); + assert.eq(e.size32(), 1); + assert.eq(e.grow64(1n), 1n); + assert.eq(e.grow32(2), 1); + assert.eq(e.size64(), 2n); + assert.eq(e.size32(), 3); +}); + +// Out-of-bounds still traps on the correct memory, and a memory64 address above 4GiB is not +// silently truncated into a valid memory32 offset. +await test(` +(module + (memory 1) + (memory i64 1) + (func (export "load32") (param i32) (result i32) (local.get 0) (i32.load 0)) + (func (export "load64") (param i64) (result i32) (local.get 0) (i32.load 1)) +)`, (e) => { + assert.throws(() => e.load32(0x10000), WebAssembly.RuntimeError, "Out of bounds memory access"); + assert.throws(() => e.load64(0x10000n), WebAssembly.RuntimeError, "Out of bounds memory access"); + // 4GiB exactly: truncating to 32 bits would make this address 0, which is in bounds. + assert.throws(() => e.load64(0x100000000n), WebAssembly.RuntimeError, "Out of bounds memory access"); +}); + +// Imported memories of mixed widths, and more than two memories. +await test(` +(module + (import "m" "a" (memory i64 1)) + (import "m" "b" (memory 1)) + (memory i64 1) + (memory 1) + (func (export "store") (param i64 i32) + (local.get 0) (local.get 1) (i32.store 0) + (i32.wrap_i64 (local.get 0)) (local.get 1) (i32.store 1) + (local.get 0) (local.get 1) (i32.store 2) + (i32.wrap_i64 (local.get 0)) (local.get 1) (i32.store 3)) + (func (export "check") (param i64) (result i32) + (i32.and + (i32.and + (i32.load 0 (local.get 0)) + (i32.load 1 (i32.wrap_i64 (local.get 0)))) + (i32.and + (i32.load 2 (local.get 0)) + (i32.load 3 (i32.wrap_i64 (local.get 0)))))) +)`, (e) => { + e.store(24n, 0x3333); + assert.eq(e.check(24n), 0x3333); +}, { + m: { + a: new WebAssembly.Memory({ initial: 1n, address: "i64" }), + b: new WebAssembly.Memory({ initial: 1 }), + }, +}); + +// Atomics resolve the address width per memory too, including the wait/notify paths. +await test(` +(module + (memory i64 1 1 shared) + (memory 1 1 shared) + (func (export "add64") (param i64 i32) (result i32) (local.get 0) (local.get 1) (i32.atomic.rmw.add 0)) + (func (export "add32") (param i32 i32) (result i32) (local.get 0) (local.get 1) (i32.atomic.rmw.add 1)) + (func (export "load64") (param i64) (result i32) (local.get 0) (i32.atomic.load 0)) + (func (export "load32") (param i32) (result i32) (local.get 0) (i32.atomic.load 1)) + (func (export "notify64") (param i64) (result i32) (local.get 0) (i32.const 1) (memory.atomic.notify 0)) + (func (export "wait64") (param i64) (result i32) + (local.get 0) (i32.const 999) (i64.const 0) (memory.atomic.wait32 0)) + (func (export "wait32") (param i32) (result i32) + (local.get 0) (i32.const 999) (i64.const 0) (memory.atomic.wait32 1)) +)`, (e) => { + e.add64(8n, 5); + e.add32(8, 7); + assert.eq(e.load64(8n), 5); + assert.eq(e.load32(8), 7); + e.notify64(8n); + // The stored value does not match, so the wait reports "not-equal" rather than blocking. A + // wrong address width would read a different memory and change that answer. + assert.eq(e.wait64(8n), 1); + assert.eq(e.wait32(8), 1); + assert.throws(() => e.wait64(0x100000000n), WebAssembly.RuntimeError, "Out of bounds memory access"); +}, {}, { threads: true }); + +// Memories past index 63 fall in the second word of the address-width bitmap. +{ + let memories = ""; + for (let i = 0; i < 70; ++i) + memories += (i % 2) ? "(memory i64 1)" : "(memory 1)"; + await test(` +(module + ${memories} + (func (export "store64") (param i64 i32) (local.get 0) (local.get 1) (i32.store 65)) + (func (export "load64") (param i64) (result i32) (local.get 0) (i32.load 65)) + (func (export "store32") (param i32 i32) (local.get 0) (local.get 1) (i32.store 66)) + (func (export "load32") (param i32) (result i32) (local.get 0) (i32.load 66)) +)`, (e) => { + e.store64(8n, 0x1111); + e.store32(8, 0x2222); + assert.eq(e.load64(8n), 0x1111); + assert.eq(e.load32(8), 0x2222); + assert.throws(() => e.load64(0x100000000n), WebAssembly.RuntimeError, "Out of bounds memory access"); + }); +} diff --git a/JSTests/wasm/stress/omg-reduce-strength-select-exception-stackmap.js b/JSTests/wasm/stress/omg-reduce-strength-select-exception-stackmap.js new file mode 100644 index 000000000000..23211b1e2648 --- /dev/null +++ b/JSTests/wasm/stress/omg-reduce-strength-select-exception-stackmap.js @@ -0,0 +1,151 @@ +// The module is hand-assembled as raw bytes rather than written in WAT because +// the current in-tree WAT assemblers do not support both GC and exceptions. + +// --- WebAssembly encoding helpers ------------------------------------------------ + +// Unsigned LEB128. +function u32(value) +{ + const result = []; + do { + let byte = value & 0x7f; + value >>>= 7; + if (value) + byte |= 0x80; + result.push(byte); + } while (value); + return result; +} + +// A name/vec(byte): length-prefixed UTF-8 bytes. +function str(value) +{ + const result = []; + for (let i = 0; i < value.length; ++i) + result.push(value.charCodeAt(i)); + return [...u32(result.length), ...result]; +} + +// section(id, payload) = id, size(payload), payload. +function section(id, payload) +{ + return [id, ...u32(payload.length), ...payload]; +} + +// A function body: vec(locals), code, 0x0b(end-of-function). `localGroups` is a +// list of [count, valtype] pairs; `code` is the instruction stream. +function body(localGroups, code) +{ + const payload = [...u32(localGroups.length)]; + for (const [count, type] of localGroups) + payload.push(...u32(count), type); + payload.push(...code, 0x0b); // 0x0b = end (of function) + return [...u32(payload.length), ...payload]; +} + +const localGet = (index) => [0x20, ...u32(index)]; // 0x20 = local.get +const localSet = (index) => [0x21, ...u32(index)]; // 0x21 = local.set +const bitsCount = 24; + +// valtype bytes: 0x7f = i32, 0x7e = i64, 0x6f = externref. Section ids: 1 type, +// 2 import, 3 function, 7 export, 10 code, 13 tag. + +function makeModule() +{ + // --- Type section (id 1): four function types ------------------------------ + const helperParams = [...new Array(bitsCount).fill(0x7e), 0x6f]; // 24 x i64, then externref + const typeSection = [ + ...u32(4), // 4 types + 0x60, 0x00, 0x01, 0x7f, // type 0: () -> i32 ($thrower) + 0x60, ...u32(helperParams.length), ...helperParams, 0x01, 0x7f, // type 1: (i64 x24, externref) -> i32 ($helper) + 0x60, 0x01, 0x7f, 0x01, 0x6f, // type 2: (i32) -> externref ($target) + 0x60, 0x00, 0x00, // type 3: () -> () (tag type) + ]; + + // --- Import section (id 2): one function + 25 mutable globals --------------- + const imports = [ + [...str("m"), ...str("thrower"), 0x00, ...u32(0)], // func "m"."thrower" : type 0 + [...str("m"), ...str("object"), 0x03, 0x6f, 0x01], // global "m"."object" : externref, mutable + ]; + for (let index = 0; index < bitsCount; ++index) + imports.push([...str("m"), ...str(`bits${index}`), 0x03, 0x7e, 0x01]); // global "m"."bitsN" : i64, mutable + const importSection = [...u32(imports.length), ...imports.flat()]; + + // --- Function section (id 3): the two defined functions --------------------- + // Imported funcs occupy index 0 ($thrower); defined funcs follow: + // func 1 = $helper (type 1), func 2 = $target (type 2). + const functionSection = [...u32(2), ...u32(1), ...u32(2)]; + + // --- Tag section (id 13): one exception tag of type 3 ----------------------- + const tagSection = [...u32(1), 0x00, ...u32(3)]; // 1 tag, attribute 0 (exception), type 3 + + // --- Export section (id 7): export $target ---------------------------------- + const exportSection = [...u32(1), ...str("target"), 0x00, ...u32(2)]; // "target" = func 2 + + // --- Code section (id 10): bodies for $helper and $target ------------------- + + // $helper: padding nops keep it above the inlining threshold, then it calls the + // imported JS thrower (which always throws). + const helperBody = body([], [ + ...new Array(600).fill(0x01), // nop x600 (0x01 = nop) + 0x10, 0x00, // call 0 ($thrower) + ]); + + // $target: the trigger. One externref local ($live = local 1; local 0 = param). + const targetBody = body([[1, 0x6f]], [ + 0x06, 0x6f, // try (result externref) + ...new Array(bitsCount).fill(0).flatMap((_, index) => [0x23, ...u32(1 + index)]), // global.get $bits0 .. $bits23 (globals 1..24) + 0xd0, 0x6f, // ref.null extern (0xd0 ref.null, 0x6f extern; constant select arm) + 0x23, 0x00, // global.get 0 ($object, the other select arm) + ...localGet(0), // local.get 0 ($predicate, the select condition) + 0x1c, 0x01, 0x6f, // select (result externref) (0x1c typed-select, 1 result type, externref) + ...localSet(1), // local.set 1 ($live = select result) + ...localGet(1), // local.get 1 ($live, passed as the call's externref arg) + 0x10, 0x01, // call 1 ($helper; exception-capable call cloned by specializeSelect) + 0x1a, // drop (discard $helper's i32 result) + ...localGet(1), // local.get 1 ($live) + 0xd4, // ref.as_non_null (0xd4; the Check whose Select specializeSelect rewrites) + 0x1a, // drop + 0xd0, 0x6f, // ref.null extern (normal-path try result; unreachable, $helper always throws) + 0x19, // catch_all (0x19) + ...localGet(1), // local.get 1 ($live -> restored externref, the function result) + 0x0b, // end (of try) + ]); + + const codeSection = [...u32(2), ...helperBody, ...targetBody]; // 2 function bodies + + return new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, // "\0asm", version 1 + ...section(1, typeSection), + ...section(2, importSection), + ...section(3, functionSection), + ...section(13, tagSection), + ...section(7, exportSection), + ...section(10, codeSection), + ]); +} + +// --- Imports: a throwing function, a marker externref, and 24 i64 sentinels ------ + +const marker = { marker: 0x1227 }; +const raw42 = 0xfffe00000000002an; +const imports = { + thrower() { + throw marker; + }, + object: new WebAssembly.Global({ value: "externref", mutable: true }, marker), +}; +for (let index = 0; index < bitsCount; ++index) { + imports[`bits${index}`] = new WebAssembly.Global( + { value: "i64", mutable: true }, + BigInt.asIntN(64, raw42 + BigInt(index) * 0x100n)); +} + +const target = new WebAssembly.Instance( + new WebAssembly.Module(makeModule()), { m: imports }).exports.target; + +for (let iteration = 0; iteration < wasmTestLoopCount; ++iteration) { + const result = target(1); + if (result !== null) + throw new Error(`expected null catch restoration, got ${String(result)} at iteration ${iteration}`); +} diff --git a/JSTests/wasm/stress/ref-cast-null-without-fault-signal-handler.js b/JSTests/wasm/stress/ref-cast-null-without-fault-signal-handler.js new file mode 100644 index 000000000000..cf13d5f9782a --- /dev/null +++ b/JSTests/wasm/stress/ref-cast-null-without-fault-signal-handler.js @@ -0,0 +1,29 @@ +//@ requireOptions("--useWasmFaultSignalHandler=false") + +// ref.cast of a null reference must trap even when the fault signal handler is +// unavailable. The JIT is allowed to skip the explicit null check only because the +// cast dereferences the reference and the handler turns the resulting fault into a +// trap; with no handler installed the check has to be emitted, or this crashes. +// +// (module +// (type $s (struct (field (mut i32)))) +// (func (export "f") (param (ref null $s)) (result (ref $s)) +// local.get 0 +// ref.cast (ref $s))) + +import * as assert from "../assert.js"; + +const bytes = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x0c, 0x02, 0x5f, 0x01, 0x7f, 0x01, 0x60, 0x01, 0x63, 0x00, 0x01, 0x64, 0x00, + 0x03, 0x02, 0x01, 0x01, + 0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00, + 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0xfb, 0x16, 0x00, 0x0b, +]); + +const instance = new WebAssembly.Instance(new WebAssembly.Module(bytes)); + +// The reported trap kind differs between tiers (CastFailure vs NullAccess), so only +// require that a trap is what comes out. +for (let i = 0; i < 2000; ++i) + assert.throws(() => instance.exports.f(null), WebAssembly.RuntimeError, ""); diff --git a/JSTests/wasm/stress/signaling-memory-large-offset-bounds-check.js b/JSTests/wasm/stress/signaling-memory-large-offset-bounds-check.js new file mode 100644 index 000000000000..1b4244b4e6d1 --- /dev/null +++ b/JSTests/wasm/stress/signaling-memory-large-offset-bounds-check.js @@ -0,0 +1,82 @@ +//@ requireOptions("--useWasmSIMD=1") +//@ skip if !$isSIMDPlatform + +// A memory32 access folds a 32-bit index and an unsigned 32-bit immediate offset in 64-bit +// arithmetic, so it can reach far above 4GiB. Signaling memories reserve 4GiB plus a redzone +// and omit the explicit bounds check for offsets the redzone can absorb, which is only sound +// while the whole access stays inside the reservation. Offsets here bracket the redzone size +// so that widening the set of accesses that skip the check turns into a missing trap rather +// than a read or write reaching whatever the next reservation holds. + +import { instantiate } from "../wabt-wrapper.js"; +import * as assert from "../assert.js"; + +const pageSize = 65536; +const iterations = 5; + +const accesses = [ + { size: 1, load: "i32.load8_u", store: "i32.store8", type: "i32", stored: "(i32.const 0xa5)", expected: 0xa5 }, + { size: 2, load: "i32.load16_u", store: "i32.store16", type: "i32", stored: "(i32.const 0xa5a5)", expected: 0xa5a5 }, + { size: 4, load: "i32.load", store: "i32.store", type: "i32", stored: "(i32.const 0x12345678)", expected: 0x12345678 }, + { size: 8, load: "i64.load", store: "i64.store", type: "i64", stored: "(i64.const 0x123456789abcdef0)", expected: 0x123456789abcdef0n }, + { size: 16, load: "v128.load", store: "v128.store", type: "i64", stored: "(v128.const i64x2 0x123456789abcdef0 0x123456789abcdef0)", expected: 0x123456789abcdef0n }, +]; + +// The redzone defaults to 128 pages, putting its end at 0x800000. The expectations below do +// not depend on that: an access is out of bounds exactly when its last byte leaves the memory. +const offsets = [0, 1, 0xffff, 0x10000, 0x7ffff1, 0x7ffff8, 0x7fffff, 0x800000, 0x800001, 0x1000000, 0x7fffffff, 0xffffffff]; +const indices = [0, 1, 0xffff, 0x10000, 0x7fffffff, 0xffffffff]; + +function loadExpression(access, offset) { + const load = `(${access.load} offset=${offset} (local.get 0))`; + return access.size === 16 ? `(i64x2.extract_lane 0 ${load})` : load; +} + +function instantiateForPages(pages) { + let functions = ""; + for (const access of accesses) { + for (const offset of offsets) { + functions += ` + (func (export "load${access.size}_${offset}") (param i32) (result ${access.type}) + ${loadExpression(access, offset)}) + (func (export "store${access.size}_${offset}") (param i32) + (${access.store} offset=${offset} (local.get 0) ${access.stored}))`; + } + } + return instantiate(`(module (memory ${pages}) ${functions})`, {}); +} + +async function testPages(pages) { + const instance = await instantiateForPages(pages); + const limit = pages * pageSize; + + for (const access of accesses) { + for (const offset of offsets) { + const load = instance.exports[`load${access.size}_${offset}`]; + const store = instance.exports[`store${access.size}_${offset}`]; + for (const index of indices) { + const argument = index | 0; + if (index + offset + access.size - 1 >= limit) { + for (let i = 0; i < iterations; ++i) { + assert.throws(() => load(argument), WebAssembly.RuntimeError, "Out of bounds memory access"); + assert.throws(() => store(argument), WebAssembly.RuntimeError, "Out of bounds memory access"); + } + } else { + for (let i = 0; i < iterations; ++i) { + store(argument); + assert.eq(load(argument), access.expected); + } + } + } + } + } +} + +async function test() { + // One page leaves every large offset out of bounds; 300 pages reaches past the default + // redzone, so the offsets that need an explicit check are also exercised in bounds. + await testPages(1); + await testPages(300); +} + +await assert.asyncTest(test()); diff --git a/JSTests/wasm/stress/table-oversized-initial-reflection.js b/JSTests/wasm/stress/table-oversized-initial-reflection.js index 06c2e87cd21c..8e4f513f9393 100644 --- a/JSTests/wasm/stress/table-oversized-initial-reflection.js +++ b/JSTests/wasm/stress/table-oversized-initial-reflection.js @@ -1,4 +1,5 @@ //@ skip if $addressBits <= 32 +//@ requireOptions("--useWasmJSTypes=true") import * as assert from "../assert.js"; // A table may declare a size larger than this implementation can create. That is a compile-time diff --git a/JSTests/wasm/stress/wasm-imported-string-constants-utf8.js b/JSTests/wasm/stress/wasm-imported-string-constants-utf8.js new file mode 100644 index 000000000000..cc3420295dc0 --- /dev/null +++ b/JSTests/wasm/stress/wasm-imported-string-constants-utf8.js @@ -0,0 +1,76 @@ +//@ skip if $addressBits <= 32 +//@ requireOptions("--useWasmJSStringBuiltins=true") + +import * as assert from '../assert.js'; + +// Import names are raw UTF-8 in the module while importedStringConstants arrives as a JS string, so +// the two match only when the JS string is compared in its UTF-8 encoding. + +function uleb(bytes, value) { + do { + const byte = value & 0x7f; + value >>>= 7; + bytes.push(value ? byte | 0x80 : byte); + } while (value); +} + +function name(bytes, utf8) { + uleb(bytes, utf8.length); + bytes.push(...utf8); +} + +// (module (import "hello" (global externref)) (export "g" (global 0))) +// An immutable externref global is the only import shape eligible to be a string constant. +function moduleWithGlobalImport(moduleName) { + const imports = [1]; + name(imports, moduleName); + name(imports, [0x68, 0x65, 0x6c, 0x6c, 0x6f]); // "hello" + imports.push(0x03, 0x6f, 0x00); + + const exports = [1]; + name(exports, [0x67]); // "g" + exports.push(0x03, 0x00); + + const bytes = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]; + bytes.push(0x02); + uleb(bytes, imports.length); + bytes.push(...imports); + bytes.push(0x07); + uleb(bytes, exports.length); + bytes.push(...exports); + return new Uint8Array(bytes); +} + +const cafeUTF8 = [0x63, 0x61, 0x66, 0xc3, 0xa9]; // "caf\u00e9" +const replacementUTF8 = [0xef, 0xbf, 0xbd]; // "\ufffd" + +function testMatch(moduleName, importedStringConstants) { + const module = new WebAssembly.Module(moduleWithGlobalImport(moduleName), { importedStringConstants }); + // The engine supplies a string constant itself, so it is hidden from imports() and needs no import object. + assert.eq(WebAssembly.Module.imports(module).length, 0); + assert.eq(new WebAssembly.Instance(module, {}).exports.g.value, "hello"); +} + +function testNoMatch(moduleName, importedStringConstants) { + const module = new WebAssembly.Module(moduleWithGlobalImport(moduleName), { importedStringConstants }); + assert.eq(WebAssembly.Module.imports(module).length, 1); + assert.throws(() => new WebAssembly.Instance(module, {}), TypeError, "must be an object"); +} + +testMatch(cafeUTF8, "caf\u00e9"); +testMatch(replacementUTF8, "\ufffd"); +testMatch([], ""); + +// A lone surrogate has no UTF-8 encoding, and import names are required to be well-formed UTF-8, so +// it can never name an import. +testNoMatch(replacementUTF8, "\ud800"); +testNoMatch(cafeUTF8, "caf\ud800"); + +testNoMatch(cafeUTF8, "cafe"); +testNoMatch(cafeUTF8, "caf\u00e9x"); + +// Builtin set names take the same encoding path, and none of these name a registered set. +for (const builtin of ["caf\u00e9", "\ud800", "\ufffd"]) { + const module = new WebAssembly.Module(moduleWithGlobalImport(cafeUTF8), { builtins: [builtin] }); + assert.eq(WebAssembly.Module.imports(module).length, 1); +} diff --git a/JSTests/wasm/wasm.json b/JSTests/wasm/wasm.json index 0f5a0f48f28c..6c4161b559a4 100644 --- a/JSTests/wasm/wasm.json +++ b/JSTests/wasm/wasm.json @@ -24,14 +24,16 @@ "arrayref": { "type": "varint7", "value": -22, "b3type": "B3::pointerType()", "width": 0 }, "ref": { "type": "varint7", "value": -28, "b3type": "B3::pointerType()", "width": 64 }, "ref_null": { "type": "varint7", "value": -29, "b3type": "B3::pointerType()", "width": 64 }, - "func": { "type": "varint7", "value": -32, "b3type": "B3::Void", "width": 0 }, - "struct": { "type": "varint7", "value": -33, "b3type": "B3::Void", "width": 0 }, - "array": { "type": "varint7", "value": -34, "b3type": "B3::Void", "width": 0 }, - "sub": { "type": "varint7", "value": -48, "b3type": "B3::Void", "width": 0 }, - "subfinal": { "type": "varint7", "value": -49, "b3type": "B3::Void", "width": 0 }, - "rec": { "type": "varint7", "value": -50, "b3type": "B3::Void", "width": 0 }, "void": { "type": "varint7", "value": -64, "b3type": "B3::Void", "width": 0 } }, + "defined_type": { + "func": { "type": "varint7", "value": -32 }, + "struct": { "type": "varint7", "value": -33 }, + "array": { "type": "varint7", "value": -34 }, + "sub": { "type": "varint7", "value": -48 }, + "subfinal": { "type": "varint7", "value": -49 }, + "rec": { "type": "varint7", "value": -50 } + }, "packed_type": { "i8": { "type": "varint7", "value": -8}, "i16": { "type": "varint7", "value": -9} diff --git a/JSTests/wasm/wpt/wpt-harness-post.js b/JSTests/wasm/wpt/wpt-harness-post.js index ed786bf37c98..83688b5a53b6 100644 --- a/JSTests/wasm/wpt/wpt-harness-post.js +++ b/JSTests/wasm/wpt/wpt-harness-post.js @@ -11,13 +11,19 @@ // is in place for the results; done() then releases the wait, and completion fires once the // promise/timer chain drains via deferredWorkTimer->runRunLoop() before exit. -// Title passed by the runner as a trailing `-- ` argument (the test's file name). The -// browser names an unnamed block-body test after the page title; the shell's ShellTestEnvironment -// hardcodes "Untitled"/"Untitled N" instead (that environment object is a closure local, so it -// cannot be patched from here). Remapping the "Untitled" prefix to the title reproduces the -// committed baseline. Single-line arrow tests derive their name from the function source in both +// Title passed by the runner as a trailing `-- <title> <baseURL>` argument pair. The browser names +// an unnamed block-body test after the page title; the shell's ShellTestEnvironment hardcodes +// "Untitled"/"Untitled N" instead (that environment object is a closure local, so it cannot be +// patched from here). Remapping the "Untitled" prefix to the title reproduces the committed +// baseline. Single-line arrow tests derive their name from the function source in both // environments, so they are unaffected. -const WPT_TITLE = (globalThis.arguments && globalThis.arguments.length) ? globalThis.arguments[0] : null; +const WPT_TITLE = (globalThis.arguments && globalThis.arguments.length > 0) ? globalThis.arguments[0] : null; + +// URL the WPT server serves this test's directory from. The WAST harness bakes a stack string into +// each assertion description, and a frame's location is the URL the browser loaded the script from +// but a bare relative load path in the shell. Prefixing each frame with this reproduces the URLs in +// the committed baselines. +const WPT_BASE_URL = (globalThis.arguments && globalThis.arguments.length > 1) ? globalThis.arguments[1] : null; function titledName(name) { if (WPT_TITLE && /^Untitled( \d+)?$/.test(name)) @@ -25,6 +31,14 @@ function titledName(name) { return name; } +// Rewrites the `name@path:line:column` frames of an assertion description so each path becomes the +// URL the WPT server would serve it from. +function urlifyStackFrames(message) { + if (!WPT_BASE_URL) + return message; + return message.replace(/@(?=[A-Za-z0-9_./-]+\.js:\d+:\d+)/g, () => "@" + WPT_BASE_URL); +} + function convertResult(status) { if (status == 0) return "PASS"; if (status == 1) return "FAIL"; @@ -55,7 +69,7 @@ add_completion_callback(function (tests, harness_status) { } // Append the message only when present; the committed baselines have no trailing // space after the name for passing subtests, and diff does not ignore trailing spaces. - out += convertResult(test.status) + " " + titledName(sanitize(test.name)) + (message ? " " + message : "") + "\n"; + out += convertResult(test.status) + " " + titledName(sanitize(test.name)) + (message ? " " + urlifyStackFrames(message) : "") + "\n"; } print(out); // print() appends the final newline, reproducing the trailing blank line. diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index 97abda7c284e..1805ed5f8449 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -80,6 +80,7 @@ http/tests/site-isolation/focus-navigation-cross-origin-iframe.html [ Skip ] http/tests/site-isolation/magnify-gesture-over-cross-origin-pdf-iframe.html [ Skip ] fast/viewport/ios [ Skip ] fast/visual-viewport/ios/ [ Skip ] +http/tests/visual-viewport/ios/ [ Skip ] fast/device-orientation [ Skip ] http/tests/device-orientation [ Skip ] fast/backgrounds/top-content-inset-fixed-attachment.html [ Skip ] @@ -865,7 +866,6 @@ imported/w3c/web-platform-tests/html/semantics/embedded-content/the-iframe-eleme imported/w3c/web-platform-tests/html/semantics/embedded-content/the-img-element/natural-size-orientation.html [ Skip ] imported/w3c/web-platform-tests/html/semantics/forms/the-button-element/button-submit-remove-children.html [ Skip ] webkit.org/b/250171 imported/w3c/web-platform-tests/html/semantics/popovers/popover-anchor-nested-display.tentative.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/html/semantics/popovers/popover-top-layer-nesting-hints.html [ Pass Failure ] imported/w3c/web-platform-tests/inert/inert-with-fullscreen-element.html [ Pass Failure ] imported/w3c/web-platform-tests/screen-orientation/active-lock.html [ Pass Failure ] imported/w3c/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/charset-2.html [ Skip ] @@ -1374,19 +1374,25 @@ imported/w3c/web-platform-tests/eventsource/dedicated-worker/eventsource-constru imported/w3c/web-platform-tests/mediacapture-fromelement/capture.html [ Failure ] imported/w3c/web-platform-tests/mediacapture-fromelement/ended.html [ Failure ] imported/w3c/web-platform-tests/mediacapture-fromelement/creation.html [ Pass Failure ] +imported/w3c/web-platform-tests/resource-timing/content-type.html [ Pass Failure ] +imported/w3c/web-platform-tests/resource-timing/connection-reuse.html [ Pass Failure ] +imported/w3c/web-platform-tests/resource-timing/render-blocking-status-link.html [ Pass Failure ] +imported/w3c/web-platform-tests/resource-timing/render-blocking-status-script.html [ Pass Failure ] +imported/w3c/web-platform-tests/resource-timing/iframe-sequence-of-events.html [ Pass Failure ] +imported/w3c/web-platform-tests/resource-timing/interim-response-times.html [ Skip ] # Timeout +imported/w3c/web-platform-tests/resource-timing/interim-response-times.h2.html [ Skip ] # Timeout +imported/w3c/web-platform-tests/resource-timing/initiator-type/link.html [ Skip ] # Timeout +imported/w3c/web-platform-tests/resource-timing/initiator-type/video.html [ Skip ] # Timeout +imported/w3c/web-platform-tests/resource-timing/initiator-type/picture.html [ Skip ] # Timeout +imported/w3c/web-platform-tests/resource-timing/content-encoding.https.html [ Skip ] # Timeout +imported/w3c/web-platform-tests/resource-timing/entries-for-network-errors.sub.https.html [ Skip ] # Timeout +imported/w3c/web-platform-tests/resource-timing/response-status-code.html [ Pass Failure ] +imported/w3c/web-platform-tests/resource-timing/nested-nav-fallback-timing.html [ Pass Failure ] imported/w3c/web-platform-tests/resource-timing/font-timestamps.html [ Failure ] imported/w3c/web-platform-tests/resource-timing/cross-origin-start-end-time-with-redirects.html [ Pass Failure ] imported/w3c/web-platform-tests/resource-timing/resource_timing_content_length.html [ Skip ] imported/w3c/web-platform-tests/resource-timing/SO-XO-SO-redirect-chain-tao.https.html [ Failure ] -webkit.org/b/180240 imported/w3c/web-platform-tests/resource-timing/single-entry-per-resource.html [ Pass Failure ] -webkit.org/b/189906 imported/w3c/web-platform-tests/resource-timing/resource_timing_buffer_full_eventually.html [ Skip ] -imported/w3c/web-platform-tests/resource-timing/resource-reload-TAO.sub.html [ Skip ] -webkit.org/b/189905 imported/w3c/web-platform-tests/resource-timing/resource_initiator_types.html [ Pass Failure ] -webkit.org/b/190523 imported/w3c/web-platform-tests/resource-timing/resource_timing_cross_origin_redirect_chain.html [ Pass Failure ] -imported/w3c/web-platform-tests/resource-timing/crossorigin-sandwich-no-TAO.sub.html [ Pass Failure ] imported/w3c/web-platform-tests/resource-timing/cors-preflight.any.html [ Failure Pass ] -imported/w3c/web-platform-tests/resource-timing/crossorigin-sandwich-TAO.sub.html [ Pass Failure ] -imported/w3c/web-platform-tests/resource-timing/crossorigin-sandwich-partial-TAO.sub.html [ Pass Failure ] imported/w3c/web-platform-tests/navigation-timing/secure-connection-start-non-zero.https.html [ Pass Failure ] imported/w3c/web-platform-tests/navigation-timing/nav2-test-attributes-values.html [ Pass Failure ] imported/w3c/web-platform-tests/resource-timing/TAO-match.html [ Pass Failure ] @@ -2349,7 +2355,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 +webkit.org/b/322327 imported/w3c/web-platform-tests/mathml/relations/html5-tree/href-navigation.html [ Skip ] +webkit.org/b/322327 imported/w3c/web-platform-tests/mathml/relations/html5-tree/a-rel-policy.html [ Skip ] +webkit.org/b/322327 imported/w3c/web-platform-tests/mathml/relations/html5-tree/href-target-base.html [ Skip ] imported/w3c/web-platform-tests/mathml/relations/text-and-math/non-mathml-children-in-annotation.tentative.html [ ImageOnlyFailure ] # This MathML test should be rewritten. @@ -2858,7 +2866,6 @@ webkit.org/b/283954 imported/w3c/web-platform-tests/svg/text/reftests/transform- [ Debug ] fast/loader/document-with-fragment-url-4.html [ Pass Timeout ] webkit.org/b/85902 [ Debug ] fast/overflow/lots-of-sibling-inline-boxes.html [ Slow ] -webkit.org/b/135053 [ Debug ] html5lib/webkit-resumer.html [ Slow ] [ Debug ] js/dfg-double-vote-fuzz.html [ Slow ] [ Debug ] js/array-sort-small-sparse-array-with-large-length.html [ Slow ] [ Debug ] js/dom/string-replacement-outofmemory.html [ Slow ] @@ -4110,9 +4117,6 @@ imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-007.html imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-008.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-009.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-011.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-016.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-017.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-018.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-019.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-021.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-030.html [ ImageOnlyFailure ] @@ -4183,7 +4187,6 @@ imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-floa imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-with-text-overflow-string-003.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-024.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-036.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-037.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-040.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-044.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-overflow/line-clamp/webkit-line-clamp-045.html [ ImageOnlyFailure ] @@ -4611,7 +4614,6 @@ imported/w3c/web-platform-tests/css/css-transitions/transitioncancel-003.html [ # The "Shared between elements within a property - shorthand" subtest compares two random() draws in # a margin shorthand for inequality and does not always get distinct ones. imported/w3c/web-platform-tests/css/css-values/random-computed.tentative.html [ Failure Pass ] -webkit.org/b/203320 imported/w3c/web-platform-tests/css/css-values/percentage-rem-low.html [ ImageOnlyFailure ] webkit.org/b/259025 imported/w3c/web-platform-tests/css/css-values/ic-unit-015.html [ ImageOnlyFailure ] webkit.org/b/277995 imported/w3c/web-platform-tests/css/css-values/calc-size/calc-size-aspect-ratio-001.html [ ImageOnlyFailure ] webkit.org/b/277995 imported/w3c/web-platform-tests/css/css-values/calc-size/calc-size-aspect-ratio-002.html [ ImageOnlyFailure ] @@ -5457,9 +5459,7 @@ imported/w3c/web-platform-tests/css/css-lists/list-style-type-decimal-vertical-l imported/w3c/web-platform-tests/css/css-lists/list-style-type-decimal-vertical-rl.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 ] webkit.org/b/202849 imported/w3c/web-platform-tests/css/css-lists/list-style-type-string-005b.html [ ImageOnlyFailure ] -webkit.org/b/202849 imported/w3c/web-platform-tests/css/css-lists/list-style-type-string-006.html [ ImageOnlyFailure ] webkit.org/b/249918 imported/w3c/web-platform-tests/css/css-lists/list-marker-symbol-bidi.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-lists/content-property/marker-text-matches-lower-greek.html [ ImageOnlyFailure ] @@ -5659,7 +5659,6 @@ webkit.org/b/204163 imported/w3c/web-platform-tests/css/css-pseudo/marker-conten webkit.org/b/204163 imported/w3c/web-platform-tests/css/css-pseudo/marker-font-variant-numeric-default.html [ ImageOnlyFailure ] webkit.org/b/204163 imported/w3c/web-platform-tests/css/css-pseudo/marker-font-variant-numeric-normal.html [ ImageOnlyFailure ] webkit.org/b/214461 imported/w3c/web-platform-tests/css/css-pseudo/marker-list-style-position.html [ ImageOnlyFailure ] -webkit.org/b/214461 imported/w3c/web-platform-tests/css/css-pseudo/marker-unicode-bidi-default.html [ ImageOnlyFailure ] webkit.org/b/214461 imported/w3c/web-platform-tests/css/css-pseudo/marker-unicode-bidi-normal.html [ ImageOnlyFailure ] webkit.org/b/214461 imported/w3c/web-platform-tests/css/css-pseudo/spelling-error-001.html [ ImageOnlyFailure ] @@ -6148,7 +6147,6 @@ imported/w3c/web-platform-tests/trusted-types/should-trusted-type-policy-creatio # Needs moveBefore support 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 ] # Flaky crash. webkit.org/b/315031 imported/w3c/web-platform-tests/dom/nodes/moveBefore/throws-exception.html [ Skip ] @@ -6478,7 +6476,6 @@ imported/w3c/web-platform-tests/resize-observer/svg.html [ Skip ] imported/w3c/web-platform-tests/resource-timing/entry-attributes.html [ Skip ] imported/w3c/web-platform-tests/resource-timing/object-not-found-after-TAO-cross-origin-redirect.html [ Skip ] imported/w3c/web-platform-tests/resource-timing/object-not-found-after-cross-origin-redirect.html [ Skip ] -imported/w3c/web-platform-tests/resource-timing/resource_timing_cross_origin_redirect.html [ Skip ] imported/w3c/web-platform-tests/service-workers/service-worker/fetch-audio-tainting.https.html [ Skip ] imported/w3c/web-platform-tests/service-workers/service-worker/unregister-immediately-before-installed.https.html [ Skip ] imported/w3c/web-platform-tests/service-workers/service-worker/unregister-immediately.https.html [ Skip ] @@ -6666,7 +6663,6 @@ 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/corner-shape/corner-shape-backdrop-filter-overflow.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-backdrop-filter-video-overflow.html [ ImageOnlyFailure Pass ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-backdrop-filter.html [ ImageOnlyFailure ] -webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/corner-shape-bevel-overflow-composite.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape/border-shape-clips-background.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape/border-shape-collapsed-shape-clips-background.html [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/border-shape/border-shape-geometry-box.html [ ImageOnlyFailure ] @@ -6688,7 +6684,6 @@ 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.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-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-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 ] @@ -6696,14 +6691,10 @@ webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/render-corner-shape.html?border-radius=5&corner-top-left-shape=0.5&corner-bottom-right-shape=-0.5&shadow-spread=10 [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/render-corner-shape.html?corner-top-left-shape=2.5&border-radius=20%&border-width=10 [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/render-corner-shape.html?corner-shape=0.8&border-radius=40&border-width=10 [ ImageOnlyFailure ] -webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/render-corner-shape.html?corner-shape=2.3&border-radius=40% [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/render-corner-shape.html?corner-bottom-right-shape=0.8&border-bottom-right-radius=50% [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/render-corner-shape.html?corner-top-left-shape=0.5&border-radius=40 [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/render-corner-shape.html?corner-top-left-shape=-0.5&border-radius=40 [ ImageOnlyFailure ] webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/render-corner-shape.html?border-top-left-radius=50%&corner-shape=0.7&border-left-width=30&border-top-width=30 [ ImageOnlyFailure ] -webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/render-corner-shape.html?corner-shape=3&border-top-right-radius=33 [ ImageOnlyFailure ] -webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/render-corner-shape.html?corner-shape=squircle&border-radius=50% [ ImageOnlyFailure ] -webkit.org/b/277912 imported/w3c/web-platform-tests/css/css-borders/corner-shape/render-corner-shape.html?corner-top-left-shape=-4&border-radius=40 [ ImageOnlyFailure ] webkit.org/b/316474 imported/w3c/web-platform-tests/css/css-borders/border-width-rounding.tentative.html [ Pass Failure ] @@ -8147,10 +8138,6 @@ imported/w3c/web-platform-tests/html/semantics/forms/the-input-element/input-tex imported/w3c/web-platform-tests/html/semantics/forms/the-select-element/filterable-select/filterable-select-appearance.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/html/semantics/forms/form-submission-target/form-target-blank-useractivation-multi-globals.html [ Skip ] # timeout -imported/w3c/web-platform-tests/css/selectors/media/media-loading-state.sub.html [ Skip ] # timeout, passes first subtest -imported/w3c/web-platform-tests/css/selectors/media/media-loading-state-timing.sub.html [ Skip ] # timeout -imported/w3c/web-platform-tests/css/selectors/invalidation/media-loading-pseudo-classes-in-has.sub.html [ Skip ] # timeout - imported/w3c/web-platform-tests/html/browsers/the-window-object/open-close/open_fires_resize.tentative.html [ Skip ] # timeout webkit.org/b/311207 imported/w3c/web-platform-tests/workers/semantics/structured-clone/shared.html [ Pass Failure ] @@ -8202,7 +8189,7 @@ imported/w3c/web-platform-tests/css/css-shapes/shape-outside/supported-shapes/sh webkit.org/b/317017 imported/w3c/web-platform-tests/largest-contentful-paint/multiple-redirects-TAO.html [ Pass Failure ] -# Compression Dictionary Transport is not implemented, so these time out waiting for a dictionary load that never happens. +# Compression Dictionary Transport is not fully implemented, so these fail waiting for a dictionary that is never registered. 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 ] @@ -8214,23 +8201,12 @@ webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-decompression.tentative.https.h2.html [ Skip ] webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-decompression.tentative.https.html [ Skip ] webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-no-cors.tentative.https.html [ Skip ] -webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-timing-001.tentative.https.html [ Skip ] -webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-timing-002.tentative.https.html [ Skip ] -webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-with-imagesrcset.tentative.https.html [ Skip ] -webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-with-link-connect-src-nonce.tentative.https.html [ Skip ] -webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-with-link-connect-src.tentative.https.html [ Skip ] webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-with-link-element-baseURL.tentative.https.html [ Skip ] -webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-with-link-element-crossorigin.tentative.https.html [ Skip ] -webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-with-link-element-events.tentative.https.html [ Skip ] webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-with-link-element-in-body.tentative.https.html [ Skip ] webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-with-link-element.tentative.https.html [ Skip ] webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-with-link-header.tentative.https.html [ Skip ] -webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-with-link-integrity.tentative.https.html [ Skip ] -webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-with-link-referrer-and-referrerpolicy.tentative.https.html [ Skip ] -webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-fetch-with-type-attribute.tentative.https.html [ Skip ] webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-match.tentative.https.html [ Skip ] webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/dictionary-registration.tentative.https.html [ Skip ] -webkit.org/b/295249 imported/w3c/web-platform-tests/fetch/compression-dictionary/fetch-destination.tentative.https.html [ Skip ] # Early Hints preload is not implemented, so these time out waiting for a preload that never happens. imported/w3c/web-platform-tests/loading/early-hints/preload-finished-before-final-response.h2.window.html [ Skip ] diff --git a/LayoutTests/accessibility-isolated-tree/TestExpectations b/LayoutTests/accessibility-isolated-tree/TestExpectations index bce4a77384c8..4265046910c3 100644 --- a/LayoutTests/accessibility-isolated-tree/TestExpectations +++ b/LayoutTests/accessibility-isolated-tree/TestExpectations @@ -59,7 +59,7 @@ accessibility/mac/text-marker-for-index.html [ Failure ] accessibility/mac/text-marker-p-tags.html [ Failure ] accessibility/mac/text-marker-string-for-document-end-replaced-node.html [ Failure ] accessibility/mac/text-marker-word-nav-collapsed-whitespace.html [ Failure ] -accessibility/mac/text-markers-for-input-with-placeholder.html [ Failure ] +accessibility/text-markers-for-input-with-placeholder.html [ Failure ] accessibility/mac/textmarker-range-for-range.html [ Failure ] accessibility/native-text-control-attributed-string.html [ Failure ] accessibility/text-marker/text-marker-previous-next.html [ Failure ] @@ -75,7 +75,7 @@ accessibility/mac/replaced-element-line-index-hang.html [ Failure ] accessibility/mac/html5-input-number.html [ Timeout ] # Fails because we don't get paint calls for SVG shapes and thus can't cache a path for them. -accessibility/mac/bezier-path-curves.html [ Failure ] +accessibility/bezier-path-curves.html [ Failure ] # Regressions from ENABLE(ACCESSIBILITY_LOCAL_FRAME). accessibility/mac/iframe-position-after-scroll.html [ Failure ] diff --git a/LayoutTests/accessibility/mac/aria-details-expected.txt b/LayoutTests/accessibility/aria-details-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/aria-details-expected.txt rename to LayoutTests/accessibility/aria-details-expected.txt diff --git a/LayoutTests/accessibility/mac/aria-details.html b/LayoutTests/accessibility/aria-details.html similarity index 96% rename from LayoutTests/accessibility/mac/aria-details.html rename to LayoutTests/accessibility/aria-details.html index c3bedd96cb01..47d87bdc6a78 100644 --- a/LayoutTests/accessibility/mac/aria-details.html +++ b/LayoutTests/accessibility/aria-details.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/aria-image-emits-object-replacement-expected.txt b/LayoutTests/accessibility/aria-image-emits-object-replacement-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/aria-image-emits-object-replacement-expected.txt rename to LayoutTests/accessibility/aria-image-emits-object-replacement-expected.txt diff --git a/LayoutTests/accessibility/mac/aria-image-emits-object-replacement.html b/LayoutTests/accessibility/aria-image-emits-object-replacement.html similarity index 96% rename from LayoutTests/accessibility/mac/aria-image-emits-object-replacement.html rename to LayoutTests/accessibility/aria-image-emits-object-replacement.html index addb13991a5f..b7a24819de90 100644 --- a/LayoutTests/accessibility/mac/aria-image-emits-object-replacement.html +++ b/LayoutTests/accessibility/aria-image-emits-object-replacement.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML> <html> <body> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> <div id="content" tabindex="0"> diff --git a/LayoutTests/accessibility/mac/bezier-path-curves-expected.txt b/LayoutTests/accessibility/bezier-path-curves-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/bezier-path-curves-expected.txt rename to LayoutTests/accessibility/bezier-path-curves-expected.txt diff --git a/LayoutTests/accessibility/mac/bezier-path-curves.html b/LayoutTests/accessibility/bezier-path-curves.html similarity index 90% rename from LayoutTests/accessibility/mac/bezier-path-curves.html rename to LayoutTests/accessibility/bezier-path-curves.html index 2828a75397db..aeebaa00e594 100644 --- a/LayoutTests/accessibility/mac/bezier-path-curves.html +++ b/LayoutTests/accessibility/bezier-path-curves.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/clipped-text-under-element-expected.txt b/LayoutTests/accessibility/clipped-text-under-element-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/clipped-text-under-element-expected.txt rename to LayoutTests/accessibility/clipped-text-under-element-expected.txt diff --git a/LayoutTests/accessibility/mac/clipped-text-under-element.html b/LayoutTests/accessibility/clipped-text-under-element.html similarity index 95% rename from LayoutTests/accessibility/mac/clipped-text-under-element.html rename to LayoutTests/accessibility/clipped-text-under-element.html index 5cfb286c416e..6803e9c564e0 100644 --- a/LayoutTests/accessibility/mac/clipped-text-under-element.html +++ b/LayoutTests/accessibility/clipped-text-under-element.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> </head> <body id="body"> diff --git a/LayoutTests/accessibility/combobox/mac/combobox-value-expected.txt b/LayoutTests/accessibility/combobox/combobox-value-expected.txt similarity index 100% rename from LayoutTests/accessibility/combobox/mac/combobox-value-expected.txt rename to LayoutTests/accessibility/combobox/combobox-value-expected.txt diff --git a/LayoutTests/accessibility/combobox/mac/combobox-value.html b/LayoutTests/accessibility/combobox/combobox-value.html similarity index 93% rename from LayoutTests/accessibility/combobox/mac/combobox-value.html rename to LayoutTests/accessibility/combobox/combobox-value.html index 5d8c4849fff8..e3a9a14167b9 100644 --- a/LayoutTests/accessibility/combobox/mac/combobox-value.html +++ b/LayoutTests/accessibility/combobox/combobox-value.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../../resources/js-test.js"></script> +<script src="../../resources/js-test.js"></script> </head> <body id="body"> diff --git a/LayoutTests/accessibility/mac/crash-in-element-for-text-marker-expected.txt b/LayoutTests/accessibility/crash-in-element-for-text-marker-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/crash-in-element-for-text-marker-expected.txt rename to LayoutTests/accessibility/crash-in-element-for-text-marker-expected.txt diff --git a/LayoutTests/accessibility/mac/crash-in-element-for-text-marker.html b/LayoutTests/accessibility/crash-in-element-for-text-marker.html similarity index 92% rename from LayoutTests/accessibility/mac/crash-in-element-for-text-marker.html rename to LayoutTests/accessibility/crash-in-element-for-text-marker.html index e416d6e261c6..f2bc7ba93fce 100644 --- a/LayoutTests/accessibility/mac/crash-in-element-for-text-marker.html +++ b/LayoutTests/accessibility/crash-in-element-for-text-marker.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body id="body" role="group"> diff --git a/LayoutTests/accessibility/mac/css-speech-speak-expected.txt b/LayoutTests/accessibility/css-speech-speak-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/css-speech-speak-expected.txt rename to LayoutTests/accessibility/css-speech-speak-expected.txt diff --git a/LayoutTests/accessibility/mac/css-speech-speak.html b/LayoutTests/accessibility/css-speech-speak.html similarity index 98% rename from LayoutTests/accessibility/mac/css-speech-speak.html rename to LayoutTests/accessibility/css-speech-speak.html index 482c8a92e02e..e087ebbd1401 100644 --- a/LayoutTests/accessibility/mac/css-speech-speak.html +++ b/LayoutTests/accessibility/css-speech-speak.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> <style> div.speech-normal { speak-as: normal; } div.speech-spellout { speak-as: spell-out; } diff --git a/LayoutTests/accessibility/mac/dynamic-modal-expected.txt b/LayoutTests/accessibility/dynamic-modal-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/dynamic-modal-expected.txt rename to LayoutTests/accessibility/dynamic-modal-expected.txt diff --git a/LayoutTests/accessibility/mac/dynamic-modal.html b/LayoutTests/accessibility/dynamic-modal.html similarity index 93% rename from LayoutTests/accessibility/mac/dynamic-modal.html rename to LayoutTests/accessibility/dynamic-modal.html index 921531934990..eb9b3e0d856a 100644 --- a/LayoutTests/accessibility/mac/dynamic-modal.html +++ b/LayoutTests/accessibility/dynamic-modal.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <style> *[role="dialog"] { border: 1px solid black; padding: 1em; margin: 1em; } diff --git a/LayoutTests/accessibility/mac/focus-crash-expected.txt b/LayoutTests/accessibility/focus-crash-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/focus-crash-expected.txt rename to LayoutTests/accessibility/focus-crash-expected.txt diff --git a/LayoutTests/accessibility/mac/focus-crash.html b/LayoutTests/accessibility/focus-crash.html similarity index 94% rename from LayoutTests/accessibility/mac/focus-crash.html rename to LayoutTests/accessibility/focus-crash.html index b90014b779d1..fa3817633b8e 100644 --- a/LayoutTests/accessibility/mac/focus-crash.html +++ b/LayoutTests/accessibility/focus-crash.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML> <html> <body> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> <input type="text" id="toBeRemoved" onfocus="focusHandler(this)"></input> diff --git a/LayoutTests/accessibility/mac/grid-add-remove-rows-expected.txt b/LayoutTests/accessibility/grid-add-remove-rows-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/grid-add-remove-rows-expected.txt rename to LayoutTests/accessibility/grid-add-remove-rows-expected.txt diff --git a/LayoutTests/accessibility/mac/grid-add-remove-rows.html b/LayoutTests/accessibility/grid-add-remove-rows.html similarity index 95% rename from LayoutTests/accessibility/mac/grid-add-remove-rows.html rename to LayoutTests/accessibility/grid-add-remove-rows.html index 361eb0a9cef0..09410637ee39 100644 --- a/LayoutTests/accessibility/mac/grid-add-remove-rows.html +++ b/LayoutTests/accessibility/grid-add-remove-rows.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/js-test.js"></script> -<script src="../../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/heading-clickpoint-expected.txt b/LayoutTests/accessibility/heading-clickpoint-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/heading-clickpoint-expected.txt rename to LayoutTests/accessibility/heading-clickpoint-expected.txt diff --git a/LayoutTests/accessibility/mac/heading-clickpoint.html b/LayoutTests/accessibility/heading-clickpoint.html similarity index 94% rename from LayoutTests/accessibility/mac/heading-clickpoint.html rename to LayoutTests/accessibility/heading-clickpoint.html index ebf8abbbde26..91bc2a6b17d7 100644 --- a/LayoutTests/accessibility/mac/heading-clickpoint.html +++ b/LayoutTests/accessibility/heading-clickpoint.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/js-test.js"></script> -<script src="../../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/index-for-zero-offset-text-marker-expected.txt b/LayoutTests/accessibility/index-for-zero-offset-text-marker-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/index-for-zero-offset-text-marker-expected.txt rename to LayoutTests/accessibility/index-for-zero-offset-text-marker-expected.txt diff --git a/LayoutTests/accessibility/mac/index-for-zero-offset-text-marker.html b/LayoutTests/accessibility/index-for-zero-offset-text-marker.html similarity index 93% rename from LayoutTests/accessibility/mac/index-for-zero-offset-text-marker.html rename to LayoutTests/accessibility/index-for-zero-offset-text-marker.html index 201ab14a98ba..43eb0fee0c00 100644 --- a/LayoutTests/accessibility/mac/index-for-zero-offset-text-marker.html +++ b/LayoutTests/accessibility/index-for-zero-offset-text-marker.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/input-type-change-crash-2-expected.txt b/LayoutTests/accessibility/input-type-change-crash-2-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/input-type-change-crash-2-expected.txt rename to LayoutTests/accessibility/input-type-change-crash-2-expected.txt diff --git a/LayoutTests/accessibility/mac/input-type-change-crash-2.html b/LayoutTests/accessibility/input-type-change-crash-2.html similarity index 100% rename from LayoutTests/accessibility/mac/input-type-change-crash-2.html rename to LayoutTests/accessibility/input-type-change-crash-2.html diff --git a/LayoutTests/accessibility/isolated-tree/aria-checkbox-sends-notification-expected.txt b/LayoutTests/accessibility/isolated-tree/aria-checkbox-sends-notification-expected.txt new file mode 100644 index 000000000000..62d4aadf77c7 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/aria-checkbox-sends-notification-expected.txt @@ -0,0 +1,11 @@ +FAIL: Timed out waiting for notifyDone to be called + +Test Checkbox +This tests that checking of an aria checkbox sends a notification. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +Got notification: AXValueChanged +Got notification: AXValueChanged + diff --git a/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-after-dynamic-edit-expected.txt b/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-after-dynamic-edit-expected.txt new file mode 100644 index 000000000000..46411e9f63dc --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-after-dynamic-edit-expected.txt @@ -0,0 +1,24 @@ +Asserts the line APIs for text control values edited after the accessibility tree was built: +the caret's line range, and, where a case names one, the last line's character range and the line +of the index one past the end of the value. + +trailing newline typed, caret on the blank final line: value "one two \n", caret at 9 +PASS: axField.numberOfCharacters === 9 +PASS: axField.stringForTextMarkerRange(lineRange) === "" +PASS: axField.textMarkerRangeLength(lineRange) === 0 +PASS: lineEnd.isEqual(caret) === true +PASS: axField.rangeForLine(lastLine) === "{9, 0}" +PASS: axField.lineForIndex(indexPastEnd) === -1 + +inserted through AXReplaceRangeWithText, caret at the end: value "one two \nthree", caret at 14 +PASS: axField.numberOfCharacters === 14 +PASS: axField.stringForTextMarkerRange(lineRange) === "three" +PASS: axField.textMarkerRangeLength(lineRange) === 5 +PASS: axField.rangeForLine(lastLine) === "{9, 5}" +PASS: axField.lineForIndex(indexPastEnd) === -1 + + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-after-dynamic-edit.html b/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-after-dynamic-edit.html new file mode 100644 index 000000000000..6eef1b29aac0 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-after-dynamic-edit.html @@ -0,0 +1,154 @@ +<!DOCTYPE HTML><!-- webkit-test-runner [ IsAccessibilityIsolatedTreeEnabled=true ] --> +<html> +<head> +<script src="../../../resources/accessibility-helper.js"></script> +<script src="../../../resources/js-test.js"></script> +</head> +<body> + +<!-- A text control whose value ends in a line break renders an empty final line, whose newline is not + a character of the value — innerTextValueFrom() strips one trailing newline "that's collapsed out + by rendering" — so the control's character count and its line ranges must not count it. Editing + represents that newline in shapes setInnerTextValue never produces, which is what these cases + cover; values set in markup are in textarea-line-range-with-trailing-newline.html. --> +<!-- The caret's line range is checked here rather than in the cross-platform character-count test + because it needs AXSelectedTextMarkerRange, which the iOS test runner doesn't implement (see + AccessibilityUIElement::selectedTextMarkerRange, which only mac overrides). --> +<textarea id="warmup" style="width: 200px; height: 60px"></textarea> +<!-- Typed into after the accessibility tree exists, because editing represents the newline that + rendering adds for the empty final line as a text node holding just that newline, where + setInnerTextValue appends a placeholder <br>. The caret's line must come out the same either way. --> +<textarea id="typed" style="width: 200px; height: 60px"></textarea> +<!-- Built through AXReplaceRangeWithText, the way VoiceOver inserts text. That path leaves the whole + value in one text node with the newline rendering adds for the empty final line at its end, so the + collapsed newline is neither a <br> nor a node of its own. + + This isn't an accessibility-only shape: AXReplaceRangeWithText is one of many callers of + Editor::replaceSelectionWithText, so pasting a value that ends in a line break produces the same + text node and reproduced the same bug. The insertion is driven through the accessibility API here + only because that needs no pasteboard. --> +<textarea id="inserted" style="width: 200px; height: 60px"></textarea> + +<script> +var output = "Asserts the line APIs for text control values edited after the accessibility tree was built:\n" + + "the caret's line range, and, where a case names one, the last line's character range and the line\n" + + "of the index one past the end of the value.\n\n"; + +var cases = [ + { id: "typed", label: "trailing newline typed, caret on the blank final line", value: "one two \n", caret: 9, lineString: "", lineLength: 0, lastLine: 1, lastLineRange: "{9, 0}", typed: true }, + // Inserting "\n" and then more text is where VoiceOver's own insertions used to leave the count and + // the line one character long: the value ends without a line break, but the control still renders + // one, and it sits at the end of the same text node as the rest of the value. + { id: "inserted", label: "inserted through AXReplaceRangeWithText, caret at the end", value: "one two \nthree", caret: 14, lineString: "three", lineLength: 5, lastLine: 1, lastLineRange: "{9, 5}", inserted: [[0, 0, "one "], [4, 0, "two "], [8, 0, "\n"], [9, 0, "three"]] }, +]; + +// expect() evaluates its expressions in its own scope, so the values it reads are globals. +var axField, caret, lineRange, lineEnd, webArea, lastLine, indexPastEnd; + +// The id of the text control the document's accessibility selection currently sits in, or null. +function idOfFieldHoldingAccessibilitySelection() { + var selection = axField.selectedTextMarkerRange(); + if (!selection) + return null; + var element = axField.accessibilityElementForTextMarker(axField.startTextMarkerForTextMarkerRange(selection)); + // The marker points into the control's inner text, so walk out to the control itself. + while (element && !element.domIdentifier) + element = element.parentElement(); + return element ? element.domIdentifier : null; +} + +// Types |value| one character at a time, the way a user would, leaving the caret somewhere other than +// |caret| so that placing it there below is a real change and posts the notification the gate waits for. +function typeValue(field, value, caret) { + field.focus(); + for (var character of value) { + if (character === "\n") + document.execCommand("insertLineBreak"); + else + document.execCommand("insertText", false, character); + } + var elsewhere = caret ? 0 : value.length; + field.setSelectionRange(elsewhere, elsewhere); +} + +async function checkCase(testCase) { + var field = document.getElementById(testCase.id); + if (testCase.typed) + typeValue(field, testCase.value, testCase.caret); + if (testCase.inserted) { + // AXReplaceRangeWithText needs the field focused, the way VoiceOver leaves it. + field.focus(); + var axFieldForInsertion = await waitForElementById(testCase.id); + for (var [location, length, text] of testCase.inserted) + axFieldForInsertion.replaceTextInRange(text, location, length); + // Leave the caret somewhere else so placing it below posts the notification the gate waits for. + field.setSelectionRange(0, 0); + } + axField = await waitForElementById(testCase.id); + if (!axField) { + output += `FAIL: ${testCase.label}: no accessibility element for #${testCase.id}\n`; + return; + } + + // The accessibility layer learns of the selection through a notification, so place the caret + // inside waitForNotification: that notification is the point at which the accessibility + // selection — which the text marker below is read from — has caught up. Waiting on this field's + // AXSelectedTextRange instead would be too weak, because that reflects the control's own + // selection, which setSelectionRange updates synchronously, so it can be satisfied while the + // accessibility selection still points into the field measured before this one. + await waitForNotification(webArea, "AXSelectedTextChanged", () => { + field.focus(); + field.setSelectionRange(testCase.caret, testCase.caret); + }); + var selectionFieldId = idOfFieldHoldingAccessibilitySelection(); + if (selectionFieldId !== testCase.id) { + output += `FAIL: ${testCase.label}: the accessibility selection reached #${selectionFieldId}, not #${testCase.id}\n`; + return; + } + + caret = axField.startTextMarkerForTextMarkerRange(axField.selectedTextMarkerRange()); + lineRange = axField.lineTextMarkerRangeForTextMarker(caret); + lineEnd = axField.endTextMarkerForTextMarkerRange(lineRange); + + output += `${testCase.label}: value ${JSON.stringify(testCase.value)}, caret at ${testCase.caret}\n`; + output += expect("axField.numberOfCharacters", `${testCase.value.length}`); + output += expect("axField.stringForTextMarkerRange(lineRange)", JSON.stringify(testCase.lineString)); + output += expect("axField.textMarkerRangeLength(lineRange)", `${testCase.lineLength}`); + // A caret on the blank final line has nothing after it, so the line it reports must end where + // the caret is. Otherwise the line names a character past the end of the value. + if (!testCase.lineLength) + output += expect("lineEnd.isEqual(caret)", "true"); + if (testCase.lastLineRange) { + lastLine = testCase.lastLine; + indexPastEnd = testCase.value.length; + output += expect("axField.rangeForLine(lastLine)", JSON.stringify(testCase.lastLineRange)); + output += expect("axField.lineForIndex(indexPastEnd)", "-1"); + } + output += "\n"; +} + +if (window.accessibilityController) { + window.jsTestIsAsync = true; + + setTimeout(async function() { + webArea = accessibilityController.rootElement.childAtIndex(0); + + // Warm up the selection machinery on a field this test doesn't measure, so that the first + // measured case doesn't race the accessibility tree's first selection update. + var warmUpField = document.getElementById("warmup"); + await waitForElementById("warmup"); + await waitForNotification(webArea, "AXSelectedTextChanged", () => { + warmUpField.focus(); + warmUpField.setSelectionRange(0, 0); + }); + + for (var testCase of cases) + await checkCase(testCase); + + debugEscaped(output); + finishJSTest(); + }, 0); +} +</script> +</body> +</html> diff --git a/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-with-trailing-newline-expected.txt b/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-with-trailing-newline-expected.txt index ab57ed6f6be9..97147899fcad 100644 --- a/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-with-trailing-newline-expected.txt +++ b/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-with-trailing-newline-expected.txt @@ -1,4 +1,6 @@ -Asserts the caret's line range for text control values that end in a line break. +Asserts the line APIs for text control values that end in a line break: the caret's line +range, and, where a case names one, the last line's character range and the line of the index +one past the end of the value. no trailing newline: value "one two ", caret at 8 PASS: axField.numberOfCharacters === 8 @@ -10,6 +12,8 @@ PASS: axField.numberOfCharacters === 9 PASS: axField.stringForTextMarkerRange(lineRange) === "" PASS: axField.textMarkerRangeLength(lineRange) === 0 PASS: lineEnd.isEqual(caret) === true +PASS: axField.rangeForLine(lastLine) === "{9, 0}" +PASS: axField.lineForIndex(indexPastEnd) === -1 trailing newline, caret on the first line: value "one two \n", caret at 4 PASS: axField.numberOfCharacters === 9 @@ -20,6 +24,8 @@ two lines, caret starting the second: value "one two \nthree", caret at 9 PASS: axField.numberOfCharacters === 14 PASS: axField.stringForTextMarkerRange(lineRange) === "three" PASS: axField.textMarkerRangeLength(lineRange) === 5 +PASS: axField.rangeForLine(lastLine) === "{9, 5}" +PASS: axField.lineForIndex(indexPastEnd) === -1 leading newline: value "one \ntwo three", caret at 5 PASS: axField.numberOfCharacters === 14 diff --git a/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-with-trailing-newline.html b/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-with-trailing-newline.html index ac865653ae76..1b63decf46ad 100644 --- a/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-with-trailing-newline.html +++ b/LayoutTests/accessibility/isolated-tree/mac/textarea-line-range-with-trailing-newline.html @@ -14,7 +14,8 @@ rendered text and so do include that newline; these cases would then be one character long. --> <!-- The caret's line range is checked here rather than in the cross-platform character-count test because it needs AXSelectedTextMarkerRange, which the iOS test runner doesn't implement (see - AccessibilityUIElement::selectedTextMarkerRange, which only mac overrides). --> + AccessibilityUIElement::selectedTextMarkerRange, which only mac overrides). Values edited after + the tree was built are in textarea-line-range-after-dynamic-edit.html. --> <textarea id="warmup" style="width: 200px; height: 60px"></textarea> <textarea id="ta0" style="width: 200px; height: 60px"></textarea> <textarea id="ta1" style="width: 200px; height: 60px"></textarea> @@ -28,15 +29,17 @@ <textarea id="ta9" style="width: 200px; height: 60px"></textarea> <script> -var output = "Asserts the caret's line range for text control values that end in a line break.\n\n"; +var output = "Asserts the line APIs for text control values that end in a line break: the caret's line\n" + + "range, and, where a case names one, the last line's character range and the line of the index\n" + + "one past the end of the value.\n\n"; // Every value is set before the accessibility tree is first built, so no case depends on the // isolated tree observing a mutation. var cases = [ { id: "ta0", label: "no trailing newline", value: "one two ", caret: 8, lineString: "one two ", lineLength: 8 }, - { id: "ta1", label: "trailing newline, caret on the blank final line", value: "one two \n", caret: 9, lineString: "", lineLength: 0 }, + { id: "ta1", label: "trailing newline, caret on the blank final line", value: "one two \n", caret: 9, lineString: "", lineLength: 0, lastLine: 1, lastLineRange: "{9, 0}" }, { id: "ta2", label: "trailing newline, caret on the first line", value: "one two \n", caret: 4, lineString: "one two \n", lineLength: 9 }, - { id: "ta3", label: "two lines, caret starting the second", value: "one two \nthree", caret: 9, lineString: "three", lineLength: 5 }, + { id: "ta3", label: "two lines, caret starting the second", value: "one two \nthree", caret: 9, lineString: "three", lineLength: 5, lastLine: 1, lastLineRange: "{9, 5}" }, { id: "ta4", label: "leading newline", value: "one \ntwo three", caret: 5, lineString: "two three", lineLength: 9 }, { id: "ta5", label: "only a newline, caret after it", value: "\n", caret: 1, lineString: "", lineLength: 0 }, { id: "ta6", label: "only a newline, caret before it", value: "\n", caret: 0, lineString: "\n", lineLength: 1 }, @@ -56,7 +59,7 @@ } // expect() evaluates its expressions in its own scope, so the values it reads are globals. -var axField, caret, lineRange, lineEnd, webArea; +var axField, caret, lineRange, lineEnd, webArea, lastLine, indexPastEnd; // The id of the text control the document's accessibility selection currently sits in, or null. function idOfFieldHoldingAccessibilitySelection() { @@ -106,6 +109,12 @@ // the caret is. Otherwise the line names a character past the end of the value. if (!testCase.lineLength) output += expect("lineEnd.isEqual(caret)", "true"); + if (testCase.lastLineRange) { + lastLine = testCase.lastLine; + indexPastEnd = testCase.value.length; + output += expect("axField.rangeForLine(lastLine)", JSON.stringify(testCase.lastLineRange)); + output += expect("axField.lineForIndex(indexPastEnd)", "-1"); + } output += "\n"; } diff --git a/LayoutTests/accessibility/isolated-tree/text-marker/textarea-character-count-with-trailing-newline-expected.txt b/LayoutTests/accessibility/isolated-tree/text-marker/textarea-character-count-with-trailing-newline-expected.txt new file mode 100644 index 000000000000..e6d19e535bd8 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/text-marker/textarea-character-count-with-trailing-newline-expected.txt @@ -0,0 +1,34 @@ +Asserts the character count of text control values that end in a line break. + +no trailing newline: value "one two " +PASS: axField.numberOfCharacters === 8 + +trailing newline: value "one two \n" +PASS: axField.numberOfCharacters === 9 + +two lines, no trailing newline: value "one two \nthree" +PASS: axField.numberOfCharacters === 14 + +only a newline: value "\n" +PASS: axField.numberOfCharacters === 1 + +two trailing newlines: value "a\n\n" +PASS: axField.numberOfCharacters === 3 + +whitespace either side of a newline: value " \n " +PASS: axField.numberOfCharacters === 5 + +trailing newline, typed: value "one two \n" +PASS: axField.numberOfCharacters === 9 + +two trailing newlines, typed: value "a\n\n" +PASS: axField.numberOfCharacters === 3 + +ARIA textbox, trailing newline: value "one two \n" +PASS: axField.numberOfCharacters === 9 + + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/isolated-tree/text-marker/textarea-character-count-with-trailing-newline.html b/LayoutTests/accessibility/isolated-tree/text-marker/textarea-character-count-with-trailing-newline.html new file mode 100644 index 000000000000..3250133cfb44 --- /dev/null +++ b/LayoutTests/accessibility/isolated-tree/text-marker/textarea-character-count-with-trailing-newline.html @@ -0,0 +1,114 @@ +<!DOCTYPE HTML><!-- webkit-test-runner [ IsAccessibilityIsolatedTreeEnabled=true ] --> +<html> +<head> +<script src="../../../resources/accessibility-helper.js"></script> +<script src="../../../resources/js-test.js"></script> +</head> +<body> + +<!-- A text control whose value ends in a line break renders an empty final line, which + HTMLTextFormControlElement::setInnerTextValue gives a line box by appending a placeholder <br>. + That <br>'s newline is not a character of the value — innerTextValueFrom() strips one trailing + newline that's collapsed out by rendering — so the control's character count must not count it. + The isolated tree builds its answer from text runs, which model rendered text and so do include + that newline; these values would then be one character too long. --> +<div id="content"> +<textarea id="ta0" style="width: 200px; height: 60px"></textarea> +<textarea id="ta1" style="width: 200px; height: 60px"></textarea> +<textarea id="ta2" style="width: 200px; height: 60px"></textarea> +<textarea id="ta3" style="width: 200px; height: 60px"></textarea> +<textarea id="ta4" style="width: 200px; height: 60px"></textarea> +<textarea id="ta5" style="width: 200px; height: 60px"></textarea> +<!-- These two are typed into after the accessibility tree exists. Editing represents the newline that + rendering adds for the empty final line as a text node holding just that newline, where + setInnerTextValue appends a placeholder <br> — the value's character count must come out the same + either way. --> +<textarea id="ta6" style="width: 200px; height: 60px"></textarea> +<textarea id="ta7" style="width: 200px; height: 60px"></textarea> +<!-- An ARIA textbox is not a native text control: its text has no collapsed trailing line break, so + a trailing newline in it *is* a character and must still be counted. --> +<div id="ce0" contenteditable role="textbox" style="width: 200px; white-space: pre-wrap"></div> +</div> + +<script> +var output = "Asserts the character count of text control values that end in a line break.\n\n"; + +// Every value is set before the accessibility tree is first built, so no case depends on the +// isolated tree observing a mutation. +var cases = [ + { id: "ta0", label: "no trailing newline", value: "one two " }, + { id: "ta1", label: "trailing newline", value: "one two \n" }, + { id: "ta2", label: "two lines, no trailing newline", value: "one two \nthree" }, + { id: "ta3", label: "only a newline", value: "\n" }, + { id: "ta4", label: "two trailing newlines", value: "a\n\n" }, + { id: "ta5", label: "whitespace either side of a newline", value: " \n " }, + { id: "ta6", label: "trailing newline, typed", value: "one two \n", typed: true }, + { id: "ta7", label: "two trailing newlines, typed", value: "a\n\n", typed: true }, + { id: "ce0", label: "ARIA textbox, trailing newline", value: "one two \n" }, +]; + +for (var testCase of cases) { + var field = document.getElementById(testCase.id); + if (testCase.typed) + continue; + if (field.isContentEditable) + field.textContent = testCase.value; + else + field.value = testCase.value; + // Force layout so the value is laid out before the accessibility tree is built. + field.offsetHeight; +} + +// expect() evaluates its expressions in its own scope, so the values it reads are globals. +var axField, webArea; + +// Types |value| one character at a time, the way a user would, then waits for the accessibility tree +// to catch up. The selection notification is a safe gate for the value: the two are updated together. +async function typeValue(field, value) { + field.focus(); + for (var character of value) { + if (character === "\n") + document.execCommand("insertLineBreak"); + else + document.execCommand("insertText", false, character); + } + // Typing leaves the caret at the end, so moving it to the start is always a real change. + await waitForNotification(webArea, "AXSelectedTextChanged", () => { + field.setSelectionRange(0, 0); + }); +} + +async function checkCase(testCase) { + axField = await waitForElementById(testCase.id); + if (!axField) { + output += `FAIL: ${testCase.label}: no accessibility element for #${testCase.id}\n`; + return; + } + + if (testCase.typed) + await typeValue(document.getElementById(testCase.id), testCase.value); + + output += `${testCase.label}: value ${JSON.stringify(testCase.value)}\n`; + output += expect("axField.numberOfCharacters", `${testCase.value.length}`); + output += "\n"; +} + +if (window.accessibilityController) { + window.jsTestIsAsync = true; + + setTimeout(async function() { + webArea = accessibilityController.rootElement.childAtIndex(0); + + for (var testCase of cases) + await checkCase(testCase); + + // Hide the test content so the output is just the assertions above, with no page text dump. + document.getElementById("content").hidden = true; + + debugEscaped(output); + finishJSTest(); + }, 0); +} +</script> +</body> +</html> diff --git a/LayoutTests/accessibility/mac/large-text-area-expected.txt b/LayoutTests/accessibility/large-text-area-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/large-text-area-expected.txt rename to LayoutTests/accessibility/large-text-area-expected.txt diff --git a/LayoutTests/accessibility/mac/large-text-area.html b/LayoutTests/accessibility/large-text-area.html similarity index 99% rename from LayoutTests/accessibility/mac/large-text-area.html rename to LayoutTests/accessibility/large-text-area.html index 912c3c9f9eae..795fef1bf955 100644 --- a/LayoutTests/accessibility/mac/large-text-area.html +++ b/LayoutTests/accessibility/large-text-area.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/js-test.js"></script> -<script src="../../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/line-requests-starting-after-first-line-expected.txt b/LayoutTests/accessibility/line-requests-starting-after-first-line-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/line-requests-starting-after-first-line-expected.txt rename to LayoutTests/accessibility/line-requests-starting-after-first-line-expected.txt diff --git a/LayoutTests/accessibility/mac/line-requests-starting-after-first-line.html b/LayoutTests/accessibility/line-requests-starting-after-first-line.html similarity index 93% rename from LayoutTests/accessibility/mac/line-requests-starting-after-first-line.html rename to LayoutTests/accessibility/line-requests-starting-after-first-line.html index 2ab021f16b74..f43e49161f24 100644 --- a/LayoutTests/accessibility/mac/line-requests-starting-after-first-line.html +++ b/LayoutTests/accessibility/line-requests-starting-after-first-line.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/list-items-ignored-expected.txt b/LayoutTests/accessibility/list-items-ignored-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/list-items-ignored-expected.txt rename to LayoutTests/accessibility/list-items-ignored-expected.txt diff --git a/LayoutTests/accessibility/mac/list-items-ignored.html b/LayoutTests/accessibility/list-items-ignored.html similarity index 95% rename from LayoutTests/accessibility/mac/list-items-ignored.html rename to LayoutTests/accessibility/list-items-ignored.html index c0875e9592ae..14d20d86ab87 100644 --- a/LayoutTests/accessibility/mac/list-items-ignored.html +++ b/LayoutTests/accessibility/list-items-ignored.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> </head> <body id="body"> diff --git a/LayoutTests/accessibility/list-marker-content-renderers-text-expected.txt b/LayoutTests/accessibility/list-marker-content-renderers-text-expected.txt new file mode 100644 index 000000000000..3e843b8745c5 --- /dev/null +++ b/LayoutTests/accessibility/list-marker-content-renderers-text-expected.txt @@ -0,0 +1,16 @@ +This tests that a marker keeping its text in content renderers does not report that text to callers asking for text without list markers. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS nameOf('string-button') is "Reply Item A" +PASS itemValue('string') is "AXValue: Item A" +PASS nameOf('counter-button') is "Reply Item B" +PASS itemValue('counter') is "AXValue: Item B" +PASS nameOf('decimal-button') is "Reply Item C" +PASS itemValue('decimal') is "AXValue: Item C" +PASS nameOf('generated-button') is "Reply radish Item D" +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/list-marker-content-renderers-text.html b/LayoutTests/accessibility/list-marker-content-renderers-text.html new file mode 100644 index 000000000000..b9b07c8d5e9e --- /dev/null +++ b/LayoutTests/accessibility/list-marker-content-renderers-text.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<html> +<head> +<script src="../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<style> +@counter-style rtl-cyclic { system: cyclic; symbols: "\627"; } +.rtl-string li { list-style-type: "\627\644\641"; } +.rtl-counter li { list-style-type: rtl-cyclic; } +.rtl-list { direction: rtl; } +.marker-content li::marker { content: "radish "; } +</style> +</head> +<body> +<div id="content"> + <ul class="rtl-string"><li id="string">Item A</li></ul> + <a role="button" href="#" id="string-button" aria-labelledby="string-button string"><span>Reply</span></a> + + <ul class="rtl-counter"><li id="counter">Item B</li></ul> + <a role="button" href="#" id="counter-button" aria-labelledby="counter-button counter"><span>Reply</span></a> + + <ol class="rtl-list"><li id="decimal">Item C</li></ol> + <a role="button" href="#" id="decimal-button" aria-labelledby="decimal-button decimal"><span>Reply</span></a> + + <ul class="marker-content"><li id="generated">Item D</li></ul> + <a role="button" href="#" id="generated-button" aria-labelledby="generated-button generated"><span>Reply</span></a> +</div> +<p id="description"></p> +<div id="console"></div> +<script> +description("This tests that a marker keeping its text in content renderers does not report that text to callers asking for text without list markers."); + +function nameOf(buttonId) { + document.getElementById(buttonId).focus(); + return platformValueForW3CName(accessibilityController.focusedElement); +} + +function itemValue(itemId) { + return accessibilityController.accessibleElementById(itemId).stringValue; +} + +if (window.accessibilityController) { + // Each list below gives its marker content renderers to hold the text with: the string and the + // counter style symbol are right-to-left, and a right-to-left list makes even decimal need bidi + // resolution. aria-labelledby asks for the name without list markers, so none of that text + // belongs in these names, nor in the list item's own value. + shouldBeEqualToString("nameOf('string-button')", "Reply Item A"); + shouldBeEqualToString("itemValue('string')", "AXValue: Item A"); + + shouldBeEqualToString("nameOf('counter-button')", "Reply Item B"); + shouldBeEqualToString("itemValue('counter')", "AXValue: Item B"); + + shouldBeEqualToString("nameOf('decimal-button')", "Reply Item C"); + shouldBeEqualToString("itemValue('decimal')", "AXValue: Item C"); + + // A `content` marker is the exception: it computes no text of its own, so the renderers holding + // its content are the only source and they keep contributing. accname asks for that text in the + // name, see accname/name/comp_name_from_pseudo_content_marker.tentative.html. + shouldBeEqualToString("nameOf('generated-button')", "Reply radish Item D"); + + document.getElementById("content").style.visibility = "hidden"; +} +</script> +</body> +</html> diff --git a/LayoutTests/accessibility/mac/listmarker-suffix-expected.txt b/LayoutTests/accessibility/listmarker-suffix-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/listmarker-suffix-expected.txt rename to LayoutTests/accessibility/listmarker-suffix-expected.txt diff --git a/LayoutTests/accessibility/mac/listmarker-suffix.html b/LayoutTests/accessibility/listmarker-suffix.html similarity index 88% rename from LayoutTests/accessibility/mac/listmarker-suffix.html rename to LayoutTests/accessibility/listmarker-suffix.html index 9cad2688ff71..ea39e54c5851 100644 --- a/LayoutTests/accessibility/mac/listmarker-suffix.html +++ b/LayoutTests/accessibility/listmarker-suffix.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/svg-duplicated.html b/LayoutTests/accessibility/mac/svg-duplicated.html deleted file mode 100644 index d40bdbf26718..000000000000 --- a/LayoutTests/accessibility/mac/svg-duplicated.html +++ /dev/null @@ -1,24 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> -<script src="../../resources/js-test.js"></script> -<script src="../../resources/accessibility-helper.js"></script> -</head> -<body id="body" role="group"> - -Hello -<img src="../resources/svg-face.svg" alt="image 1" /> -<img src="../resources/svg-face.svg" alt="image 2" /> -world - -<script> -var output = "This test verifies that we don't enter an infinite loop when we have two references to the same remote SVG file.\n"; - -if (window.accessibilityController) { - var container = accessibilityController.accessibleElementById("body"); - container.textMarkerRangeForElement(container) - debug(output); -} -</script> -</body> -</html> diff --git a/LayoutTests/accessibility/mac/textarea-line-range-after-dynamic-edit-expected.txt b/LayoutTests/accessibility/mac/textarea-line-range-after-dynamic-edit-expected.txt new file mode 100644 index 000000000000..46411e9f63dc --- /dev/null +++ b/LayoutTests/accessibility/mac/textarea-line-range-after-dynamic-edit-expected.txt @@ -0,0 +1,24 @@ +Asserts the line APIs for text control values edited after the accessibility tree was built: +the caret's line range, and, where a case names one, the last line's character range and the line +of the index one past the end of the value. + +trailing newline typed, caret on the blank final line: value "one two \n", caret at 9 +PASS: axField.numberOfCharacters === 9 +PASS: axField.stringForTextMarkerRange(lineRange) === "" +PASS: axField.textMarkerRangeLength(lineRange) === 0 +PASS: lineEnd.isEqual(caret) === true +PASS: axField.rangeForLine(lastLine) === "{9, 0}" +PASS: axField.lineForIndex(indexPastEnd) === -1 + +inserted through AXReplaceRangeWithText, caret at the end: value "one two \nthree", caret at 14 +PASS: axField.numberOfCharacters === 14 +PASS: axField.stringForTextMarkerRange(lineRange) === "three" +PASS: axField.textMarkerRangeLength(lineRange) === 5 +PASS: axField.rangeForLine(lastLine) === "{9, 5}" +PASS: axField.lineForIndex(indexPastEnd) === -1 + + +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/accessibility/mac/textarea-line-range-after-dynamic-edit.html b/LayoutTests/accessibility/mac/textarea-line-range-after-dynamic-edit.html new file mode 100644 index 000000000000..4097ff87f48b --- /dev/null +++ b/LayoutTests/accessibility/mac/textarea-line-range-after-dynamic-edit.html @@ -0,0 +1,154 @@ +<!DOCTYPE HTML> +<html> +<head> +<script src="../../resources/accessibility-helper.js"></script> +<script src="../../resources/js-test.js"></script> +</head> +<body> + +<!-- A text control whose value ends in a line break renders an empty final line, whose newline is not + a character of the value — innerTextValueFrom() strips one trailing newline "that's collapsed out + by rendering" — so the control's character count and its line ranges must not count it. Editing + represents that newline in shapes setInnerTextValue never produces, which is what these cases + cover; values set in markup are in textarea-line-range-with-trailing-newline.html. --> +<!-- The caret's line range is checked here rather than in the cross-platform character-count test + because it needs AXSelectedTextMarkerRange, which the iOS test runner doesn't implement (see + AccessibilityUIElement::selectedTextMarkerRange, which only mac overrides). --> +<textarea id="warmup" style="width: 200px; height: 60px"></textarea> +<!-- Typed into after the accessibility tree exists, because editing represents the newline that + rendering adds for the empty final line as a text node holding just that newline, where + setInnerTextValue appends a placeholder <br>. The caret's line must come out the same either way. --> +<textarea id="typed" style="width: 200px; height: 60px"></textarea> +<!-- Built through AXReplaceRangeWithText, the way VoiceOver inserts text. That path leaves the whole + value in one text node with the newline rendering adds for the empty final line at its end, so the + collapsed newline is neither a <br> nor a node of its own. + + This isn't an accessibility-only shape: AXReplaceRangeWithText is one of many callers of + Editor::replaceSelectionWithText, so pasting a value that ends in a line break produces the same + text node and reproduced the same bug. The insertion is driven through the accessibility API here + only because that needs no pasteboard. --> +<textarea id="inserted" style="width: 200px; height: 60px"></textarea> + +<script> +var output = "Asserts the line APIs for text control values edited after the accessibility tree was built:\n" + + "the caret's line range, and, where a case names one, the last line's character range and the line\n" + + "of the index one past the end of the value.\n\n"; + +var cases = [ + { id: "typed", label: "trailing newline typed, caret on the blank final line", value: "one two \n", caret: 9, lineString: "", lineLength: 0, lastLine: 1, lastLineRange: "{9, 0}", typed: true }, + // Inserting "\n" and then more text is where VoiceOver's own insertions used to leave the count and + // the line one character long: the value ends without a line break, but the control still renders + // one, and it sits at the end of the same text node as the rest of the value. + { id: "inserted", label: "inserted through AXReplaceRangeWithText, caret at the end", value: "one two \nthree", caret: 14, lineString: "three", lineLength: 5, lastLine: 1, lastLineRange: "{9, 5}", inserted: [[0, 0, "one "], [4, 0, "two "], [8, 0, "\n"], [9, 0, "three"]] }, +]; + +// expect() evaluates its expressions in its own scope, so the values it reads are globals. +var axField, caret, lineRange, lineEnd, webArea, lastLine, indexPastEnd; + +// The id of the text control the document's accessibility selection currently sits in, or null. +function idOfFieldHoldingAccessibilitySelection() { + var selection = axField.selectedTextMarkerRange(); + if (!selection) + return null; + var element = axField.accessibilityElementForTextMarker(axField.startTextMarkerForTextMarkerRange(selection)); + // The marker points into the control's inner text, so walk out to the control itself. + while (element && !element.domIdentifier) + element = element.parentElement(); + return element ? element.domIdentifier : null; +} + +// Types |value| one character at a time, the way a user would, leaving the caret somewhere other than +// |caret| so that placing it there below is a real change and posts the notification the gate waits for. +function typeValue(field, value, caret) { + field.focus(); + for (var character of value) { + if (character === "\n") + document.execCommand("insertLineBreak"); + else + document.execCommand("insertText", false, character); + } + var elsewhere = caret ? 0 : value.length; + field.setSelectionRange(elsewhere, elsewhere); +} + +async function checkCase(testCase) { + var field = document.getElementById(testCase.id); + if (testCase.typed) + typeValue(field, testCase.value, testCase.caret); + if (testCase.inserted) { + // AXReplaceRangeWithText needs the field focused, the way VoiceOver leaves it. + field.focus(); + var axFieldForInsertion = await waitForElementById(testCase.id); + for (var [location, length, text] of testCase.inserted) + axFieldForInsertion.replaceTextInRange(text, location, length); + // Leave the caret somewhere else so placing it below posts the notification the gate waits for. + field.setSelectionRange(0, 0); + } + axField = await waitForElementById(testCase.id); + if (!axField) { + output += `FAIL: ${testCase.label}: no accessibility element for #${testCase.id}\n`; + return; + } + + // The accessibility layer learns of the selection through a notification, so place the caret + // inside waitForNotification: that notification is the point at which the accessibility + // selection — which the text marker below is read from — has caught up. Waiting on this field's + // AXSelectedTextRange instead would be too weak, because that reflects the control's own + // selection, which setSelectionRange updates synchronously, so it can be satisfied while the + // accessibility selection still points into the field measured before this one. + await waitForNotification(webArea, "AXSelectedTextChanged", () => { + field.focus(); + field.setSelectionRange(testCase.caret, testCase.caret); + }); + var selectionFieldId = idOfFieldHoldingAccessibilitySelection(); + if (selectionFieldId !== testCase.id) { + output += `FAIL: ${testCase.label}: the accessibility selection reached #${selectionFieldId}, not #${testCase.id}\n`; + return; + } + + caret = axField.startTextMarkerForTextMarkerRange(axField.selectedTextMarkerRange()); + lineRange = axField.lineTextMarkerRangeForTextMarker(caret); + lineEnd = axField.endTextMarkerForTextMarkerRange(lineRange); + + output += `${testCase.label}: value ${JSON.stringify(testCase.value)}, caret at ${testCase.caret}\n`; + output += expect("axField.numberOfCharacters", `${testCase.value.length}`); + output += expect("axField.stringForTextMarkerRange(lineRange)", JSON.stringify(testCase.lineString)); + output += expect("axField.textMarkerRangeLength(lineRange)", `${testCase.lineLength}`); + // A caret on the blank final line has nothing after it, so the line it reports must end where + // the caret is. Otherwise the line names a character past the end of the value. + if (!testCase.lineLength) + output += expect("lineEnd.isEqual(caret)", "true"); + if (testCase.lastLineRange) { + lastLine = testCase.lastLine; + indexPastEnd = testCase.value.length; + output += expect("axField.rangeForLine(lastLine)", JSON.stringify(testCase.lastLineRange)); + output += expect("axField.lineForIndex(indexPastEnd)", "-1"); + } + output += "\n"; +} + +if (window.accessibilityController) { + window.jsTestIsAsync = true; + + setTimeout(async function() { + webArea = accessibilityController.rootElement.childAtIndex(0); + + // Warm up the selection machinery on a field this test doesn't measure, so that the first + // measured case doesn't race the accessibility tree's first selection update. + var warmUpField = document.getElementById("warmup"); + await waitForElementById("warmup"); + await waitForNotification(webArea, "AXSelectedTextChanged", () => { + warmUpField.focus(); + warmUpField.setSelectionRange(0, 0); + }); + + for (var testCase of cases) + await checkCase(testCase); + + debugEscaped(output); + finishJSTest(); + }, 0); +} +</script> +</body> +</html> diff --git a/LayoutTests/accessibility/mac/textarea-line-range-with-trailing-newline-expected.txt b/LayoutTests/accessibility/mac/textarea-line-range-with-trailing-newline-expected.txt index ab57ed6f6be9..97147899fcad 100644 --- a/LayoutTests/accessibility/mac/textarea-line-range-with-trailing-newline-expected.txt +++ b/LayoutTests/accessibility/mac/textarea-line-range-with-trailing-newline-expected.txt @@ -1,4 +1,6 @@ -Asserts the caret's line range for text control values that end in a line break. +Asserts the line APIs for text control values that end in a line break: the caret's line +range, and, where a case names one, the last line's character range and the line of the index +one past the end of the value. no trailing newline: value "one two ", caret at 8 PASS: axField.numberOfCharacters === 8 @@ -10,6 +12,8 @@ PASS: axField.numberOfCharacters === 9 PASS: axField.stringForTextMarkerRange(lineRange) === "" PASS: axField.textMarkerRangeLength(lineRange) === 0 PASS: lineEnd.isEqual(caret) === true +PASS: axField.rangeForLine(lastLine) === "{9, 0}" +PASS: axField.lineForIndex(indexPastEnd) === -1 trailing newline, caret on the first line: value "one two \n", caret at 4 PASS: axField.numberOfCharacters === 9 @@ -20,6 +24,8 @@ two lines, caret starting the second: value "one two \nthree", caret at 9 PASS: axField.numberOfCharacters === 14 PASS: axField.stringForTextMarkerRange(lineRange) === "three" PASS: axField.textMarkerRangeLength(lineRange) === 5 +PASS: axField.rangeForLine(lastLine) === "{9, 5}" +PASS: axField.lineForIndex(indexPastEnd) === -1 leading newline: value "one \ntwo three", caret at 5 PASS: axField.numberOfCharacters === 14 diff --git a/LayoutTests/accessibility/mac/textarea-line-range-with-trailing-newline.html b/LayoutTests/accessibility/mac/textarea-line-range-with-trailing-newline.html index c5a3804d2a06..d3d1f484c2ea 100644 --- a/LayoutTests/accessibility/mac/textarea-line-range-with-trailing-newline.html +++ b/LayoutTests/accessibility/mac/textarea-line-range-with-trailing-newline.html @@ -14,7 +14,8 @@ rendered text and so do include that newline; these cases would then be one character long. --> <!-- The caret's line range is checked here rather than in the cross-platform character-count test because it needs AXSelectedTextMarkerRange, which the iOS test runner doesn't implement (see - AccessibilityUIElement::selectedTextMarkerRange, which only mac overrides). --> + AccessibilityUIElement::selectedTextMarkerRange, which only mac overrides). Values edited after + the tree was built are in textarea-line-range-after-dynamic-edit.html. --> <textarea id="warmup" style="width: 200px; height: 60px"></textarea> <textarea id="ta0" style="width: 200px; height: 60px"></textarea> <textarea id="ta1" style="width: 200px; height: 60px"></textarea> @@ -28,15 +29,17 @@ <textarea id="ta9" style="width: 200px; height: 60px"></textarea> <script> -var output = "Asserts the caret's line range for text control values that end in a line break.\n\n"; +var output = "Asserts the line APIs for text control values that end in a line break: the caret's line\n" + + "range, and, where a case names one, the last line's character range and the line of the index\n" + + "one past the end of the value.\n\n"; // Every value is set before the accessibility tree is first built, so no case depends on the // isolated tree observing a mutation. var cases = [ { id: "ta0", label: "no trailing newline", value: "one two ", caret: 8, lineString: "one two ", lineLength: 8 }, - { id: "ta1", label: "trailing newline, caret on the blank final line", value: "one two \n", caret: 9, lineString: "", lineLength: 0 }, + { id: "ta1", label: "trailing newline, caret on the blank final line", value: "one two \n", caret: 9, lineString: "", lineLength: 0, lastLine: 1, lastLineRange: "{9, 0}" }, { id: "ta2", label: "trailing newline, caret on the first line", value: "one two \n", caret: 4, lineString: "one two \n", lineLength: 9 }, - { id: "ta3", label: "two lines, caret starting the second", value: "one two \nthree", caret: 9, lineString: "three", lineLength: 5 }, + { id: "ta3", label: "two lines, caret starting the second", value: "one two \nthree", caret: 9, lineString: "three", lineLength: 5, lastLine: 1, lastLineRange: "{9, 5}" }, { id: "ta4", label: "leading newline", value: "one \ntwo three", caret: 5, lineString: "two three", lineLength: 9 }, { id: "ta5", label: "only a newline, caret after it", value: "\n", caret: 1, lineString: "", lineLength: 0 }, { id: "ta6", label: "only a newline, caret before it", value: "\n", caret: 0, lineString: "\n", lineLength: 1 }, @@ -56,7 +59,7 @@ } // expect() evaluates its expressions in its own scope, so the values it reads are globals. -var axField, caret, lineRange, lineEnd, webArea; +var axField, caret, lineRange, lineEnd, webArea, lastLine, indexPastEnd; // The id of the text control the document's accessibility selection currently sits in, or null. function idOfFieldHoldingAccessibilitySelection() { @@ -106,6 +109,12 @@ // the caret is. Otherwise the line names a character past the end of the value. if (!testCase.lineLength) output += expect("lineEnd.isEqual(caret)", "true"); + if (testCase.lastLineRange) { + lastLine = testCase.lastLine; + indexPastEnd = testCase.value.length; + output += expect("axField.rangeForLine(lastLine)", JSON.stringify(testCase.lastLineRange)); + output += expect("axField.lineForIndex(indexPastEnd)", "-1"); + } output += "\n"; } diff --git a/LayoutTests/accessibility/mac/mixed-checkbox-expected.txt b/LayoutTests/accessibility/mixed-checkbox-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/mixed-checkbox-expected.txt rename to LayoutTests/accessibility/mixed-checkbox-expected.txt diff --git a/LayoutTests/accessibility/mac/mixed-checkbox.html b/LayoutTests/accessibility/mixed-checkbox.html similarity index 96% rename from LayoutTests/accessibility/mac/mixed-checkbox.html rename to LayoutTests/accessibility/mixed-checkbox.html index 7c5a4708657f..4523e03e413b 100644 --- a/LayoutTests/accessibility/mac/mixed-checkbox.html +++ b/LayoutTests/accessibility/mixed-checkbox.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> </head> <body id="body"> diff --git a/LayoutTests/accessibility/mac/native-vs-nonnative-checkboxes-expected.txt b/LayoutTests/accessibility/native-vs-nonnative-checkboxes-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/native-vs-nonnative-checkboxes-expected.txt rename to LayoutTests/accessibility/native-vs-nonnative-checkboxes-expected.txt diff --git a/LayoutTests/accessibility/mac/native-vs-nonnative-checkboxes.html b/LayoutTests/accessibility/native-vs-nonnative-checkboxes.html similarity index 91% rename from LayoutTests/accessibility/mac/native-vs-nonnative-checkboxes.html rename to LayoutTests/accessibility/native-vs-nonnative-checkboxes.html index c59cc1cf9c8b..5ae8f25e7cbe 100644 --- a/LayoutTests/accessibility/mac/native-vs-nonnative-checkboxes.html +++ b/LayoutTests/accessibility/native-vs-nonnative-checkboxes.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/nested-modal-expected.txt b/LayoutTests/accessibility/nested-modal-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/nested-modal-expected.txt rename to LayoutTests/accessibility/nested-modal-expected.txt diff --git a/LayoutTests/accessibility/mac/nested-modal.html b/LayoutTests/accessibility/nested-modal.html similarity index 95% rename from LayoutTests/accessibility/mac/nested-modal.html rename to LayoutTests/accessibility/nested-modal.html index baa99cd949e5..0e17f3ad3b9d 100644 --- a/LayoutTests/accessibility/mac/nested-modal.html +++ b/LayoutTests/accessibility/nested-modal.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <style> *[role="dialog"] { border: 1px solid black; padding: 1em; margin: 1em; } diff --git a/LayoutTests/accessibility/mac/offset-from-root-outside-text-run-expected.txt b/LayoutTests/accessibility/offset-from-root-outside-text-run-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/offset-from-root-outside-text-run-expected.txt rename to LayoutTests/accessibility/offset-from-root-outside-text-run-expected.txt diff --git a/LayoutTests/accessibility/mac/offset-from-root-outside-text-run.html b/LayoutTests/accessibility/offset-from-root-outside-text-run.html similarity index 89% rename from LayoutTests/accessibility/mac/offset-from-root-outside-text-run.html rename to LayoutTests/accessibility/offset-from-root-outside-text-run.html index d16f8de5ea8a..1f5273d571dc 100644 --- a/LayoutTests/accessibility/mac/offset-from-root-outside-text-run.html +++ b/LayoutTests/accessibility/offset-from-root-outside-text-run.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/prefers-non-blinking-cursor-expected.txt b/LayoutTests/accessibility/prefers-non-blinking-cursor-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/prefers-non-blinking-cursor-expected.txt rename to LayoutTests/accessibility/prefers-non-blinking-cursor-expected.txt diff --git a/LayoutTests/accessibility/mac/prefers-non-blinking-cursor.html b/LayoutTests/accessibility/prefers-non-blinking-cursor.html similarity index 92% rename from LayoutTests/accessibility/mac/prefers-non-blinking-cursor.html rename to LayoutTests/accessibility/prefers-non-blinking-cursor.html index d91d9821f393..cef87e94f95c 100644 --- a/LayoutTests/accessibility/mac/prefers-non-blinking-cursor.html +++ b/LayoutTests/accessibility/prefers-non-blinking-cursor.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/progress-element-min-max-expected.txt b/LayoutTests/accessibility/progress-element-min-max-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/progress-element-min-max-expected.txt rename to LayoutTests/accessibility/progress-element-min-max-expected.txt diff --git a/LayoutTests/accessibility/mac/progress-element-min-max.html b/LayoutTests/accessibility/progress-element-min-max.html similarity index 92% rename from LayoutTests/accessibility/mac/progress-element-min-max.html rename to LayoutTests/accessibility/progress-element-min-max.html index 1b7a77a7d11d..46042418292d 100644 --- a/LayoutTests/accessibility/mac/progress-element-min-max.html +++ b/LayoutTests/accessibility/progress-element-min-max.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> </head> <body id="body"> diff --git a/LayoutTests/accessibility/mac/radio-button-checkbox-size-expected.txt b/LayoutTests/accessibility/radio-button-checkbox-size-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/radio-button-checkbox-size-expected.txt rename to LayoutTests/accessibility/radio-button-checkbox-size-expected.txt diff --git a/LayoutTests/accessibility/mac/radio-button-checkbox-size.html b/LayoutTests/accessibility/radio-button-checkbox-size.html similarity index 93% rename from LayoutTests/accessibility/mac/radio-button-checkbox-size.html rename to LayoutTests/accessibility/radio-button-checkbox-size.html index ecb548472633..fa8602f23213 100644 --- a/LayoutTests/accessibility/mac/radio-button-checkbox-size.html +++ b/LayoutTests/accessibility/radio-button-checkbox-size.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/range-from-webarea-expected.txt b/LayoutTests/accessibility/range-from-webarea-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/range-from-webarea-expected.txt rename to LayoutTests/accessibility/range-from-webarea-expected.txt diff --git a/LayoutTests/accessibility/mac/range-from-webarea.html b/LayoutTests/accessibility/range-from-webarea.html similarity index 91% rename from LayoutTests/accessibility/mac/range-from-webarea.html rename to LayoutTests/accessibility/range-from-webarea.html index a8974a664df6..a5a6cdaaf67b 100644 --- a/LayoutTests/accessibility/mac/range-from-webarea.html +++ b/LayoutTests/accessibility/range-from-webarea.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML> <html> <head> -<script src="../../resources/js-test.js"></script> -<script src="../../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> </head> <body id="body"> diff --git a/LayoutTests/accessibility/mac/replace-text-with-empty-range-expected.txt b/LayoutTests/accessibility/replace-text-with-empty-range-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/replace-text-with-empty-range-expected.txt rename to LayoutTests/accessibility/replace-text-with-empty-range-expected.txt diff --git a/LayoutTests/accessibility/mac/replace-text-with-empty-range.html b/LayoutTests/accessibility/replace-text-with-empty-range.html similarity index 96% rename from LayoutTests/accessibility/mac/replace-text-with-empty-range.html rename to LayoutTests/accessibility/replace-text-with-empty-range.html index f6995d593a85..fdeed416998a 100644 --- a/LayoutTests/accessibility/mac/replace-text-with-empty-range.html +++ b/LayoutTests/accessibility/replace-text-with-empty-range.html @@ -1,7 +1,7 @@ <!DOCTYPE html> <html> <head> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> </head> <body id="body"> diff --git a/LayoutTests/accessibility/mac/replace-text-with-range-expected.txt b/LayoutTests/accessibility/replace-text-with-range-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/replace-text-with-range-expected.txt rename to LayoutTests/accessibility/replace-text-with-range-expected.txt diff --git a/LayoutTests/accessibility/mac/replace-text-with-range-on-webarea-element-expected.txt b/LayoutTests/accessibility/replace-text-with-range-on-webarea-element-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/replace-text-with-range-on-webarea-element-expected.txt rename to LayoutTests/accessibility/replace-text-with-range-on-webarea-element-expected.txt diff --git a/LayoutTests/accessibility/mac/replace-text-with-range-on-webarea-element.html b/LayoutTests/accessibility/replace-text-with-range-on-webarea-element.html similarity index 92% rename from LayoutTests/accessibility/mac/replace-text-with-range-on-webarea-element.html rename to LayoutTests/accessibility/replace-text-with-range-on-webarea-element.html index e856088f9a4e..1005e314de5d 100644 --- a/LayoutTests/accessibility/mac/replace-text-with-range-on-webarea-element.html +++ b/LayoutTests/accessibility/replace-text-with-range-on-webarea-element.html @@ -1,7 +1,7 @@ <!DOCTYPE html> <html> <head> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> </head> <body id="body" contenteditable="true" role="textbox"> hello diff --git a/LayoutTests/accessibility/mac/replace-text-with-range.html b/LayoutTests/accessibility/replace-text-with-range.html similarity index 94% rename from LayoutTests/accessibility/mac/replace-text-with-range.html rename to LayoutTests/accessibility/replace-text-with-range.html index 11301e531010..6982c6bd46a4 100644 --- a/LayoutTests/accessibility/mac/replace-text-with-range.html +++ b/LayoutTests/accessibility/replace-text-with-range.html @@ -1,8 +1,8 @@ <!DOCTYPE html> <html> <head> -<script src="../../resources/js-test.js"></script> -<script src="../../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> </head> <body id="body"> <div id="content"> diff --git a/LayoutTests/accessibility/mac/search-predicate-visited-links-expected.txt b/LayoutTests/accessibility/search-predicate-visited-links-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/search-predicate-visited-links-expected.txt rename to LayoutTests/accessibility/search-predicate-visited-links-expected.txt diff --git a/LayoutTests/accessibility/mac/search-predicate-visited-links.html b/LayoutTests/accessibility/search-predicate-visited-links.html similarity index 96% rename from LayoutTests/accessibility/mac/search-predicate-visited-links.html rename to LayoutTests/accessibility/search-predicate-visited-links.html index d9e6d90375df..07698f3083e6 100644 --- a/LayoutTests/accessibility/mac/search-predicate-visited-links.html +++ b/LayoutTests/accessibility/search-predicate-visited-links.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body id="body"> <a id="link" href="#image">link</a> diff --git a/LayoutTests/accessibility/shadow-dom/reference-target/mac/aria-controls-expected.txt b/LayoutTests/accessibility/shadow-dom/reference-target/aria-controls-expected.txt similarity index 100% rename from LayoutTests/accessibility/shadow-dom/reference-target/mac/aria-controls-expected.txt rename to LayoutTests/accessibility/shadow-dom/reference-target/aria-controls-expected.txt diff --git a/LayoutTests/accessibility/shadow-dom/reference-target/mac/aria-controls.html b/LayoutTests/accessibility/shadow-dom/reference-target/aria-controls.html similarity index 91% rename from LayoutTests/accessibility/shadow-dom/reference-target/mac/aria-controls.html rename to LayoutTests/accessibility/shadow-dom/reference-target/aria-controls.html index 123478b14da5..3654075d1e6b 100644 --- a/LayoutTests/accessibility/shadow-dom/reference-target/mac/aria-controls.html +++ b/LayoutTests/accessibility/shadow-dom/reference-target/aria-controls.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML> <html> <head> - <script src="../../../../resources/accessibility-helper.js"></script> - <script src="../../../../resources/js-test.js"></script> + <script src="../../../resources/accessibility-helper.js"></script> + <script src="../../../resources/js-test.js"></script> </head> <body> <ul id="tablist-1" role="tablist"> diff --git a/LayoutTests/accessibility/shadow-dom/reference-target/mac/aria-describedby-expected.txt b/LayoutTests/accessibility/shadow-dom/reference-target/aria-describedby-expected.txt similarity index 100% rename from LayoutTests/accessibility/shadow-dom/reference-target/mac/aria-describedby-expected.txt rename to LayoutTests/accessibility/shadow-dom/reference-target/aria-describedby-expected.txt diff --git a/LayoutTests/accessibility/shadow-dom/reference-target/mac/aria-describedby.html b/LayoutTests/accessibility/shadow-dom/reference-target/aria-describedby.html similarity index 84% rename from LayoutTests/accessibility/shadow-dom/reference-target/mac/aria-describedby.html rename to LayoutTests/accessibility/shadow-dom/reference-target/aria-describedby.html index 4fae7f5eed56..8ac75e0e76d4 100644 --- a/LayoutTests/accessibility/shadow-dom/reference-target/mac/aria-describedby.html +++ b/LayoutTests/accessibility/shadow-dom/reference-target/aria-describedby.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML> <html> <head> - <script src="../../../../resources/accessibility-helper.js"></script> - <script src="../../../../resources/js-test.js"></script> + <script src="../../../resources/accessibility-helper.js"></script> + <script src="../../../resources/js-test.js"></script> </head> <body> <div class="container"> diff --git a/LayoutTests/accessibility/mac/stitched-text-marker-range-for-ui-element-expected.txt b/LayoutTests/accessibility/stitched-text-marker-range-for-ui-element-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/stitched-text-marker-range-for-ui-element-expected.txt rename to LayoutTests/accessibility/stitched-text-marker-range-for-ui-element-expected.txt diff --git a/LayoutTests/accessibility/mac/stitched-text-marker-range-for-ui-element.html b/LayoutTests/accessibility/stitched-text-marker-range-for-ui-element.html similarity index 91% rename from LayoutTests/accessibility/mac/stitched-text-marker-range-for-ui-element.html rename to LayoutTests/accessibility/stitched-text-marker-range-for-ui-element.html index ad44d34dd898..96472f361b70 100644 --- a/LayoutTests/accessibility/mac/stitched-text-marker-range-for-ui-element.html +++ b/LayoutTests/accessibility/stitched-text-marker-range-for-ui-element.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML><!-- webkit-test-runner [ AccessibilityTextStitchingEnabled=true ] --> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/string-range-contains-listmarker-expected.txt b/LayoutTests/accessibility/string-range-contains-listmarker-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/string-range-contains-listmarker-expected.txt rename to LayoutTests/accessibility/string-range-contains-listmarker-expected.txt diff --git a/LayoutTests/accessibility/mac/string-range-contains-listmarker.html b/LayoutTests/accessibility/string-range-contains-listmarker.html similarity index 96% rename from LayoutTests/accessibility/mac/string-range-contains-listmarker.html rename to LayoutTests/accessibility/string-range-contains-listmarker.html index dbcd06b6ee4f..7031e58eb354 100644 --- a/LayoutTests/accessibility/mac/string-range-contains-listmarker.html +++ b/LayoutTests/accessibility/string-range-contains-listmarker.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><!-- webkit-test-runner [ runSingly=true AccessibilityTextStitchingEnabled=false ] --> <html> <head> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> </head> <body id="body"> diff --git a/LayoutTests/accessibility/mac/submit-button-default-value-expected.txt b/LayoutTests/accessibility/submit-button-default-value-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/submit-button-default-value-expected.txt rename to LayoutTests/accessibility/submit-button-default-value-expected.txt diff --git a/LayoutTests/accessibility/mac/submit-button-default-value.html b/LayoutTests/accessibility/submit-button-default-value.html similarity index 86% rename from LayoutTests/accessibility/mac/submit-button-default-value.html rename to LayoutTests/accessibility/submit-button-default-value.html index 88ac4415a333..7161ddf73c15 100644 --- a/LayoutTests/accessibility/mac/submit-button-default-value.html +++ b/LayoutTests/accessibility/submit-button-default-value.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/svg-duplicated-expected.txt b/LayoutTests/accessibility/svg-duplicated-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/svg-duplicated-expected.txt rename to LayoutTests/accessibility/svg-duplicated-expected.txt diff --git a/LayoutTests/accessibility/svg-duplicated.html b/LayoutTests/accessibility/svg-duplicated.html new file mode 100644 index 000000000000..c8640915187e --- /dev/null +++ b/LayoutTests/accessibility/svg-duplicated.html @@ -0,0 +1,24 @@ +<!DOCTYPE HTML> +<html> +<head> +<script src="../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +</head> +<body id="body" role="group"> + +Hello +<img src="resources/svg-face.svg" alt="image 1" /> +<img src="resources/svg-face.svg" alt="image 2" /> +world + +<script> +var output = "This test verifies that we don't enter an infinite loop when we have two references to the same remote SVG file.\n"; + +if (window.accessibilityController) { + var container = accessibilityController.accessibleElementById("body"); + container.textMarkerRangeForElement(container) + debug(output); +} +</script> +</body> +</html> diff --git a/LayoutTests/accessibility/svg-text.html b/LayoutTests/accessibility/svg-text.html index 655078470c82..629d9579b10d 100644 --- a/LayoutTests/accessibility/svg-text.html +++ b/LayoutTests/accessibility/svg-text.html @@ -17,11 +17,17 @@ if (window.accessibilityController) { text1 = accessibilityController.accessibleElementById("text1"); - shouldBe("text1.role", "'AXRole: AXGroup'"); - shouldBe("text1.childrenCount", "1"); - staticText = text1.childAtIndex(0); - shouldBe("staticText.role", "'AXRole: AXStaticText'"); - shouldBe("staticText.stringValue", "'AXValue: Hello World!'"); + if (accessibilityController.platformName == "atspi") { + shouldBe("text1.role", "'AXRole: AXSection'"); + shouldBe("text1.childrenCount", "0"); + shouldBe("text1.stringValue", "'AXValue: Hello World!'"); + } else { + shouldBe("text1.role", "'AXRole: AXGroup'"); + shouldBe("text1.childrenCount", "1"); + staticText = text1.childAtIndex(0); + shouldBe("staticText.role", "'AXRole: AXStaticText'"); + shouldBe("staticText.stringValue", "'AXValue: Hello World!'"); + } } </script> </body> diff --git a/LayoutTests/accessibility/mac/text-marker-emitted-newlines-expected.txt b/LayoutTests/accessibility/text-marker-emitted-newlines-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/text-marker-emitted-newlines-expected.txt rename to LayoutTests/accessibility/text-marker-emitted-newlines-expected.txt diff --git a/LayoutTests/accessibility/mac/text-marker-emitted-newlines.html b/LayoutTests/accessibility/text-marker-emitted-newlines.html similarity index 97% rename from LayoutTests/accessibility/mac/text-marker-emitted-newlines.html rename to LayoutTests/accessibility/text-marker-emitted-newlines.html index ce0427fe50c1..d6587c49b195 100644 --- a/LayoutTests/accessibility/mac/text-marker-emitted-newlines.html +++ b/LayoutTests/accessibility/text-marker-emitted-newlines.html @@ -2,7 +2,7 @@ <html> <head> <meta charset="utf-8"> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> </head> <body id="body"> diff --git a/LayoutTests/accessibility/mac/text-marker-length-expected.txt b/LayoutTests/accessibility/text-marker-length-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/text-marker-length-expected.txt rename to LayoutTests/accessibility/text-marker-length-expected.txt diff --git a/LayoutTests/accessibility/mac/text-marker-length.html b/LayoutTests/accessibility/text-marker-length.html similarity index 81% rename from LayoutTests/accessibility/mac/text-marker-length.html rename to LayoutTests/accessibility/text-marker-length.html index 2a549a376299..b79b3b8333e7 100644 --- a/LayoutTests/accessibility/mac/text-marker-length.html +++ b/LayoutTests/accessibility/text-marker-length.html @@ -1,8 +1,8 @@ <!DOCTYPE HTML> <html> <head> -<script src="../../resources/accessibility-helper.js"></script> -<script src="../../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> </head> <body> diff --git a/LayoutTests/accessibility/mac/text-marker-string-excludes-generated-content-expected.txt b/LayoutTests/accessibility/text-marker-string-excludes-generated-content-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/text-marker-string-excludes-generated-content-expected.txt rename to LayoutTests/accessibility/text-marker-string-excludes-generated-content-expected.txt diff --git a/LayoutTests/accessibility/mac/text-marker-string-excludes-generated-content.html b/LayoutTests/accessibility/text-marker-string-excludes-generated-content.html similarity index 88% rename from LayoutTests/accessibility/mac/text-marker-string-excludes-generated-content.html rename to LayoutTests/accessibility/text-marker-string-excludes-generated-content.html index fa1f67565033..7f0fef1c06f9 100644 --- a/LayoutTests/accessibility/mac/text-marker-string-excludes-generated-content.html +++ b/LayoutTests/accessibility/text-marker-string-excludes-generated-content.html @@ -1,8 +1,8 @@ <!doctype html><!-- webkit-test-runner [ runSingly=true AccessibilityTextStitchingEnabled=false ] --> <html> <head> -<script src="../../resources/js-test.js"></script> -<script src="../../resources/accessibility-helper.js"></script> +<script src="../resources/js-test.js"></script> +<script src="../resources/accessibility-helper.js"></script> <style> #target::before { content: "before-"; } #target::after { content: "-after"; } diff --git a/LayoutTests/accessibility/text-marker/textarea-character-count-with-trailing-newline-expected.txt b/LayoutTests/accessibility/text-marker/textarea-character-count-with-trailing-newline-expected.txt index 26fcee16e477..e6d19e535bd8 100644 --- a/LayoutTests/accessibility/text-marker/textarea-character-count-with-trailing-newline-expected.txt +++ b/LayoutTests/accessibility/text-marker/textarea-character-count-with-trailing-newline-expected.txt @@ -18,6 +18,12 @@ PASS: axField.numberOfCharacters === 3 whitespace either side of a newline: value " \n " PASS: axField.numberOfCharacters === 5 +trailing newline, typed: value "one two \n" +PASS: axField.numberOfCharacters === 9 + +two trailing newlines, typed: value "a\n\n" +PASS: axField.numberOfCharacters === 3 + ARIA textbox, trailing newline: value "one two \n" PASS: axField.numberOfCharacters === 9 diff --git a/LayoutTests/accessibility/text-marker/textarea-character-count-with-trailing-newline.html b/LayoutTests/accessibility/text-marker/textarea-character-count-with-trailing-newline.html index 828ae8e18bbd..cb7112f20698 100644 --- a/LayoutTests/accessibility/text-marker/textarea-character-count-with-trailing-newline.html +++ b/LayoutTests/accessibility/text-marker/textarea-character-count-with-trailing-newline.html @@ -19,6 +19,12 @@ <textarea id="ta3" style="width: 200px; height: 60px"></textarea> <textarea id="ta4" style="width: 200px; height: 60px"></textarea> <textarea id="ta5" style="width: 200px; height: 60px"></textarea> +<!-- These two are typed into after the accessibility tree exists. Editing represents the newline that + rendering adds for the empty final line as a text node holding just that newline, where + setInnerTextValue appends a placeholder <br> — the value's character count must come out the same + either way. --> +<textarea id="ta6" style="width: 200px; height: 60px"></textarea> +<textarea id="ta7" style="width: 200px; height: 60px"></textarea> <!-- An ARIA textbox is not a native text control: its text has no collapsed trailing line break, so a trailing newline in it *is* a character and must still be counted. --> <div id="ce0" contenteditable role="textbox" style="width: 200px; white-space: pre-wrap"></div> @@ -36,11 +42,15 @@ { id: "ta3", label: "only a newline", value: "\n" }, { id: "ta4", label: "two trailing newlines", value: "a\n\n" }, { id: "ta5", label: "whitespace either side of a newline", value: " \n " }, + { id: "ta6", label: "trailing newline, typed", value: "one two \n", typed: true }, + { id: "ta7", label: "two trailing newlines, typed", value: "a\n\n", typed: true }, { id: "ce0", label: "ARIA textbox, trailing newline", value: "one two \n" }, ]; for (var testCase of cases) { var field = document.getElementById(testCase.id); + if (testCase.typed) + continue; if (field.isContentEditable) field.textContent = testCase.value; else @@ -50,7 +60,23 @@ } // expect() evaluates its expressions in its own scope, so the values it reads are globals. -var axField; +var axField, webArea; + +// Types |value| one character at a time, the way a user would, then waits for the accessibility tree +// to catch up. The selection notification is a safe gate for the value: the two are updated together. +async function typeValue(field, value) { + field.focus(); + for (var character of value) { + if (character === "\n") + document.execCommand("insertLineBreak"); + else + document.execCommand("insertText", false, character); + } + // Typing leaves the caret at the end, so moving it to the start is always a real change. + await waitForNotification(webArea, "AXSelectedTextChanged", () => { + field.setSelectionRange(0, 0); + }); +} async function checkCase(testCase) { axField = await waitForElementById(testCase.id); @@ -59,6 +85,9 @@ return; } + if (testCase.typed) + await typeValue(document.getElementById(testCase.id), testCase.value); + output += `${testCase.label}: value ${JSON.stringify(testCase.value)}\n`; output += expect("axField.numberOfCharacters", `${testCase.value.length}`); output += "\n"; @@ -68,6 +97,8 @@ window.jsTestIsAsync = true; setTimeout(async function() { + webArea = accessibilityController.rootElement.childAtIndex(0); + for (var testCase of cases) await checkCase(testCase); diff --git a/LayoutTests/accessibility/mac/text-markers-for-input-with-placeholder-expected.txt b/LayoutTests/accessibility/text-markers-for-input-with-placeholder-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/text-markers-for-input-with-placeholder-expected.txt rename to LayoutTests/accessibility/text-markers-for-input-with-placeholder-expected.txt diff --git a/LayoutTests/accessibility/mac/text-markers-for-input-with-placeholder.html b/LayoutTests/accessibility/text-markers-for-input-with-placeholder.html similarity index 95% rename from LayoutTests/accessibility/mac/text-markers-for-input-with-placeholder.html rename to LayoutTests/accessibility/text-markers-for-input-with-placeholder.html index 10bfaeed6769..baf2fa6dac2c 100644 --- a/LayoutTests/accessibility/mac/text-markers-for-input-with-placeholder.html +++ b/LayoutTests/accessibility/text-markers-for-input-with-placeholder.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> </head> <body id="body"> diff --git a/LayoutTests/accessibility/mac/updating-attribute-in-table-row-crash-expected.txt b/LayoutTests/accessibility/updating-attribute-in-table-row-crash-expected.txt similarity index 100% rename from LayoutTests/accessibility/mac/updating-attribute-in-table-row-crash-expected.txt rename to LayoutTests/accessibility/updating-attribute-in-table-row-crash-expected.txt diff --git a/LayoutTests/accessibility/mac/updating-attribute-in-table-row-crash.html b/LayoutTests/accessibility/updating-attribute-in-table-row-crash.html similarity index 96% rename from LayoutTests/accessibility/mac/updating-attribute-in-table-row-crash.html rename to LayoutTests/accessibility/updating-attribute-in-table-row-crash.html index bb578c46af51..443bfe11e393 100644 --- a/LayoutTests/accessibility/mac/updating-attribute-in-table-row-crash.html +++ b/LayoutTests/accessibility/updating-attribute-in-table-row-crash.html @@ -1,7 +1,7 @@ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> -<script src="../../resources/js-test.js"></script> +<script src="../resources/js-test.js"></script> </head> <body id="body"> diff --git a/LayoutTests/animations/transform-percent-in-single-keyframe-with-height-expected.html b/LayoutTests/animations/transform-percent-in-single-keyframe-with-height-expected.html new file mode 100644 index 000000000000..5c39b9011b23 --- /dev/null +++ b/LayoutTests/animations/transform-percent-in-single-keyframe-with-height-expected.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<style> + +div { + width: 10px; + height: 200px; + background-color: black; + transform: translateY(50%); +} + +</style> +</head> +<body> +<div></div> +</body> +</html> diff --git a/LayoutTests/animations/transform-percent-in-single-keyframe-with-height.html b/LayoutTests/animations/transform-percent-in-single-keyframe-with-height.html new file mode 100644 index 000000000000..4809fb58b282 --- /dev/null +++ b/LayoutTests/animations/transform-percent-in-single-keyframe-with-height.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html> +<head> +<title>Animating the "transform" property with a percent value in a single keyframe while also animating "height" + + + +
+ + + diff --git a/LayoutTests/animations/transform-percent-in-single-keyframe-with-width-expected.html b/LayoutTests/animations/transform-percent-in-single-keyframe-with-width-expected.html new file mode 100644 index 000000000000..199fc664a3d9 --- /dev/null +++ b/LayoutTests/animations/transform-percent-in-single-keyframe-with-width-expected.html @@ -0,0 +1,18 @@ + + + + + + +
+ + diff --git a/LayoutTests/animations/transform-percent-in-single-keyframe-with-width.html b/LayoutTests/animations/transform-percent-in-single-keyframe-with-width.html new file mode 100644 index 000000000000..e419e3f0a3de --- /dev/null +++ b/LayoutTests/animations/transform-percent-in-single-keyframe-with-width.html @@ -0,0 +1,48 @@ + + + +Animating the "transform" property with a percent value in a single keyframe while also animating "width" + + + +
+ + + diff --git a/LayoutTests/compositing/corner-shape-ancestor-clip-expected.html b/LayoutTests/compositing/corner-shape-ancestor-clip-expected.html new file mode 100644 index 000000000000..f98766d466f3 --- /dev/null +++ b/LayoutTests/compositing/corner-shape-ancestor-clip-expected.html @@ -0,0 +1,37 @@ + + + + CSS Borders and Box Decorations 4: 'corner-shape' on a non-composited ancestor clips a composited descendant — reference + + + + +
+
+
+ + diff --git a/LayoutTests/compositing/corner-shape-ancestor-clip-shape-mask-expected.txt b/LayoutTests/compositing/corner-shape-ancestor-clip-shape-mask-expected.txt new file mode 100644 index 000000000000..dfb8ebc77f6a --- /dev/null +++ b/LayoutTests/compositing/corner-shape-ancestor-clip-shape-mask-expected.txt @@ -0,0 +1,6 @@ +round: mask layer absent, expected absent - PASS +square: mask layer absent, expected absent - PASS +scoop: mask layer present, expected present - PASS +bevel: mask layer present, expected present - PASS +notch: mask layer present, expected present - PASS +superellipse(-2): mask layer present, expected present - PASS diff --git a/LayoutTests/compositing/corner-shape-ancestor-clip-shape-mask.html b/LayoutTests/compositing/corner-shape-ancestor-clip-shape-mask.html new file mode 100644 index 000000000000..d7e7a95dedda --- /dev/null +++ b/LayoutTests/compositing/corner-shape-ancestor-clip-shape-mask.html @@ -0,0 +1,86 @@ + + + + + corner-shape: an ancestor clip needs a shape mask layer only when it is not a rounded rect + + + + +
+
+
+
+
+
+

+
+
diff --git a/LayoutTests/compositing/corner-shape-ancestor-clip.html b/LayoutTests/compositing/corner-shape-ancestor-clip.html
new file mode 100644
index 000000000000..413c5701c53a
--- /dev/null
+++ b/LayoutTests/compositing/corner-shape-ancestor-clip.html
@@ -0,0 +1,38 @@
+
+
+
+    CSS Borders and Box Decorations 4: 'corner-shape' on a non-composited ancestor clips a composited descendant
+    
+    
+
+
+    
+
+
+ + diff --git a/LayoutTests/compositing/corner-shape-mask-layer-offset-expected.html b/LayoutTests/compositing/corner-shape-mask-layer-offset-expected.html new file mode 100644 index 000000000000..af643b271268 --- /dev/null +++ b/LayoutTests/compositing/corner-shape-mask-layer-offset-expected.html @@ -0,0 +1,38 @@ + + + + + CSS Borders and Box Decorations 4: 'corner-shape' mask is positioned by the mask layer's offset from the renderer — reference + + + +
+
+
+ + diff --git a/LayoutTests/compositing/corner-shape-mask-layer-offset.html b/LayoutTests/compositing/corner-shape-mask-layer-offset.html new file mode 100644 index 000000000000..7408c97c30de --- /dev/null +++ b/LayoutTests/compositing/corner-shape-mask-layer-offset.html @@ -0,0 +1,41 @@ + + + + + CSS Borders and Box Decorations 4: 'corner-shape' mask is positioned by the mask layer's offset from the renderer + + + + +
+
+
+ + diff --git a/LayoutTests/compositing/geometry/composited-bounds-single-axis-clip-taller-than-document-expected.txt b/LayoutTests/compositing/geometry/composited-bounds-single-axis-clip-taller-than-document-expected.txt new file mode 100644 index 000000000000..54c34f4b6b77 --- /dev/null +++ b/LayoutTests/compositing/geometry/composited-bounds-single-axis-clip-taller-than-document-expected.txt @@ -0,0 +1,36 @@ +Bug 295662: a backing-sharing layer clipped in one axis but taller than the document (as in a code editor with a large paste) must get bounds covering its full content, not clamped to the document rect. + +(GraphicsLayer + (anchor 0.00 0.00) + (bounds 800.00 600.00) + (children 1 + (GraphicsLayer + (bounds 800.00 600.00) + (contentsOpaque 1) + (children 2 + (GraphicsLayer + (position 8.00 68.00) + (bounds 400.00 300.00) + (children 1 + (GraphicsLayer + (bounds 50.00 50.00) + ) + ) + ) + (GraphicsLayer + (position 8.00 68.00) + (bounds 400.00 300.00) + (children 1 + (GraphicsLayer + (position 20.00 20.00) + (bounds 300.00 3000.00) + (usingTiledLayer 1) + (drawsContent 1) + ) + ) + ) + ) + ) + ) +) + diff --git a/LayoutTests/compositing/geometry/composited-bounds-single-axis-clip-taller-than-document.html b/LayoutTests/compositing/geometry/composited-bounds-single-axis-clip-taller-than-document.html new file mode 100644 index 000000000000..0376cc6590e5 --- /dev/null +++ b/LayoutTests/compositing/geometry/composited-bounds-single-axis-clip-taller-than-document.html @@ -0,0 +1,62 @@ + + + + + + + + +

Bug 295662: a backing-sharing layer clipped in one axis but taller than the document (as in a code editor with a large paste) must get bounds covering its full content, not clamped to the document rect.

+
+
+
+
+

+
+
diff --git a/LayoutTests/compositing/geometry/composited-bounds-with-single-axis-clip-expected.txt b/LayoutTests/compositing/geometry/composited-bounds-with-single-axis-clip-expected.txt
index fee2b4939c61..5035e0578d8e 100644
--- a/LayoutTests/compositing/geometry/composited-bounds-with-single-axis-clip-expected.txt
+++ b/LayoutTests/compositing/geometry/composited-bounds-with-single-axis-clip-expected.txt
@@ -14,7 +14,7 @@ Bug 295662: a backing-sharing layer whose clip comes from an ancestor that clips
         )
         (GraphicsLayer
           (position 20.00 20.00)
-          (bounds 300.00 600.00)
+          (bounds 300.00 300.00)
           (drawsContent 1)
         )
       )
diff --git a/LayoutTests/compositing/video/corner-shape-video-expected.html b/LayoutTests/compositing/video/corner-shape-video-expected.html
new file mode 100644
index 000000000000..0409b6aa407c
--- /dev/null
+++ b/LayoutTests/compositing/video/corner-shape-video-expected.html
@@ -0,0 +1,39 @@
+
+
+
+    CSS Borders and Box Decorations 4: 'corner-shape' on a composited video
+    
+
+
+    
+
+
+ + diff --git a/LayoutTests/compositing/video/corner-shape-video-overflow-expected.html b/LayoutTests/compositing/video/corner-shape-video-overflow-expected.html new file mode 100644 index 000000000000..920561a1f235 --- /dev/null +++ b/LayoutTests/compositing/video/corner-shape-video-overflow-expected.html @@ -0,0 +1,42 @@ + + + + CSS Borders and Box Decorations 4: 'corner-shape' clipping a composited video with overflow + + + +
+
+
+ + diff --git a/LayoutTests/compositing/video/corner-shape-video-overflow.html b/LayoutTests/compositing/video/corner-shape-video-overflow.html new file mode 100644 index 000000000000..046b488e6e85 --- /dev/null +++ b/LayoutTests/compositing/video/corner-shape-video-overflow.html @@ -0,0 +1,74 @@ + + + + CSS Borders and Box Decorations 4: 'corner-shape' clipping a composited video with overflow + + + + + + + + +
+ +
+
+ + diff --git a/LayoutTests/compositing/video/corner-shape-video.html b/LayoutTests/compositing/video/corner-shape-video.html new file mode 100644 index 000000000000..c4bd185cfba2 --- /dev/null +++ b/LayoutTests/compositing/video/corner-shape-video.html @@ -0,0 +1,70 @@ + + + + CSS Borders and Box Decorations 4: 'corner-shape' on a composited video + + + + + + + +
+ +
+
+ + diff --git a/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes-expected.txt b/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes-expected.txt index 2571323c3213..ea2a871b5088 100644 --- a/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes-expected.txt +++ b/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes-expected.txt @@ -1,10 +1,10 @@ -This tests indenting into a new blockquote when blockquotes are styled as -webkit-user-select: all. -webkit-user-select: all causes the new blockquote element to be uneditable so we should bail out and not indent. +This tests indenting into a new blockquote when blockquotes are styled as -webkit-user-select: all. Editability now overrides user-select: all, so the new blockquote is editable and the indent goes through. See indent-user-select-all-blockquotes-legacy.html for the legacy behavior, where user-select: all made the blockquote uneditable and we bailed out instead of indenting. On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". -PASS No blockquote created +PASS Blockquote created PASS successfullyParsed is true TEST COMPLETE diff --git a/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes-legacy-expected.txt b/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes-legacy-expected.txt new file mode 100644 index 000000000000..2571323c3213 --- /dev/null +++ b/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes-legacy-expected.txt @@ -0,0 +1,11 @@ +This tests indenting into a new blockquote when blockquotes are styled as -webkit-user-select: all. -webkit-user-select: all causes the new blockquote element to be uneditable so we should bail out and not indent. + + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS No blockquote created +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes-legacy.html b/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes-legacy.html new file mode 100644 index 000000000000..683bf002ccda --- /dev/null +++ b/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes-legacy.html @@ -0,0 +1,35 @@ + + + + + + +

+ + + + diff --git a/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes.html b/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes.html index c92236a2b65a..76f0ca9b562b 100644 --- a/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes.html +++ b/LayoutTests/editing/execCommand/indent-user-select-all-blockquotes.html @@ -9,9 +9,9 @@

- + - diff --git a/LayoutTests/editing/input/compositionend-after-clicking-outside-editable-expected.txt b/LayoutTests/editing/input/compositionend-after-clicking-outside-editable-expected.txt new file mode 100644 index 000000000000..bad64e51bf69 --- /dev/null +++ b/LayoutTests/editing/input/compositionend-after-clicking-outside-editable-expected.txt @@ -0,0 +1,11 @@ +Some text.^ +Not editable. +compositionstart target=editable data="" +compositionupdate target=editable data="^" +compositionend target=editable data="^" +activeElement=BODY +editable.textContent="Some text.^" +hasMarkedText=false + +PASS compositionend is fired on the editable element when a composition is aborted by clicking outside of it + diff --git a/LayoutTests/editing/input/compositionend-after-clicking-outside-editable.html b/LayoutTests/editing/input/compositionend-after-clicking-outside-editable.html new file mode 100644 index 000000000000..341e29361374 --- /dev/null +++ b/LayoutTests/editing/input/compositionend-after-clicking-outside-editable.html @@ -0,0 +1,49 @@ + + + + + + + +
Some text.
+
Not editable.
+

+
+
+
diff --git a/LayoutTests/editing/input/compositionend-data-after-aborting-composition-expected.txt b/LayoutTests/editing/input/compositionend-data-after-aborting-composition-expected.txt
new file mode 100644
index 000000000000..833dfefd61c7
--- /dev/null
+++ b/LayoutTests/editing/input/compositionend-data-after-aborting-composition-expected.txt
@@ -0,0 +1,5 @@
+Some text.^
+Not editable.
+
+PASS compositionend reports the text left behind in the document when a composition is aborted
+
diff --git a/LayoutTests/editing/input/compositionend-data-after-aborting-composition.html b/LayoutTests/editing/input/compositionend-data-after-aborting-composition.html
new file mode 100644
index 000000000000..b93ac43ce344
--- /dev/null
+++ b/LayoutTests/editing/input/compositionend-data-after-aborting-composition.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
Some text.
+
Not editable.
+ + + diff --git a/LayoutTests/editing/mac/spelling/context-menu-spelling-guess-input-events-expected.txt b/LayoutTests/editing/mac/spelling/context-menu-spelling-guess-input-events-expected.txt new file mode 100644 index 000000000000..833c96173ffb --- /dev/null +++ b/LayoutTests/editing/mac/spelling/context-menu-spelling-guess-input-events-expected.txt @@ -0,0 +1,23 @@ + +Verifies that choosing a spelling suggestion from the context menu fires beforeinput and input events with inputType 'insertReplacementText' rather than 'insertText'. To manually test, right click the misspelled word and choose one of the suggestions at the top of the menu. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +Rich text (contenteditable): +PASS inputTypes() is "beforeinput=insertReplacementText, input=insertReplacementText" +PASS events[0].data is null +PASS events[0].dataTransfer is non-null. +PASS events[0].cancelable is true +PASS events[1].cancelable is false + +Plain text (input): +PASS inputTypes() is "beforeinput=insertReplacementText, input=insertReplacementText" +PASS events[0].data.length > 0 is true +PASS events[0].dataTransfer is null +PASS events[0].cancelable is true +PASS events[1].cancelable is false +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/editing/mac/spelling/context-menu-spelling-guess-input-events.html b/LayoutTests/editing/mac/spelling/context-menu-spelling-guess-input-events.html new file mode 100644 index 000000000000..3f16c12e9509 --- /dev/null +++ b/LayoutTests/editing/mac/spelling/context-menu-spelling-guess-input-events.html @@ -0,0 +1,95 @@ + + + + + + +
welllcome
+ +

+

+ + + diff --git a/LayoutTests/editing/pasteboard/copy-content-with-user-select-none-expected.txt b/LayoutTests/editing/pasteboard/copy-content-with-user-select-none-expected.txt index bdd4bf96fc2a..6620021ab08a 100644 --- a/LayoutTests/editing/pasteboard/copy-content-with-user-select-none-expected.txt +++ b/LayoutTests/editing/pasteboard/copy-content-with-user-select-none-expected.txt @@ -6,14 +6,14 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE PASS getSelection().toString().includes("hello") is true PASS getSelection().toString().includes("world") is false -PASS getSelection().toString().includes("WebKit") is true +PASS getSelection().toString().includes("WebKit") is false PASS getSelection().toString().includes("rocks") is false PASS getSelection().toString().includes("because") is false PASS getSelection().toString().includes("foo") is false PASS getSelection().toString().includes("bar") is true PASS event.clipboardData.getData("text/plain").includes("hello") is true PASS event.clipboardData.getData("text/plain").includes("world") is false -PASS event.clipboardData.getData("text/plain").includes("WebKit") is true +PASS event.clipboardData.getData("text/plain").includes("WebKit") is false PASS event.clipboardData.getData("text/plain").includes("rocks") is false PASS event.clipboardData.getData("text/plain").includes("beacuse") is false PASS event.clipboardData.getData("text/plain").includes("foo") is false @@ -22,9 +22,9 @@ PASS event.clipboardData.getData("text/html").includes("hello") is true PASS event.clipboardData.getData("text/html").includes("world") is false PASS event.clipboardData.getData("text/html").includes("") is false -PASS event.clipboardData.getData("text/html").includes("WebKit") is true -PASS event.clipboardData.getData("text/html").includes("") is true +PASS event.clipboardData.getData("text/html").includes("WebKit") is false +PASS event.clipboardData.getData("text/html").includes("") is false PASS event.clipboardData.getData("text/html").includes("rocks") is false PASS event.clipboardData.getData("text/html").includes("") is false PASS event.clipboardData.getData("text/html").includes("") is false @@ -39,5 +39,5 @@ PASS successfullyParsed is true TEST COMPLETE hello world WebKit rocks because foo bar -hello WebKit bar +hello bar diff --git a/LayoutTests/editing/pasteboard/copy-content-with-user-select-none-legacy-expected.txt b/LayoutTests/editing/pasteboard/copy-content-with-user-select-none-legacy-expected.txt new file mode 100644 index 000000000000..bdd4bf96fc2a --- /dev/null +++ b/LayoutTests/editing/pasteboard/copy-content-with-user-select-none-legacy-expected.txt @@ -0,0 +1,43 @@ +This tests copying excludes content with user-select: none. +To manually test, copy "hello world foo bar" below then paste. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS getSelection().toString().includes("hello") is true +PASS getSelection().toString().includes("world") is false +PASS getSelection().toString().includes("WebKit") is true +PASS getSelection().toString().includes("rocks") is false +PASS getSelection().toString().includes("because") is false +PASS getSelection().toString().includes("foo") is false +PASS getSelection().toString().includes("bar") is true +PASS event.clipboardData.getData("text/plain").includes("hello") is true +PASS event.clipboardData.getData("text/plain").includes("world") is false +PASS event.clipboardData.getData("text/plain").includes("WebKit") is true +PASS event.clipboardData.getData("text/plain").includes("rocks") is false +PASS event.clipboardData.getData("text/plain").includes("beacuse") is false +PASS event.clipboardData.getData("text/plain").includes("foo") is false +PASS event.clipboardData.getData("text/plain").includes("bar") is true +PASS event.clipboardData.getData("text/html").includes("hello") is true +PASS event.clipboardData.getData("text/html").includes("world") is false +PASS event.clipboardData.getData("text/html").includes("") is false +PASS event.clipboardData.getData("text/html").includes("WebKit") is true +PASS event.clipboardData.getData("text/html").includes("") is true +PASS event.clipboardData.getData("text/html").includes("rocks") is false +PASS event.clipboardData.getData("text/html").includes("") is false +PASS event.clipboardData.getData("text/html").includes("") is false +PASS event.clipboardData.getData("text/html").includes("because") is false +PASS event.clipboardData.getData("text/html").includes("") is false +PASS event.clipboardData.getData("text/html").includes("") is false +PASS event.clipboardData.getData("text/html").includes("") is false +PASS event.clipboardData.getData("text/html").includes("") is false +PASS event.clipboardData.getData("text/html").includes("foo") is false +PASS event.clipboardData.getData("text/html").includes("bar") is true +PASS successfullyParsed is true + +TEST COMPLETE +hello world WebKit rocks because foo bar +hello WebKit bar + diff --git a/LayoutTests/editing/pasteboard/copy-content-with-user-select-none-legacy.html b/LayoutTests/editing/pasteboard/copy-content-with-user-select-none-legacy.html new file mode 100644 index 000000000000..dcc527bd578c --- /dev/null +++ b/LayoutTests/editing/pasteboard/copy-content-with-user-select-none-legacy.html @@ -0,0 +1,73 @@ + + + +
hello world WebKit rocks because foo bar
+
+

+
+
+
+
diff --git a/LayoutTests/editing/pasteboard/copy-content-with-user-select-none.html b/LayoutTests/editing/pasteboard/copy-content-with-user-select-none.html
index 9cf9bef5c1c0..34fa6465537d 100644
--- a/LayoutTests/editing/pasteboard/copy-content-with-user-select-none.html
+++ b/LayoutTests/editing/pasteboard/copy-content-with-user-select-none.html
@@ -19,7 +19,7 @@
 source.addEventListener("copy", () => {
     shouldBeTrue('getSelection().toString().includes("hello")');
     shouldBeFalse('getSelection().toString().includes("world")');
-    shouldBeTrue('getSelection().toString().includes("WebKit")');
+    shouldBeFalse('getSelection().toString().includes("WebKit")');
     shouldBeFalse('getSelection().toString().includes("rocks")');
     shouldBeFalse('getSelection().toString().includes("because")');
     shouldBeFalse('getSelection().toString().includes("foo")');
@@ -29,7 +29,7 @@
 destination.addEventListener("paste", () => {
     shouldBeTrue('event.clipboardData.getData("text/plain").includes("hello")');
     shouldBeFalse('event.clipboardData.getData("text/plain").includes("world")');
-    shouldBeTrue('event.clipboardData.getData("text/plain").includes("WebKit")');
+    shouldBeFalse('event.clipboardData.getData("text/plain").includes("WebKit")');
     shouldBeFalse('event.clipboardData.getData("text/plain").includes("rocks")');
     shouldBeFalse('event.clipboardData.getData("text/plain").includes("beacuse")');
     shouldBeFalse('event.clipboardData.getData("text/plain").includes("foo")');
@@ -38,9 +38,9 @@
     shouldBeFalse('event.clipboardData.getData("text/html").includes("world")');
     shouldBeFalse('event.clipboardData.getData("text/html").includes("")');
-    shouldBeTrue('event.clipboardData.getData("text/html").includes("WebKit")');
-    shouldBeTrue('event.clipboardData.getData("text/html").includes("")');
+    shouldBeFalse('event.clipboardData.getData("text/html").includes("WebKit")');
+    shouldBeFalse('event.clipboardData.getData("text/html").includes("")');
     shouldBeFalse('event.clipboardData.getData("text/html").includes("rocks")');
     shouldBeFalse('event.clipboardData.getData("text/html").includes("")');
     shouldBeFalse('event.clipboardData.getData("text/html").includes("")');
diff --git a/LayoutTests/editing/pasteboard/dataTransfer-setData-getData-expected.txt b/LayoutTests/editing/pasteboard/dataTransfer-setData-getData-expected.txt
index 238ca1c3b259..ac80e5c58658 100644
--- a/LayoutTests/editing/pasteboard/dataTransfer-setData-getData-expected.txt
+++ b/LayoutTests/editing/pasteboard/dataTransfer-setData-getData-expected.txt
@@ -8,8 +8,7 @@ PASS getDataResultType is "string"
 PASS getDataResult is "http://test.com"
 --- Test set/get 'URL' with multiple URLs:
 PASS getDataResultType is "string"
-FAIL getDataResult should be http://test.com. Was http://test.com
-http://check.com.
+PASS getDataResult is "http://test.com"
 --- Test set/get 'text/uri-list':
 PASS getDataResultType is "string"
 PASS getDataResult is "http://test.com\r\nhttp://check.com"
@@ -18,28 +17,22 @@ PASS getDataResultType is "string"
 PASS getDataResult is "http://test.com\nhttp://check.com"
 --- Test set 'text/uri-list', get 'URL':
 PASS getDataResultType is "string"
-FAIL getDataResult should be http://test.com. Was http://test.com
-http://check.com.
+PASS getDataResult is "http://test.com"
 --- Test set 'URL', get 'text/uri-list':
 PASS getDataResultType is "string"
 PASS getDataResult is "http://test.com\r\nhttp://check.com"
 --- Test set 'text/uri-list', get 'URL', using only '\n':
 PASS getDataResultType is "string"
-FAIL getDataResult should be http://test.com. Was http://test.com
-http://check.com.
+PASS getDataResult is "http://test.com"
 --- Test set/get 'text/uri-list' with comments:
 PASS getDataResultType is "string"
 PASS getDataResult is "# comment\r\nhttp://test.com\r\nhttp://check.com"
 --- Test set 'text/uri-list', get 'URL' with comments:
 PASS getDataResultType is "string"
-FAIL getDataResult should be http://test.com. Was # comment
-http://test.com
-http://check.com.
+PASS getDataResult is "http://test.com"
 --- Test set 'text/uri-list', get 'URL' with only comments:
 PASS getDataResultType is "string"
-FAIL getDataResult should be . Was # comment
-# comment 2
-# comment 3.
+PASS getDataResult is ""
 --- Test set/get 'text/plain':
 PASS getDataResultType is "string"
 PASS getDataResult is "Lorem ipsum dolor sit amet."
diff --git a/LayoutTests/editing/selection/user-select-all-selection-expected.txt b/LayoutTests/editing/selection/user-select-all-selection-expected.txt
index 838d2c238e7d..757d1c5c7dd2 100644
--- a/LayoutTests/editing/selection/user-select-all-selection-expected.txt
+++ b/LayoutTests/editing/selection/user-select-all-selection-expected.txt
@@ -9,23 +9,23 @@ After extend forward character:
 |   
 |     id="descendant"
 |     style="border: solid red 1px"
-|     "user "
+|     "u<#selection-focus>ser "
 |     
 |       "select all"
 |     " area"
 | 
-|   "<#selection-focus> Test -webkit-user-select all"
+|   " Test -webkit-user-select all"
 
 After extend backward character:
 | 
-|   "Test -webkit-user-select all <#selection-caret>"
+|   "Test -webkit-user-select all <#selection-focus>"
 | 
 |   class="userSelectAll"
 |   id="allArea"
 |   
 |     id="descendant"
 |     style="border: solid red 1px"
-|     "user "
+|     "<#selection-anchor>user "
 |     
 |       "select all"
 |     " area"
@@ -41,23 +41,23 @@ After extend right character:
 |   
 |     id="descendant"
 |     style="border: solid red 1px"
-|     "user "
+|     "u<#selection-focus>ser "
 |     
 |       "select all"
 |     " area"
 | 
-|   "<#selection-focus> Test -webkit-user-select all"
+|   " Test -webkit-user-select all"
 
 After extend left character:
 | 
-|   "Test -webkit-user-select all <#selection-caret>"
+|   "Test -webkit-user-select all <#selection-focus>"
 | 
 |   class="userSelectAll"
 |   id="allArea"
 |   
 |     id="descendant"
 |     style="border: solid red 1px"
-|     "user "
+|     "<#selection-anchor>user "
 |     
 |       "select all"
 |     " area"
@@ -73,12 +73,12 @@ After move forward character:
 |   
 |     id="descendant"
 |     style="border: solid red 1px"
-|     "user "
+|     "u<#selection-caret>ser "
 |     
 |       "select all"
 |     " area"
 | 
-|   "<#selection-caret> Test -webkit-user-select all"
+|   " Test -webkit-user-select all"
 
 After move backward character:
 | 
@@ -105,12 +105,12 @@ After move right character:
 |   
 |     id="descendant"
 |     style="border: solid red 1px"
-|     "user "
+|     "u<#selection-caret>ser "
 |     
 |       "select all"
 |     " area"
 | 
-|   "<#selection-caret> Test -webkit-user-select all"
+|   " Test -webkit-user-select all"
 
 After move left character:
 | 
@@ -131,18 +131,16 @@ After move left character:
 After click:
 | 
 |   "Test -webkit-user-select all "
-| <#selection-anchor>
 | 
 |   class="userSelectAll"
 |   id="allArea"
 |   
 |     id="descendant"
 |     style="border: solid red 1px"
-|     "user "
+|     "u<#selection-caret>ser "
 |     
 |       "select all"
 |     " area"
-| <#selection-focus>
 | 
 |   " Test -webkit-user-select all"
 
@@ -155,25 +153,23 @@ After extending selection from left by mouse:
 |   
 |     id="descendant"
 |     style="border: solid red 1px"
-|     "user "
+|     "use<#selection-focus>r "
 |     
 |       "select all"
 |     " area"
-| <#selection-focus>
 | 
 |   " Test -webkit-user-select all"
 
 After extending selection from right by mouse:
 | 
 |   "Test -webkit-user-select all "
-| <#selection-focus>
 | 
 |   class="userSelectAll"
 |   id="allArea"
 |   
 |     id="descendant"
 |     style="border: solid red 1px"
-|     "user "
+|     "u<#selection-focus>ser "
 |     
 |       "select all"
 |     " area"
diff --git a/LayoutTests/editing/selection/user-select-all-selection-legacy-expected.txt b/LayoutTests/editing/selection/user-select-all-selection-legacy-expected.txt
new file mode 100644
index 000000000000..838d2c238e7d
--- /dev/null
+++ b/LayoutTests/editing/selection/user-select-all-selection-legacy-expected.txt
@@ -0,0 +1,199 @@
+ Test -webkit-user-select all selection movements and extensions (left right forward backward)
+
+After extend forward character:
+| 
+|   "Test -webkit-user-select all <#selection-anchor>"
+| 
+|   class="userSelectAll"
+|   id="allArea"
+|   
+|     id="descendant"
+|     style="border: solid red 1px"
+|     "user "
+|     
+|       "select all"
+|     " area"
+| 
+|   "<#selection-focus> Test -webkit-user-select all"
+
+After extend backward character:
+| 
+|   "Test -webkit-user-select all <#selection-caret>"
+| 
+|   class="userSelectAll"
+|   id="allArea"
+|   
+|     id="descendant"
+|     style="border: solid red 1px"
+|     "user "
+|     
+|       "select all"
+|     " area"
+| 
+|   " Test -webkit-user-select all"
+
+After extend right character:
+| 
+|   "Test -webkit-user-select all <#selection-anchor>"
+| 
+|   class="userSelectAll"
+|   id="allArea"
+|   
+|     id="descendant"
+|     style="border: solid red 1px"
+|     "user "
+|     
+|       "select all"
+|     " area"
+| 
+|   "<#selection-focus> Test -webkit-user-select all"
+
+After extend left character:
+| 
+|   "Test -webkit-user-select all <#selection-caret>"
+| 
+|   class="userSelectAll"
+|   id="allArea"
+|   
+|     id="descendant"
+|     style="border: solid red 1px"
+|     "user "
+|     
+|       "select all"
+|     " area"
+| 
+|   " Test -webkit-user-select all"
+
+After move forward character:
+| 
+|   "Test -webkit-user-select all "
+| 
+|   class="userSelectAll"
+|   id="allArea"
+|   
+|     id="descendant"
+|     style="border: solid red 1px"
+|     "user "
+|     
+|       "select all"
+|     " area"
+| 
+|   "<#selection-caret> Test -webkit-user-select all"
+
+After move backward character:
+| 
+|   "Test -webkit-user-select all <#selection-caret>"
+| 
+|   class="userSelectAll"
+|   id="allArea"
+|   
+|     id="descendant"
+|     style="border: solid red 1px"
+|     "user "
+|     
+|       "select all"
+|     " area"
+| 
+|   " Test -webkit-user-select all"
+
+After move right character:
+| 
+|   "Test -webkit-user-select all "
+| 
+|   class="userSelectAll"
+|   id="allArea"
+|   
+|     id="descendant"
+|     style="border: solid red 1px"
+|     "user "
+|     
+|       "select all"
+|     " area"
+| 
+|   "<#selection-caret> Test -webkit-user-select all"
+
+After move left character:
+| 
+|   "Test -webkit-user-select all <#selection-caret>"
+| 
+|   class="userSelectAll"
+|   id="allArea"
+|   
+|     id="descendant"
+|     style="border: solid red 1px"
+|     "user "
+|     
+|       "select all"
+|     " area"
+| 
+|   " Test -webkit-user-select all"
+
+After click:
+| 
+|   "Test -webkit-user-select all "
+| <#selection-anchor>
+| 
+|   class="userSelectAll"
+|   id="allArea"
+|   
+|     id="descendant"
+|     style="border: solid red 1px"
+|     "user "
+|     
+|       "select all"
+|     " area"
+| <#selection-focus>
+| 
+|   " Test -webkit-user-select all"
+
+After extending selection from left by mouse:
+| 
+|   "<#selection-anchor>Test -webkit-user-select all "
+| 
+|   class="userSelectAll"
+|   id="allArea"
+|   
+|     id="descendant"
+|     style="border: solid red 1px"
+|     "user "
+|     
+|       "select all"
+|     " area"
+| <#selection-focus>
+| 
+|   " Test -webkit-user-select all"
+
+After extending selection from right by mouse:
+| 
+|   "Test -webkit-user-select all "
+| <#selection-focus>
+| 
+|   class="userSelectAll"
+|   id="allArea"
+|   
+|     id="descendant"
+|     style="border: solid red 1px"
+|     "user "
+|     
+|       "select all"
+|     " area"
+| 
+|   " Test -webkit-user-select all<#selection-anchor>"
+
+After programmatic selection:
+| 
+|   "Test -webkit-user-select all "
+| 
+|   class="userSelectAll"
+|   id="allArea"
+|   
+|     id="descendant"
+|     style="border: solid red 1px"
+|     "user "
+|     
+|       <#selection-anchor>
+|       "select all"
+|       <#selection-focus>
+|     " area"
+| 
+|   " Test -webkit-user-select all"
diff --git a/LayoutTests/editing/selection/user-select-all-selection-legacy.html b/LayoutTests/editing/selection/user-select-all-selection-legacy.html
new file mode 100644
index 000000000000..2b665079b669
--- /dev/null
+++ b/LayoutTests/editing/selection/user-select-all-selection-legacy.html
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
Test -webkit-user-select all user select all area Test -webkit-user-select all
+
+ + + diff --git a/LayoutTests/editing/selection/user-select-all-selection.html b/LayoutTests/editing/selection/user-select-all-selection.html index 2fe0ac166443..631f008a1853 100644 --- a/LayoutTests/editing/selection/user-select-all-selection.html +++ b/LayoutTests/editing/selection/user-select-all-selection.html @@ -4,7 +4,6 @@ .userSelectAll {-webkit-user-select: all; } - - diff --git a/LayoutTests/editing/selection/user-select-auto-in-modal-dialog-inside-inert-expected.txt b/LayoutTests/editing/selection/user-select-auto-in-modal-dialog-inside-inert-expected.txt new file mode 100644 index 000000000000..851aec4dde94 --- /dev/null +++ b/LayoutTests/editing/selection/user-select-auto-in-modal-dialog-inside-inert-expected.txt @@ -0,0 +1,7 @@ + +PASS 'user-select: auto' inside a modal dialog is selectable +PASS A modal dialog marks outside nodes as inert, making them unselectable +PASS 'user-select: auto' inside a modal dialog stays selectable when an ancestor of the dialog is inert +PASS 'user-select: text' in an inert subtree is not selectable +PASS 'user-select: auto' inside an inert modal dialog is not selectable +wrapper explicitText diff --git a/LayoutTests/editing/selection/user-select-auto-in-modal-dialog-inside-inert.html b/LayoutTests/editing/selection/user-select-auto-in-modal-dialog-inside-inert.html new file mode 100644 index 000000000000..136a66a805d4 --- /dev/null +++ b/LayoutTests/editing/selection/user-select-auto-in-modal-dialog-inside-inert.html @@ -0,0 +1,58 @@ + + +user-select: auto resolves against the ancestor's used value without considering inertness + + + +
+
+ wrapper + explicitText + + dialog + autoChild + +
+ + + diff --git a/LayoutTests/editing/selection/user-select-in-text-control-expected.txt b/LayoutTests/editing/selection/user-select-in-text-control-expected.txt index 0c15fb0991d2..e78c2e92f144 100644 --- a/LayoutTests/editing/selection/user-select-in-text-control-expected.txt +++ b/LayoutTests/editing/selection/user-select-in-text-control-expected.txt @@ -1,9 +1,9 @@ edixtableParagraph -editableParagraphWebkitUserSelectAll +edixtableParagraphWebkitUserSelectAll -Verifies the used user-select value for the inner text element of a text control, and contrasts it with ordinary editable content. +Verifies the used user-select value for the inner text element of a text control. On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". @@ -26,17 +26,17 @@ PASS extendOneCharacterIn("readOnly") is "r" PASS extendOneCharacterIn("textarea") is "t" PASS extendOneCharacterIn("textareaReadOnly") is "t" -'all' makes the value atomically selectable, whether the text is editable or not. -PASS extendOneCharacterIn("webkitUserSelectAll") is "webkitUserSelectAll" -PASS extendOneCharacterIn("textareaWebkitUserSelectAll") is "textareaWebkitUserSelectAll" +'all' makes the value atomically selectable, but it doesn't apply if editable. +PASS extendOneCharacterIn("webkitUserSelectAll") is "w" +PASS extendOneCharacterIn("textareaWebkitUserSelectAll") is "t" PASS extendOneCharacterIn("readOnlyWebkitUserSelectAll") is "readOnlyWebkitUserSelectAll" PASS extendOneCharacterIn("textareaReadOnlyWebkitUserSelectAll") is "textareaReadOnlyWebkitUserSelectAll" -'all' removes editability from ordinary editable content +'all' no longer removes editability from ordinary editable content. PASS typedTextLandsAt("editableParagraph") is true -PASS typedTextLandsAt("editableParagraphWebkitUserSelectAll") is false +PASS typedTextLandsAt("editableParagraphWebkitUserSelectAll") is true -Exceptionally, 'all' does not remove editability from editable text controls. +'all' does not remove editability from editable text controls. PASS typedTextLandsAt("mutable") is true PASS typedTextLandsAt("textarea") is true PASS typedTextLandsAt("readOnly") is false diff --git a/LayoutTests/editing/selection/user-select-in-text-control-legacy-expected.txt b/LayoutTests/editing/selection/user-select-in-text-control-legacy-expected.txt new file mode 100644 index 000000000000..da556628ebf9 --- /dev/null +++ b/LayoutTests/editing/selection/user-select-in-text-control-legacy-expected.txt @@ -0,0 +1,51 @@ + +edixtableParagraph + +editableParagraphWebkitUserSelectAll + +Verifies the used user-select value for the inner text element of a text control. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +By default, a control's value is selectable. +PASS selectTextIn("mutable") is "mutable" +PASS selectTextIn("readOnly") is "readOnly" +PASS selectTextIn("textarea") is "textarea" +PASS selectTextIn("textareaReadOnly") is "textareaReadOnly" + +'none' makes it unselectable, but it is overridden if the text is editable. +PASS selectTextIn("webkitUserSelectNone") is "webkitUserSelectNone" +PASS selectTextIn("textareaWebkitUserSelectNone") is "textareaWebkitUserSelectNone" +PASS selectTextIn("readOnlyWebkitUserSelectNone") is "" +PASS selectTextIn("textareaReadOnlyWebkitUserSelectNone") is "" + +By default, a control's value is not atomically selectable (all). +PASS extendOneCharacterIn("mutable") is "m" +PASS extendOneCharacterIn("readOnly") is "r" +PASS extendOneCharacterIn("textarea") is "t" +PASS extendOneCharacterIn("textareaReadOnly") is "t" + +[LEGACY] 'all' makes the value atomically selectable, whether the text is editable or not. +PASS extendOneCharacterIn("webkitUserSelectAll") is "webkitUserSelectAll" +PASS extendOneCharacterIn("textareaWebkitUserSelectAll") is "textareaWebkitUserSelectAll" +PASS extendOneCharacterIn("readOnlyWebkitUserSelectAll") is "readOnlyWebkitUserSelectAll" +PASS extendOneCharacterIn("textareaReadOnlyWebkitUserSelectAll") is "textareaReadOnlyWebkitUserSelectAll" + +[LEGACY] 'all' removes editability from ordinary editable content +PASS typedTextLandsAt("editableParagraph") is true +PASS typedTextLandsAt("editableParagraphWebkitUserSelectAll") is false + +'all' does not remove editability from editable text controls. +PASS typedTextLandsAt("mutable") is true +PASS typedTextLandsAt("textarea") is true +PASS typedTextLandsAt("readOnly") is false +PASS typedTextLandsAt("textareaReadOnly") is false +PASS typedTextLandsAt("webkitUserSelectAll") is true +PASS typedTextLandsAt("textareaWebkitUserSelectAll") is true +PASS typedTextLandsAt("readOnlyWebkitUserSelectAll") is false +PASS typedTextLandsAt("textareaReadOnlyWebkitUserSelectAll") is false +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/editing/selection/user-select-in-text-control-legacy.html b/LayoutTests/editing/selection/user-select-in-text-control-legacy.html new file mode 100644 index 000000000000..53a3b4ed3b4c --- /dev/null +++ b/LayoutTests/editing/selection/user-select-in-text-control-legacy.html @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

editableParagraph

+

editableParagraphWebkitUserSelectAll

+ +
+
+ + + diff --git a/LayoutTests/editing/selection/user-select-in-text-control-unprefixed-expected.txt b/LayoutTests/editing/selection/user-select-in-text-control-unprefixed-expected.txt new file mode 100644 index 000000000000..7b6b91043d12 --- /dev/null +++ b/LayoutTests/editing/selection/user-select-in-text-control-unprefixed-expected.txt @@ -0,0 +1,51 @@ + +edixtableParagraph + +edixtableParagraphUserSelectAll + +Verifies the used user-select value for the inner text element of a text control, and contrasts it with ordinary editable content. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +By default, a control's value is selectable. +PASS selectTextIn("mutable") is "mutable" +PASS selectTextIn("readOnly") is "readOnly" +PASS selectTextIn("textarea") is "textarea" +PASS selectTextIn("textareaReadOnly") is "textareaReadOnly" + +'none' makes it unselectable, but it is overridden if the text is editable. +PASS selectTextIn("userSelectNone") is "userSelectNone" +PASS selectTextIn("textareaUserSelectNone") is "textareaUserSelectNone" +PASS selectTextIn("readOnlyUserSelectNone") is "" +PASS selectTextIn("textareaReadOnlyUserSelectNone") is "" + +By default, a control's value is not atomically selectable (all). +PASS extendOneCharacterIn("mutable") is "m" +PASS extendOneCharacterIn("readOnly") is "r" +PASS extendOneCharacterIn("textarea") is "t" +PASS extendOneCharacterIn("textareaReadOnly") is "t" + +'all' makes the value atomically selectable, but it doesn't apply if editable. +PASS extendOneCharacterIn("userSelectAll") is "u" +PASS extendOneCharacterIn("textareaUserSelectAll") is "t" +PASS extendOneCharacterIn("readOnlyUserSelectAll") is "readOnlyUserSelectAll" +PASS extendOneCharacterIn("textareaReadOnlyUserSelectAll") is "textareaReadOnlyUserSelectAll" + +'all' no longer removes editability from ordinary editable content. +PASS typedTextLandsAt("editableParagraph") is true +PASS typedTextLandsAt("editableParagraphUserSelectAll") is true + +'all' does not remove editability from editable text controls either. +PASS typedTextLandsAt("mutable") is true +PASS typedTextLandsAt("textarea") is true +PASS typedTextLandsAt("readOnly") is false +PASS typedTextLandsAt("textareaReadOnly") is false +PASS typedTextLandsAt("userSelectAll") is true +PASS typedTextLandsAt("textareaUserSelectAll") is true +PASS typedTextLandsAt("readOnlyUserSelectAll") is false +PASS typedTextLandsAt("textareaReadOnlyUserSelectAll") is false +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/editing/selection/user-select-in-text-control-unprefixed-legacy-expected.txt b/LayoutTests/editing/selection/user-select-in-text-control-unprefixed-legacy-expected.txt new file mode 100644 index 000000000000..e67b04735c93 --- /dev/null +++ b/LayoutTests/editing/selection/user-select-in-text-control-unprefixed-legacy-expected.txt @@ -0,0 +1,51 @@ + +edixtableParagraph + +edixtableParagraphUserSelectAll + +Verifies the used user-select value for the inner text element of a text control, and contrasts it with ordinary editable content. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +By default, a control's value is selectable. +PASS selectTextIn("mutable") is "mutable" +PASS selectTextIn("readOnly") is "readOnly" +PASS selectTextIn("textarea") is "textarea" +PASS selectTextIn("textareaReadOnly") is "textareaReadOnly" + +'user-select: none' has no effect at all, so every value stays selectable. +PASS selectTextIn("userSelectNone") is "userSelectNone" +PASS selectTextIn("textareaUserSelectNone") is "textareaUserSelectNone" +PASS selectTextIn("readOnlyUserSelectNone") is "readOnlyUserSelectNone" +PASS selectTextIn("textareaReadOnlyUserSelectNone") is "textareaReadOnlyUserSelectNone" + +By default, a control's value is not atomically selectable (all). +PASS extendOneCharacterIn("mutable") is "m" +PASS extendOneCharacterIn("readOnly") is "r" +PASS extendOneCharacterIn("textarea") is "t" +PASS extendOneCharacterIn("textareaReadOnly") is "t" + +'user-select: all' has no effect either, so nothing becomes atomically selectable. +PASS extendOneCharacterIn("userSelectAll") is "u" +PASS extendOneCharacterIn("textareaUserSelectAll") is "t" +PASS extendOneCharacterIn("readOnlyUserSelectAll") is "r" +PASS extendOneCharacterIn("textareaReadOnlyUserSelectAll") is "t" + +Editability is untouched, in ordinary editable content and in text controls alike. +PASS typedTextLandsAt("editableParagraph") is true +PASS typedTextLandsAt("editableParagraphUserSelectAll") is true + +Only a control's read-only state decides whether typing reaches its value. +PASS typedTextLandsAt("mutable") is true +PASS typedTextLandsAt("textarea") is true +PASS typedTextLandsAt("readOnly") is false +PASS typedTextLandsAt("textareaReadOnly") is false +PASS typedTextLandsAt("userSelectAll") is true +PASS typedTextLandsAt("textareaUserSelectAll") is true +PASS typedTextLandsAt("readOnlyUserSelectAll") is false +PASS typedTextLandsAt("textareaReadOnlyUserSelectAll") is false +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/editing/selection/user-select-in-text-control-unprefixed-legacy.html b/LayoutTests/editing/selection/user-select-in-text-control-unprefixed-legacy.html new file mode 100644 index 000000000000..927d91e61405 --- /dev/null +++ b/LayoutTests/editing/selection/user-select-in-text-control-unprefixed-legacy.html @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

editableParagraph

+

editableParagraphUserSelectAll

+ +
+
+ + + diff --git a/LayoutTests/editing/selection/user-select-in-text-control-unprefixed.html b/LayoutTests/editing/selection/user-select-in-text-control-unprefixed.html new file mode 100644 index 000000000000..e975c49c5068 --- /dev/null +++ b/LayoutTests/editing/selection/user-select-in-text-control-unprefixed.html @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

editableParagraph

+

editableParagraphUserSelectAll

+ +
+
+ + + diff --git a/LayoutTests/editing/selection/user-select-in-text-control.html b/LayoutTests/editing/selection/user-select-in-text-control.html index ab1275be1d46..9d5cf80422f9 100644 --- a/LayoutTests/editing/selection/user-select-in-text-control.html +++ b/LayoutTests/editing/selection/user-select-in-text-control.html @@ -6,9 +6,10 @@ - + @@ -33,7 +34,7 @@
+ + + +
+
webkitNoneHost + webkitAutoChild + unprefixedTextChild +
+
unprefixedNoneHost + unprefixedAutoChild + webkitTextChild + bothSpellingsChild +
+
+ +
+
+ + + diff --git a/LayoutTests/editing/selection/user-select-supersedes-webkit-user-select.html b/LayoutTests/editing/selection/user-select-supersedes-webkit-user-select.html new file mode 100644 index 000000000000..9dbd8892ecdb --- /dev/null +++ b/LayoutTests/editing/selection/user-select-supersedes-webkit-user-select.html @@ -0,0 +1,56 @@ + + + + + + + + +
+
webkitNoneHost + webkitAutoChild + unprefixedTextChild +
+
unprefixedNoneHost + unprefixedAutoChild + webkitTextChild + bothSpellingsChild +
+
+ +
+
+ + + diff --git a/LayoutTests/fast/animation/css-animation-range-calc-crash-expected.txt b/LayoutTests/fast/animation/css-animation-range-calc-crash-expected.txt new file mode 100644 index 000000000000..d380fbf76e1a --- /dev/null +++ b/LayoutTests/fast/animation/css-animation-range-calc-crash-expected.txt @@ -0,0 +1,2 @@ +This test passes if it doesn't crash. + diff --git a/LayoutTests/fast/animation/css-animation-range-calc-crash.html b/LayoutTests/fast/animation/css-animation-range-calc-crash.html new file mode 100644 index 000000000000..262da8a349fe --- /dev/null +++ b/LayoutTests/fast/animation/css-animation-range-calc-crash.html @@ -0,0 +1,26 @@ + + + + + + +
This test passes if it doesn't crash.
+
+ + + diff --git a/LayoutTests/fast/block/inside-inlines/block-in-inline-partial-relayout-crash-expected.txt b/LayoutTests/fast/block/inside-inlines/block-in-inline-partial-relayout-crash-expected.txt new file mode 100644 index 000000000000..c2541f4f3dd7 --- /dev/null +++ b/LayoutTests/fast/block/inside-inlines/block-in-inline-partial-relayout-crash-expected.txt @@ -0,0 +1 @@ +PASS if no crash. diff --git a/LayoutTests/fast/block/inside-inlines/block-in-inline-partial-relayout-crash.html b/LayoutTests/fast/block/inside-inlines/block-in-inline-partial-relayout-crash.html new file mode 100644 index 000000000000..fc46581481b1 --- /dev/null +++ b/LayoutTests/fast/block/inside-inlines/block-in-inline-partial-relayout-crash.html @@ -0,0 +1,25 @@ + + +
aa bb cc
xx
yy zz ww vv
dd ee ff
+ diff --git a/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.gradient.html b/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.gradient.html index 8675e3569a5c..50dfcb289512 100644 --- a/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.gradient.html +++ b/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.gradient.html @@ -25,7 +25,7 @@ ctx.fillText("X", 0, 80, -10); } -function doDeferredTest() { +function doDeferredTest(ctx) { drawCanvas(ctx); // Check that the letter rendered appropriately @@ -52,17 +52,19 @@ testRunner.waitUntilDone(); } -var canvas = document.getElementById('c'); -var ctx = canvas.getContext("2d"); -ctx.font = "100px Ahem"; +addEventListener("load", async () => { + const font = "100px Ahem" + await document.fonts.load(font); -// Kick off loading of the font -ctx.fillText(" ", 0, 0); + var canvas = document.getElementById('c'); + var ctx = canvas.getContext("2d"); + ctx.font = font; -// Wait for the font to load, then run -setTimeout(function() { - doDeferredTest(); -}, 50); + doDeferredTest(ctx); +}, () => { + if (window.testRunner) + testRunner.notifyDone(); +}); diff --git a/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.negative.html b/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.negative.html index 683d5f1c7749..5fa51329773d 100644 --- a/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.negative.html +++ b/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.negative.html @@ -20,7 +20,7 @@ ctx.fillText("X", 0, 100, -5); } -function doDeferredTest() { +function doDeferredTest(ctx) { drawCanvas(ctx); // Check that the letter rendered appropriately @@ -47,17 +47,19 @@ testRunner.waitUntilDone(); } -var canvas = document.getElementById('c'); -var ctx = canvas.getContext("2d"); -ctx.font = "200px Ahem"; +addEventListener("load", async () => { + const font = "200px Ahem" + await document.fonts.load(font); -// Kick off loading of the font -ctx.fillText(" ", 0, 0); + var canvas = document.getElementById('c'); + var ctx = canvas.getContext("2d"); + ctx.font = font; -// Wait for the font to load, then run -setTimeout(function() { - doDeferredTest(); -}, 50); + doDeferredTest(ctx); +}, () => { + if (window.testRunner) + testRunner.notifyDone(); +}); diff --git a/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.veryLarge.html b/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.veryLarge.html index e2a5812ade8b..b16c4f10cf4a 100644 --- a/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.veryLarge.html +++ b/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.veryLarge.html @@ -20,7 +20,7 @@ ctx.fillText("X", -100, 100, 200); } -function doDeferredTest() { +function doDeferredTest(ctx) { drawCanvas(ctx); // Check that the letter rendered appropriately @@ -47,17 +47,19 @@ testRunner.waitUntilDone(); } -var canvas = document.getElementById('c'); -var ctx = canvas.getContext("2d"); -ctx.font = "100px Ahem"; +addEventListener("load", async () => { + const font = "100px Ahem"; + await document.fonts.load(font); -// Kick off loading of the font -ctx.fillText(" ", 0, 0); + var canvas = document.getElementById('c'); + var ctx = canvas.getContext("2d"); + ctx.font = font; -// Wait for the font to load, then run -setTimeout(function() { - doDeferredTest(); -}, 50); + doDeferredTest(ctx); +}, () => { + if (window.testRunner) + testRunner.notifyDone(); +}); diff --git a/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.verySmall.html b/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.verySmall.html index b98b1c901ee3..ebc590f8de14 100644 --- a/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.verySmall.html +++ b/LayoutTests/fast/canvas/2d.text.draw.fill.maxWidth.verySmall.html @@ -20,7 +20,7 @@ ctx.fillText("XX", -10, 100, 10); } -function doDeferredTest() { +function doDeferredTest(ctx) { drawCanvas(ctx); // Check that the letter rendered appropriately @@ -47,17 +47,19 @@ testRunner.waitUntilDone(); } -var canvas = document.getElementById('c'); -var ctx = canvas.getContext("2d"); -ctx.font = "100px Ahem"; +addEventListener('load', async () => { + const font = "100px Ahem"; + await document.fonts.load(font); -// Kick off loading of the font -ctx.fillText(" ", 0, 0); + var canvas = document.getElementById('c'); + var ctx = canvas.getContext("2d"); + ctx.font = font; -// Wait for the font to load, then run -setTimeout(function() { - doDeferredTest(); -}, 50); + doDeferredTest(ctx); +}, () => { + if (window.testRunner) + testRunner.notifyDone(); +}); diff --git a/LayoutTests/fast/canvas/canvas-composite-text-alpha.html b/LayoutTests/fast/canvas/canvas-composite-text-alpha.html index 5ad7ed5b5196..5b7bce2ab41f 100644 --- a/LayoutTests/fast/canvas/canvas-composite-text-alpha.html +++ b/LayoutTests/fast/canvas/canvas-composite-text-alpha.html @@ -430,16 +430,19 @@ name: "stroke text" }; - function draw() - { + addEventListener("load", async () => { + await document.fonts.load("20px Ahem"); drawTable(useFillText); drawTable(useStrokeText); if (window.testRunner) testRunner.notifyDone(); - } + }, () => { + if (window.testRunner) + testRunner.notifyDone(); + }); - +

This test exercises a bunch of alpha composition operations on text. The top-left rectangles are the source images and bottom-right rectangles are the destination images.

diff --git a/LayoutTests/fast/canvas/font-update.html b/LayoutTests/fast/canvas/font-update.html index d621d8943e76..f8c488b2140a 100644 --- a/LayoutTests/fast/canvas/font-update.html +++ b/LayoutTests/fast/canvas/font-update.html @@ -15,11 +15,15 @@ canvas.parentNode.removeChild(canvas); if (window.testRunner) testRunner.waitUntilDone(); - setTimeout(function() - { + + addEventListener("load", async () => { + try { + await document.fonts.load(ctx.font); + } catch (e) {} + ctx.fillText("A", 0, 100); document.body.appendChild(canvas); if (window.testRunner) testRunner.notifyDone(); - }, 50); + }); diff --git a/LayoutTests/fast/css/style-change-draggable-text-expected.txt b/LayoutTests/fast/css/style-change-draggable-text-expected.txt index 8d691e520e6f..00fd06087e69 100644 --- a/LayoutTests/fast/css/style-change-draggable-text-expected.txt +++ b/LayoutTests/fast/css/style-change-draggable-text-expected.txt @@ -1,4 +1,3 @@ Test changing style with draggable text. The test passes if WebKit doesn't crash or hit an assertion - -a a + diff --git a/LayoutTests/fast/css/style-change-draggable-text-legacy-expected.txt b/LayoutTests/fast/css/style-change-draggable-text-legacy-expected.txt new file mode 100644 index 000000000000..8d691e520e6f --- /dev/null +++ b/LayoutTests/fast/css/style-change-draggable-text-legacy-expected.txt @@ -0,0 +1,4 @@ +Test changing style with draggable text. The test passes if WebKit doesn't crash or hit an assertion + +a +a diff --git a/LayoutTests/fast/css/style-change-draggable-text-legacy.html b/LayoutTests/fast/css/style-change-draggable-text-legacy.html new file mode 100644 index 000000000000..30599d13cfeb --- /dev/null +++ b/LayoutTests/fast/css/style-change-draggable-text-legacy.html @@ -0,0 +1,15 @@ + + + +Test changing style with draggable text. The test passes if WebKit doesn't crash or hit an assertion