diff --git a/Configurations/Version.xcconfig b/Configurations/Version.xcconfig index 21cb2ace1206..cfcca3a32eb6 100644 --- a/Configurations/Version.xcconfig +++ b/Configurations/Version.xcconfig @@ -35,6 +35,7 @@ SHORT_VERSION_STRING = $(SHORT_VERSION_STRING_$(CONFIGURATION)) SYSTEM_VERSION_MAJOR_SHORT_MACOS = $(SYSTEM_VERSION_MAJOR_SHORT_MACOS_$(TARGET_MAC_OS_X_VERSION_MAJOR)) SYSTEM_VERSION_MAJOR_SHORT_MACOS_150000 = 15 SYSTEM_VERSION_MAJOR_SHORT_MACOS_160000 = 16 +SYSTEM_VERSION_MAJOR_SHORT_MACOS_260000 = 26 SYSTEM_VERSION_MAJOR_SHORT_MACOS_270000 = 27 SYSTEM_VERSION_MAJOR_SHORT_MACOS_280000 = 28 SYSTEM_VERSION_MAJOR_SHORT_MACOS_290000 = 29 @@ -44,6 +45,7 @@ SYSTEM_VERSION_MAJOR_SHORT_MACOS_300000 = 30 SYSTEM_VERSION_PREFIX = $(SYSTEM_VERSION_PREFIX_$(PLATFORM_NAME)_$(SYSTEM_VERSION_MAJOR_SHORT_MACOS)) SYSTEM_VERSION_PREFIX_macosx_15 = 20 SYSTEM_VERSION_PREFIX_macosx_16 = 21 +SYSTEM_VERSION_PREFIX_macosx_26 = 21 SYSTEM_VERSION_PREFIX_macosx_27 = 22 SYSTEM_VERSION_PREFIX_macosx_28 = 23 SYSTEM_VERSION_PREFIX_macosx_29 = 24 diff --git a/JSTests/microbenchmarks/get-by-val-array-storage-sparse-hole.js b/JSTests/microbenchmarks/get-by-val-array-storage-sparse-hole.js new file mode 100644 index 000000000000..ef68d4097357 --- /dev/null +++ b/JSTests/microbenchmarks/get-by-val-array-storage-sparse-hole.js @@ -0,0 +1,32 @@ +//@ skip if $model == "Apple Watch Series 3" # added by mark-jsc-stress-test.py +// Hole/miss-dominated reads of a large sparse array (ArrayStorage mode with a sparse map). Every read +// hits an in-bounds hole that is absent from the sparse map, so the int-indexed GetByVal slow path +// (operationGetByValArrayStorageInt) must resolve it to undefined via the (sane) prototype chain. +function get(array, i) +{ + return array[i]; +} +noInline(get); + +var maxIndex = 200000; +var step = 16; + +var array = []; +for (var i = maxIndex - step; i >= 0; i -= step) + array[i] = i + 1; + +var expectedPass = 0; +for (var i = 1; i < maxIndex; i += step) // i = 1, 17, 33, ... are all holes (never written). + ++expectedPass; + +var iterations = 400; +var holes = 0; +for (var iter = 0; iter < iterations; ++iter) { + for (var i = 1; i < maxIndex; i += step) { + if (get(array, i) === void 0) + ++holes; + } +} + +if (holes !== expectedPass * iterations) + throw "Error: bad hole count: " + holes; diff --git a/JSTests/microbenchmarks/get-by-val-array-storage-sparse.js b/JSTests/microbenchmarks/get-by-val-array-storage-sparse.js new file mode 100644 index 000000000000..2cd433b339d0 --- /dev/null +++ b/JSTests/microbenchmarks/get-by-val-array-storage-sparse.js @@ -0,0 +1,31 @@ +//@ skip if $model == "Apple Watch Series 3" # added by mark-jsc-stress-test.py +// Hit-dominated reads of a large sparse array (ArrayStorage mode with a sparse map). Every read +// resolves through the int-indexed GetByVal slow path (operationGetByValArrayStorageInt) and finds +// its value in the sparse map. +function get(array, i) +{ + return array[i]; +} +noInline(get); + +var maxIndex = 200000; +var step = 16; // 1/16 density (< 1/8) keeps the array in ArrayStorage with a sparse map. + +var array = []; +// Descending writes: the first one is far beyond length, forcing ArrayStorage + sparse map. +for (var i = maxIndex - step; i >= 0; i -= step) + array[i] = i + 1; + +var expectedPass = 0; +for (var i = 0; i < maxIndex; i += step) + expectedPass += i + 1; + +var iterations = 800; +var sum = 0; +for (var iter = 0; iter < iterations; ++iter) { + for (var i = 0; i < maxIndex; i += step) + sum += get(array, i); +} + +if (sum !== expectedPass * iterations) + throw "Error: bad sum: " + sum; diff --git a/JSTests/stress/arguments-elimination-multiple-inline-call-frames.js b/JSTests/stress/arguments-elimination-multiple-inline-call-frames.js new file mode 100644 index 000000000000..8a87b89cc5cd --- /dev/null +++ b/JSTests/stress/arguments-elimination-multiple-inline-call-frames.js @@ -0,0 +1,38 @@ +//@ skip if $buildType == "debug" +//@ runDefault("--useConcurrentJIT=false", "--jitPolicyScale=0", "--maximumFunctionForCallInlineCandidateBytecodeCostForFTL=500") + +let g = 0; +function restY(c, ...r) { g = c ? 1 : 2; return r; } +function h(c, ...r) { return r; } +function h2(...r) { return r; } +function sink() { + let out = []; + for (let i = 0; i < arguments.length; i++) out.push(arguments[i]); + return out; +} +noInline(sink); + +function restX(c, ...rx) { + let arr = [...rx, ...restY(c, ...rx)]; + let dummy = [0, 0, 0, 0, 0, 0, 0, h(50, 9.9, 8.8)]; + return [sink.apply(null, arr), dummy]; +} +for (let i = 0; i < 1000000; i++) restX(i & 1, 0.1, 0.2); + +function makeSrc(k) { +return ` +(function() { +function victim${k}(c1) { + let q = restX(c1, 0.1, 0.2); + let z = h2(7.7, 6.6); + return [q, z]; +} +noInline(victim${k}); +for (let i = 0; i < 1000000; i++) { + victim${k}(i & 1); +} +})() +`; +} + +for (let k = 0; k < 30; k++) eval(makeSrc(k)); diff --git a/JSTests/stress/array-indexof-ensure-still-alive.js b/JSTests/stress/array-indexof-ensure-still-alive.js new file mode 100644 index 000000000000..3ede4c9daf16 --- /dev/null +++ b/JSTests/stress/array-indexof-ensure-still-alive.js @@ -0,0 +1,15 @@ +//@ runDefault("--useFTLJIT=1", "--jitPolicyScale=0.1", "--useConcurrentJIT=0", "--useConcurrentGC=0", "--sweepSynchronously=1", "--collectContinuously=1") + +function opt(s, needle) { + return [s + "A", s + "B", s + "C", s + "D", s + "E", s + "F", s + "G", s + "H"].indexOf(needle); +} +noInline(opt); + +let big = "Q".repeat(1024 * 1024); +let needleStr = "Z".repeat(big.length + 1); + +for (let i = 0; i < 2000; i++) + opt(big, (i & 1) ? needleStr : 1234); + +for (let i = 0; i < 10000; i++) + opt(big, needleStr); diff --git a/JSTests/stress/dfg-ensure-absence-cached-dictionary-then.js b/JSTests/stress/dfg-ensure-absence-cached-dictionary-then.js new file mode 100644 index 000000000000..5edeb5ee04f8 --- /dev/null +++ b/JSTests/stress/dfg-ensure-absence-cached-dictionary-then.js @@ -0,0 +1,87 @@ +function createObject1() { + const tmp = { + toJSON: 1, + a: 1, + }; + + Object.create(tmp); + + return tmp; +} + +function createObject2() { + const tmp = { + b: 1, + toJSON: {} + }; + + Object.create(tmp); + + return tmp; +} + +function opt(container1, object2, array, thenable, flags) { + const promise = new Promise(() => {}); + + container1.x; + thenable.x; + + const object1 = Object.getPrototypeOf(container1); + + let tmp = object1; + object1.a; + + if (flags & 1) { + tmp = object2; + tmp.b; + + 0[0]; + } + + +tmp.toJSON; + +tmp.toJSON; + + array[0]; + Promise.resolve(+tmp.toJSON === 1 ? thenable : promise); + + array[0] = 2.3023e-320; +} + +function main() { + noDFG(main); + + const object1 = createObject1(); + const object2 = createObject2(); + + createObject1().z = 1; + + const container1 = Object.create(object1); + + const thenable = { + x: 1 + }; + + for (let i = 0; i < 100; i++) { + thenable['a' + i] = 1; + } + + const array = { + 0: 1.1 + }; + + JSON.stringify(container1); + + for (let i = 0; i < 200; i++) { + opt(container1, object2, array, thenable, i); + } + + thenable.__defineGetter__('then', () => { + array[0] = {}; + }); + + opt(container1, object2, array, thenable, 0); + + array[0].x; +} + +main(); diff --git a/JSTests/stress/multi-put-by-offset-reallocating-storage-needs-write-barrier.js b/JSTests/stress/multi-put-by-offset-reallocating-storage-needs-write-barrier.js new file mode 100644 index 000000000000..3539c166af87 --- /dev/null +++ b/JSTests/stress/multi-put-by-offset-reallocating-storage-needs-write-barrier.js @@ -0,0 +1,32 @@ +//@ runDefault("--useConcurrentJIT=false", "--jitPolicyScale=0.1", "--useConcurrentGC=false", "--verifyGC=true", "--gcMaxHeapSize=500000") + +var g_value = {}; + +function makeObj() { return {}; } + +function foo(cond) { + let a = makeObj(); + a.p1 = 1; + a.p2 = 1; + a.p3 = 1; + a.p4 = 1; + a.p5 = 1; + if (cond) + a.y = 1; + else + a.z = 1; + a.x = g_value; + return a; +} +noInline(foo); + +for (let i = 0; i < testLoopCount; ++i) { + g_value = {}; + foo(i & 1); +} + +var holder = new Array(100000).fill(null); +fullGC(); + +for (let i = 0; i < testLoopCount * 10; ++i) + holder[i % 100000] = foo(i & 1); diff --git a/JSTests/stress/regexp-many-non-greedy-paren-groups.js b/JSTests/stress/regexp-many-non-greedy-paren-groups.js new file mode 100644 index 000000000000..bedfd82896b5 --- /dev/null +++ b/JSTests/stress/regexp-many-non-greedy-paren-groups.js @@ -0,0 +1,25 @@ +// Test that regular expressions with many sequential non-greedy quantified +// parenthesized groups produce correct results at various sizes. + +function testLargeNonGreedyParens(n) { + let s = '(?:a){0,2}?'.repeat(n); + + let r = new RegExp(s); + + let result = 'aaa'.match(r); + if (result === null) + throw new Error("Expected match for n=" + n); + if (result.index !== 0) + throw new Error("Expected index 0 for n=" + n + ", got " + result.index); + + let replaced = 'a'.replace(r, 'x'); + if (typeof replaced !== 'string') + throw new Error("replace failed for n=" + n); +} + +testLargeNonGreedyParens(10); +testLargeNonGreedyParens(100); +testLargeNonGreedyParens(1000); +testLargeNonGreedyParens(2000); +testLargeNonGreedyParens(4000); +testLargeNonGreedyParens(8193); diff --git a/JSTests/stress/regress-174630697.js b/JSTests/stress/regress-174630697.js new file mode 100644 index 000000000000..5b422c5fafdd --- /dev/null +++ b/JSTests/stress/regress-174630697.js @@ -0,0 +1,25 @@ +const icCount = 100; +const structCount = 16; + +let body = "var x = 0;\n"; +for (let i = 0; i < icCount; i++) + body += "x += o.p;\n"; +body += "return x;\n"; + +let objs = []; +for (let i = 0; i < structCount; i++) { + let o = {}; + o["k" + i] = i; + o.p = 1; + objs.push(o); +} + +let f = new Function("o", body); + +for (let j = 0; j < 130; j++) + f(objs[j % structCount]); + +for (let j = 0; j < 100000; j++) + f(objs[j % structCount]); + +f(42); diff --git a/JSTests/stress/slowputarraystorage-stale-structure-bit.js b/JSTests/stress/slowputarraystorage-stale-structure-bit.js new file mode 100644 index 000000000000..0fe63895e357 --- /dev/null +++ b/JSTests/stress/slowputarraystorage-stale-structure-bit.js @@ -0,0 +1,34 @@ +Object.defineProperty(Object.prototype, 0, { get() {}, configurable: true }); +delete Object.prototype[0]; +let target = [1, 2, 3]; +Object.defineProperty(target, "length", { writable: false }); + +let proxyGet = new Proxy(target, { + get: (t, k) => k === "length" ? 999 : t[k] +}); + +try { + let lengthLie = proxyGet.length; + if (lengthLie === 999) { + throw "\"get\" trap successfully returned a lying value (999) for a non-configurable, non-writable property!"; + } +} catch (e) { + if (!(e instanceof TypeError)) { + throw "Expected TypeError for \"get\" trap invariant violation, got: " + e; + } +} + +let proxySet = new Proxy(target, { + set: (t, k, v) => true +}); + +try { + let setSuccess = Reflect.set(proxySet, "length", 999); + if (setSuccess === true && target.length !== 999) { + throw "Reflect.set returned true claiming success on a non-configurable, non-writable property!"; + } +} catch (e) { + if (!(e instanceof TypeError)) { + throw "Expected TypeError for \"set\" trap invariant violation, got: " + e; + } +} diff --git a/JSTests/stress/spread-with-OnlyAtomStringsStructure.js b/JSTests/stress/spread-with-OnlyAtomStringsStructure.js new file mode 100644 index 000000000000..da8a25a94426 --- /dev/null +++ b/JSTests/stress/spread-with-OnlyAtomStringsStructure.js @@ -0,0 +1,6 @@ +//@ runDefault("--forceEagerCompilation=1", "--validateAbstractInterpreterState=1") + +const array = [""]; + +for (let index = 0; index < testLoopCount; index++) + (() => {})(...array); diff --git a/JSTests/stress/typedarray-from-oob.js b/JSTests/stress/typedarray-from-oob.js new file mode 100644 index 000000000000..eba79c459c51 --- /dev/null +++ b/JSTests/stress/typedarray-from-oob.js @@ -0,0 +1,47 @@ +function testResize(ctor, bytesPerElement, initialBytes, shrinkBytes, resizeAt) { + const newCount = shrinkBytes / bytesPerElement; + const resizableArrayBuffer = new ArrayBuffer(initialBytes, { maxByteLength: initialBytes * 4 }); + const source = new ctor(resizableArrayBuffer); + source[Symbol.iterator] = null; + + let callbacks = 0; + ctor.from(source, (val, index) => { + if (index === resizeAt) + resizableArrayBuffer.resize(shrinkBytes); + callbacks++; + return val; + }); + + const expected = Math.max(resizeAt + 1, newCount); + if (callbacks > expected) + throw new Error(ctor.name + ": " + callbacks + " callbacks (expected <= " + expected + ")"); +} + +function testDetach(detachAt) { + const arrayBuffer = new ArrayBuffer(256); + const source = new Int32Array(arrayBuffer); + source[Symbol.iterator] = null; + + let callbacks = 0; + try { + Int32Array.from(source, (val, index) => { + if (index === detachAt) + arrayBuffer.transfer(); + callbacks++; + return val; + }); + } catch (e) { + return; + } + + if (callbacks > detachAt + 1) + throw new Error("detach: " + callbacks + " callbacks (expected <= " + (detachAt + 1) + ")"); +} + +testResize(Int32Array, 4, 4096, 16, 4); +testResize(Int32Array, 4, 4096, 8, 8); +testResize(Float64Array, 8, 8192, 32, 8); +testResize(Uint8Array, 1, 1024, 4, 8); +testResize(Int32Array, 4, 4096, 8, 0); +testDetach(4); +testDetach(0); diff --git a/JSTests/stress/yarr-negative-offset.js b/JSTests/stress/yarr-negative-offset.js new file mode 100644 index 000000000000..c9a51ec18814 --- /dev/null +++ b/JSTests/stress/yarr-negative-offset.js @@ -0,0 +1,33 @@ +//@ skip if $memoryLimited + +// YarrJIT OOB crash — page-aligned variant +// +// sizeof(StringImpl) = 0x14, page_size = 0x4000 (Apple Silicon 16K pages) +// Solve: 0x14 + (K + 0x40000000)*2 ≡ 0 (mod 0x4000) → K = 0x1FF6 +// +// Pattern: \u0100{K} [\u0100] \u0100{0x3FFFFFFF} flags: y +// Subject: "\u0100".repeat(K + 0x40000000) +// +// The OOB ldrh lands at alloc_base + page_size*N exactly → unmapped → SIGSEGV + +"use strict"; + +const K = 0x1FF6; +const LEN = K + 0x40000000; // 0x40001FF6 +const QUANT = 0x3FFFFFFF; + +// allocate +let s; +try { + s = "\u0100".repeat(LEN); +} catch (e) { + print("[!] OOM: " + e); + quit(); +} +if(s.length !== 0x40001ff6) throw new Error("unexpected s.length "+s.length); + +let pattern = "\\u0100{" + K + "}[\\u0100]\\u0100{" + QUANT + "}"; +let re = new RegExp(pattern, "y"); +re.lastIndex = 0; + +re.test(s); // should SIGSEGV if the bug is present diff --git a/JSTests/wasm/gc/private-fields-and-methods.js b/JSTests/wasm/gc/private-fields-and-methods.js new file mode 100644 index 000000000000..89c37c87e30f --- /dev/null +++ b/JSTests/wasm/gc/private-fields-and-methods.js @@ -0,0 +1,170 @@ +import * as assert from "../assert.js"; +import { instantiate } from "./wast-wrapper.js"; + +function testPrivateMethodOnStruct() { + let m = instantiate(` + (module + (type (struct (field i32))) + (func (export "make") (result anyref) + (struct.new 0 (i32.const 42))) + (func (export "use") (param (ref 0)) (result i32) + (struct.get 0 0 (local.get 0))) + ) + `); + const s = m.exports.make(); + + class B { constructor() { return s; } } + class D extends B { + #m() {} + constructor() { super(); } + } + + assert.throws( + () => new D(), + TypeError, + "Cannot add private method to a WebAssembly GC object" + ); + + // Struct must remain usable after the rejected attempt. + assert.eq(m.exports.use(s), 42); +} + +function testPrivateFieldOnStruct() { + let m = instantiate(` + (module + (type (struct (field i32))) + (func (export "make") (result anyref) + (struct.new 0 (i32.const 42))) + (func (export "use") (param (ref 0)) (result i32) + (struct.get 0 0 (local.get 0))) + ) + `); + const s = m.exports.make(); + + class B { constructor() { return s; } } + class D extends B { + #x = 1; + constructor() { super(); } + } + + assert.throws( + () => new D(), + TypeError, + "Cannot define private field on a WebAssembly GC object" + ); + + assert.eq(m.exports.use(s), 42); +} + +function testPrivateMethodOnArray() { + let m = instantiate(` + (module + (type (array i32)) + (func (export "make") (result anyref) + (array.new 0 (i32.const 7) (i32.const 3))) + (func (export "use") (param (ref 0) i32) (result i32) + (array.get 0 (local.get 0) (local.get 1))) + ) + `); + const a = m.exports.make(); + + class B { constructor() { return a; } } + class D extends B { + #m() {} + constructor() { super(); } + } + + assert.throws( + () => new D(), + TypeError, + "Cannot add private method to a WebAssembly GC object" + ); + + assert.eq(m.exports.use(a, 0), 7); +} + +function testPrivateFieldOnArray() { + let m = instantiate(` + (module + (type (array i32)) + (func (export "make") (result anyref) + (array.new 0 (i32.const 7) (i32.const 3))) + (func (export "use") (param (ref 0) i32) (result i32) + (array.get 0 (local.get 0) (local.get 1))) + ) + `); + const a = m.exports.make(); + + class B { constructor() { return a; } } + class D extends B { + #x = 1; + constructor() { super(); } + } + + assert.throws( + () => new D(), + TypeError, + "Cannot define private field on a WebAssembly GC object" + ); + + assert.eq(m.exports.use(a, 0), 7); +} + +function testPrivateGetterOnStruct() { + let m = instantiate(` + (module + (type (struct (field i32))) + (func (export "make") (result anyref) + (struct.new 0 (i32.const 42))) + (func (export "use") (param (ref 0)) (result i32) + (struct.get 0 0 (local.get 0))) + ) + `); + const s = m.exports.make(); + + class B { constructor() { return s; } } + class D extends B { + get #x() { return 1; } + constructor() { super(); } + } + + assert.throws( + () => new D(), + TypeError, + "Cannot add private method to a WebAssembly GC object" + ); + + assert.eq(m.exports.use(s), 42); +} + +function testGCSurvival() { + let m = instantiate(` + (module + (type (struct (field i32))) + (func (export "make") (result anyref) + (struct.new 0 (i32.const 42))) + (func (export "use") (param (ref 0)) (result i32) + (struct.get 0 0 (local.get 0))) + ) + `); + const s = m.exports.make(); + + class B { constructor() { return s; } } + class D extends B { + #m() {} + constructor() { super(); } + } + + try { new D(); } catch {} + + // The struct must survive GC with its structure intact. + gc(); + assert.eq(m.exports.use(s), 42); +} + +testPrivateMethodOnStruct(); +testPrivateFieldOnStruct(); +testPrivateMethodOnArray(); +testPrivateFieldOnArray(); +testPrivateGetterOnStruct(); +testGCSurvival(); diff --git a/LayoutTests/fast/css/highlight-pseudo-fill-applies-color-filter-expected.html b/LayoutTests/fast/css/highlight-pseudo-fill-applies-color-filter-expected.html new file mode 100644 index 000000000000..c6e7d04568b5 --- /dev/null +++ b/LayoutTests/fast/css/highlight-pseudo-fill-applies-color-filter-expected.html @@ -0,0 +1,17 @@ + + + + + + +

PASS

+ + + diff --git a/LayoutTests/fast/css/highlight-pseudo-fill-applies-color-filter.html b/LayoutTests/fast/css/highlight-pseudo-fill-applies-color-filter.html new file mode 100644 index 000000000000..6136535bf7c1 --- /dev/null +++ b/LayoutTests/fast/css/highlight-pseudo-fill-applies-color-filter.html @@ -0,0 +1,17 @@ + + + + + + +

PASS

+ + + diff --git a/LayoutTests/fast/custom-elements/xml-parser-reparent-during-construction-crash-expected.txt b/LayoutTests/fast/custom-elements/xml-parser-reparent-during-construction-crash-expected.txt new file mode 100644 index 000000000000..49004868ff5d --- /dev/null +++ b/LayoutTests/fast/custom-elements/xml-parser-reparent-during-construction-crash-expected.txt @@ -0,0 +1,3 @@ +This test passes if it does not crash. + + diff --git a/LayoutTests/fast/custom-elements/xml-parser-reparent-during-construction-crash.xhtml b/LayoutTests/fast/custom-elements/xml-parser-reparent-during-construction-crash.xhtml new file mode 100644 index 000000000000..fd96782b0ae3 --- /dev/null +++ b/LayoutTests/fast/custom-elements/xml-parser-reparent-during-construction-crash.xhtml @@ -0,0 +1,19 @@ + + + + + + +

This test passes if it does not crash.

+
+ + diff --git a/LayoutTests/fast/mediasession/metadata/artwork-image-loader-callback-crash-expected.txt b/LayoutTests/fast/mediasession/metadata/artwork-image-loader-callback-crash-expected.txt new file mode 100644 index 000000000000..730ebf66a0da --- /dev/null +++ b/LayoutTests/fast/mediasession/metadata/artwork-image-loader-callback-crash-expected.txt @@ -0,0 +1 @@ +This test passes if it doesn't crash. diff --git a/LayoutTests/fast/mediasession/metadata/artwork-image-loader-callback-crash.html b/LayoutTests/fast/mediasession/metadata/artwork-image-loader-callback-crash.html new file mode 100644 index 000000000000..a800754e8aa4 --- /dev/null +++ b/LayoutTests/fast/mediasession/metadata/artwork-image-loader-callback-crash.html @@ -0,0 +1,48 @@ + + + + + + +This test passes if it doesn't crash. + + \ No newline at end of file diff --git a/LayoutTests/fast/mediastream/applyConstraints-with-takePhoto-expected.txt b/LayoutTests/fast/mediastream/applyConstraints-with-takePhoto-expected.txt new file mode 100644 index 000000000000..ce0583968657 --- /dev/null +++ b/LayoutTests/fast/mediastream/applyConstraints-with-takePhoto-expected.txt @@ -0,0 +1,3 @@ + +PASS applyConstraints while takePhoto is happening on a cloned track + diff --git a/LayoutTests/fast/mediastream/applyConstraints-with-takePhoto.html b/LayoutTests/fast/mediastream/applyConstraints-with-takePhoto.html new file mode 100644 index 000000000000..15bb77cc7738 --- /dev/null +++ b/LayoutTests/fast/mediastream/applyConstraints-with-takePhoto.html @@ -0,0 +1,32 @@ + + + + + + + + + + + diff --git a/LayoutTests/fast/webcodecs/audio-data-close-during-gc-crash-expected.txt b/LayoutTests/fast/webcodecs/audio-data-close-during-gc-crash-expected.txt new file mode 100644 index 000000000000..03831620f648 --- /dev/null +++ b/LayoutTests/fast/webcodecs/audio-data-close-during-gc-crash-expected.txt @@ -0,0 +1 @@ +Test passes if it does not crash. diff --git a/LayoutTests/fast/webcodecs/audio-data-close-during-gc-crash.html b/LayoutTests/fast/webcodecs/audio-data-close-during-gc-crash.html new file mode 100644 index 000000000000..26498a31beeb --- /dev/null +++ b/LayoutTests/fast/webcodecs/audio-data-close-during-gc-crash.html @@ -0,0 +1,64 @@ + + + + diff --git a/LayoutTests/fast/webgpu/nocrash/fuzz-176882934-expected.txt b/LayoutTests/fast/webgpu/nocrash/fuzz-176882934-expected.txt new file mode 100644 index 000000000000..0533f45490c1 --- /dev/null +++ b/LayoutTests/fast/webgpu/nocrash/fuzz-176882934-expected.txt @@ -0,0 +1,2 @@ +Pass + diff --git a/LayoutTests/fast/webgpu/nocrash/fuzz-176882934.html b/LayoutTests/fast/webgpu/nocrash/fuzz-176882934.html new file mode 100644 index 000000000000..a1c824b04dfe --- /dev/null +++ b/LayoutTests/fast/webgpu/nocrash/fuzz-176882934.html @@ -0,0 +1,60 @@ + + + diff --git a/LayoutTests/fast/webgpu/pipeline-id-collision-bindgroup-validation-bypass-expected.txt b/LayoutTests/fast/webgpu/pipeline-id-collision-bindgroup-validation-bypass-expected.txt new file mode 100644 index 000000000000..9490d44ca416 --- /dev/null +++ b/LayoutTests/fast/webgpu/pipeline-id-collision-bindgroup-validation-bypass-expected.txt @@ -0,0 +1 @@ +PASS: validation error raised for undersized buffer in render pass after compute cache priming CONTROL: PASS: validation error raised for fresh BindGroup with undersized buffer diff --git a/LayoutTests/fast/webgpu/pipeline-id-collision-bindgroup-validation-bypass.html b/LayoutTests/fast/webgpu/pipeline-id-collision-bindgroup-validation-bypass.html new file mode 100644 index 000000000000..0fba47cb94d6 --- /dev/null +++ b/LayoutTests/fast/webgpu/pipeline-id-collision-bindgroup-validation-bypass.html @@ -0,0 +1,120 @@ + + + diff --git a/LayoutTests/fast/webgpu/regression/repro_176812014-expected.txt b/LayoutTests/fast/webgpu/regression/repro_176812014-expected.txt new file mode 100644 index 000000000000..ee4a6c18face --- /dev/null +++ b/LayoutTests/fast/webgpu/regression/repro_176812014-expected.txt @@ -0,0 +1,2 @@ +PASS dispatch was rejected: Auto-generated layouts mismatch + diff --git a/LayoutTests/fast/webgpu/regression/repro_176812014.html b/LayoutTests/fast/webgpu/regression/repro_176812014.html new file mode 100644 index 000000000000..238837725c97 --- /dev/null +++ b/LayoutTests/fast/webgpu/regression/repro_176812014.html @@ -0,0 +1,73 @@ + + + diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/directive-name-case-insensitive-expected.txt b/LayoutTests/http/tests/security/contentSecurityPolicy/directive-name-case-insensitive-expected.txt new file mode 100644 index 000000000000..b32cb0600d27 --- /dev/null +++ b/LayoutTests/http/tests/security/contentSecurityPolicy/directive-name-case-insensitive-expected.txt @@ -0,0 +1,5 @@ +CONSOLE MESSAGE: The Content Security Policy directive 'frame-ancestors' is ignored when delivered via an HTML meta element. +CONSOLE MESSAGE: The Content Security Policy directive 'report-uri' is ignored when delivered via an HTML meta element. +CONSOLE MESSAGE: Unrecognized Content-Security-Policy directive 'aaa'. + +This tests that mixed-case Content Security Policy directive names are lowercased, so the console warnings refer to them in lowercase. diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/directive-name-case-insensitive-strict-dynamic-expected.txt b/LayoutTests/http/tests/security/contentSecurityPolicy/directive-name-case-insensitive-strict-dynamic-expected.txt new file mode 100644 index 000000000000..d7523311b6a4 --- /dev/null +++ b/LayoutTests/http/tests/security/contentSecurityPolicy/directive-name-case-insensitive-strict-dynamic-expected.txt @@ -0,0 +1,4 @@ +CONSOLE MESSAGE: Refused to load http://127.0.0.1:8000/security/contentSecurityPolicy/resources/simpleSourcedScript.js because it does not appear in the script-src directive of the Content Security Policy. + +PASS An uppercase SCRIPT-SRC directive still applies strict-dynamic, so a parser-inserted script is blocked. + diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/directive-name-case-insensitive-strict-dynamic.html b/LayoutTests/http/tests/security/contentSecurityPolicy/directive-name-case-insensitive-strict-dynamic.html new file mode 100644 index 000000000000..4d2e72118528 --- /dev/null +++ b/LayoutTests/http/tests/security/contentSecurityPolicy/directive-name-case-insensitive-strict-dynamic.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/LayoutTests/http/tests/security/contentSecurityPolicy/directive-name-case-insensitive.html b/LayoutTests/http/tests/security/contentSecurityPolicy/directive-name-case-insensitive.html new file mode 100644 index 000000000000..c1cdbea3f05c --- /dev/null +++ b/LayoutTests/http/tests/security/contentSecurityPolicy/directive-name-case-insensitive.html @@ -0,0 +1,13 @@ + + + + + + + +

This tests that mixed-case Content Security Policy directive names are lowercased, so the console warnings refer to them in lowercase.

+ + diff --git a/LayoutTests/http/tests/storageAccess/request-storage-access-rejected-without-gesture-should-not-activate-expected.txt b/LayoutTests/http/tests/storageAccess/request-storage-access-rejected-without-gesture-should-not-activate-expected.txt new file mode 100644 index 000000000000..38eed2435aec --- /dev/null +++ b/LayoutTests/http/tests/storageAccess/request-storage-access-rejected-without-gesture-should-not-activate-expected.txt @@ -0,0 +1,10 @@ +Tests that rejecting requestStorageAccess() without a user gesture does not synthesize user activation. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS Rejection handler correctly had no user activation. +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/http/tests/storageAccess/request-storage-access-rejected-without-gesture-should-not-activate.html b/LayoutTests/http/tests/storageAccess/request-storage-access-rejected-without-gesture-should-not-activate.html new file mode 100644 index 000000000000..5b9aea0c27da --- /dev/null +++ b/LayoutTests/http/tests/storageAccess/request-storage-access-rejected-without-gesture-should-not-activate.html @@ -0,0 +1,49 @@ + + + + + + + + + + + diff --git a/LayoutTests/http/tests/storageAccess/resources/request-storage-access-without-gesture-check-activation-iframe.html b/LayoutTests/http/tests/storageAccess/resources/request-storage-access-without-gesture-check-activation-iframe.html new file mode 100644 index 000000000000..5610afddfdf8 --- /dev/null +++ b/LayoutTests/http/tests/storageAccess/resources/request-storage-access-without-gesture-check-activation-iframe.html @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/LayoutTests/http/wpt/site-isolation/history-traversal/history-traversal-navigate-parent-while-child-loading-expected.txt b/LayoutTests/http/wpt/site-isolation/history-traversal/history-traversal-navigate-parent-while-child-loading-expected.txt new file mode 100644 index 000000000000..fc07a2561091 --- /dev/null +++ b/LayoutTests/http/wpt/site-isolation/history-traversal/history-traversal-navigate-parent-while-child-loading-expected.txt @@ -0,0 +1,4 @@ + + +PASS pushState() in parent while child is doing initial navigation, then go back + diff --git a/LayoutTests/http/wpt/site-isolation/history-traversal/history-traversal-navigate-parent-while-child-loading.html b/LayoutTests/http/wpt/site-isolation/history-traversal/history-traversal-navigate-parent-while-child-loading.html new file mode 100644 index 000000000000..769278f25057 --- /dev/null +++ b/LayoutTests/http/wpt/site-isolation/history-traversal/history-traversal-navigate-parent-while-child-loading.html @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/LayoutTests/http/wpt/webcodecs/audio-encoder-pcm-format-change-expected.txt b/LayoutTests/http/wpt/webcodecs/audio-encoder-pcm-format-change-expected.txt new file mode 100644 index 000000000000..3d61793dea0e --- /dev/null +++ b/LayoutTests/http/wpt/webcodecs/audio-encoder-pcm-format-change-expected.txt @@ -0,0 +1,3 @@ + +PASS AudioEncoder handles switching PCM sample format from f32-planar to s16 mid-stream + diff --git a/LayoutTests/http/wpt/webcodecs/audio-encoder-pcm-format-change.html b/LayoutTests/http/wpt/webcodecs/audio-encoder-pcm-format-change.html new file mode 100644 index 000000000000..5544ce2eccf0 --- /dev/null +++ b/LayoutTests/http/wpt/webcodecs/audio-encoder-pcm-format-change.html @@ -0,0 +1,51 @@ + + + + + + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/at-function-cssom-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/at-function-cssom-expected.txt index 111ae2e696d4..0861559d1851 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/at-function-cssom-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/at-function-cssom-expected.txt @@ -13,24 +13,24 @@ PASS item() PASS Indexed property getter PASS @supports in body PASS CSSFunctionRule.name -FAIL CSSFunctionRule.getParameters() assert_object_equals: property "type" expected "" got "*" -FAIL CSSFunctionRule.returnType assert_equals: expected "" but got "*" +PASS CSSFunctionRule.getParameters() +PASS CSSFunctionRule.returnType PASS CSSFunctionRule escapes PASS CSSFunctionRule.cssText (--empty) -FAIL CSSFunctionRule.cssText (--ret-length) assert_equals: expected "@function --ret-length() returns { }" but got "@function --ret-length() { }" -FAIL CSSFunctionRule.cssText (--ret-length-auto) assert_equals: expected "@function --ret-length-auto() returns type( | auto) { }" but got "@function --ret-length-auto() { }" +PASS CSSFunctionRule.cssText (--ret-length) +PASS CSSFunctionRule.cssText (--ret-length-auto) PASS CSSFunctionRule.cssText (--param-single) -FAIL CSSFunctionRule.cssText (--param-typed) assert_equals: expected "@function --param-typed(--x ) { }" but got "@function --param-typed(--x) { }" -FAIL CSSFunctionRule.cssText (--param-typed-default) assert_equals: expected "@function --param-typed-default(--x : 10px) { }" but got "@function --param-typed-default(--x: 10px) { }" +PASS CSSFunctionRule.cssText (--param-typed) +PASS CSSFunctionRule.cssText (--param-typed-default) PASS CSSFunctionRule.cssText (--param-default) PASS CSSFunctionRule.cssText (--param-multi) -FAIL CSSFunctionRule.cssText (--param-multi-mixed) assert_equals: expected "@function --param-multi-mixed(--x: 10px, --y, --z ) { }" but got "@function --param-multi-mixed(--x: 10px, --y, --z) { }" +PASS CSSFunctionRule.cssText (--param-multi-mixed) PASS CSSFunctionRule.cssText (--body-result) PASS CSSFunctionRule.cssText (--body-locals) -FAIL CSSFunctionRule.cssText (--param-type-fn) assert_equals: expected "@function --param-type-fn(--x ) { }" but got "@function --param-type-fn(--x) { }" +PASS CSSFunctionRule.cssText (--param-type-fn) PASS CSSFunctionRule.cssText (--param-type-fn-uni) PASS CSSFunctionRule.cssText (--ret-type-fn) PASS CSSFunctionRule.cssText (--ret-type-fn-uni) PASS CSSFunctionRule.cssText (--body-result-multi) -FAIL CSSFunctionRule.cssText (--escaped-) assert_equals: expected "@function --escaped-\\9 -tab(--param-\\9 -tab) { --local-\\9 -tab: 1px; }" but got "@function --escaped-\\9 -tab(--param-\\9 -tab) { --local-\t-tab: 1px; }" +PASS CSSFunctionRule.cssText (--escaped-) diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/dashed-function-cycles-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/dashed-function-cycles-expected.txt index 6add1050a037..46035d2c174e 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/dashed-function-cycles-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/dashed-function-cycles-expected.txt @@ -20,8 +20,8 @@ PASS Cycle through global, self PASS Cycle through local, other function PASS Cycle through local, other function, fallback in function PASS Cycle through various variables and other functions -FAIL Function in a cycle with its own default assert_equals: expected "PASS" but got "10px" -FAIL Cyclic defaults assert_equals: expected "42px PASS-y PASS-z" but got "42px var(--z) var(--y)" -FAIL Cyclic outer --b shadows custom property assert_equals: expected "PASS" but got "var(--b)" +PASS Function in a cycle with its own default +PASS Cyclic defaults +PASS Cyclic outer --b shadows custom property PASS Locals are function specific diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/dashed-function-eval-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/dashed-function-eval-expected.txt index 2cefce64fa61..fed4beebcf02 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/dashed-function-eval-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/dashed-function-eval-expected.txt @@ -19,19 +19,19 @@ PASS Parameter with complex type (px) PASS Passing argument to inner function PASS var() in argument resolved before call PASS var() in argument resolved before call, typed -FAIL Argument captures IACVT due to invalid var() assert_equals: expected "PASS" but got "" -FAIL Argument captures IACVT due to invalid var(), typed assert_equals: expected "PASS" but got "" +PASS Argument captures IACVT due to invalid var() +PASS Argument captures IACVT due to invalid var(), typed PASS Argument captures IACVT due to type mismatch PASS Single parameter with default value PASS Multiple parameters with defaults PASS Multiple parameters with defaults, typed -FAIL Default referencing another parameter assert_equals: expected "5px 5px" but got "5px var(--x)" -FAIL Default referencing another parameter, local interference assert_equals: expected "17px 5px" but got "17px var(--x)" -FAIL Default referencing another defaulted parameter assert_equals: expected "5px 5px" but got "5px var(--x)" +PASS Default referencing another parameter +PASS Default referencing another parameter, local interference +PASS Default referencing another defaulted parameter FAIL Typed default with reference assert_equals: expected "5px 6px" but got "" -FAIL IACVT arguments are defaulted assert_equals: expected "1 2 3" but got "" -FAIL IACVT arguments are defaulted, typed assert_equals: expected "1 2 3" but got "" -FAIL Arguments are defaulted on type mismatch assert_equals: expected "1 2 3" but got "" +PASS IACVT arguments are defaulted +PASS IACVT arguments are defaulted, typed +PASS Arguments are defaulted on type mismatch PASS Unused local PASS Local does not affect outer scope PASS Substituting local in result @@ -55,9 +55,9 @@ PASS Inner function call should see resolved outer locals PASS Inner function call should see resolved outer locals (reverse) PASS Parameter shadows custom property PASS Local shadows parameter -FAIL IACVT argument shadows outer scope assert_equals: expected "PASS" but got "" -FAIL IACVT argument shadows outer scope, typed assert_equals: expected "PASS" but got "" -FAIL IACVT argument shadows outer scope, type mismatch assert_equals: expected "PASS" but got "FAIL" +PASS IACVT argument shadows outer scope +PASS IACVT argument shadows outer scope, typed +PASS IACVT argument shadows outer scope, type mismatch PASS Missing only argument PASS Missing one argument of several FAIL Passing list as only argument assert_equals: expected "1px,2px" but got "{1px,2px}" @@ -68,7 +68,7 @@ FAIL Passing {} as argument assert_equals: expected "{}" but got "{{}}" FAIL Passing non-whole-value {} as argument assert_equals: expected "foo{}" but got "{foo{}}" PASS Local variable with initial keyword PASS Local variable with initial keyword, defaulted -FAIL Local variable with initial keyword, no value via IACVT-capture assert_equals: expected "PASS" but got "" +PASS Local variable with initial keyword, no value via IACVT-capture PASS Default with initial keyword FAIL initial appearing via fallback assert_equals: expected "PASS" but got "" PASS Local variable with inherit keyword diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-attr-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-attr-expected.txt index 5d1141889307..b03177520afe 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-attr-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-mixins/function-attr-expected.txt @@ -7,7 +7,7 @@ PASS Return attr(type()) from untyped function PASS Return attr(type()) from typed function PASS Return attr(type(*)) from typed function PASS Return attr(type(*)) from untyped function -FAIL attr() in default parameter value assert_equals: expected "42px" but got "784px" +PASS attr() in default parameter value PASS attr() in local variable PASS Returned url() is attr-tainted PASS Returned url() is attr-tainted, typed attr() diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-pseudo/highlight-fill-color-ignores-stroke-color-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-pseudo/highlight-fill-color-ignores-stroke-color-expected.html new file mode 100644 index 000000000000..f2d20af47c8b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-pseudo/highlight-fill-color-ignores-stroke-color-expected.html @@ -0,0 +1,18 @@ + + +CSS Pseudo-Elements Reference: highlight text fill color uses 'color', not 'stroke-color' + + + +

Test passes if the word below is green. +

PASS
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-pseudo/highlight-fill-color-ignores-stroke-color-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-pseudo/highlight-fill-color-ignores-stroke-color-ref.html new file mode 100644 index 000000000000..f2d20af47c8b --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-pseudo/highlight-fill-color-ignores-stroke-color-ref.html @@ -0,0 +1,18 @@ + + +CSS Pseudo-Elements Reference: highlight text fill color uses 'color', not 'stroke-color' + + + +

Test passes if the word below is green. +

PASS
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-pseudo/highlight-fill-color-ignores-stroke-color.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-pseudo/highlight-fill-color-ignores-stroke-color.html new file mode 100644 index 000000000000..59367b367307 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-pseudo/highlight-fill-color-ignores-stroke-color.html @@ -0,0 +1,24 @@ + + +CSS Pseudo-Elements Test: highlight text fill color uses 'color', not 'stroke-color' + + + + + + + +

Test passes if the word below is green. +

PASS
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/cssom/variable-names-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/cssom/variable-names-expected.txt index 205fff67ec8f..05ae5a07ca87 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/cssom/variable-names-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/cssom/variable-names-expected.txt @@ -1,8 +1,8 @@ PASS custom property '--a' -FAIL custom property '--a;b' assert_equals: appears on specified style (after serialization/re-parsing) expected 1 but got 0 +PASS custom property '--a;b' PASS custom property '---' -FAIL custom property '--\' assert_equals: appears on specified style (after serialization/re-parsing) expected 1 but got 0 +PASS custom property '--\' PASS custom property '--ab' PASS custom property '--0' diff --git a/LayoutTests/imported/w3c/web-platform-tests/svg/animations/animateMotion-spline-invalid-keyTimes-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/svg/animations/animateMotion-spline-invalid-keyTimes-expected.txt index 1c94220c7f29..6cc630b55bd8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/svg/animations/animateMotion-spline-invalid-keyTimes-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/svg/animations/animateMotion-spline-invalid-keyTimes-expected.txt @@ -1,3 +1,3 @@ -FAIL keyTimes does not end with one, calcMode=spline assert_equals: motion path not applied expected 0 but got -10 +PASS keyTimes does not end with one, calcMode=spline diff --git a/LayoutTests/imported/w3c/web-platform-tests/svg/styling/attr-style-media-dynamic-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/svg/styling/attr-style-media-dynamic-expected.txt index 83cccce10a03..59051c3044a0 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/svg/styling/attr-style-media-dynamic-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/svg/styling/attr-style-media-dynamic-expected.txt @@ -1,5 +1,5 @@ -FAIL Changing the media attribute updates the associated stylesheet assert_equals: fill should switch once the second sheet's media matches expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)" -FAIL Removing the media attribute updates the associated stylesheet assert_equals: removing the media attribute should make the rule apply expected "rgb(0, 128, 0)" but got "rgb(0, 0, 0)" +PASS Changing the media attribute updates the associated stylesheet +PASS Removing the media attribute updates the associated stylesheet diff --git a/LayoutTests/imported/w3c/web-platform-tests/svg/styling/attr-style-type-dynamic-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/svg/styling/attr-style-type-dynamic-expected.txt index d2211c6e4944..313644a41c58 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/svg/styling/attr-style-type-dynamic-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/svg/styling/attr-style-type-dynamic-expected.txt @@ -1,6 +1,6 @@ PASS Initial state: invalid type leaves the sheet unprocessed -FAIL Changing the type from invalid to valid creates the stylesheet assert_equals: sheet should be created once the type becomes CSS expected 1 but got 0 +PASS Changing the type from invalid to valid creates the stylesheet PASS Changing the type from valid to invalid removes the stylesheet -FAIL Removing the type attribute restores the default CSS handling assert_equals: sheet should be created once the type attribute is removed expected 1 but got 0 +PASS Removing the type attribute restores the default CSS handling diff --git a/LayoutTests/imported/w3c/web-platform-tests/svg/text/reftests/textpath-combining-marks-expected.html b/LayoutTests/imported/w3c/web-platform-tests/svg/text/reftests/textpath-combining-marks-expected.html new file mode 100644 index 000000000000..0798211e668a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/svg/text/reftests/textpath-combining-marks-expected.html @@ -0,0 +1,17 @@ + + + +SVG text: combining marks on a textPath shape as one typographic character (reference) + + + + + + + + café exposé + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/svg/text/reftests/textpath-combining-marks-ref.html b/LayoutTests/imported/w3c/web-platform-tests/svg/text/reftests/textpath-combining-marks-ref.html new file mode 100644 index 000000000000..0798211e668a --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/svg/text/reftests/textpath-combining-marks-ref.html @@ -0,0 +1,17 @@ + + + +SVG text: combining marks on a textPath shape as one typographic character (reference) + + + + + + + + café exposé + + + + + diff --git a/LayoutTests/imported/w3c/web-platform-tests/svg/text/reftests/textpath-combining-marks.html b/LayoutTests/imported/w3c/web-platform-tests/svg/text/reftests/textpath-combining-marks.html new file mode 100644 index 000000000000..7ab7b5361916 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/svg/text/reftests/textpath-combining-marks.html @@ -0,0 +1,23 @@ + + + + +SVG text: combining marks on a textPath shape as one typographic character + + + + + + + + + + + café exposé + + + + + diff --git a/LayoutTests/inspector/runtime/getDisplayableProperties-expected.txt b/LayoutTests/inspector/runtime/getDisplayableProperties-expected.txt index 9688c4fddf4b..cbfd72377605 100644 --- a/LayoutTests/inspector/runtime/getDisplayableProperties-expected.txt +++ b/LayoutTests/inspector/runtime/getDisplayableProperties-expected.txt @@ -184,89 +184,173 @@ Internal Properties: Evaluating expression... Getting displayable properties... Properties: - "#instancePrivateProperty" => "instancePrivatePropertyValue parent" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "#parentInstancePrivateProperty" => "parentInstancePrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "instancePublicProperty" => "instancePublicPropertyValue parent" (string) [writable | enumerable | configurable | isOwn] - "parentInstancePublicProperty" => "parentInstancePublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] - "__proto__" => "PrivateMembersTestClassParent" (object) [writable | configurable | isOwn] + "#instancePrivateProperty" => "instancePrivatePropertyValue parent" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#parentInstancePrivateProperty" => "parentInstancePrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#instancePrivateGetter" => get "get #instancePrivateGetter() { parent }" (function) [isOwn | isPrivate] + "#instancePrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#instancePrivateGetterSetter" => get "get #instancePrivateGetterSetter() { parent }" (function) [isOwn | isPrivate] + "#instancePrivateGetterSetter" => set "set #instancePrivateGetterSetter(x) { parent }" (function) [isOwn | isPrivate] + "#instancePrivateMethod" => "#instancePrivateMethod() { parent }" (function) [isOwn | isPrivate] + "#instancePrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#instancePrivateSetter" => set "set #instancePrivateSetter(x) { parent }" (function) [isOwn | isPrivate] + "#parentInstancePrivateGetter" => get "get #parentInstancePrivateGetter() { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#parentInstancePrivateGetterSetter" => get "get #parentInstancePrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateGetterSetter" => set "set #parentInstancePrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateMethod" => "#parentInstancePrivateMethod() { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#parentInstancePrivateSetter" => set "set #parentInstancePrivateSetter(x) { }" (function) [isOwn | isPrivate] + "instancePublicProperty" => "instancePublicPropertyValue parent" (string) [writable | enumerable | configurable | isOwn] + "parentInstancePublicProperty" => "parentInstancePublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] + "__proto__" => "PrivateMembersTestClassParent" (object) [writable | configurable | isOwn] -- Running test case: Runtime.getDisplayableProperties.Private.Instance.Child Evaluating expression... Getting displayable properties... Properties: - "#instancePrivateProperty" => "instancePrivatePropertyValue parent" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "#parentInstancePrivateProperty" => "parentInstancePrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "#instancePrivateProperty" => "instancePrivatePropertyValue child" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "#childInstancePrivateProperty" => "childInstancePrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "instancePublicProperty" => "instancePublicPropertyValue child" (string) [writable | enumerable | configurable | isOwn] - "parentInstancePublicProperty" => "parentInstancePublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] - "childInstancePublicProperty" => "childInstancePublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] - "__proto__" => "PrivateMembersTestClassChild" (object) [writable | configurable | isOwn] + "#instancePrivateProperty" => "instancePrivatePropertyValue parent" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#parentInstancePrivateProperty" => "parentInstancePrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#instancePrivateProperty" => "instancePrivatePropertyValue child" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#childInstancePrivateProperty" => "childInstancePrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#instancePrivateGetter" => get "get #instancePrivateGetter() { parent }" (function) [isOwn | isPrivate] + "#instancePrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#instancePrivateGetterSetter" => get "get #instancePrivateGetterSetter() { parent }" (function) [isOwn | isPrivate] + "#instancePrivateGetterSetter" => set "set #instancePrivateGetterSetter(x) { parent }" (function) [isOwn | isPrivate] + "#instancePrivateMethod" => "#instancePrivateMethod() { parent }" (function) [isOwn | isPrivate] + "#instancePrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#instancePrivateSetter" => set "set #instancePrivateSetter(x) { parent }" (function) [isOwn | isPrivate] + "#parentInstancePrivateGetter" => get "get #parentInstancePrivateGetter() { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#parentInstancePrivateGetterSetter" => get "get #parentInstancePrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateGetterSetter" => set "set #parentInstancePrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateMethod" => "#parentInstancePrivateMethod() { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#parentInstancePrivateSetter" => set "set #parentInstancePrivateSetter(x) { }" (function) [isOwn | isPrivate] + "#childInstancePrivateGetter" => get "get #childInstancePrivateGetter() { }" (function) [isOwn | isPrivate] + "#childInstancePrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#childInstancePrivateGetterSetter" => get "get #childInstancePrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#childInstancePrivateGetterSetter" => set "set #childInstancePrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#childInstancePrivateMethod" => "#childInstancePrivateMethod() { }" (function) [isOwn | isPrivate] + "#childInstancePrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#childInstancePrivateSetter" => set "set #childInstancePrivateSetter(x) { }" (function) [isOwn | isPrivate] + "#instancePrivateGetter" => get "get #instancePrivateGetter() { child }" (function) [isOwn | isPrivate] + "#instancePrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#instancePrivateGetterSetter" => get "get #instancePrivateGetterSetter() { child }" (function) [isOwn | isPrivate] + "#instancePrivateGetterSetter" => set "set #instancePrivateGetterSetter(x) { child }" (function) [isOwn | isPrivate] + "#instancePrivateMethod" => "#instancePrivateMethod() { child }" (function) [isOwn | isPrivate] + "#instancePrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#instancePrivateSetter" => set "set #instancePrivateSetter(x) { child }" (function) [isOwn | isPrivate] + "instancePublicProperty" => "instancePublicPropertyValue child" (string) [writable | enumerable | configurable | isOwn] + "parentInstancePublicProperty" => "parentInstancePublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] + "childInstancePublicProperty" => "childInstancePublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] + "__proto__" => "PrivateMembersTestClassChild" (object) [writable | configurable | isOwn] -- Running test case: Runtime.getDisplayableProperties.Private.Constructor.Parent Evaluating expression... Getting displayable properties... Properties: - "#classPrivateProperty" => "classPrivatePropertyValue parent" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "#parentClassPrivateProperty" => "parentClassPrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "length" => 0 (number) [configurable | isOwn] - "name" => "PrivateMembersTestClassParent" (string) [configurable | isOwn] - "prototype" => "PrivateMembersTestClassParent" (object) [isOwn] - "classPublicMethod" => "classPublicMethod() { parent }" (function) [writable | configurable | isOwn] - "classPublicGetter" => get "get classPublicGetter() { parent }" (function) [configurable | isOwn] - "classPublicGetter" => set undefined (undefined) [configurable | isOwn] - "classPublicSetter" => get undefined (undefined) [configurable | isOwn] - "classPublicSetter" => set "set classPublicSetter(x) { parent }" (function) [configurable | isOwn] - "classPublicGetterSetter" => get "get classPublicGetterSetter() { parent }" (function) [configurable | isOwn] - "classPublicGetterSetter" => set "set classPublicGetterSetter(x) { parent }" (function) [configurable | isOwn] - "parentClassPublicMethod" => "parentClassPublicMethod() { }" (function) [writable | configurable | isOwn] - "parentClassPublicGetter" => get "get parentClassPublicGetter() { }" (function) [configurable | isOwn] - "parentClassPublicGetter" => set undefined (undefined) [configurable | isOwn] - "parentClassPublicSetter" => get undefined (undefined) [configurable | isOwn] - "parentClassPublicSetter" => set "set parentClassPublicSetter(x) { }" (function) [configurable | isOwn] - "parentClassPublicGetterSetter" => get "get parentClassPublicGetterSetter() { }" (function) [configurable | isOwn] - "parentClassPublicGetterSetter" => set "set parentClassPublicGetterSetter(x) { }" (function) [configurable | isOwn] - "toString" => "toString() { return \"\"; }" (function) [writable | configurable | isOwn] - "classPublicProperty" => "classPublicPropertyValue parent" (string) [writable | enumerable | configurable | isOwn] - "parentClassPublicProperty" => "parentClassPublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] - "arguments" => "TypeError: 'arguments', 'callee', and 'caller' cannot be accessed in this context." (object error) [wasThrown] - "caller" => "TypeError: 'arguments', 'callee', and 'caller' cannot be accessed in this context." (object error) [wasThrown] - "__proto__" => "function () {\n [native code]\n}" (function) [writable | configurable | isOwn] + "#classPrivateProperty" => "classPrivatePropertyValue parent" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#parentClassPrivateProperty" => "parentClassPrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#classPrivateGetter" => get "get #classPrivateGetter() { parent }" (function) [isOwn | isPrivate] + "#classPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#classPrivateGetterSetter" => get "get #classPrivateGetterSetter() { parent }" (function) [isOwn | isPrivate] + "#classPrivateGetterSetter" => set "set #classPrivateGetterSetter(x) { parent }" (function) [isOwn | isPrivate] + "#classPrivateMethod" => "#classPrivateMethod() { parent }" (function) [isOwn | isPrivate] + "#classPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#classPrivateSetter" => set "set #classPrivateSetter(x) { parent }" (function) [isOwn | isPrivate] + "#parentClassPrivateGetter" => get "get #parentClassPrivateGetter() { }" (function) [isOwn | isPrivate] + "#parentClassPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#parentClassPrivateGetterSetter" => get "get #parentClassPrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#parentClassPrivateGetterSetter" => set "set #parentClassPrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#parentClassPrivateMethod" => "#parentClassPrivateMethod() { }" (function) [isOwn | isPrivate] + "#parentClassPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#parentClassPrivateSetter" => set "set #parentClassPrivateSetter(x) { }" (function) [isOwn | isPrivate] + "length" => 0 (number) [configurable | isOwn] + "name" => "PrivateMembersTestClassParent" (string) [configurable | isOwn] + "prototype" => "PrivateMembersTestClassParent" (object) [isOwn] + "classPublicMethod" => "classPublicMethod() { parent }" (function) [writable | configurable | isOwn] + "classPublicGetter" => get "get classPublicGetter() { parent }" (function) [configurable | isOwn] + "classPublicGetter" => set undefined (undefined) [configurable | isOwn] + "classPublicSetter" => get undefined (undefined) [configurable | isOwn] + "classPublicSetter" => set "set classPublicSetter(x) { parent }" (function) [configurable | isOwn] + "classPublicGetterSetter" => get "get classPublicGetterSetter() { parent }" (function) [configurable | isOwn] + "classPublicGetterSetter" => set "set classPublicGetterSetter(x) { parent }" (function) [configurable | isOwn] + "parentClassPublicMethod" => "parentClassPublicMethod() { }" (function) [writable | configurable | isOwn] + "parentClassPublicGetter" => get "get parentClassPublicGetter() { }" (function) [configurable | isOwn] + "parentClassPublicGetter" => set undefined (undefined) [configurable | isOwn] + "parentClassPublicSetter" => get undefined (undefined) [configurable | isOwn] + "parentClassPublicSetter" => set "set parentClassPublicSetter(x) { }" (function) [configurable | isOwn] + "parentClassPublicGetterSetter" => get "get parentClassPublicGetterSetter() { }" (function) [configurable | isOwn] + "parentClassPublicGetterSetter" => set "set parentClassPublicGetterSetter(x) { }" (function) [configurable | isOwn] + "toString" => "toString() { return \"\"; }" (function) [writable | configurable | isOwn] + "classPublicProperty" => "classPublicPropertyValue parent" (string) [writable | enumerable | configurable | isOwn] + "parentClassPublicProperty" => "parentClassPublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] + "arguments" => "TypeError: 'arguments', 'callee', and 'caller' cannot be accessed in this context." (object error) [wasThrown] + "caller" => "TypeError: 'arguments', 'callee', and 'caller' cannot be accessed in this context." (object error) [wasThrown] + "__proto__" => "function () {\n [native code]\n}" (function) [writable | configurable | isOwn] -- Running test case: Runtime.getDisplayableProperties.Private.Constructor.Child Evaluating expression... Getting displayable properties... Properties: - "#classPrivateProperty" => "classPrivatePropertyValue child" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "#childClassPrivateProperty" => "childClassPrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "length" => 0 (number) [configurable | isOwn] - "name" => "PrivateMembersTestClassChild" (string) [configurable | isOwn] - "prototype" => "PrivateMembersTestClassChild" (object) [isOwn] - "classPublicMethod" => "classPublicMethod() { child }" (function) [writable | configurable | isOwn] - "classPublicGetter" => get "get classPublicGetter() { child }" (function) [configurable | isOwn] - "classPublicGetter" => set undefined (undefined) [configurable | isOwn] - "classPublicSetter" => get undefined (undefined) [configurable | isOwn] - "classPublicSetter" => set "set classPublicSetter(x) { child }" (function) [configurable | isOwn] - "classPublicGetterSetter" => get "get classPublicGetterSetter() { child }" (function) [configurable | isOwn] - "classPublicGetterSetter" => set "set classPublicGetterSetter(x) { child }" (function) [configurable | isOwn] - "childClassPublicMethod" => "childClassPublicMethod() { }" (function) [writable | configurable | isOwn] - "childClassPublicGetter" => get "get childClassPublicGetter() { }" (function) [configurable | isOwn] - "childClassPublicGetter" => set undefined (undefined) [configurable | isOwn] - "childClassPublicSetter" => get undefined (undefined) [configurable | isOwn] - "childClassPublicSetter" => set "set childClassPublicSetter(x) { }" (function) [configurable | isOwn] - "childClassPublicGetterSetter" => get "get childClassPublicGetterSetter() { }" (function) [configurable | isOwn] - "childClassPublicGetterSetter" => set "set childClassPublicGetterSetter(x) { }" (function) [configurable | isOwn] - "toString" => "toString() { return \"\"; }" (function) [writable | configurable | isOwn] - "classPublicProperty" => "classPublicPropertyValue child" (string) [writable | enumerable | configurable | isOwn] - "childClassPublicProperty" => "childClassPublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] - "arguments" => "TypeError: 'arguments', 'callee', and 'caller' cannot be accessed in this context." (object error) [wasThrown] - "caller" => "TypeError: 'arguments', 'callee', and 'caller' cannot be accessed in this context." (object error) [wasThrown] - "__proto__" => "" (function class) [writable | configurable | isOwn] + "#classPrivateProperty" => "classPrivatePropertyValue child" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#childClassPrivateProperty" => "childClassPrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#childClassPrivateGetter" => get "get #childClassPrivateGetter() { }" (function) [isOwn | isPrivate] + "#childClassPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#childClassPrivateGetterSetter" => get "get #childClassPrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#childClassPrivateGetterSetter" => set "set #childClassPrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#childClassPrivateMethod" => "#childClassPrivateMethod() { }" (function) [isOwn | isPrivate] + "#childClassPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#childClassPrivateSetter" => set "set #childClassPrivateSetter(x) { }" (function) [isOwn | isPrivate] + "#classPrivateGetter" => get "get #classPrivateGetter() { child }" (function) [isOwn | isPrivate] + "#classPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#classPrivateGetterSetter" => get "get #classPrivateGetterSetter() { child }" (function) [isOwn | isPrivate] + "#classPrivateGetterSetter" => set "set #classPrivateGetterSetter(x) { child }" (function) [isOwn | isPrivate] + "#classPrivateMethod" => "#classPrivateMethod() { child }" (function) [isOwn | isPrivate] + "#classPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#classPrivateSetter" => set "set #classPrivateSetter(x) { child }" (function) [isOwn | isPrivate] + "length" => 0 (number) [configurable | isOwn] + "name" => "PrivateMembersTestClassChild" (string) [configurable | isOwn] + "prototype" => "PrivateMembersTestClassChild" (object) [isOwn] + "classPublicMethod" => "classPublicMethod() { child }" (function) [writable | configurable | isOwn] + "classPublicGetter" => get "get classPublicGetter() { child }" (function) [configurable | isOwn] + "classPublicGetter" => set undefined (undefined) [configurable | isOwn] + "classPublicSetter" => get undefined (undefined) [configurable | isOwn] + "classPublicSetter" => set "set classPublicSetter(x) { child }" (function) [configurable | isOwn] + "classPublicGetterSetter" => get "get classPublicGetterSetter() { child }" (function) [configurable | isOwn] + "classPublicGetterSetter" => set "set classPublicGetterSetter(x) { child }" (function) [configurable | isOwn] + "childClassPublicMethod" => "childClassPublicMethod() { }" (function) [writable | configurable | isOwn] + "childClassPublicGetter" => get "get childClassPublicGetter() { }" (function) [configurable | isOwn] + "childClassPublicGetter" => set undefined (undefined) [configurable | isOwn] + "childClassPublicSetter" => get undefined (undefined) [configurable | isOwn] + "childClassPublicSetter" => set "set childClassPublicSetter(x) { }" (function) [configurable | isOwn] + "childClassPublicGetterSetter" => get "get childClassPublicGetterSetter() { }" (function) [configurable | isOwn] + "childClassPublicGetterSetter" => set "set childClassPublicGetterSetter(x) { }" (function) [configurable | isOwn] + "toString" => "toString() { return \"\"; }" (function) [writable | configurable | isOwn] + "classPublicProperty" => "classPublicPropertyValue child" (string) [writable | enumerable | configurable | isOwn] + "childClassPublicProperty" => "childClassPublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] + "arguments" => "TypeError: 'arguments', 'callee', and 'caller' cannot be accessed in this context." (object error) [wasThrown] + "caller" => "TypeError: 'arguments', 'callee', and 'caller' cannot be accessed in this context." (object error) [wasThrown] + "__proto__" => "" (function class) [writable | configurable | isOwn] -- Running test case: Runtime.getDisplayableProperties.Private.Prototype.Parent Evaluating expression... Getting displayable properties... Properties: + "#classPrivateGetter" => get "get #classPrivateGetter() { parent }" (function) [isOwn | isPrivate] + "#classPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#classPrivateGetterSetter" => get "get #classPrivateGetterSetter() { parent }" (function) [isOwn | isPrivate] + "#classPrivateGetterSetter" => set "set #classPrivateGetterSetter(x) { parent }" (function) [isOwn | isPrivate] + "#classPrivateMethod" => "#classPrivateMethod() { parent }" (function) [isOwn | isPrivate] + "#classPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#classPrivateSetter" => set "set #classPrivateSetter(x) { parent }" (function) [isOwn | isPrivate] + "#parentClassPrivateGetter" => get "get #parentClassPrivateGetter() { }" (function) [isOwn | isPrivate] + "#parentClassPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#parentClassPrivateGetterSetter" => get "get #parentClassPrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#parentClassPrivateGetterSetter" => set "set #parentClassPrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#parentClassPrivateMethod" => "#parentClassPrivateMethod() { }" (function) [isOwn | isPrivate] + "#parentClassPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#parentClassPrivateSetter" => set "set #parentClassPrivateSetter(x) { }" (function) [isOwn | isPrivate] "constructor" => "" (function class) [writable | configurable | isOwn] "instancePublicMethod" => "instancePublicMethod() { parent }" (function) [writable | configurable | isOwn] "instancePublicGetter" => get "get instancePublicGetter() { parent }" (function) [configurable | isOwn] @@ -288,6 +372,20 @@ Properties: Evaluating expression... Getting displayable properties... Properties: + "#childClassPrivateGetter" => get "get #childClassPrivateGetter() { }" (function) [isOwn | isPrivate] + "#childClassPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#childClassPrivateGetterSetter" => get "get #childClassPrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#childClassPrivateGetterSetter" => set "set #childClassPrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#childClassPrivateMethod" => "#childClassPrivateMethod() { }" (function) [isOwn | isPrivate] + "#childClassPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#childClassPrivateSetter" => set "set #childClassPrivateSetter(x) { }" (function) [isOwn | isPrivate] + "#classPrivateGetter" => get "get #classPrivateGetter() { child }" (function) [isOwn | isPrivate] + "#classPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#classPrivateGetterSetter" => get "get #classPrivateGetterSetter() { child }" (function) [isOwn | isPrivate] + "#classPrivateGetterSetter" => set "set #classPrivateGetterSetter(x) { child }" (function) [isOwn | isPrivate] + "#classPrivateMethod" => "#classPrivateMethod() { child }" (function) [isOwn | isPrivate] + "#classPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#classPrivateSetter" => set "set #classPrivateSetter(x) { child }" (function) [isOwn | isPrivate] "constructor" => "" (function class) [writable | configurable | isOwn] "instancePublicMethod" => "instancePublicMethod() { child }" (function) [writable | configurable | isOwn] "instancePublicGetter" => get "get instancePublicGetter() { child }" (function) [configurable | isOwn] diff --git a/LayoutTests/inspector/runtime/getProperties-expected.txt b/LayoutTests/inspector/runtime/getProperties-expected.txt index f5f04a2239da..eb2d8ac3fe14 100644 --- a/LayoutTests/inspector/runtime/getProperties-expected.txt +++ b/LayoutTests/inspector/runtime/getProperties-expected.txt @@ -158,85 +158,169 @@ Internal Properties: Evaluating expression... Getting own properties... Properties: - "#instancePrivateProperty" => "instancePrivatePropertyValue parent" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "#parentInstancePrivateProperty" => "parentInstancePrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "instancePublicProperty" => "instancePublicPropertyValue parent" (string) [writable | enumerable | configurable | isOwn] - "parentInstancePublicProperty" => "parentInstancePublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] - "__proto__" => "PrivateMembersTestClassParent" (object) [writable | configurable | isOwn] + "#instancePrivateProperty" => "instancePrivatePropertyValue parent" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#parentInstancePrivateProperty" => "parentInstancePrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#instancePrivateGetter" => get "get #instancePrivateGetter() { parent }" (function) [isOwn | isPrivate] + "#instancePrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#instancePrivateGetterSetter" => get "get #instancePrivateGetterSetter() { parent }" (function) [isOwn | isPrivate] + "#instancePrivateGetterSetter" => set "set #instancePrivateGetterSetter(x) { parent }" (function) [isOwn | isPrivate] + "#instancePrivateMethod" => "#instancePrivateMethod() { parent }" (function) [isOwn | isPrivate] + "#instancePrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#instancePrivateSetter" => set "set #instancePrivateSetter(x) { parent }" (function) [isOwn | isPrivate] + "#parentInstancePrivateGetter" => get "get #parentInstancePrivateGetter() { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#parentInstancePrivateGetterSetter" => get "get #parentInstancePrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateGetterSetter" => set "set #parentInstancePrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateMethod" => "#parentInstancePrivateMethod() { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#parentInstancePrivateSetter" => set "set #parentInstancePrivateSetter(x) { }" (function) [isOwn | isPrivate] + "instancePublicProperty" => "instancePublicPropertyValue parent" (string) [writable | enumerable | configurable | isOwn] + "parentInstancePublicProperty" => "parentInstancePublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] + "__proto__" => "PrivateMembersTestClassParent" (object) [writable | configurable | isOwn] -- Running test case: Runtime.getProperties.Private.Instance.Child Evaluating expression... Getting own properties... Properties: - "#instancePrivateProperty" => "instancePrivatePropertyValue parent" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "#parentInstancePrivateProperty" => "parentInstancePrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "#instancePrivateProperty" => "instancePrivatePropertyValue child" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "#childInstancePrivateProperty" => "childInstancePrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "instancePublicProperty" => "instancePublicPropertyValue child" (string) [writable | enumerable | configurable | isOwn] - "parentInstancePublicProperty" => "parentInstancePublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] - "childInstancePublicProperty" => "childInstancePublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] - "__proto__" => "PrivateMembersTestClassChild" (object) [writable | configurable | isOwn] + "#instancePrivateProperty" => "instancePrivatePropertyValue parent" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#parentInstancePrivateProperty" => "parentInstancePrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#instancePrivateProperty" => "instancePrivatePropertyValue child" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#childInstancePrivateProperty" => "childInstancePrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#instancePrivateGetter" => get "get #instancePrivateGetter() { parent }" (function) [isOwn | isPrivate] + "#instancePrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#instancePrivateGetterSetter" => get "get #instancePrivateGetterSetter() { parent }" (function) [isOwn | isPrivate] + "#instancePrivateGetterSetter" => set "set #instancePrivateGetterSetter(x) { parent }" (function) [isOwn | isPrivate] + "#instancePrivateMethod" => "#instancePrivateMethod() { parent }" (function) [isOwn | isPrivate] + "#instancePrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#instancePrivateSetter" => set "set #instancePrivateSetter(x) { parent }" (function) [isOwn | isPrivate] + "#parentInstancePrivateGetter" => get "get #parentInstancePrivateGetter() { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#parentInstancePrivateGetterSetter" => get "get #parentInstancePrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateGetterSetter" => set "set #parentInstancePrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateMethod" => "#parentInstancePrivateMethod() { }" (function) [isOwn | isPrivate] + "#parentInstancePrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#parentInstancePrivateSetter" => set "set #parentInstancePrivateSetter(x) { }" (function) [isOwn | isPrivate] + "#childInstancePrivateGetter" => get "get #childInstancePrivateGetter() { }" (function) [isOwn | isPrivate] + "#childInstancePrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#childInstancePrivateGetterSetter" => get "get #childInstancePrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#childInstancePrivateGetterSetter" => set "set #childInstancePrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#childInstancePrivateMethod" => "#childInstancePrivateMethod() { }" (function) [isOwn | isPrivate] + "#childInstancePrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#childInstancePrivateSetter" => set "set #childInstancePrivateSetter(x) { }" (function) [isOwn | isPrivate] + "#instancePrivateGetter" => get "get #instancePrivateGetter() { child }" (function) [isOwn | isPrivate] + "#instancePrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#instancePrivateGetterSetter" => get "get #instancePrivateGetterSetter() { child }" (function) [isOwn | isPrivate] + "#instancePrivateGetterSetter" => set "set #instancePrivateGetterSetter(x) { child }" (function) [isOwn | isPrivate] + "#instancePrivateMethod" => "#instancePrivateMethod() { child }" (function) [isOwn | isPrivate] + "#instancePrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#instancePrivateSetter" => set "set #instancePrivateSetter(x) { child }" (function) [isOwn | isPrivate] + "instancePublicProperty" => "instancePublicPropertyValue child" (string) [writable | enumerable | configurable | isOwn] + "parentInstancePublicProperty" => "parentInstancePublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] + "childInstancePublicProperty" => "childInstancePublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] + "__proto__" => "PrivateMembersTestClassChild" (object) [writable | configurable | isOwn] -- Running test case: Runtime.getProperties.Private.Constructor.Parent Evaluating expression... Getting own properties... Properties: - "#classPrivateProperty" => "classPrivatePropertyValue parent" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "#parentClassPrivateProperty" => "parentClassPrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "length" => 0 (number) [configurable | isOwn] - "name" => "PrivateMembersTestClassParent" (string) [configurable | isOwn] - "prototype" => "PrivateMembersTestClassParent" (object) [isOwn] - "classPublicMethod" => "classPublicMethod() { parent }" (function) [writable | configurable | isOwn] - "classPublicGetter" => get "get classPublicGetter() { parent }" (function) [configurable | isOwn] - "classPublicGetter" => set undefined (undefined) [configurable | isOwn] - "classPublicSetter" => get undefined (undefined) [configurable | isOwn] - "classPublicSetter" => set "set classPublicSetter(x) { parent }" (function) [configurable | isOwn] - "classPublicGetterSetter" => get "get classPublicGetterSetter() { parent }" (function) [configurable | isOwn] - "classPublicGetterSetter" => set "set classPublicGetterSetter(x) { parent }" (function) [configurable | isOwn] - "parentClassPublicMethod" => "parentClassPublicMethod() { }" (function) [writable | configurable | isOwn] - "parentClassPublicGetter" => get "get parentClassPublicGetter() { }" (function) [configurable | isOwn] - "parentClassPublicGetter" => set undefined (undefined) [configurable | isOwn] - "parentClassPublicSetter" => get undefined (undefined) [configurable | isOwn] - "parentClassPublicSetter" => set "set parentClassPublicSetter(x) { }" (function) [configurable | isOwn] - "parentClassPublicGetterSetter" => get "get parentClassPublicGetterSetter() { }" (function) [configurable | isOwn] - "parentClassPublicGetterSetter" => set "set parentClassPublicGetterSetter(x) { }" (function) [configurable | isOwn] - "toString" => "toString() { return \"\"; }" (function) [writable | configurable | isOwn] - "classPublicProperty" => "classPublicPropertyValue parent" (string) [writable | enumerable | configurable | isOwn] - "parentClassPublicProperty" => "parentClassPublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] - "__proto__" => "function () {\n [native code]\n}" (function) [writable | configurable | isOwn] + "#classPrivateProperty" => "classPrivatePropertyValue parent" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#parentClassPrivateProperty" => "parentClassPrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#classPrivateGetter" => get "get #classPrivateGetter() { parent }" (function) [isOwn | isPrivate] + "#classPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#classPrivateGetterSetter" => get "get #classPrivateGetterSetter() { parent }" (function) [isOwn | isPrivate] + "#classPrivateGetterSetter" => set "set #classPrivateGetterSetter(x) { parent }" (function) [isOwn | isPrivate] + "#classPrivateMethod" => "#classPrivateMethod() { parent }" (function) [isOwn | isPrivate] + "#classPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#classPrivateSetter" => set "set #classPrivateSetter(x) { parent }" (function) [isOwn | isPrivate] + "#parentClassPrivateGetter" => get "get #parentClassPrivateGetter() { }" (function) [isOwn | isPrivate] + "#parentClassPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#parentClassPrivateGetterSetter" => get "get #parentClassPrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#parentClassPrivateGetterSetter" => set "set #parentClassPrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#parentClassPrivateMethod" => "#parentClassPrivateMethod() { }" (function) [isOwn | isPrivate] + "#parentClassPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#parentClassPrivateSetter" => set "set #parentClassPrivateSetter(x) { }" (function) [isOwn | isPrivate] + "length" => 0 (number) [configurable | isOwn] + "name" => "PrivateMembersTestClassParent" (string) [configurable | isOwn] + "prototype" => "PrivateMembersTestClassParent" (object) [isOwn] + "classPublicMethod" => "classPublicMethod() { parent }" (function) [writable | configurable | isOwn] + "classPublicGetter" => get "get classPublicGetter() { parent }" (function) [configurable | isOwn] + "classPublicGetter" => set undefined (undefined) [configurable | isOwn] + "classPublicSetter" => get undefined (undefined) [configurable | isOwn] + "classPublicSetter" => set "set classPublicSetter(x) { parent }" (function) [configurable | isOwn] + "classPublicGetterSetter" => get "get classPublicGetterSetter() { parent }" (function) [configurable | isOwn] + "classPublicGetterSetter" => set "set classPublicGetterSetter(x) { parent }" (function) [configurable | isOwn] + "parentClassPublicMethod" => "parentClassPublicMethod() { }" (function) [writable | configurable | isOwn] + "parentClassPublicGetter" => get "get parentClassPublicGetter() { }" (function) [configurable | isOwn] + "parentClassPublicGetter" => set undefined (undefined) [configurable | isOwn] + "parentClassPublicSetter" => get undefined (undefined) [configurable | isOwn] + "parentClassPublicSetter" => set "set parentClassPublicSetter(x) { }" (function) [configurable | isOwn] + "parentClassPublicGetterSetter" => get "get parentClassPublicGetterSetter() { }" (function) [configurable | isOwn] + "parentClassPublicGetterSetter" => set "set parentClassPublicGetterSetter(x) { }" (function) [configurable | isOwn] + "toString" => "toString() { return \"\"; }" (function) [writable | configurable | isOwn] + "classPublicProperty" => "classPublicPropertyValue parent" (string) [writable | enumerable | configurable | isOwn] + "parentClassPublicProperty" => "parentClassPublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] + "__proto__" => "function () {\n [native code]\n}" (function) [writable | configurable | isOwn] -- Running test case: Runtime.getProperties.Private.Constructor.Child Evaluating expression... Getting own properties... Properties: - "#classPrivateProperty" => "classPrivatePropertyValue child" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "#childClassPrivateProperty" => "childClassPrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] - "length" => 0 (number) [configurable | isOwn] - "name" => "PrivateMembersTestClassChild" (string) [configurable | isOwn] - "prototype" => "PrivateMembersTestClassChild" (object) [isOwn] - "classPublicMethod" => "classPublicMethod() { child }" (function) [writable | configurable | isOwn] - "classPublicGetter" => get "get classPublicGetter() { child }" (function) [configurable | isOwn] - "classPublicGetter" => set undefined (undefined) [configurable | isOwn] - "classPublicSetter" => get undefined (undefined) [configurable | isOwn] - "classPublicSetter" => set "set classPublicSetter(x) { child }" (function) [configurable | isOwn] - "classPublicGetterSetter" => get "get classPublicGetterSetter() { child }" (function) [configurable | isOwn] - "classPublicGetterSetter" => set "set classPublicGetterSetter(x) { child }" (function) [configurable | isOwn] - "childClassPublicMethod" => "childClassPublicMethod() { }" (function) [writable | configurable | isOwn] - "childClassPublicGetter" => get "get childClassPublicGetter() { }" (function) [configurable | isOwn] - "childClassPublicGetter" => set undefined (undefined) [configurable | isOwn] - "childClassPublicSetter" => get undefined (undefined) [configurable | isOwn] - "childClassPublicSetter" => set "set childClassPublicSetter(x) { }" (function) [configurable | isOwn] - "childClassPublicGetterSetter" => get "get childClassPublicGetterSetter() { }" (function) [configurable | isOwn] - "childClassPublicGetterSetter" => set "set childClassPublicGetterSetter(x) { }" (function) [configurable | isOwn] - "toString" => "toString() { return \"\"; }" (function) [writable | configurable | isOwn] - "classPublicProperty" => "classPublicPropertyValue child" (string) [writable | enumerable | configurable | isOwn] - "childClassPublicProperty" => "childClassPublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] - "__proto__" => "" (function class) [writable | configurable | isOwn] + "#classPrivateProperty" => "classPrivatePropertyValue child" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#childClassPrivateProperty" => "childClassPrivatePropertyValue" (string) [writable | enumerable | configurable | isOwn | isPrivate] + "#childClassPrivateGetter" => get "get #childClassPrivateGetter() { }" (function) [isOwn | isPrivate] + "#childClassPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#childClassPrivateGetterSetter" => get "get #childClassPrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#childClassPrivateGetterSetter" => set "set #childClassPrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#childClassPrivateMethod" => "#childClassPrivateMethod() { }" (function) [isOwn | isPrivate] + "#childClassPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#childClassPrivateSetter" => set "set #childClassPrivateSetter(x) { }" (function) [isOwn | isPrivate] + "#classPrivateGetter" => get "get #classPrivateGetter() { child }" (function) [isOwn | isPrivate] + "#classPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#classPrivateGetterSetter" => get "get #classPrivateGetterSetter() { child }" (function) [isOwn | isPrivate] + "#classPrivateGetterSetter" => set "set #classPrivateGetterSetter(x) { child }" (function) [isOwn | isPrivate] + "#classPrivateMethod" => "#classPrivateMethod() { child }" (function) [isOwn | isPrivate] + "#classPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#classPrivateSetter" => set "set #classPrivateSetter(x) { child }" (function) [isOwn | isPrivate] + "length" => 0 (number) [configurable | isOwn] + "name" => "PrivateMembersTestClassChild" (string) [configurable | isOwn] + "prototype" => "PrivateMembersTestClassChild" (object) [isOwn] + "classPublicMethod" => "classPublicMethod() { child }" (function) [writable | configurable | isOwn] + "classPublicGetter" => get "get classPublicGetter() { child }" (function) [configurable | isOwn] + "classPublicGetter" => set undefined (undefined) [configurable | isOwn] + "classPublicSetter" => get undefined (undefined) [configurable | isOwn] + "classPublicSetter" => set "set classPublicSetter(x) { child }" (function) [configurable | isOwn] + "classPublicGetterSetter" => get "get classPublicGetterSetter() { child }" (function) [configurable | isOwn] + "classPublicGetterSetter" => set "set classPublicGetterSetter(x) { child }" (function) [configurable | isOwn] + "childClassPublicMethod" => "childClassPublicMethod() { }" (function) [writable | configurable | isOwn] + "childClassPublicGetter" => get "get childClassPublicGetter() { }" (function) [configurable | isOwn] + "childClassPublicGetter" => set undefined (undefined) [configurable | isOwn] + "childClassPublicSetter" => get undefined (undefined) [configurable | isOwn] + "childClassPublicSetter" => set "set childClassPublicSetter(x) { }" (function) [configurable | isOwn] + "childClassPublicGetterSetter" => get "get childClassPublicGetterSetter() { }" (function) [configurable | isOwn] + "childClassPublicGetterSetter" => set "set childClassPublicGetterSetter(x) { }" (function) [configurable | isOwn] + "toString" => "toString() { return \"\"; }" (function) [writable | configurable | isOwn] + "classPublicProperty" => "classPublicPropertyValue child" (string) [writable | enumerable | configurable | isOwn] + "childClassPublicProperty" => "childClassPublicPropertyValue" (string) [writable | enumerable | configurable | isOwn] + "__proto__" => "" (function class) [writable | configurable | isOwn] -- Running test case: Runtime.getProperties.Private.Prototype.Parent Evaluating expression... Getting own properties... Properties: + "#classPrivateGetter" => get "get #classPrivateGetter() { parent }" (function) [isOwn | isPrivate] + "#classPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#classPrivateGetterSetter" => get "get #classPrivateGetterSetter() { parent }" (function) [isOwn | isPrivate] + "#classPrivateGetterSetter" => set "set #classPrivateGetterSetter(x) { parent }" (function) [isOwn | isPrivate] + "#classPrivateMethod" => "#classPrivateMethod() { parent }" (function) [isOwn | isPrivate] + "#classPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#classPrivateSetter" => set "set #classPrivateSetter(x) { parent }" (function) [isOwn | isPrivate] + "#parentClassPrivateGetter" => get "get #parentClassPrivateGetter() { }" (function) [isOwn | isPrivate] + "#parentClassPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#parentClassPrivateGetterSetter" => get "get #parentClassPrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#parentClassPrivateGetterSetter" => set "set #parentClassPrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#parentClassPrivateMethod" => "#parentClassPrivateMethod() { }" (function) [isOwn | isPrivate] + "#parentClassPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#parentClassPrivateSetter" => set "set #parentClassPrivateSetter(x) { }" (function) [isOwn | isPrivate] "constructor" => "" (function class) [writable | configurable | isOwn] "instancePublicMethod" => "instancePublicMethod() { parent }" (function) [writable | configurable | isOwn] "instancePublicGetter" => get "get instancePublicGetter() { parent }" (function) [configurable | isOwn] @@ -258,6 +342,20 @@ Properties: Evaluating expression... Getting own properties... Properties: + "#childClassPrivateGetter" => get "get #childClassPrivateGetter() { }" (function) [isOwn | isPrivate] + "#childClassPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#childClassPrivateGetterSetter" => get "get #childClassPrivateGetterSetter() { }" (function) [isOwn | isPrivate] + "#childClassPrivateGetterSetter" => set "set #childClassPrivateGetterSetter(x) { }" (function) [isOwn | isPrivate] + "#childClassPrivateMethod" => "#childClassPrivateMethod() { }" (function) [isOwn | isPrivate] + "#childClassPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#childClassPrivateSetter" => set "set #childClassPrivateSetter(x) { }" (function) [isOwn | isPrivate] + "#classPrivateGetter" => get "get #classPrivateGetter() { child }" (function) [isOwn | isPrivate] + "#classPrivateGetter" => set undefined (undefined) [isOwn | isPrivate] + "#classPrivateGetterSetter" => get "get #classPrivateGetterSetter() { child }" (function) [isOwn | isPrivate] + "#classPrivateGetterSetter" => set "set #classPrivateGetterSetter(x) { child }" (function) [isOwn | isPrivate] + "#classPrivateMethod" => "#classPrivateMethod() { child }" (function) [isOwn | isPrivate] + "#classPrivateSetter" => get undefined (undefined) [isOwn | isPrivate] + "#classPrivateSetter" => set "set #classPrivateSetter(x) { child }" (function) [isOwn | isPrivate] "constructor" => "" (function class) [writable | configurable | isOwn] "instancePublicMethod" => "instancePublicMethod() { child }" (function) [writable | configurable | isOwn] "instancePublicGetter" => get "get instancePublicGetter() { child }" (function) [configurable | isOwn] diff --git a/LayoutTests/ipc/coreipc.js b/LayoutTests/ipc/coreipc.js index 03ba39ec36c5..a15c63ed87c4 100644 --- a/LayoutTests/ipc/coreipc.js +++ b/LayoutTests/ipc/coreipc.js @@ -759,6 +759,10 @@ export class ArgumentSerializer { if (argument === null) { return []; } else throw new SerializationError(`std::nullptr_t is not null`); + case 'std::monostate': + if (argument === null) { + return []; + } else throw new SerializationError(`std::monostate is not null`); case 'WebCore::SharedMemory::Handle': case 'WebCore::SharedMemoryHandle': case 'MachSendRight': @@ -1199,7 +1203,9 @@ export class ArgumentParser { return [position, {parsedValue: result, parsedType: 'String'}]; } case 'std::nullptr_t': - return [position, {parsedValue: 'null', parsedType: 'std::nullptr_t'}]; + return [position, {parsedValue: null, parsedType: 'std::nullptr_t'}]; + case 'std::monostate': + return [position, {parsedValue: null, parsedType: 'std::monostate'}]; } return undefined; } diff --git a/LayoutTests/ipc/forged-resource-load-statistics-storage-access-expected.txt b/LayoutTests/ipc/forged-resource-load-statistics-storage-access-expected.txt new file mode 100644 index 000000000000..dfa02d6ca0b0 --- /dev/null +++ b/LayoutTests/ipc/forged-resource-load-statistics-storage-access-expected.txt @@ -0,0 +1,4 @@ +Test that a WebContent process cannot forge a persistent storage-access grant by sending storageAccessUnderTopFrameDomains via NetworkConnectionToWebProcess::ResourceLoadStatisticsUpdated. + +PASS: no storage-access grant was created from forged statistics + diff --git a/LayoutTests/ipc/forged-resource-load-statistics-storage-access.html b/LayoutTests/ipc/forged-resource-load-statistics-storage-access.html new file mode 100644 index 000000000000..1f5fdad59173 --- /dev/null +++ b/LayoutTests/ipc/forged-resource-load-statistics-storage-access.html @@ -0,0 +1,110 @@ + + + +

Test that a WebContent process cannot forge a persistent storage-access grant by sending +storageAccessUnderTopFrameDomains via NetworkConnectionToWebProcess::ResourceLoadStatisticsUpdated.

+

+
+
+
diff --git a/LayoutTests/ipc/loadping-firstpartyforcookies-message-check-expected.txt b/LayoutTests/ipc/loadping-firstpartyforcookies-message-check-expected.txt
new file mode 100644
index 000000000000..b7c9dd005623
--- /dev/null
+++ b/LayoutTests/ipc/loadping-firstpartyforcookies-message-check-expected.txt
@@ -0,0 +1,3 @@
+
+PASS LoadPing rejects forged firstPartyForCookies via MESSAGE_CHECK
+
diff --git a/LayoutTests/ipc/loadping-firstpartyforcookies-message-check.html b/LayoutTests/ipc/loadping-firstpartyforcookies-message-check.html
new file mode 100644
index 000000000000..8b5c383161f3
--- /dev/null
+++ b/LayoutTests/ipc/loadping-firstpartyforcookies-message-check.html
@@ -0,0 +1,149 @@
+
+Test that LoadPing validates firstPartyForCookies via MESSAGE_CHECK
+
+
+
+
+
diff --git a/LayoutTests/ipc/networksindexeddb-close-connection-during-version-change-expected.txt b/LayoutTests/ipc/networksindexeddb-close-connection-during-version-change-expected.txt
new file mode 100644
index 000000000000..654ddf7f17ef
--- /dev/null
+++ b/LayoutTests/ipc/networksindexeddb-close-connection-during-version-change-expected.txt
@@ -0,0 +1 @@
+This test passes if it does not crash.
diff --git a/LayoutTests/ipc/networksindexeddb-close-connection-during-version-change.html b/LayoutTests/ipc/networksindexeddb-close-connection-during-version-change.html
new file mode 100644
index 000000000000..c692c667b7f7
--- /dev/null
+++ b/LayoutTests/ipc/networksindexeddb-close-connection-during-version-change.html
@@ -0,0 +1,117 @@
+
+This test passes if it does not crash.
+
diff --git a/LayoutTests/ipc/rgba16f-getpixelbuffer-colorspace-mismatch-heap-disclosure-expected.txt b/LayoutTests/ipc/rgba16f-getpixelbuffer-colorspace-mismatch-heap-disclosure-expected.txt
new file mode 100644
index 000000000000..69cfc5a98db7
--- /dev/null
+++ b/LayoutTests/ipc/rgba16f-getpixelbuffer-colorspace-mismatch-heap-disclosure-expected.txt
@@ -0,0 +1,2 @@
+PASS
+
diff --git a/LayoutTests/ipc/rgba16f-getpixelbuffer-colorspace-mismatch-heap-disclosure.html b/LayoutTests/ipc/rgba16f-getpixelbuffer-colorspace-mismatch-heap-disclosure.html
new file mode 100644
index 000000000000..960a47394d31
--- /dev/null
+++ b/LayoutTests/ipc/rgba16f-getpixelbuffer-colorspace-mismatch-heap-disclosure.html
@@ -0,0 +1,120 @@
+
+

+
diff --git a/LayoutTests/ipc/use-redirection-for-current-navigation-message-check-expected.txt b/LayoutTests/ipc/use-redirection-for-current-navigation-message-check-expected.txt
new file mode 100644
index 000000000000..8155bcd41270
--- /dev/null
+++ b/LayoutTests/ipc/use-redirection-for-current-navigation-message-check-expected.txt
@@ -0,0 +1,2 @@
+PASS: UseRedirectionForCurrentNavigation with non-redirect response was rejected
+
diff --git a/LayoutTests/ipc/use-redirection-for-current-navigation-message-check.html b/LayoutTests/ipc/use-redirection-for-current-navigation-message-check.html
new file mode 100644
index 000000000000..f22480e8d0fc
--- /dev/null
+++ b/LayoutTests/ipc/use-redirection-for-current-navigation-message-check.html
@@ -0,0 +1,71 @@
+
+
+

+
+
diff --git a/LayoutTests/media/media-source/media-source-fudge-factor-expected.txt b/LayoutTests/media/media-source/media-source-fudge-factor-expected.txt
deleted file mode 100644
index cb7f1408b773..000000000000
--- a/LayoutTests/media/media-source/media-source-fudge-factor-expected.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-
-RUN(video.src = URL.createObjectURL(source))
-EVENT(loadedmetadata)
-EVENT(update)
-Samples with presentation times after currentTime should not cause loadedData.
-EVENT(update)
-EXPECTED (video.readyState == '1') OK
-Samples with presentation times very close to currentTime should cause loadedData.
-EVENT(loadeddata)
-EVENT(update)
-EXPECTED (video.readyState == '2') OK
-Samples with presentation end times very close to currentTime should not cause canPlay.
-EVENT(update)
-EXPECTED (video.readyState == '2') OK
-Continuous samples with presentation end times after currentTime should cause canPlay.
-EVENT(canplay)
-EXPECTED (video.readyState >= '3') OK
-END OF TEST
-
diff --git a/LayoutTests/media/media-source/media-source-fudge-factor.html b/LayoutTests/media/media-source/media-source-fudge-factor.html
deleted file mode 100644
index e0c0a6697da4..000000000000
--- a/LayoutTests/media/media-source/media-source-fudge-factor.html
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-    media-source-fudge-factor
-    
-    
-    
-
-
-    
-
-
diff --git a/LayoutTests/media/media-source/media-source-gap-policy-expected.txt b/LayoutTests/media/media-source/media-source-gap-policy-expected.txt
new file mode 100644
index 000000000000..631529d4e6fb
--- /dev/null
+++ b/LayoutTests/media/media-source/media-source-gap-policy-expected.txt
@@ -0,0 +1,30 @@
+
+RUN(video.src = URL.createObjectURL(source))
+EVENT(update)
+EVENT(loadedmetadata)
+EVENT(update)
+EVENT(update)
+=== Start-of-stream allowance (1 s) ===
+Video [1500, 4000]: 1.5 s start gap > 1 s, readyState stays HAVE_METADATA.
+EVENT(update)
+EXPECTED (video.readyState == '1') OK
+Video [500, 1000]: 500 ms start gap <= 1 s, canplay fires.
+EVENT(canplay)
+EVENT(update)
+EXPECTED (video.readyState >= '3') OK
+=== Mid-stream gap tolerance (125 ms) ===
+Remove audio [1000, 1500) so it no longer bridges the 500 ms video gap.
+EVENT(update)
+500 ms gap > 125 ms tolerance: readyState below HAVE_ENOUGH_DATA.
+EXPECTED (video.readyState < '4') OK
+=== Audio-covered gap tolerance (250 ms) ===
+Re-append audio [1000, 1500): audio bridges the video gap.
+EVENT(update)
+500 ms gap > 250 ms audio-covered tolerance: readyState still below HAVE_ENOUGH_DATA.
+EXPECTED (video.readyState < '4') OK
+Append video [1200, 1500] to shrink the gap to 200 ms: canplaythrough fires.
+EVENT(canplaythrough)
+EVENT(update)
+EXPECTED (video.readyState == '4') OK
+END OF TEST
+
diff --git a/LayoutTests/media/media-source/media-source-gap-policy.html b/LayoutTests/media/media-source/media-source-gap-policy.html
new file mode 100644
index 000000000000..a1a7a4042153
--- /dev/null
+++ b/LayoutTests/media/media-source/media-source-gap-policy.html
@@ -0,0 +1,88 @@
+
+
+
+    media-source-gap-policy
+    
+    
+    
+
+
+    
+
+
diff --git a/LayoutTests/navigation-api/navigation-navigate-throwing-argument-conversion-expected.txt b/LayoutTests/navigation-api/navigation-navigate-throwing-argument-conversion-expected.txt
new file mode 100644
index 000000000000..6705aa6c2453
--- /dev/null
+++ b/LayoutTests/navigation-api/navigation-navigate-throwing-argument-conversion-expected.txt
@@ -0,0 +1,16 @@
+Tests that navigation.navigate() returns a valid NavigationResult when argument conversion throws
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS typeof result is 'object'
+PASS 'committed' in result is true
+PASS 'finished' in result is true
+PASS result.committed instanceof Promise is true
+PASS result.finished instanceof Promise is true
+PASS committedError.message is "test error"
+PASS finishedError.message is "test error"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/navigation-api/navigation-navigate-throwing-argument-conversion.html b/LayoutTests/navigation-api/navigation-navigate-throwing-argument-conversion.html
new file mode 100644
index 000000000000..5e60aa03bfce
--- /dev/null
+++ b/LayoutTests/navigation-api/navigation-navigate-throwing-argument-conversion.html
@@ -0,0 +1,39 @@
+
+
+
diff --git a/LayoutTests/platform/ios/TestExpectations b/LayoutTests/platform/ios/TestExpectations
index a889ed73c5e9..3683fe80edfd 100644
--- a/LayoutTests/platform/ios/TestExpectations
+++ b/LayoutTests/platform/ios/TestExpectations
@@ -2377,7 +2377,7 @@ webkit.org/b/148806 imported/w3c/web-platform-tests/css/css-multicol/multicol-sp
 fast/dom/linkify-phone-numbers.html [ Pass ]
 
 accessibility/table-exposure-updates-dynamically.html [ Pass ]
-accessibility/accessibility-node-reparent.html [ Pass ]
+accessibility/accessibility-node-reparent.html [ Pass Timeout ] # rdar://178168616
 accessibility/area-element-bounding-box.html [ Pass ]
 accessibility/aria-actions.html [ Pass ]
 accessibility/aria-owns-text-stitching.html [ Pass ]
@@ -2408,7 +2408,7 @@ accessibility/canvas-drawFocusIfNeeded-bounds-with-object-fit.html [ Pass ]
 accessibility/canvas-drawFocusIfNeeded-bounds-with-transform.html [ Pass ]
 accessibility/changing-aria-hidden-with-display-none-parent.html [ Pass ]
 accessibility/checkbox-mixed-value.html [ Pass ]
-accessibility/clip-path-bounding-box.html [ Pass ]
+accessibility/clip-path-bounding-box.html [ Pass Failure Timeout ] # rdar://177742349, rdar://179387348
 accessibility/css-content-alt-text.html [ Pass ]
 accessibility/dialog-slotted-content.html [ Pass ]
 accessibility/dirty-relations-and-modal-tree-update-crash.html [ Pass ]
@@ -2470,7 +2470,7 @@ accessibility/nested-custom-element-accname.html [ Pass ]
 accessibility/node-only-inert-object.html [ Pass ]
 accessibility/node-only-object-element-rect.html [ Pass ]
 accessibility/checkbox-radio-element-rect.html [ Pass ]
-accessibility/opacity-0-bounding-box.html [ Pass ]
+accessibility/opacity-0-bounding-box.html [ Pass Failure ] # rdar://177981724
 accessibility/out-of-bounds-rowspan.html [ Pass ]
 accessibility/out-of-bounds-rowspan-display-none.html [ Pass ]
 accessibility/out-of-bounds-rowspan-aria-hidden.html [ Pass ]
@@ -3274,7 +3274,7 @@ fast/loader/plain-text-document-dark-mode.html [ Pass ]
 
 fast/forms/auto-fill-button/caps-lock-indicator-should-be-visible-after-hiding-auto-fill-strong-password-button.html [ Pass ]
 fast/forms/auto-fill-button/caps-lock-indicator-should-not-be-visible-when-auto-fill-strong-password-button-is-visible.html [ Pass ]
-fast/forms/password-scrolled-after-caps-lock-toggled.html [ Pass ]
+fast/forms/password-scrolled-after-caps-lock-toggled.html [ Pass Timeout Failure ]
 
 #  REGRESSION (iOS 13): Three cookie layout tests failing
 http/wpt/beacon/cors/cors-preflight-cookie.html [ Failure ]
@@ -3555,7 +3555,7 @@ http/wpt/mediarecorder/mute-tracks.html [ Pass Failure ]
 # rdar://80396502 ([ iOS15 ] http/wpt/mediarecorder/pause-recording.html is a flaky crash)
 http/wpt/mediarecorder/pause-recording.html [ Pass Crash ]
 
-accessibility/misspelling-range.html [ Pass ]
+accessibility/misspelling-range.html [ Pass Failure ] # rdar://131398291
 
 # Behavior of navigator-language-ru changed in iOS 15.
 fast/text/international/system-language/navigator-language/navigator-language-ru.html [ Failure ]
@@ -7751,7 +7751,7 @@ webkit.org/b/290670 imported/w3c/web-platform-tests/css/css-viewport/zoom/relati
 
 webkit.org/b/290767 [ x86_64 ] imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworklet-denormals.https.window.html [ Failure ]
 
-webkit.org/b/290790 editing/selection/ios/show-selection-in-transformed-container.html [ Failure ]
+webkit.org/b/290790 editing/selection/ios/show-selection-in-transformed-container.html [ Pass Failure Timeout ] # rdar://177980999
 
 webkit.org/b/290794 fast/viewport/ios/content-visibility-layout-viewport-during-unstable-scroll.html [ Failure ]
 
@@ -8417,7 +8417,7 @@ fast/dom/HTMLLinkElement/prefetch-too-many-clients.html [ Skip ]
 fast/dynamic/crash-paint-no-documentElement-renderer.html [ Skip ]
 fast/editing/ruby-with-edited-text-crash.html [ Skip ]
 fast/events/key-events-in-frame.html [ Skip ]
-fast/forms/textarea/textarea-state-restore.html [ Pass Timeout ]
+fast/forms/textarea/textarea-state-restore.html [ Pass Timeout Failure ] # rdar://179387373
 [ Debug ] fast/layers/top-layer-ancestor-opacity-and-transform-crash.html [ Skip ]
 [ Release ] fast/mediastream/MediaStream-page-muted.html [ Pass Timeout ]
 fast/table/double-height-table-no-tbody.html [ Skip ]
@@ -8464,7 +8464,7 @@ webkit.org/b/316014 [ Debug ] fast/dom/move-embedded-during-update.html [ Pass F
 
 webkit.org/b/316218 fast/scrolling/ios/scroll-into-view-smooth-after-snap.html [ Pass Failure ]
 
-webkit.org/b/316228 fast/dom/Orientation/no-orientation-change-event-when-unparenting-view.html [ Failure ]
+webkit.org/b/316228 fast/dom/Orientation/no-orientation-change-event-when-unparenting-view.html [ Pass Timeout Failure ]
 
 webkit.org/b/316118 imported/w3c/web-platform-tests/url/IdnaTestV2.any.html [ Failure ]
 
@@ -8495,6 +8495,105 @@ imported/w3c/web-platform-tests/webrtc/RTCPeerConnection-setLocalDescription-off
 
 imported/w3c/web-platform-tests/css/css-scroll-snap/input/keyboard-snap-interruption.html [ Skip ] # timeout
 
+compositing/backing/backing-store-attachment-animating-outside-viewport.html [ Pass Failure ] # rdar://178675997
+compositing/color-matching/image-color-matching.html [ Pass Crash ] # rdar://178675338
+editing/editable-region/hit-test-fixed.html [ Pass Failure ] # rdar://178675880
+editing/execCommand/typing-should-not-trigger-scrolling-when-selection-is-visible.html [ Pass Failure ] # rdar://178172917
+editing/input/cocoa/autocorrect-on.html [ Pass Timeout ]
+editing/input/cocoa/extended-proofreading.html [ Pass Timeout ] # rdar://177981315
+editing/input/ios/compose-accent-with-hardware-keyboard-when-preventing-keydown.html [ Pass Failure Timeout ] # rdar://176615540
+editing/selection/ios/caret-rect-after-inserting-newlines-in-textarea.html [ Pass Timeout Crash ] # rdar://177980999
+editing/selection/ios/select-text-by-long-press-with-focused-element.html [ Pass Timeout ] # rdar://180007873
+editing/selection/ios/select-text-by-long-press-with-hardware-keyboard.html [ Pass Timeout ] # rdar://180007873
+editing/selection/ios/select-word-for-replacement-extends-grammar-marker.html [ Pass Timeout ] # rdar://180007873
+editing/selection/ios/selection-clip-in-position-relative-text-field.html [ Pass Timeout ] # rdar://177980999
+editing/selection/ios/selection-moves-between-composited-layers.html [ Pass Timeout Crash ] # rdar://177980999, rdar://179387140
+editing/selection/ios/show-edit-menu-with-transparent-caret.html [ Pass Timeout ] # rdar://177980999
+editing/selection/ios/show-grammar-replacements-on-tap.html [ Pass Timeout ] # rdar://177980999
+editing/selection/ios/tap-focused-input-clears-outside-selection.html [ Timeout ] # rdar://180007798
+editing/text-placeholder/caret-before-zero-width-placeholder-in-content-editable-start-of-word.html [ Pass Timeout ] # rdar://178158441
+fast/dynamic/anchor-lock.html [ Pass Failure ] # rdar://178675751
+fast/events/autoscroll-when-input-is-offscreen.html [ Pass Failure ]
+fast/events/ios/pdf-modifer-key-down-crash.html [ Pass Crash ]
+fast/forms/datalist/data-list-search-input-with-appearance-none.html [ Timeout ] # rdar://178168549
+fast/forms/datalist/datalist-textinput-dynamically-add-options-on-keydown.html [ Pass Timeout ]
+fast/forms/ios/select-option-update-1000.html [ Failure Timeout ] # rdar://178168662
+fast/images/imageDocument-title.html [ Pass Crash ] # rdar://178675308
+fast/mediastream/audio-session-category-capture-audio-context.html [ Pass Timeout ]
+fast/mediastream/media-stream-video-track-interrupted.html [ Pass Failure ] # rdar://178673961
+fast/mediastream/mediastreamtrack-clone-muted.html [ Pass Timeout ] # rdar://178215298
+fast/screen-orientation/orientation-in-resize-event.html [ Pass Failure Timeout ] # rdar://177741597
+fast/scrolling/scroll-anchoring/heuristic-enabled-below-threshold.html [ Pass Failure ] # rdar://178168995
+fast/text-extraction/text-extraction-scroll-fallback-to-large-container.html [ Pass Failure ] # rdar://178169238
+fast/viewport/ios/shrink-to-fit-for-page-without-viewport-meta.html [ Failure ] # rdar://178168569
+fast/viewport/ios/width-is-device-width-overflowing-body-overflow-hidden-tall.html [ Failure ] # rdar://178168569
+fast/visual-viewport/ios/visual-viewport-dimensions-during-scroll-with-keyboard.html [ Timeout ] # rdar://178016084
+http/tests/cache/cancel-multiple-post-xhrs.html [ Pass Failure ] # rdar://178676120
+http/tests/download/anchor-download-redirect-cross-origin.html [ Pass Crash ] # rdar://178214780
+http/tests/privateClickMeasurement/database-disabled-in-ephemeral-session.html [ Pass Timeout Crash ] # rdar://176608751, rdar://179387095
+http/tests/security/referrer-policy-header-invalid.html [ Pass Timeout ] # rdar://178168636
+http/tests/site-isolation/datalist-cross-origin-iframe.html [ Timeout ] # rdar://179387257
+http/tests/site-isolation/selection-focus.html [ ImageOnlyFailure ] # rdar://163216880
+http/tests/site-isolation/type-in-cross-origin-iframe.html [ Failure Timeout ] # rdar://178168718
+http/wpt/service-workers/persistent-modules.html [ Pass Failure ] # rdar://178676392
+imported/w3c/web-platform-tests/IndexedDB/interleaved-cursors-small.any.html [ Pass Failure ] # rdar://178158917
+imported/w3c/web-platform-tests/IndexedDB/nested-cloning-large-multiple.any.html [ Pass Failure ] # rdar://178158917
+imported/w3c/web-platform-tests/IndexedDB/nested-cloning-large-multiple.any.worker.html [ Pass Failure ] # rdar://178158917
+imported/w3c/web-platform-tests/css/css-anchor-position/scroll-to-anchored-fixed-000.html [ Pass ImageOnlyFailure ] # rdar://178676781
+imported/w3c/web-platform-tests/css/css-overflow/line-clamp/line-clamp-033.html [ Pass Crash ImageOnlyFailure ]
+imported/w3c/web-platform-tests/css/css-shapes/spec-examples/shape-outside-011.html [ Pass Failure ] # rdar://178676728
+imported/w3c/web-platform-tests/css/css-text-decor/text-emphasis-position-auto-002.html [ Pass ImageOnlyFailure ] # rdar://178676895
+imported/w3c/web-platform-tests/css/css-view-transitions/view-transition-waituntil-finished-promise.html [ Pass Failure ] # rdar://178676670
+imported/w3c/web-platform-tests/css/css-writing-modes/text-orientation-upright-srl-018.xht [ Pass ImageOnlyFailure ] # rdar://178676919
+imported/w3c/web-platform-tests/css/cssom-view/scrollIntoView-smooth.html [ Pass Failure ] # rdar://178676605
+imported/w3c/web-platform-tests/custom-elements/reactions/Document.html [ Pass Crash ] # rdar://178158852
+imported/w3c/web-platform-tests/html/browsers/browsing-the-web/navigating-across-documents/cross-origin-top-navigation-with-user-activation-in-parent.window.html [ Pass Failure ] # rdar://178214939
+imported/w3c/web-platform-tests/html/canvas/element/manual/imagebitmap/createImageBitmap-origin.sub.html [ Failure ] # rdar://178012093
+imported/w3c/web-platform-tests/html/cross-origin-embedder-policy/anonymous-iframe/cookie.tentative.https.window.html [ Pass Crash Timeout ] # rdar://178158581
+imported/w3c/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/video-default-object-height-constrained-by-max-width.html [ Crash ] # rdar://178169483
+imported/w3c/web-platform-tests/html/rendering/widgets/shadow-dom.html [ Pass Failure ] # rdar://178546346
+imported/w3c/web-platform-tests/html/semantics/embedded-content/the-canvas-element/security.pattern.fillStyle.sub.html [ Failure ] # rdar://178018070
+imported/w3c/web-platform-tests/html/semantics/forms/form-submission-target/form-target-blank-useractivation.html [ Pass Failure ] # rdar://180007963
+imported/w3c/web-platform-tests/navigation-api/navigate-event/defer/tentative/defer-same-document.html [ Pass Crash ]
+imported/w3c/web-platform-tests/scroll-to-text-fragment/redirects.html [ Pass Failure ] # rdar://178158764
+imported/w3c/web-platform-tests/shadow-dom/event-on-pseudo-element-crash.html [ Pass Timeout ] # rdar://179387553
+imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audiocontext-interface/audiocontext-getoutputtimestamp-cross-realm.html [ Pass Failure ] # rdar://179388032
+imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioworklet-interface/audioworkletnode-automatic-pull.https.html [ Pass Failure ] # rdar://178172495
+media/audio-session-category-play-unmute-pause.html [ Failure ] # rdar://178158431
+media/media-visible-in-viewport-in-fullscreen.html [ Timeout ] # rdar://178016189
+media/media-vp8-webm-with-poster.html [ Timeout Crash ] # rdar://178016189
+media/video-pause-immediately.html [ Pass Failure ] # rdar://178675666
+media/video-webm-seek-multi-audio-tracks.html [ Timeout ] # rdar://180007922
+quicklook/word-legacy.html [ Failure ] # rdar://174850780
+quicklook/word.html [ Failure ] # rdar://174850780
+svg/filters/feMorphology-negative-radius.html [ Pass Crash ] # rdar://178675229
+webgl/1.0.x/conformance/textures/misc/texture-srgb-upload.html [ Timeout ] # rdar://176399952
+workers/worker-set-delete-terminate-crash.html [ Timeout ] # rdar://178590046
+svg/transforms/nested-svg-transform-attribute-creates-layer.html [ Failure ] # rdar://180457293
+svg/compositing/segment-removed-after-anchor-decomposited-layer-tree.html [ Failure ] # rdar://180457293
+svg/compositing/transform-change-repainting-viewBox-repaintRects.html [ Failure ] # rdar://180457293
+svg/compositing/anonymous-RenderSVGViewportContainer-no-repaints.html [ Failure ] # rdar://180457293
+fast/events/touch/ios/touch-event-regions-layer-tree/svg-image-with-layer-based-svg-engine.html [ Failure ] # rdar://180457293
+fast/events/touch/ios/touch-event-regions-layer-tree/svg-path-with-layer-based-svg-engine.html [ Failure ] # rdar://180457293
+fast/events/touch/ios/touch-event-regions-layer-tree/svg-text-with-layer-based-svg-engine.html [ Failure ] # rdar://180457293
+fast/events/touch/ios/touch-event-regions-layer-tree/mousemove-mouseup.html [ Failure ] # rdar://180457293
+fast/forms/ios/click-should-not-suppress-misspelling.html [ Failure ] # webkit.org/b/318249
+fast/forms/ios/force-gregorian-calendar-for-credit-card-expiry.html [ Failure ] # webkit.org/b/318249
+fast/forms/ios/insert-autofill-suggestion.html [ Failure ] # webkit.org/b/318249
+fast/forms/ios/suppress-software-keyboard-while-focusing-input.html [ Failure ] # webkit.org/b/318249
+editing/selection/ios/do-not-hide-selection-in-visible-field.html [ Timeout ] # webkit.org/b/318250
+editing/selection/ios/update-selection-after-iframe-scroll.html [ Timeout ] # webkit.org/b/318250
+fast/events/ios/autocorrect-with-apostrophe.html [ Timeout ] # webkit.org/b/318251
+fast/events/ios/do-not-show-keyboard-when-focusing-after-blur.html [ Timeout ] # webkit.org/b/318251
+fast/forms/ios/hide-keyboard-on-node-removal.html [ Timeout ] # webkit.org/b/318252
+fast/forms/ios/zoom-to-reveal-focused-element-after-delay.html [ Timeout ] # webkit.org/b/318252
+editing/caret/ios/place-caret-after-autocorrected-word.html [ Failure ] # webkit.org/b/318253
+editing/input/ios/typing-with-inline-predictions.html [ Pass Timeout ] # webkit.org/b/318254
+imported/w3c/web-platform-tests/content-security-policy/inheritance/history.sub.html [ Failure ] # webkit.org/b/318255
+imported/w3c/web-platform-tests/digital-credentials/mdoc/mixed-requests.https.html [ Failure ] # webkit.org/b/318256
+imported/w3c/web-platform-tests/html/semantics/forms/the-input-element/number-constraint-validation.html [ Pass Failure ] # webkit.org/b/318257
+ipc/fecolormatrix-type-values-mismatch-crash.html [ Failure ] # webkit.org/b/318258
+
 webkit.org/b/317884 imported/w3c/web-platform-tests/digital-credentials/get-non-fully-active.https.html [ Skip ]
 
 webkit.org/b/318157 [ Debug ] imported/w3c/web-platform-tests/IndexedDB/interleaved-cursors-small.any.sharedworker.html [ Failure ]
diff --git a/LayoutTests/platform/ios/svg/compositing/anonymous-RenderSVGViewportContainer-no-repaints-expected.txt b/LayoutTests/platform/ios/svg/compositing/anonymous-RenderSVGViewportContainer-no-repaints-expected.txt
index 568ce4182ec1..64274d2d4bfe 100644
--- a/LayoutTests/platform/ios/svg/compositing/anonymous-RenderSVGViewportContainer-no-repaints-expected.txt
+++ b/LayoutTests/platform/ios/svg/compositing/anonymous-RenderSVGViewportContainer-no-repaints-expected.txt
@@ -13,16 +13,22 @@
           (children 1
             (GraphicsLayer
               (bounds 784.00 784.00)
-              (children 2
+              (children 1
                 (GraphicsLayer
-                  (position 100.00 100.00)
-                  (bounds 100.00 100.00)
-                  (drawsContent 1)
-                  (transform [0.71 0.71 0.00 0.00] [-0.71 0.71 0.00 0.00] [0.00 0.00 1.00 0.00] [0.00 0.00 0.00 1.00])
-                )
-                (GraphicsLayer
-                  (bounds 784.00 784.00)
+                  (offsetFromRenderer width=79 height=79)
+                  (position 79.00 79.00)
+                  (anchor -0.56 -0.56)
+                  (bounds 142.00 142.00)
                   (drawsContent 1)
+                  (transform [2.61 0.00 0.00 0.00] [0.00 2.61 0.00 0.00] [0.00 0.00 1.00 0.00] [0.00 0.00 0.00 1.00])
+                  (children 1
+                    (GraphicsLayer
+                      (position 21.00 21.00)
+                      (bounds 100.00 100.00)
+                      (drawsContent 1)
+                      (transform [0.71 0.71 0.00 0.00] [-0.71 0.71 0.00 0.00] [0.00 0.00 1.00 0.00] [0.00 0.00 0.00 1.00])
+                    )
+                  )
                 )
               )
             )
diff --git a/LayoutTests/platform/ios/svg/compositing/segment-removed-after-anchor-decomposited-layer-tree-expected.txt b/LayoutTests/platform/ios/svg/compositing/segment-removed-after-anchor-decomposited-layer-tree-expected.txt
new file mode 100644
index 000000000000..0f5a7082fe05
--- /dev/null
+++ b/LayoutTests/platform/ios/svg/compositing/segment-removed-after-anchor-decomposited-layer-tree-expected.txt
@@ -0,0 +1,91 @@
+ === Initial: middle  painted into trailing segment ===
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 1
+        (GraphicsLayer
+          (bounds 200.00 200.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (bounds 200.00 200.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=10 height=10)
+                  (position 10.00 10.00)
+                  (anchor -0.06 -0.06)
+                  (bounds 180.00 180.00)
+                  (drawsContent 1)
+                  (children 3
+                    (GraphicsLayer
+                      (anchor -0.08 -0.08)
+                      (bounds 120.00 120.00)
+                      (drawsContent 1)
+                    )
+                    (GraphicsLayer
+                      (offsetFromRenderer width=10 height=10)
+                      (bounds 180.00 180.00)
+                      (drawsContent 1)
+                    )
+                    (GraphicsLayer
+                      (position 60.00 60.00)
+                      (anchor -0.58 -0.58)
+                      (bounds 120.00 120.00)
+                      (drawsContent 1)
+                    )
+                  )
+                )
+              )
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
+=== After mutation: middle  segment layer dropped ===
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 931.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 931.00)
+      (contentsOpaque 1)
+      (children 1
+        (GraphicsLayer
+          (bounds 200.00 200.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (bounds 200.00 200.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=10 height=10)
+                  (position 10.00 10.00)
+                  (anchor -0.06 -0.06)
+                  (bounds 180.00 180.00)
+                  (drawsContent 1)
+                  (children 1
+                    (GraphicsLayer
+                      (position 60.00 60.00)
+                      (anchor -0.58 -0.58)
+                      (bounds 120.00 120.00)
+                      (drawsContent 1)
+                    )
+                  )
+                )
+              )
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
+
diff --git a/LayoutTests/platform/ios/svg/compositing/transform-change-repainting-viewBox-repaintRects-expected.txt b/LayoutTests/platform/ios/svg/compositing/transform-change-repainting-viewBox-repaintRects-expected.txt
new file mode 100644
index 000000000000..596a7c7736a5
--- /dev/null
+++ b/LayoutTests/platform/ios/svg/compositing/transform-change-repainting-viewBox-repaintRects-expected.txt
@@ -0,0 +1,49 @@
+ (repaint rects
+  (rect 124 24 270 270)
+  (rect 124 24 270 270)
+  (rect 68 8 382 302)
+  (rect 8 8 502 302)
+)
+(GraphicsLayer
+  (anchor 0.00 0.00)
+  (bounds 800.00 600.00)
+  (children 1
+    (GraphicsLayer
+      (bounds 800.00 600.00)
+      (contentsOpaque 1)
+      (children 1
+        (GraphicsLayer
+          (position 8.00 8.00)
+          (bounds 502.00 302.00)
+          (drawsContent 1)
+          (children 1
+            (GraphicsLayer
+              (offsetFromRenderer width=1 height=1)
+              (position 1.00 1.00)
+              (bounds 500.00 300.00)
+              (children 1
+                (GraphicsLayer
+                  (offsetFromRenderer width=-27.50 height=-27.50)
+                  (position -27.50 -27.50)
+                  (anchor 0.11 0.11)
+                  (bounds 255.00 255.00)
+                  (drawsContent 1)
+                  (transform [1.50 0.00 0.00 0.00] [0.00 1.50 0.00 0.00] [0.00 0.00 1.00 0.00] [100.00 0.00 0.00 1.00])
+                  (children 1
+                    (GraphicsLayer
+                      (position 37.50 37.50)
+                      (bounds 180.00 180.00)
+                      (drawsContent 1)
+                      (transform [0.71 0.71 0.00 0.00] [-0.71 0.71 0.00 0.00] [0.00 0.00 1.00 0.00] [0.00 0.00 0.00 1.00])
+                    )
+                  )
+                )
+              )
+            )
+          )
+        )
+      )
+    )
+  )
+)
+
diff --git a/LayoutTests/platform/ios/svg/transforms/nested-svg-transform-attribute-creates-layer-expected.txt b/LayoutTests/platform/ios/svg/transforms/nested-svg-transform-attribute-creates-layer-expected.txt
new file mode 100644
index 000000000000..dba642154834
--- /dev/null
+++ b/LayoutTests/platform/ios/svg/transforms/nested-svg-transform-attribute-creates-layer-expected.txt
@@ -0,0 +1,13 @@
+layer at (0,0) size 800x600
+  RenderView at (0,0) size 800x600
+layer at (0,0) size 800x205
+  RenderBlock {HTML} at (0,0) size 800x204.50
+    RenderBody {BODY} at (0,0) size 800x204.50
+      RenderText {#text} at (0,0) size 0x0
+layer at (0,0) size 200x200
+  RenderSVGRoot {svg} at (0,0) size 200x200
+layer at (0,0) size 200x200
+  RenderSVGViewportContainer at (0,0) size 200x200
+layer at (0,0) size 200x200
+  RenderSVGViewportContainer {svg} at (0,0) size 200x200
+    RenderSVGRect {rect} at (60,90) size 80x20 [fill={[type=SOLID] [color=#008000]}] [x=60.00] [y=90.00] [width=80.00] [height=20.00]
diff --git a/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/repaint/svg-outline-repaint-on-hover-expected.txt b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/repaint/svg-outline-repaint-on-hover-expected.txt
new file mode 100644
index 000000000000..9826a460f4b0
--- /dev/null
+++ b/LayoutTests/platform/mac-tahoe-wk2-lbse-text/svg/repaint/svg-outline-repaint-on-hover-expected.txt
@@ -0,0 +1,17 @@
+ SVG border box: 100x100 at (50,50)
+
+Repaint rects on hover (outline added):
+(repaint rects
+  (rect 50 50 100 100)
+  (rect 0 0 800 204)
+  (rect 35 35 130 130)
+)
+
+Repaint rects on un-hover (outline removed):
+(repaint rects
+  (rect 35 35 130 130)
+  (rect 50 50 100 100)
+  (rect 0 0 800 204)
+  (rect 35 35 130 130)
+)
+
diff --git a/LayoutTests/storage/indexeddb/index-cursor-reverse-delete-next-higher-key-private-expected.txt b/LayoutTests/storage/indexeddb/index-cursor-reverse-delete-next-higher-key-private-expected.txt
new file mode 100644
index 000000000000..df3122fe29ba
--- /dev/null
+++ b/LayoutTests/storage/indexeddb/index-cursor-reverse-delete-next-higher-key-private-expected.txt
@@ -0,0 +1,16 @@
+Tests that a reverse index cursor doesn't use-after-free when a record is deleted whose index key is the next-higher value from the cursor's current position.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+indexedDB = self.indexedDB || self.webkitIndexedDB || self.mozIndexedDB || self.msIndexedDB || self.OIndexedDB;
+
+PASS cursorKeys.length is 3
+PASS cursorKeys[0] is 30
+PASS cursorKeys[1] is 20
+PASS cursorKeys[2] is 10
+Transaction completed successfully.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/storage/indexeddb/index-cursor-reverse-delete-next-higher-key-private.html b/LayoutTests/storage/indexeddb/index-cursor-reverse-delete-next-higher-key-private.html
new file mode 100644
index 000000000000..6ea56d78bdeb
--- /dev/null
+++ b/LayoutTests/storage/indexeddb/index-cursor-reverse-delete-next-higher-key-private.html
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/LayoutTests/webaudio/biquadfilternode-set-type-channel-count-race-expected.txt b/LayoutTests/webaudio/biquadfilternode-set-type-channel-count-race-expected.txt
new file mode 100644
index 000000000000..13e6354090c4
--- /dev/null
+++ b/LayoutTests/webaudio/biquadfilternode-set-type-channel-count-race-expected.txt
@@ -0,0 +1,3 @@
+This test passes if it does not crash when toggling BiquadFilterNode.type while the channel count changes on the audio thread.
+
+PASS: did not crash.
diff --git a/LayoutTests/webaudio/biquadfilternode-set-type-channel-count-race.html b/LayoutTests/webaudio/biquadfilternode-set-type-channel-count-race.html
new file mode 100644
index 000000000000..d1af2217d775
--- /dev/null
+++ b/LayoutTests/webaudio/biquadfilternode-set-type-channel-count-race.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+

This test passes if it does not crash when toggling BiquadFilterNode.type while the channel count changes on the audio thread.

+

+ + diff --git a/LayoutTests/workers/weblock-manager-query-crash-expected.txt b/LayoutTests/workers/weblock-manager-query-crash-expected.txt new file mode 100644 index 000000000000..730ebf66a0da --- /dev/null +++ b/LayoutTests/workers/weblock-manager-query-crash-expected.txt @@ -0,0 +1 @@ +This test passes if it doesn't crash. diff --git a/LayoutTests/workers/weblock-manager-query-crash.html b/LayoutTests/workers/weblock-manager-query-crash.html new file mode 100644 index 000000000000..de3aa909f638 --- /dev/null +++ b/LayoutTests/workers/weblock-manager-query-crash.html @@ -0,0 +1,42 @@ + + + + + + +This test passes if it doesn't crash. + + diff --git a/Source/JavaScriptCore/builtins/TypedArrayConstructor.js b/Source/JavaScriptCore/builtins/TypedArrayConstructor.js index 785388293703..8e757caae907 100644 --- a/Source/JavaScriptCore/builtins/TypedArrayConstructor.js +++ b/Source/JavaScriptCore/builtins/TypedArrayConstructor.js @@ -109,11 +109,17 @@ function from(items /* [ , mapfn [ , thisArg ] ] */) @throwTypeError("TypedArray.from constructed typed array of insufficient length"); for (var k = 0; k < arrayLikeLength; k++) { + if (@isTypedArrayView(arrayLike) && (@isDetached(arrayLike) || k >= @typedArrayLength(arrayLike))) + break; var value = arrayLike[k]; if (mapFn === @undefined) result[k] = value; - else - result[k] = thisArg === @undefined ? mapFn(value, k) : mapFn.@call(thisArg, value, k); + else { + var mapped = thisArg === @undefined ? mapFn(value, k) : mapFn.@call(thisArg, value, k); + if (@isTypedArrayView(result) && (k >= @typedArrayLength(result) || @isDetached(result))) + break; + result[k] = mapped; + } } return result; diff --git a/Source/JavaScriptCore/bytecode/Repatch.cpp b/Source/JavaScriptCore/bytecode/Repatch.cpp index 081e1f33a76e..fd98ba46efc3 100644 --- a/Source/JavaScriptCore/bytecode/Repatch.cpp +++ b/Source/JavaScriptCore/bytecode/Repatch.cpp @@ -796,6 +796,7 @@ void repatchGetBy(JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue ba // Mainly used to transition from megamorphic case to generic case. void repatchGetBySlowPathCall(CodeBlock* codeBlock, PropertyInlineCache& propertyCache, GetByKind kind) { + ConcurrentJSLocker locker(codeBlock->m_lock); resetGetBy(codeBlock, propertyCache, kind); repatchSlowPathCall(codeBlock, propertyCache, appropriateGetByGaveUpFunction(kind)); } @@ -1000,6 +1001,7 @@ static CodePtr NODELETE appropriatePutByGaveUpFunction(PutByKin // Mainly used to transition from megamorphic case to generic case. void repatchPutBySlowPathCall(CodeBlock* codeBlock, PropertyInlineCache& propertyCache, PutByKind kind) { + ConcurrentJSLocker locker(codeBlock->m_lock); resetPutBy(codeBlock, propertyCache, kind); repatchSlowPathCall(codeBlock, propertyCache, appropriatePutByGaveUpFunction(kind)); } @@ -1631,6 +1633,7 @@ inline CodePtr NODELETE appropriateInByGaveUpFunction(InByKind // Mainly used to transition from megamorphic case to generic case. void repatchInBySlowPathCall(CodeBlock* codeBlock, PropertyInlineCache& propertyCache, InByKind kind) { + ConcurrentJSLocker locker(codeBlock->m_lock); resetInBy(codeBlock, propertyCache, kind); repatchSlowPathCall(codeBlock, propertyCache, appropriateInByGaveUpFunction(kind)); } diff --git a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h index c4a337809bb3..2acbc4bfd89c 100644 --- a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h +++ b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h @@ -3860,7 +3860,12 @@ bool AbstractInterpreter::executeEffects(unsigned clobberLimi break; } - setForNode(node, m_vm.cellButterflyStructure(CopyOnWriteArrayWithContiguous)); + { + RegisteredStructureSet structureSet; + structureSet.add(m_graph.registerStructure(m_vm.cellButterflyStructure(CopyOnWriteArrayWithContiguous))); + structureSet.add(m_graph.registerStructure(m_vm.cellButterflyOnlyAtomStringsStructure.get())); + setForNode(node, structureSet); + } break; case NewArrayBuffer: diff --git a/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp b/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp index 509c0ccf40d1..b90a0b9649cf 100644 --- a/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp +++ b/Source/JavaScriptCore/dfg/DFGArgumentsEliminationPhase.cpp @@ -604,7 +604,7 @@ class ArgumentsEliminationPhase : public Phase { return interfere; }; - auto removeViaKill = [&](BasicBlock* block, unsigned nodeIndex, Node* candidate) { + auto removeViaKill = [&](BasicBlock* block, const unsigned nodeIndex, Node* candidate) { if (!m_candidates.contains(candidate)) return; @@ -660,21 +660,17 @@ class ArgumentsEliminationPhase : public Phase { } // This loop considers all nodes up to the nodeIndex, excluding the nodeIndex. + // scanIndex is a working copy so each inline call frame independently + // scans the full [0, nodeIndex) range. // - // Note: nodeIndex here has a double meaning. Before entering this - // while loop, it refers to the remaining number of nodes that have - // yet to be processed. Inside the loop, it refers to the index - // of the current node to process (after we decrement it). - // - // If the remaining number of nodes is 0, we should not decrement nodeIndex. - // Hence, we must only decrement nodeIndex inside the while loop instead of - // in its condition statement. Note that this while loop is embedded in an - // outer for loop. If we decrement nodeIndex in the condition statement, a - // nodeIndex of 0 will become UINT_MAX, and the outer loop will wrongly - // treat this as there being UINT_MAX remaining nodes to process. - while (nodeIndex) { - --nodeIndex; - Node* node = block->at(nodeIndex); + // If the remaining number of nodes is 0, we should not decrement + // scanIndex. Hence, we must only decrement it inside the while loop + // instead of in its condition statement; otherwise a scanIndex of 0 would + // become UINT_MAX. + unsigned scanIndex = nodeIndex; + while (scanIndex) { + --scanIndex; + Node* node = block->at(scanIndex); if (node == candidate) break; @@ -693,7 +689,7 @@ class ArgumentsEliminationPhase : public Phase { NoOpClobberize()); if (found) { - dataLogLnIf(DFGArgumentsEliminationPhaseInternal::verbose, "eliminating candidate: ", candidate, " because it is clobbered by ", block->at(nodeIndex)); + dataLogLnIf(DFGArgumentsEliminationPhaseInternal::verbose, "eliminating candidate: ", candidate, " because it is clobbered by ", block->at(scanIndex)); transitivelyRemoveCandidate(candidate); return; } diff --git a/Source/JavaScriptCore/dfg/DFGGraph.cpp b/Source/JavaScriptCore/dfg/DFGGraph.cpp index 9c169d6f3ef4..9acd38e1d10e 100644 --- a/Source/JavaScriptCore/dfg/DFGGraph.cpp +++ b/Source/JavaScriptCore/dfg/DFGGraph.cpp @@ -1540,6 +1540,10 @@ ObjectPropertyConditionSet Graph::tryEnsureAbsence(JSGlobalObject* globalObject, return ObjectPropertyConditionSet::invalid(); auto isAbsenceCacheable = [&](Structure* structure) { + // Absences cannot be cached on any dictionaries, including CachedDictionaryKind, because + // new properties can be added without structure transitions. + if (structure->isDictionary()) + return false; if (structure->typeInfo().overridesGetOwnPropertySlot()) return false; if (!structure->propertyAccessesAreCacheable()) diff --git a/Source/JavaScriptCore/dfg/DFGOperations.cpp b/Source/JavaScriptCore/dfg/DFGOperations.cpp index cac3bb0681e3..8c19646ba8fb 100644 --- a/Source/JavaScriptCore/dfg/DFGOperations.cpp +++ b/Source/JavaScriptCore/dfg/DFGOperations.cpp @@ -847,6 +847,40 @@ ALWAYS_INLINE EncodedJSValue getByValCellInt(JSGlobalObject* globalObject, VM& v return JSValue::encode(JSValue(base).get(globalObject, static_cast(index))); } +ALWAYS_INLINE EncodedJSValue getByValArrayStorageInt(JSGlobalObject* globalObject, VM& vm, JSObject* base, int32_t index) +{ + ASSERT(hasAnyArrayStorage(base->indexingType())); + if (index >= 0) [[likely]] { + unsigned i = static_cast(index); + ArrayStorage* storage = base->butterfly()->arrayStorage(); + SparseArrayValueMap* map = storage->m_sparseMap.get(); + // Only the standard ArrayStorage layout is handled here: indices < vectorLength live in the + // vector, overflow indices live in a non-sparse-mode map with no per-entry attributes. When the + // map is in sparse mode (frozen/sealed/read-only/accessor arrays), values for in-vector indices + // may have migrated into the map and entries carry attributes, so defer to the generic path. + if (!map || !map->sparseMode()) [[likely]] { + if (i < storage->vectorLength()) { + JSValue value = storage->m_vector[i].get(); + if (value) + return JSValue::encode(value); + } else if (map) { + SparseArrayValueMap::iterator it = map->find(i); + if (it != map->notFound()) { + if (it->value.attributes()) [[unlikely]] // accessor / special: needs the full slot path. + return JSValue::encode(JSValue(base).get(globalObject, i)); + return JSValue::encode(it->value.getNonSparseMode()); + } + } + + // Missing own indexed property: undefined unless the prototype chain can intercept it. + if (!base->structure()->holesMustForwardToPrototype(base)) + return JSValue::encode(jsUndefined()); + } + } + // Negative index, sparse/dictionary mode, accessor entry, or intercepting prototype chain. + return getByValCellInt(globalObject, vm, base, index); +} + JSC_DEFINE_JIT_OPERATION(operationGetByValObjectInt, EncodedJSValue, (JSGlobalObject* globalObject, JSObject* base, int32_t index)) { VM& vm = globalObject->vm(); @@ -857,6 +891,16 @@ JSC_DEFINE_JIT_OPERATION(operationGetByValObjectInt, EncodedJSValue, (JSGlobalOb OPERATION_RETURN(scope, getByValCellInt(globalObject, vm, base, index)); } +JSC_DEFINE_JIT_OPERATION(operationGetByValArrayStorageInt, EncodedJSValue, (JSGlobalObject* globalObject, JSObject* base, int32_t index)) +{ + VM& vm = globalObject->vm(); + CallFrame* callFrame = DECLARE_CALL_FRAME(vm); + JITOperationPrologueCallFrameTracer tracer(vm, callFrame); + auto scope = DECLARE_THROW_SCOPE(vm); + + OPERATION_RETURN(scope, getByValArrayStorageInt(globalObject, vm, base, index)); +} + JSC_DEFINE_JIT_OPERATION(operationGetByValStringInt, EncodedJSValue, (JSGlobalObject* globalObject, JSString* base, int32_t index)) { VM& vm = globalObject->vm(); diff --git a/Source/JavaScriptCore/dfg/DFGOperations.h b/Source/JavaScriptCore/dfg/DFGOperations.h index 686fcc6db4c0..d750fef9c82d 100644 --- a/Source/JavaScriptCore/dfg/DFGOperations.h +++ b/Source/JavaScriptCore/dfg/DFGOperations.h @@ -117,6 +117,7 @@ JSC_DECLARE_JIT_OPERATION(operationArithTrunc, EncodedJSValue, (JSGlobalObject*, JSC_DECLARE_NOEXCEPT_JIT_OPERATION(operationArithMinMultipleDouble, double, (const double* buffer, unsigned elementCount)); JSC_DECLARE_NOEXCEPT_JIT_OPERATION(operationArithMaxMultipleDouble, double, (const double* buffer, unsigned elementCount)); JSC_DECLARE_JIT_OPERATION(operationGetByValObjectInt, EncodedJSValue, (JSGlobalObject*, JSObject*, int32_t)); +JSC_DECLARE_JIT_OPERATION(operationGetByValArrayStorageInt, EncodedJSValue, (JSGlobalObject*, JSObject*, int32_t)); JSC_DECLARE_JIT_OPERATION(operationGetByValStringInt, EncodedJSValue, (JSGlobalObject*, JSString*, int32_t)); JSC_DECLARE_JIT_OPERATION(operationGetByValObjectString, EncodedJSValue, (JSGlobalObject*, JSCell*, JSCell* string)); JSC_DECLARE_JIT_OPERATION(operationGetByValObjectSymbol, EncodedJSValue, (JSGlobalObject*, JSCell*, JSCell* symbol)); diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp index abb1f2a38960..06c06e28c75b 100644 --- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp +++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp @@ -2112,7 +2112,7 @@ void SpeculativeJIT::compileGetByVal(Node* node, const ScopedLambda class StoreBarrierInsertionPhase : public Phase { public: @@ -369,9 +371,20 @@ class StoreBarrierInsertionPhase : public Phase { break; } - case MultiPutByOffset: + case MultiPutByOffset: { + // This node may cause a transition before performing a store if it reallocates + // storage. This is different from the usual assumption in this phase a node's fast + // path either does GC or performs a store, but not both within the same node (a + // slow path call to an operation always performs its own barrier). In this special + // case, bump the epoch before considering the barrier. + if (m_node->multiPutByOffsetData().reallocatesStorage()) + m_currentEpoch.bump(); + considerBarrier(m_node->child1()); + break; + } + case MultiDeleteByOffset: { - // These nodes may cause transition too. + // This node may cause a transition but does not GC. considerBarrier(m_node->child1()); break; } diff --git a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp index 5bb87453fe4a..0f8ee575a4e9 100644 --- a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp +++ b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp @@ -6746,7 +6746,7 @@ IGNORE_CLANG_WARNINGS_END m_out.appendTo(slowCase, continuation); ValueFromBlock slowResult = m_out.anchor( - vmCall(Int64, operationGetByValObjectInt, weakPointer(globalObject), base, index)); + vmCall(Int64, operationGetByValArrayStorageInt, weakPointer(globalObject), base, index)); m_out.jump(continuation); m_out.appendTo(continuation, lastNext); diff --git a/Source/JavaScriptCore/inspector/InjectedScriptSource.js b/Source/JavaScriptCore/inspector/InjectedScriptSource.js index 87e20d1d2d75..36aae68bf3fd 100644 --- a/Source/JavaScriptCore/inspector/InjectedScriptSource.js +++ b/Source/JavaScriptCore/inspector/InjectedScriptSource.js @@ -842,6 +842,29 @@ let InjectedScript = class InjectedScript extends PrototypelessObjectBase break; } + if (shouldBreak) + break; + + let privateMethods = InjectedScriptHost.getOwnPrivatePropertyMethods(o, isOwnProperty); + for (let i = 0; i < privateMethods.length; ++i) { + let privateMethod = privateMethods[i]; + let descriptor = @createObjectWithoutPrototype(); + descriptor.name = privateMethod.name; + if (@Object.@hasOwn(privateMethod, "value")) + descriptor.value = privateMethod.value; + if (@Object.@hasOwn(privateMethod, "get")) + descriptor.get = privateMethod.get; + if (@Object.@hasOwn(privateMethod, "set")) + descriptor.set = privateMethod.set; + if (isOwnProperty) + descriptor.isOwn = true; + descriptor.isPrivate = true; + let result = processDescriptor(descriptor, isOwnProperty); + shouldBreak = result === InjectedScript.PropertyFetchAction.Stop; + if (shouldBreak) + break; + } + if (shouldBreak) break; diff --git a/Source/JavaScriptCore/inspector/JSInjectedScriptHost.cpp b/Source/JavaScriptCore/inspector/JSInjectedScriptHost.cpp index ba1287ef8556..ee1c95accc67 100644 --- a/Source/JavaScriptCore/inspector/JSInjectedScriptHost.cpp +++ b/Source/JavaScriptCore/inspector/JSInjectedScriptHost.cpp @@ -46,10 +46,12 @@ #include "JSCInlines.h" #include "JSFinalizationRegistry.h" #include "JSInjectedScriptHostPrototype.h" +#include "JSLexicalEnvironment.h" #include "JSMap.h" #include "JSMapIterator.h" #include "JSPromise.h" #include "JSPromisePrototype.h" +#include "JSScope.h" #include "JSSet.h" #include "JSSetIterator.h" #include "JSStringIterator.h" @@ -66,6 +68,7 @@ #include "ScopedArguments.h" #include "SourceCode.h" #include "StructureCreateInlines.h" +#include "SymbolTable.h" #include #include #include @@ -305,7 +308,7 @@ static JSObject* constructInternalProperty(JSGlobalObject* globalObject, const S JSValue JSInjectedScriptHost::getOwnPrivatePropertySymbols(JSGlobalObject* globalObject, CallFrame* callFrame) { - if (callFrame->argumentCount() < 1) + if (callFrame->argumentCount() < 1) [[unlikely]] return jsUndefined(); VM& vm = globalObject->vm(); @@ -316,7 +319,7 @@ JSValue JSInjectedScriptHost::getOwnPrivatePropertySymbols(JSGlobalObject* globa RETURN_IF_EXCEPTION(scope, JSValue()); JSObject* object = dynamicDowncast(value); - if (!object) + if (!object) [[unlikely]] return result; unsigned index = 0; @@ -336,6 +339,176 @@ JSValue JSInjectedScriptHost::getOwnPrivatePropertySymbols(JSGlobalObject* globa return result; } +JSValue JSInjectedScriptHost::getOwnPrivatePropertyMethods(JSGlobalObject* globalObject, CallFrame* callFrame) +{ + if (callFrame->argumentCount() < 1) [[unlikely]] + return jsUndefined(); + + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSValue value = callFrame->uncheckedArgument(0); + + // Static private methods/accessors are not accessible through the prototype chain, so only + // surface them when the class constructor or its `prototype` is the object being inspected. + bool isDirectlyInspectingObject = callFrame->argument(1).toBoolean(globalObject); + + JSArray* result = constructEmptyArray(globalObject, nullptr); + RETURN_IF_EXCEPTION(scope, JSValue()); + + JSObject* object = dynamicDowncast(value); + if (!object) [[unlikely]] + return result; + + Identifier nameIdentifier = Identifier::fromString(vm, "name"_s); + Identifier valueIdentifier = Identifier::fromString(vm, "value"_s); + Identifier getIdentifier = Identifier::fromString(vm, "get"_s); + Identifier setIdentifier = Identifier::fromString(vm, "set"_s); + + enum class IncludeStatic : bool { No, Yes }; + + unsigned index = 0; + auto appendMembers = [&](JSScope* classScope, IncludeStatic includeStatic) { + SymbolTable* symbolTable = classScope->symbolTable(); + if (!symbolTable) + return; + + Vector, PrivateNameEntry>> members; + { + ConcurrentJSLocker locker(symbolTable->m_lock); + if (!symbolTable->hasPrivateNames()) + return; + auto privateNames = symbolTable->privateNames(); + for (auto end = privateNames.end(), iter = privateNames.begin(); iter != end; ++iter) { + const PrivateNameEntry& entry = iter->value; + if (!entry.isPrivateMethodOrAccessor() || entry.isStatic() != (includeStatic == IncludeStatic::Yes)) + continue; + members.append({ iter->key.get(), entry }); + } + } + + std::sort(members.begin(), members.end(), [](const auto& a, const auto& b) { + return codePointCompareLessThan(StringView(a.first.get()), StringView(b.first.get())); + }); + + for (const auto& [name, entry] : members) { + Identifier memberIdentifier = Identifier::fromUid(vm, name.get()); + JSValue memberValue = classScope->get(globalObject, memberIdentifier); + RETURN_IF_EXCEPTION(scope, void()); + + JSObject* descriptor = constructEmptyObject(globalObject); + descriptor->putDirect(vm, nameIdentifier, jsString(vm, memberIdentifier.string())); + if (entry.isMethod()) + descriptor->putDirect(vm, valueIdentifier, memberValue); + else { + JSValue getter = jsUndefined(); + JSValue setter = jsUndefined(); + if (JSObject* holder = dynamicDowncast(memberValue)) { + getter = holder->get(globalObject, vm.propertyNames->builtinNames().getPrivateName()); + RETURN_IF_EXCEPTION(scope, void()); + setter = holder->get(globalObject, vm.propertyNames->builtinNames().setPrivateName()); + RETURN_IF_EXCEPTION(scope, void()); + } + descriptor->putDirect(vm, getIdentifier, getter); + descriptor->putDirect(vm, setIdentifier, setter); + } + + result->putDirectIndex(globalObject, index++, descriptor); + RETURN_IF_EXCEPTION(scope, void()); + } + }; + + auto scopeHasPrivateMethod = [&](SymbolTable* symbolTable, IncludeStatic includeStatic) -> bool { + ConcurrentJSLocker locker(symbolTable->m_lock); + if (!symbolTable->hasPrivateNames()) + return false; + auto privateNames = symbolTable->privateNames(); + for (auto end = privateNames.end(), iter = privateNames.begin(); iter != end; ++iter) { + if (iter->value.isPrivateMethodOrAccessor() && iter->value.isStatic() == (includeStatic == IncludeStatic::Yes)) + return true; + } + return false; + }; + + // Static private methods/accessors live in the class scope behind a "brand" that is the class itself. + auto appendStaticPrivateMethods = [&](JSFunction* classConstructor) { + for (JSScope* classScope = classConstructor->scope(); classScope; classScope = classScope->next()) { + SymbolTable* symbolTable = classScope->symbolTable(); + if (!symbolTable || !scopeHasPrivateMethod(symbolTable, IncludeStatic::Yes)) + continue; + + JSValue classBrand = classScope->get(globalObject, vm.propertyNames->builtinNames().privateClassBrandPrivateName()); + RETURN_IF_EXCEPTION(scope, void()); + if (classBrand != classConstructor) + continue; + + appendMembers(classScope, IncludeStatic::Yes); + RETURN_IF_EXCEPTION(scope, void()); + break; + } + }; + + if (isDirectlyInspectingObject) { + // When inspecting the class constructor directly. + if (JSFunction* function = dynamicDowncast(object)) { + appendStaticPrivateMethods(function); + RETURN_IF_EXCEPTION(scope, { }); + return result; + } + + // When inspecting the class `prototype`, either directly or via the instance. + JSValue classConstructorValue = object->getDirect(vm, vm.propertyNames->constructor); + if (JSFunction* classConstructor = classConstructorValue ? dynamicDowncast(classConstructorValue) : nullptr) { + JSValue classConstructorPrototype = classConstructor->get(globalObject, vm.propertyNames->prototype); + RETURN_IF_EXCEPTION(scope, { }); + if (classConstructorPrototype == object) { + appendStaticPrivateMethods(classConstructor); + RETURN_IF_EXCEPTION(scope, { }); + } + } + } + + // Instance private methods/accessors live in the class scope behind a structural brand carried + // by every instance (including instances of superclasses, via `super()`). Walk the prototype + // chain to reach each class scope, keeping only those whose brand this object actually carries. + MarkedVector seenSymbolTables; + MarkedVector instanceScopes; + for (JSValue prototype = object->getPrototypeDirect(); prototype.isObject(); prototype = asObject(prototype)->getPrototypeDirect()) { + JSValue constructorValue = asObject(prototype)->getDirect(vm, vm.propertyNames->constructor); + if (!constructorValue) + continue; + JSFunction* constructorFunction = dynamicDowncast(constructorValue); + if (!constructorFunction) + continue; + + for (JSScope* classScope = constructorFunction->scope(); classScope; classScope = classScope->next()) { + SymbolTable* symbolTable = classScope->symbolTable(); + if (!symbolTable || std::find(seenSymbolTables.begin(), seenSymbolTables.end(), symbolTable) != seenSymbolTables.end()) + continue; + + seenSymbolTables.append(symbolTable); + + if (!scopeHasPrivateMethod(symbolTable, IncludeStatic::No)) + continue; + + JSValue instanceBrand = classScope->get(globalObject, vm.propertyNames->builtinNames().privateBrandPrivateName()); + RETURN_IF_EXCEPTION(scope, { }); + if (!instanceBrand.isSymbol()) + continue; + + if (object->hasPrivateBrand(globalObject, instanceBrand)) + instanceScopes.append(classScope); + } + } + + // Emit superclass members before subclass members, matching how private fields are ordered. + for (size_t i = instanceScopes.size(); i--;) { + appendMembers(instanceScopes[i], IncludeStatic::No); + RETURN_IF_EXCEPTION(scope, { }); + } + + return result; +} + JSValue JSInjectedScriptHost::getInternalProperties(JSGlobalObject* globalObject, CallFrame* callFrame) { if (callFrame->argumentCount() < 1) diff --git a/Source/JavaScriptCore/inspector/JSInjectedScriptHost.h b/Source/JavaScriptCore/inspector/JSInjectedScriptHost.h index f47b40dbb850..a163a627c889 100644 --- a/Source/JavaScriptCore/inspector/JSInjectedScriptHost.h +++ b/Source/JavaScriptCore/inspector/JSInjectedScriptHost.h @@ -71,6 +71,7 @@ class JSInjectedScriptHost final : public JSC::JSNonFinalObject { JSC::JSValue subtype(JSC::JSGlobalObject*, JSC::CallFrame*); JSC::JSValue functionDetails(JSC::JSGlobalObject*, JSC::CallFrame*); JSC::JSValue getOwnPrivatePropertySymbols(JSC::JSGlobalObject*, JSC::CallFrame*); + JSC::JSValue getOwnPrivatePropertyMethods(JSC::JSGlobalObject*, JSC::CallFrame*); JSC::JSValue getInternalProperties(JSC::JSGlobalObject*, JSC::CallFrame*); JSC::JSValue NODELETE proxyTargetValue(JSC::CallFrame*); JSC::JSValue weakRefTargetValue(JSC::JSGlobalObject*, JSC::CallFrame*); diff --git a/Source/JavaScriptCore/inspector/JSInjectedScriptHostPrototype.cpp b/Source/JavaScriptCore/inspector/JSInjectedScriptHostPrototype.cpp index bf4f64d0a6da..5b934f9a3fb1 100644 --- a/Source/JavaScriptCore/inspector/JSInjectedScriptHostPrototype.cpp +++ b/Source/JavaScriptCore/inspector/JSInjectedScriptHostPrototype.cpp @@ -37,6 +37,7 @@ using namespace JSC; static JSC_DECLARE_HOST_FUNCTION(jsInjectedScriptHostPrototypeFunctionSubtype); static JSC_DECLARE_HOST_FUNCTION(jsInjectedScriptHostPrototypeFunctionFunctionDetails); static JSC_DECLARE_HOST_FUNCTION(jsInjectedScriptHostPrototypeFunctionGetOwnPrivatePropertySymbols); +static JSC_DECLARE_HOST_FUNCTION(jsInjectedScriptHostPrototypeFunctionGetOwnPrivatePropertyMethods); static JSC_DECLARE_HOST_FUNCTION(jsInjectedScriptHostPrototypeFunctionGetInternalProperties); static JSC_DECLARE_HOST_FUNCTION(jsInjectedScriptHostPrototypeFunctionInternalConstructorName); static JSC_DECLARE_HOST_FUNCTION(jsInjectedScriptHostPrototypeFunctionIsHTMLAllCollection); @@ -65,6 +66,7 @@ void JSInjectedScriptHostPrototype::finishCreation(VM& vm, JSGlobalObject* globa JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("subtype"_s, jsInjectedScriptHostPrototypeFunctionSubtype, static_cast(PropertyAttribute::DontEnum), 1, ImplementationVisibility::Private); JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("functionDetails"_s, jsInjectedScriptHostPrototypeFunctionFunctionDetails, static_cast(PropertyAttribute::DontEnum), 1, ImplementationVisibility::Private); JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("getOwnPrivatePropertySymbols"_s, jsInjectedScriptHostPrototypeFunctionGetOwnPrivatePropertySymbols, static_cast(PropertyAttribute::DontEnum), 1, ImplementationVisibility::Private); + JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("getOwnPrivatePropertyMethods"_s, jsInjectedScriptHostPrototypeFunctionGetOwnPrivatePropertyMethods, static_cast(PropertyAttribute::DontEnum), 2, ImplementationVisibility::Private); JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("getInternalProperties"_s, jsInjectedScriptHostPrototypeFunctionGetInternalProperties, static_cast(PropertyAttribute::DontEnum), 1, ImplementationVisibility::Private); JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("internalConstructorName"_s, jsInjectedScriptHostPrototypeFunctionInternalConstructorName, static_cast(PropertyAttribute::DontEnum), 1, ImplementationVisibility::Private); JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("isHTMLAllCollection"_s, jsInjectedScriptHostPrototypeFunctionIsHTMLAllCollection, static_cast(PropertyAttribute::DontEnum), 1, ImplementationVisibility::Private); @@ -319,6 +321,19 @@ JSC_DEFINE_HOST_FUNCTION(jsInjectedScriptHostPrototypeFunctionGetOwnPrivatePrope return JSValue::encode(castedThis->getOwnPrivatePropertySymbols(globalObject, callFrame)); } +JSC_DEFINE_HOST_FUNCTION(jsInjectedScriptHostPrototypeFunctionGetOwnPrivatePropertyMethods, (JSGlobalObject* globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue thisValue = callFrame->thisValue(); + JSInjectedScriptHost* castedThis = dynamicDowncast(thisValue); + if (!castedThis) + return throwVMTypeError(globalObject, scope); + + return JSValue::encode(castedThis->getOwnPrivatePropertyMethods(globalObject, callFrame)); +} + JSC_DEFINE_HOST_FUNCTION(jsInjectedScriptHostPrototypeFunctionGetInternalProperties, (JSGlobalObject* globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); diff --git a/Source/JavaScriptCore/runtime/JSObjectInlines.h b/Source/JavaScriptCore/runtime/JSObjectInlines.h index 915538b10146..86e5face3845 100644 --- a/Source/JavaScriptCore/runtime/JSObjectInlines.h +++ b/Source/JavaScriptCore/runtime/JSObjectInlines.h @@ -919,7 +919,7 @@ inline bool JSObject::getPrivateField(JSGlobalObject* globalObject, PropertyName ASSERT(!slot.isVMInquiry()); if (!JSObject::getPrivateFieldSlot(this, globalObject, propertyName, slot)) { throwException(globalObject, scope, createInvalidPrivateNameError(globalObject)); - RELEASE_AND_RETURN(scope, false); + return false; } EXCEPTION_ASSERT(!scope.exception()); RELEASE_AND_RETURN(scope, true); @@ -932,7 +932,7 @@ inline void JSObject::setPrivateField(JSGlobalObject* globalObject, PropertyName PropertySlot slot(this, PropertySlot::InternalMethodType::HasProperty); if (!JSObject::getPrivateFieldSlot(this, globalObject, propertyName, slot)) { throwException(globalObject, scope, createInvalidPrivateNameError(globalObject)); - RELEASE_AND_RETURN(scope, void()); + return; } EXCEPTION_ASSERT(!scope.exception()); @@ -944,10 +944,16 @@ inline void JSObject::definePrivateField(JSGlobalObject* globalObject, PropertyN { VM& vm = getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); + + if (type() == WebAssemblyGCObjectType) { + throwTypeError(globalObject, scope, "Cannot define private field on a WebAssembly GC object"_s); + return; + } + PropertySlot slot(this, PropertySlot::InternalMethodType::HasProperty); if (JSObject::getPrivateFieldSlot(this, globalObject, propertyName, slot)) { throwException(globalObject, scope, createRedefinedPrivateNameError(globalObject)); - RELEASE_AND_RETURN(scope, void()); + return; } EXCEPTION_ASSERT(!scope.exception()); @@ -1008,10 +1014,15 @@ inline void JSObject::setPrivateBrand(JSGlobalObject* globalObject, JSValue bran Structure* structure = this->structure(); if (structure->isBrandedStructure() && uncheckedDowncast(structure)->checkBrand(asSymbol(brand))) { throwException(globalObject, scope, createReinstallPrivateMethodError(globalObject)); - RELEASE_AND_RETURN(scope, void()); + return; } EXCEPTION_ASSERT(!scope.exception()); + if (type() == WebAssemblyGCObjectType) { + throwTypeError(globalObject, scope, "Cannot add private method to a WebAssembly GC object"_s); + return; + } + scope.release(); DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure); diff --git a/Source/JavaScriptCore/runtime/OptionsList.h b/Source/JavaScriptCore/runtime/OptionsList.h index a7a9275b8801..dba9d6e9411b 100644 --- a/Source/JavaScriptCore/runtime/OptionsList.h +++ b/Source/JavaScriptCore/runtime/OptionsList.h @@ -339,6 +339,7 @@ bool hasCapacityToUseLargeGigacage(); v(Unsigned, maximumBinaryStringSwitchCaseLength, 50, Normal, nullptr) \ v(Unsigned, maximumBinaryStringSwitchTotalLength, 2000, Normal, nullptr) \ v(Unsigned, maximumRegExpTestInlineCodesize, 500, Normal, "Maximum code size in bytes for inlined RegExp.test JIT code."_s) \ + v(Unsigned, maximumRegExpJITCodeSize, 16 * MB, Normal, "Maximum generated code size in bytes for RegExp JIT compilation before falling back to the interpreter."_s) \ \ v(Unsigned, wasmInliningMaximumDepth, 7, Normal, "Maximum inlining depth to consider inlining a wasm function."_s) \ v(Unsigned, wasmInliningMaximumWasmCalleeSize, 500, Normal, "Maximum wasm size in bytes to consider inlining a wasm function."_s) \ diff --git a/Source/JavaScriptCore/runtime/Structure.cpp b/Source/JavaScriptCore/runtime/Structure.cpp index 9fcfeb3702a1..bd9e01f437e4 100644 --- a/Source/JavaScriptCore/runtime/Structure.cpp +++ b/Source/JavaScriptCore/runtime/Structure.cpp @@ -245,15 +245,16 @@ Structure::Structure(VM& vm, JSGlobalObject* globalObject, JSValue prototype, co { bool hasStaticNonEnumerableProperty = m_classInfo->hasStaticPropertyWithAnyOfAttributes(static_cast(PropertyAttribute::DontEnum)); bool hasStaticNonConfigurableProperty = m_classInfo->hasStaticPropertyWithAnyOfAttributes(static_cast(PropertyAttribute::DontDelete)); + bool isArrayStorage = hasAnyArrayStorage(indexingType); setDictionaryKind(NoneDictionaryKind); setIsPinnedPropertyTable(false); setHasAnyKindOfGetterSetterProperties(m_classInfo->hasStaticPropertyWithAnyOfAttributes(static_cast(PropertyAttribute::AccessorOrCustomAccessorOrValue))); setHasReadOnlyOrGetterSetterPropertiesExcludingProto(hasAnyKindOfGetterSetterProperties() || m_classInfo->hasStaticPropertyWithAnyOfAttributes(static_cast(PropertyAttribute::ReadOnly))); - setHasNonEnumerableProperties(hasStaticNonEnumerableProperty || typeInfo.overridesGetOwnPropertySlot()); + setHasNonEnumerableProperties(hasStaticNonEnumerableProperty || typeInfo.overridesGetOwnPropertySlot() || isArrayStorage); setHasSpecialProperties(false); - setHasNonConfigurableProperties(hasStaticNonConfigurableProperty || typeInfo.overridesGetOwnPropertySlot()); - setHasNonConfigurableReadOnlyOrGetterSetterProperties(hasStaticNonConfigurableProperty || (typeInfo.overridesGetOwnPropertySlot() && typeInfo.type() != ArrayType)); + setHasNonConfigurableProperties(hasStaticNonConfigurableProperty || typeInfo.overridesGetOwnPropertySlot() || isArrayStorage); + setHasNonConfigurableReadOnlyOrGetterSetterProperties(hasStaticNonConfigurableProperty || (typeInfo.overridesGetOwnPropertySlot() && typeInfo.type() != ArrayType) || isArrayStorage); setHasUnderscoreProtoPropertyExcludingOriginalProto(false); setIsQuickPropertyAccessAllowedForEnumeration(true); setTransitionPropertyAttributes(0); diff --git a/Source/JavaScriptCore/runtime/Structure.h b/Source/JavaScriptCore/runtime/Structure.h index 85df7c709c6c..de5071e12692 100644 --- a/Source/JavaScriptCore/runtime/Structure.h +++ b/Source/JavaScriptCore/runtime/Structure.h @@ -347,6 +347,8 @@ class Structure : public JSCell { bool propertyAccessesAreCacheableForAbsence() { + // FIXME: dictionaries cannot be cached for absence, so check for dictionaries here instead + // of at all call sites. return !typeInfo().getOwnPropertySlotIsImpureForPropertyAbsence(); } diff --git a/Source/JavaScriptCore/wasm/WasmCallee.cpp b/Source/JavaScriptCore/wasm/WasmCallee.cpp index 778b440e9a1e..7a6a8ade547e 100644 --- a/Source/JavaScriptCore/wasm/WasmCallee.cpp +++ b/Source/JavaScriptCore/wasm/WasmCallee.cpp @@ -585,7 +585,6 @@ unsigned OptimizingJITCallee::computeCodeHashImpl() const BBQCallee::~BBQCallee() { if (Options::freeRetiredWasmCode() && m_osrEntryCallee) { - ASSERT(m_osrEntryCallee->hasOneRef()); m_osrEntryCallee->reportToVMsForDestruction(); } } diff --git a/Source/JavaScriptCore/wasm/WasmCalleeGroup.cpp b/Source/JavaScriptCore/wasm/WasmCalleeGroup.cpp index 4de1d1668868..db99bffd98e1 100644 --- a/Source/JavaScriptCore/wasm/WasmCalleeGroup.cpp +++ b/Source/JavaScriptCore/wasm/WasmCalleeGroup.cpp @@ -346,14 +346,9 @@ void CalleeGroup::updateCallsitesToCallUs(const AbstractLocker& locker, CodeLoca // This is necessary since Callees are released under `Heap::stopThePeriphery()`, but that only stops JS compiler // threads and not wasm ones. So a weakly held BBQCallee and its OMGOSREntryCallee could die between the time we - // collect the callsites and when we actually repatch its callsites. Since BBQCallee owns OMGOSREntryCallee, - // keeping BBQCallee alive is enough to ensure that both are alive for the required duration. - // - // There is however an edge case here - it can happen that a BBQCallee has been freed but its OMGOSREntryCallee - // has been added to the pending-destruction set and not yet free'd. This means that m_osrEntryCallees will still - // hold a weak ref to it. In this scenario, BBQCallee won't be kept alive since it does not exist so we manually - // have to keep the OMGOSREntryCallee alive separately. This should only be done in this scenario else we will - // end up with multiple owners for OMGOSREntryCallee. + // collect the callsites and when we actually repatch its callsites. Additionally, after a re-tier (where a fresh + // BBQCallee replaces a retired one), m_osrEntryCallees may hold a stale weak ref to an OMGOSREntryCallee not + // owned by the current BBQCallee, so we always keep it alive unconditionally. // FIXME: These inline capacities were picked semi-randomly. We should figure out if there's a better number. #if ENABLE(WEBASSEMBLY_BBQJIT) @@ -384,8 +379,6 @@ void CalleeGroup::updateCallsitesToCallUs(const AbstractLocker& locker, CodeLoca if (!tuple) return; - bool bbqCalleeKeptAlive = false; - UNUSED_VARIABLE(bbqCalleeKeptAlive); #if ENABLE(WEBASSEMBLY_BBQJIT) // This callee could be weak but we still need to update it since it could call our BBQ callee // that we're going to want to destroy. @@ -398,25 +391,15 @@ void CalleeGroup::updateCallsitesToCallUs(const AbstractLocker& locker, CodeLoca collectCallsites(bbqCallee.get()); ASSERT(!bbqCallee->osrEntryCallee() || m_osrEntryCallees.find(callerIndex) != m_osrEntryCallees.end()); keepAliveBBQCallees.append(bbqCallee.releaseNonNull()); - bbqCalleeKeptAlive = true; } #endif #if ENABLE(WEBASSEMBLY_OMGJIT) collectCallsites(tuple->m_omgCallee.get()); if (auto iter = m_osrEntryCallees.find(callerIndex); iter != m_osrEntryCallees.end()) { if (RefPtr callee = iter->value.get()) { + // Since there is a OMGOSREntryCallee, we need to collect all the callsites there and also keep it alive until we patch it. collectCallsites(callee.get()); - // If we track the OMGOSREntryCallee as a callsite there are 2 possibilities - - // 1. The BBQCallee is already being tracked - in this case we don't have to - // track the OMGOSREntryCallee since the BBQCallee owns it and keeping the - // BBQCallee alive is good enough to keep the OMGOSREntryCallee alive. Also, - // OMGOSREntryCallee is only supposed to be owned by BBQCallee - // 2. The BBQCallee is not tracked - This happens if the BBQCallee is already - // released but the OMGOSREntryCallee is still alive. In this case there is - // no other strong reference to OMGOSREntryCallee so we have to keep it - // alive here. - if (!bbqCalleeKeptAlive) - keepAliveOSREntryCallees.append(callee.releaseNonNull()); + keepAliveOSREntryCallees.append(callee.releaseNonNull()); } else m_osrEntryCallees.remove(iter); } diff --git a/Source/JavaScriptCore/yarr/YarrJIT.cpp b/Source/JavaScriptCore/yarr/YarrJIT.cpp index 224ee1fb527d..dccdd0c365a0 100644 --- a/Source/JavaScriptCore/yarr/YarrJIT.cpp +++ b/Source/JavaScriptCore/yarr/YarrJIT.cpp @@ -1326,9 +1326,9 @@ class YarrGenerator final : public YarrJITInfo { Checked characterOffset(-static_cast(negativeCharacterOffset)); if (m_charSize == CharSize::Char8) - return MacroAssembler::BaseIndex(m_regs.input, indexReg, MacroAssembler::TimesOne, characterOffset * static_cast(sizeof(char))); + return MacroAssembler::BaseIndex(base, indexReg, MacroAssembler::TimesOne, characterOffset * static_cast(sizeof(char))); - return MacroAssembler::BaseIndex(m_regs.input, indexReg, MacroAssembler::TimesTwo, characterOffset * static_cast(sizeof(char16_t))); + return MacroAssembler::BaseIndex(base, indexReg, MacroAssembler::TimesTwo, characterOffset * static_cast(sizeof(char16_t))); } #if ENABLE(YARR_JIT_UNICODE_EXPRESSIONS) @@ -4525,7 +4525,7 @@ class YarrGenerator final : public YarrJITInfo { } ++opIndex; - } while (opIndex < m_ops.size()); + } while (opIndex < m_ops.size() && !hasExceededCodeSizeLimit()); termMatchTargets.takeLast(); } @@ -5332,7 +5332,18 @@ class YarrGenerator final : public YarrJITInfo { case YarrOpCode::MatchFailed: break; } - } while (opIndex); + } while (opIndex && !hasExceededCodeSizeLimit()); + } + + bool hasExceededCodeSizeLimit() + { + if (m_failureReason) + return true; + if (m_jit.m_assembler.buffer().codeSize() > Options::maximumRegExpJITCodeSize()) [[unlikely]] { + m_failureReason = JITFailureReason::GeneratedCodeSizeTooLarge; + return true; + } + return false; } // Compilation methods: @@ -6951,9 +6962,17 @@ class YarrGenerator final : public YarrJITInfo { generate(); if (m_disassembler) m_disassembler->setEndOfGenerate(m_jit.label()); + if (m_failureReason) { + codeBlock.setFallBackWithFailureReason(*m_failureReason); + return; + } backtrack(); if (m_disassembler) m_disassembler->setEndOfBacktrack(m_jit.label()); + if (m_failureReason) { + codeBlock.setFallBackWithFailureReason(*m_failureReason); + return; + } ptrdiff_t codeSize = MacroAssembler::differenceBetween(startOfMainCode, m_jit.label()); bool canInline = ([&] -> bool { @@ -7585,6 +7604,9 @@ static void dumpCompileFailure(JITFailureReason failure) case JITFailureReason::OffsetTooLarge: dataLog("Can't JIT because pattern exceeds string length limits\n"); break; + case JITFailureReason::GeneratedCodeSizeTooLarge: + dataLog("Can't JIT because generated code size exceeds limit\n"); + break; } } diff --git a/Source/JavaScriptCore/yarr/YarrJIT.h b/Source/JavaScriptCore/yarr/YarrJIT.h index caa9a4b94bc3..4932afa28a40 100644 --- a/Source/JavaScriptCore/yarr/YarrJIT.h +++ b/Source/JavaScriptCore/yarr/YarrJIT.h @@ -63,6 +63,7 @@ enum class JITFailureReason : uint8_t { ParenthesisNestedTooDeep, ExecutableMemoryAllocationFailure, OffsetTooLarge, + GeneratedCodeSizeTooLarge, }; class BoyerMooreFastCandidates { diff --git a/Source/ThirdParty/ANGLE/ANGLE.xcodeproj/project.pbxproj b/Source/ThirdParty/ANGLE/ANGLE.xcodeproj/project.pbxproj index 643352fd41ab..9f220a9cfb6c 100644 --- a/Source/ThirdParty/ANGLE/ANGLE.xcodeproj/project.pbxproj +++ b/Source/ThirdParty/ANGLE/ANGLE.xcodeproj/project.pbxproj @@ -479,6 +479,7 @@ 7B8EC8472DF2E5FC00105EB6 /* EnsureLoopForwardProgress.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B8EC8442DF2E5FC00105EB6 /* EnsureLoopForwardProgress.h */; }; 7B8EC8482DF2E5FC00105EB6 /* EnsureLoopForwardProgress.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B8EC8442DF2E5FC00105EB6 /* EnsureLoopForwardProgress.h */; }; 7B8EC8492DF2E5FC00105EB6 /* EnsureLoopForwardProgress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7B8EC8452DF2E5FC00105EB6 /* EnsureLoopForwardProgress.cpp */; }; + 7B91C9A82F8CE63A00420427 /* VertexArrayMtl_unittest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7B91C9A62F8CDB5400420427 /* VertexArrayMtl_unittest.mm */; }; 7B9A99BA2DDC685100160B6E /* ReduceInterfaceBlocks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7B9A99B92DDC685100160B6E /* ReduceInterfaceBlocks.cpp */; }; 7B9A99BB2DDC685100160B6E /* ReduceInterfaceBlocks.h in Headers */ = {isa = PBXBuildFile; fileRef = 7B9A99B82DDC685100160B6E /* ReduceInterfaceBlocks.h */; }; 7B9A99BC2DDC685100160B6E /* ReduceInterfaceBlocks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7B9A99B92DDC685100160B6E /* ReduceInterfaceBlocks.cpp */; }; @@ -2402,6 +2403,7 @@ 7B8EC83F2DF1BC9100105EB6 /* ImageTestMetal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ImageTestMetal.mm; sourceTree = ""; }; 7B8EC8442DF2E5FC00105EB6 /* EnsureLoopForwardProgress.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EnsureLoopForwardProgress.h; sourceTree = ""; }; 7B8EC8452DF2E5FC00105EB6 /* EnsureLoopForwardProgress.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = EnsureLoopForwardProgress.cpp; sourceTree = ""; }; + 7B91C9A62F8CDB5400420427 /* VertexArrayMtl_unittest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = VertexArrayMtl_unittest.mm; sourceTree = ""; }; 7B9829372E0EB05200F1E9FB /* ANGLETranslator.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ANGLETranslator.xcconfig; sourceTree = ""; }; 7B9A99B82DDC685100160B6E /* ReduceInterfaceBlocks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReduceInterfaceBlocks.h; sourceTree = ""; }; 7B9A99B92DDC685100160B6E /* ReduceInterfaceBlocks.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ReduceInterfaceBlocks.cpp; sourceTree = ""; }; @@ -4711,6 +4713,7 @@ FF81FEA425818D6800894E24 /* TransformFeedbackMtl.mm */, FF81FE9725818D6800894E24 /* VertexArrayMtl.h */, FF81FE9A25818D6800894E24 /* VertexArrayMtl.mm */, + 7B91C9A62F8CDB5400420427 /* VertexArrayMtl_unittest.mm */, ); path = metal; sourceTree = ""; @@ -6547,6 +6550,7 @@ 7BFAF5C12DE59E0900327F7F /* UniformTest.cpp in Sources */, 7BFAF6062DE59E0900327F7F /* UnpackAlignmentTest.cpp in Sources */, 7BFAF5F22DE59E0900327F7F /* UnpackRowLength.cpp in Sources */, + 7B91C9A82F8CE63A00420427 /* VertexArrayMtl_unittest.mm in Sources */, 7BFAF5FE2DE59E0900327F7F /* VertexAttributeTest.cpp in Sources */, 7BFAF5FC2DE59E0900327F7F /* ViewportTest.cpp in Sources */, 7BFAF6152DE59E2300327F7F /* WebGLCompatibilityTest.cpp in Sources */, diff --git a/Source/WTF/wtf/PlatformHave.h b/Source/WTF/wtf/PlatformHave.h index 863bfaa16646..54b4e0458811 100644 --- a/Source/WTF/wtf/PlatformHave.h +++ b/Source/WTF/wtf/PlatformHave.h @@ -1235,6 +1235,51 @@ #define HAVE_WK_SECURE_CODING_PKDATECOMPONENTSRANGE 1 #endif +#if ((PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 150400) \ + || ((PLATFORM(IOS) || PLATFORM(MACCATALYST)) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 180400) \ + || (PLATFORM(VISION) && __VISION_OS_VERSION_MIN_REQUIRED >= 20400)) \ + || (PLATFORM(WATCHOS) && __WATCH_OS_VERSION_MIN_REQUIRED >= 110400) \ + || (PLATFORM(APPLETV) && __TV_OS_VERSION_MIN_REQUIRED >= 180400) +#define HAVE_WK_SECURE_CODING_NSURLPROTECTIONSPACE 1 +#endif + +#if ((PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 150400) \ + || ((PLATFORM(IOS) || PLATFORM(MACCATALYST)) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 180400) \ + || (PLATFORM(VISION) && __VISION_OS_VERSION_MIN_REQUIRED >= 20400)) \ + || (PLATFORM(WATCHOS) && __WATCH_OS_VERSION_MIN_REQUIRED >= 110400) \ + || (PLATFORM(APPLETV) && __TV_OS_VERSION_MIN_REQUIRED >= 180400) +#define HAVE_WK_SECURE_CODING_NSURLCREDENTIAL 1 +#endif + +#if ((PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 160000) \ + || ((PLATFORM(IOS) || PLATFORM(MACCATALYST)) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 190000) \ + || (PLATFORM(VISION) && __VISION_OS_VERSION_MIN_REQUIRED >= 30000)) \ + || (PLATFORM(WATCHOS) && __WATCH_OS_VERSION_MIN_REQUIRED >= 120000) \ + || (PLATFORM(APPLETV) && __TV_OS_VERSION_MIN_REQUIRED >= 190000) +#define HAVE_WK_SECURE_CODING_SECTRUST 1 +#endif + +#if ((PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 160000) \ + || ((PLATFORM(IOS) || PLATFORM(MACCATALYST)) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 190000) \ + || (PLATFORM(VISION) && __VISION_OS_VERSION_MIN_REQUIRED >= 30000)) \ + || (PLATFORM(WATCHOS) && __WATCH_OS_VERSION_MIN_REQUIRED >= 120000) \ + || (PLATFORM(APPLETV) && __TV_OS_VERSION_MIN_REQUIRED >= 190000) +#define HAVE_WK_SECURE_CODING_AVOUTPUTCONTEXT 1 +#endif + +#if ((PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 270000) \ + || ((PLATFORM(IOS) || PLATFORM(MACCATALYST)) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 270000) \ + || (PLATFORM(VISION) && __VISION_OS_VERSION_MIN_REQUIRED >= 270000)) \ + || (PLATFORM(WATCHOS) && __WATCH_OS_VERSION_MIN_REQUIRED >= 270000) \ + || (PLATFORM(APPLETV) && __TV_OS_VERSION_MIN_REQUIRED >= 270000) +#define HAVE_WK_SECURE_CODING_PKPAYMENTMETHOD 1 +#define HAVE_WK_SECURE_CODING_PKPAYMENTTOKEN 1 +#define HAVE_WK_SECURE_CODING_PKSHIPPINGMETHOD 1 +#define HAVE_WK_SECURE_CODING_PKPAYMENTMERCHANTSESSION 1 +#define HAVE_WK_SECURE_CODING_PKPAYMENT 1 +#define HAVE_WK_SECURE_CODING_PKPAYMENTSETUPFEATURE 1 +#endif + #if PLATFORM(COCOA) #define HAVE_CFNETWORK_SEPARATE_CREDENTIAL_STORAGE 1 #define HAVE_STRICT_DECODABLE_CNCONTACT 1 diff --git a/Source/WTF/wtf/TinyLRUCache.h b/Source/WTF/wtf/TinyLRUCache.h index eb801103a79b..0c078d06ac74 100644 --- a/Source/WTF/wtf/TinyLRUCache.h +++ b/Source/WTF/wtf/TinyLRUCache.h @@ -28,6 +28,7 @@ #include #include #include +#include #include namespace WTF { @@ -44,45 +45,195 @@ templatem_findResult == this); + m_cache->invalidateIterators(); + } + } + + explicit operator bool() const + { + return m_ptr; + } + + const ValueType& operator*() const + { + RELEASE_ASSERT(m_ptr); + return *m_ptr; + } + + const ValueType* operator->() const + { + RELEASE_ASSERT(m_ptr); + return m_ptr; + } + + private: + friend class TinyLRUCache; + + using Cache = TinyLRUCache; + + FindResult(const ValueType* ptr, Cache& cache) + : m_ptr(ptr) + , m_cache(ptr ? &cache : nullptr) + { + if (m_cache) + m_cache->trackFindResult(this); + } + + void assertValid() const + { + RELEASE_ASSERT_IMPLIES(m_ptr, m_cache); + } + + const ValueType* m_ptr { nullptr }; + Cache* m_cache { nullptr }; + }; + const ValueType& get(const KeyType& key) { + invalidateIterators(); + if (Policy::isKeyNull(key)) { static NeverDestroyed valueForNull = Policy::createValueForNullKey(); return valueForNull; } + if (auto* found = findInternal(key)) + return *found; + + insertInternal(key, Policy::createValueForKey(key)); + return cacheBuffer()[m_size - 1].second; + } + + FindResult findIfCached(const KeyType& key) + { + invalidateIterators(); + + if (Policy::isKeyNull(key)) + return { nullptr, *this }; + + return { findInternal(key), *this }; + } + + void insert(const KeyType& key, ValueType&& value) + { + ASSERT(!Policy::isKeyNull(key)); +#if ASSERT_ENABLED + { + auto cacheBuffer = this->cacheBuffer(); + for (size_t i = 0; i < m_size; ++i) + ASSERT(!(cacheBuffer[i].first == key)); + } +#endif + invalidateIterators(); + insertInternal(key, WTF::move(value)); + } + + void clear() + { + invalidateIterators(); + auto cacheBuffer = this->cacheBuffer(); + for (size_t i = 0; i < m_size; ++i) + cacheBuffer[i] = Entry { }; + m_size = 0; + } + + size_t size() const { return m_size; } + +private: + void trackFindResult(FindResult* p) + { + ASSERT(p); + ASSERT(m_findResult != p); + invalidateIterators(); + m_findResult = p; + } + + void invalidateIterators() + { + if (m_findResult) { + ASSERT(m_findResult->m_cache == this); + m_findResult->m_cache = nullptr; + m_findResult->m_ptr = nullptr; + m_findResult = nullptr; + } + } + + ALWAYS_INLINE const ValueType* findInternal(const KeyType& key) + { auto cacheBuffer = this->cacheBuffer(); for (size_t i = m_size; i-- > 0;) { if (cacheBuffer[i].first == key) { if (i < m_size - 1) { - // Move entry to the end of the cache if necessary. auto entry = WTF::move(cacheBuffer[i]); do { cacheBuffer[i] = WTF::move(cacheBuffer[i + 1]); } while (++i < m_size - 1); cacheBuffer[m_size - 1] = WTF::move(entry); } - return cacheBuffer[m_size - 1].second; + return &cacheBuffer[m_size - 1].second; } } + return nullptr; + } - // cacheBuffer[0] is the LRU entry, so remove it. + ALWAYS_INLINE void insertInternal(const KeyType& key, ValueType&& value) + { + auto cacheBuffer = this->cacheBuffer(); if (m_size == capacity) { for (size_t i = 0; i < m_size - 1; ++i) cacheBuffer[i] = WTF::move(cacheBuffer[i + 1]); } else ++m_size; - - cacheBuffer[m_size - 1] = std::pair { Policy::createKeyForStorage(key), Policy::createValueForKey(key) }; - return cacheBuffer[m_size - 1].second; + cacheBuffer[m_size - 1] = std::pair { Policy::createKeyForStorage(key), WTF::move(value) }; } -private: using Entry = std::pair; std::span cacheBuffer() { return m_cacheBuffer; } alignas(Entry) std::array m_cacheBuffer; size_t m_size { 0 }; + FindResult* m_findResult { nullptr }; }; } // namespace WTF diff --git a/Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.cpp b/Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.cpp index 99773da3464b..f054bb5150fd 100644 --- a/Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.cpp +++ b/Source/WebCore/Modules/indexeddb/server/MemoryIndexCursor.cpp @@ -221,7 +221,12 @@ void MemoryIndexCursor::indexRecordsAllChanged() void MemoryIndexCursor::indexValueChanged(const IDBKeyData& key, const IDBKeyData& primaryKey) { - if (m_currentKey != key || m_currentPrimaryKey != primaryKey) + // For Prev/Prevunique cursors, m_currentIterator wraps std::set reverse_iterators + // whose stored base() points one element past the logical position. Erasing that + // adjacent element leaves base() dangling even though m_currentKey/m_currentPrimaryKey + // are unchanged, so for reverse cursors we must invalidate on any index mutation + // and let iterate() re-seek via reverseFind(). + if (info().isDirectionForward() && (m_currentKey != key || m_currentPrimaryKey != primaryKey)) return; m_currentIterator.invalidate(); diff --git a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp index 7fea86ececa1..5e2024f19b39 100644 --- a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp +++ b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.cpp @@ -1379,6 +1379,11 @@ void UniqueIDBDatabase::connectionClosedFromClient(UniqueIDBDatabaseConnection& handleTransactions(); } +bool UniqueIDBDatabase::isVersionChangeTransactionActive(const UniqueIDBDatabaseConnection& connection) const +{ + return m_versionChangeDatabaseConnection == &connection && m_versionChangeTransaction; +} + void UniqueIDBDatabase::connectionClosedFromServer(UniqueIDBDatabaseConnection& connection) { ASSERT(!isMainThread()); diff --git a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h index b934e0720987..5a49d2aaa7b5 100644 --- a/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h +++ b/Source/WebCore/Modules/indexeddb/server/UniqueIDBDatabase.h @@ -110,6 +110,7 @@ class UniqueIDBDatabase final : public CanMakeWeakPtr, public void didFinishHandlingVersionChange(UniqueIDBDatabaseConnection&, const IDBResourceIdentifier& transactionIdentifier); void connectionClosedFromClient(UniqueIDBDatabaseConnection&); + WEBCORE_EXPORT bool isVersionChangeTransactionActive(const UniqueIDBDatabaseConnection&) const; void didFireVersionChangeEvent(UniqueIDBDatabaseConnection&, const IDBResourceIdentifier& requestIdentifier, IndexedDB::ConnectionClosedOnBehalfOfServer); WEBCORE_EXPORT void openDBRequestCancelled(const IDBResourceIdentifier& requestIdentifier); diff --git a/Source/WebCore/Modules/mediasession/MediaMetadata.cpp b/Source/WebCore/Modules/mediasession/MediaMetadata.cpp index f3d7164e63a8..2b7a6408cccb 100644 --- a/Source/WebCore/Modules/mediasession/MediaMetadata.cpp +++ b/Source/WebCore/Modules/mediasession/MediaMetadata.cpp @@ -270,14 +270,17 @@ void MediaMetadata::tryNextArtworkImage(uint32_t index, Vector&& artworks) String artworkImageSrc = artworks[index].src; - m_artworkLoader = ArtworkImageLoader::create(*document, artworkImageSrc, [this, index, artworkImageSrc, artworks = WTF::move(artworks)](Image* image) mutable { + m_artworkLoader = ArtworkImageLoader::create(*document, artworkImageSrc, [weakThis = WeakPtr { *this }, index, artworkImageSrc, artworks = WTF::move(artworks)](Image* image) mutable { + RefPtr strongThis = weakThis; + if (!strongThis) + return; if (image && image->data() && image->width() && image->height()) { IntSize size { int(image->width()), int(image->height()) }; float imageScore = imageDimensionsScore(size.width(), size.height(), s_minimumSize, s_idealSize); - if (!index || (m_artworkImage && (imageDimensionsScore(protect(m_artworkImage)->width(), protect(m_artworkImage)->height(), s_minimumSize, s_idealSize) < imageScore))) { - m_artworkImageSrc = artworkImageSrc; - setArtworkImage(image); - metadataUpdated(); + if (!index || (strongThis->m_artworkImage && (imageDimensionsScore(protect(strongThis->m_artworkImage)->width(), protect(strongThis->m_artworkImage)->height(), s_minimumSize, s_idealSize) < imageScore))) { + strongThis->m_artworkImageSrc = artworkImageSrc; + strongThis->setArtworkImage(image); + strongThis->metadataUpdated(); } // If selection from `sizes` attribute yielded a valid image, or we have downloaded an image bigger than the ideal size we stop. if (artworks[index].score >= 0 || size.maxDimension() >= s_idealSize) @@ -285,7 +288,7 @@ void MediaMetadata::tryNextArtworkImage(uint32_t index, Vector&& artworks) } if (++index < artworks.size()) - tryNextArtworkImage(index, WTF::move(artworks)); + strongThis->tryNextArtworkImage(index, WTF::move(artworks)); }); protect(m_artworkLoader)->requestImageResource(); } diff --git a/Source/WebCore/Modules/mediasession/MediaMetadata.h b/Source/WebCore/Modules/mediasession/MediaMetadata.h index a4d20458a387..932c83f49997 100644 --- a/Source/WebCore/Modules/mediasession/MediaMetadata.h +++ b/Source/WebCore/Modules/mediasession/MediaMetadata.h @@ -75,7 +75,7 @@ class ArtworkImageLoader final : public CachedImageClient, public RefCounted m_cachedImage; }; -class MediaMetadata final : public RefCounted { +class MediaMetadata final : public RefCountedAndCanMakeWeakPtr { public: static ExceptionOr> create(ScriptExecutionContext&, std::optional&&); static Ref create(MediaSession&, Vector&&); diff --git a/Source/WebCore/Modules/mediasource/MediaSource.cpp b/Source/WebCore/Modules/mediasource/MediaSource.cpp index 10291bce97b3..82d59f918bc5 100644 --- a/Source/WebCore/Modules/mediasource/MediaSource.cpp +++ b/Source/WebCore/Modules/mediasource/MediaSource.cpp @@ -264,7 +264,6 @@ void MediaSource::setPrivateAndOpen(Ref&& mediaSourcePrivate ASSERT(!m_private); setPrivate(WTF::move(mediaSourcePrivate)); - protect(m_private)->setTimeFudgeFactor(currentTimeFudgeFactor()); open(); } @@ -405,13 +404,6 @@ ExceptionOr MediaSource::clearLiveSeekableRange() return { }; } -const MediaTime& MediaSource::currentTimeFudgeFactor() -{ - // Allow hasCurrentTime() to be off by as much as the length of two 24fps video frames - static NeverDestroyed fudgeFactor(2002, 24000); - return fudgeFactor; -} - bool MediaSource::contentTypeShouldGenerateTimestamps(const ContentType& contentType) { return contentType.containerType() == "audio/aac"_s || contentType.containerType() == "audio/mpeg"_s; diff --git a/Source/WebCore/Modules/mediasource/MediaSource.h b/Source/WebCore/Modules/mediasource/MediaSource.h index 238194945d61..90a305ba6c4e 100644 --- a/Source/WebCore/Modules/mediasource/MediaSource.h +++ b/Source/WebCore/Modules/mediasource/MediaSource.h @@ -136,7 +136,6 @@ class MediaSource ScriptExecutionContext* NODELETE scriptExecutionContext() const final; - static const MediaTime& NODELETE currentTimeFudgeFactor(); static bool contentTypeShouldGenerateTimestamps(const ContentType&); #if !RELEASE_LOG_DISABLED diff --git a/Source/WebCore/Modules/web-locks/WebLockManager.cpp b/Source/WebCore/Modules/web-locks/WebLockManager.cpp index 32bbd739d03f..69691abf3fa7 100644 --- a/Source/WebCore/Modules/web-locks/WebLockManager.cpp +++ b/Source/WebCore/Modules/web-locks/WebLockManager.cpp @@ -96,8 +96,8 @@ class WebLockManager::MainThreadBridge : public ThreadSafeRefCounted&&, Function&& lockStolenHandler); void releaseLock(WebLockIdentifier, const String& name); - void abortLockRequest(WebLockIdentifier, const String& name, CompletionHandler&&); - void query(CompletionHandler&&); + void abortLockRequest(WebLockIdentifier, const String& name, Function&&); + void query(Function&&); void clientIsGoingAway(); private: @@ -137,23 +137,23 @@ void WebLockManager::MainThreadBridge::releaseLock(WebLockIdentifier lockIdentif }); } -void WebLockManager::MainThreadBridge::abortLockRequest(WebLockIdentifier lockIdentifier, const String& name, CompletionHandler&& completionHandler) +void WebLockManager::MainThreadBridge::abortLockRequest(WebLockIdentifier lockIdentifier, const String& name, Function&& callback) { - callOnMainThread([this, protectedThis = Ref { *this }, lockIdentifier, name = crossThreadCopy(name), completionHandler = WTF::move(completionHandler)]() mutable { - WebLockRegistry::singleton().abortLockRequest(m_sessionID, m_clientOrigin, lockIdentifier, m_clientID, name, [clientID = m_clientID, completionHandler = WTF::move(completionHandler)](bool wasAborted) mutable { - ScriptExecutionContext::ensureOnContextThread(clientID, [completionHandler = WTF::move(completionHandler), wasAborted](auto&) mutable { - completionHandler(wasAborted); + callOnMainThread([this, protectedThis = Ref { *this }, lockIdentifier, name = crossThreadCopy(name), callback = WTF::move(callback)]() mutable { + WebLockRegistry::singleton().abortLockRequest(m_sessionID, m_clientOrigin, lockIdentifier, m_clientID, name, [clientID = m_clientID, callback = WTF::move(callback)](bool wasAborted) mutable { + ScriptExecutionContext::ensureOnContextThread(clientID, [callback = WTF::move(callback), wasAborted](auto&) mutable { + callback(wasAborted); }); }); }); } -void WebLockManager::MainThreadBridge::query(CompletionHandler&& completionHandler) +void WebLockManager::MainThreadBridge::query(Function&& callback) { - callOnMainThread([this, protectedThis = Ref { *this }, completionHandler = WTF::move(completionHandler)]() mutable { - WebLockRegistry::singleton().snapshot(m_sessionID, m_clientOrigin, [clientID = m_clientID, completionHandler = WTF::move(completionHandler)](Snapshot&& snapshot) mutable { - ScriptExecutionContext::ensureOnContextThread(clientID, [completionHandler = WTF::move(completionHandler), snapshot = crossThreadCopy(snapshot)](auto&) mutable { - completionHandler(WTF::move(snapshot)); + callOnMainThread([this, protectedThis = Ref { *this }, callback = WTF::move(callback)]() mutable { + WebLockRegistry::singleton().snapshot(m_sessionID, m_clientOrigin, [clientID = m_clientID, callback = WTF::move(callback)](Snapshot&& snapshot) mutable { + ScriptExecutionContext::ensureOnContextThread(clientID, [callback = WTF::move(callback), snapshot = crossThreadCopy(snapshot)](auto&) mutable { + callback(WTF::move(snapshot)); }); }); }); @@ -322,11 +322,17 @@ void WebLockManager::query(Ref&& promise) return; } - m_mainThreadBridge->query([weakThis = WeakPtr { *this }, promise = WTF::move(promise)](Snapshot&& snapshot) mutable { + auto promiseIdentifier = WebLockIdentifier::generate(); + m_queryPromises.add(promiseIdentifier, WTF::move(promise)); + m_mainThreadBridge->query([weakThis = WeakPtr { *this }, promiseIdentifier](Snapshot&& snapshot) mutable { RefPtr protectedThis = weakThis.get(); if (!protectedThis) return; + auto promise = protectedThis->m_queryPromises.take(promiseIdentifier); + if (!promise) + return; + queueTaskKeepingObjectAlive(*protectedThis, TaskSource::DOMManipulation, [promise = WTF::move(promise), snapshot = WTF::move(snapshot)](auto&) mutable { promise->resolve>(WTF::move(snapshot)); }); @@ -373,16 +379,16 @@ void WebLockManager::stop() void WebLockManager::clientIsGoingAway() { - if (m_pendingRequests.isEmpty() && m_releasePromises.isEmpty()) - return; - for (auto& request : m_pendingRequests.values()) request.removeSignalAlgorithm(); - // Reject all pending promises before clearing - for (Ref promise : m_releasePromises.values()) - promise->reject(ExceptionCode::AbortError, "Promise was rejected because the browsing context is going away"_s); - + // Reject all pending promises before clearing. + auto releasePromises = std::exchange(m_releasePromises, { }); + for (auto& promise : releasePromises.values()) + protect(promise)->reject(ExceptionCode::AbortError, "Promise was rejected because the browsing context is going away"_s); + auto queryPromises = std::exchange(m_queryPromises, { }); + for (auto& promise : queryPromises.values()) + protect(promise)->reject(ExceptionCode::AbortError, "Promise was rejected because the browsing context is going away"_s); m_pendingRequests.clear(); m_releasePromises.clear(); @@ -392,7 +398,7 @@ void WebLockManager::clientIsGoingAway() bool WebLockManager::virtualHasPendingActivity() const { - return !m_pendingRequests.isEmpty() || !m_releasePromises.isEmpty(); + return !m_pendingRequests.isEmpty() || !m_releasePromises.isEmpty() || !m_queryPromises.isEmpty(); } void WebLockManager::suspend(ReasonForSuspension reason) diff --git a/Source/WebCore/Modules/web-locks/WebLockManager.h b/Source/WebCore/Modules/web-locks/WebLockManager.h index 22a8294a448b..14d5fd99bb1a 100644 --- a/Source/WebCore/Modules/web-locks/WebLockManager.h +++ b/Source/WebCore/Modules/web-locks/WebLockManager.h @@ -83,6 +83,8 @@ class WebLockManager : public RefCounted, public ActiveDOMObject struct LockRequest; HashMap m_pendingRequests; + + HashMap> m_queryPromises; }; } // namespace WebCore diff --git a/Source/WebCore/Modules/webaudio/BiquadFilterNode.cpp b/Source/WebCore/Modules/webaudio/BiquadFilterNode.cpp index 501879620549..795387fc6b52 100644 --- a/Source/WebCore/Modules/webaudio/BiquadFilterNode.cpp +++ b/Source/WebCore/Modules/webaudio/BiquadFilterNode.cpp @@ -28,6 +28,8 @@ #if ENABLE(WEB_AUDIO) #include "BiquadFilterNode.h" + +#include "BaseAudioContext.h" #include "ExceptionOr.h" #include #include @@ -70,6 +72,11 @@ BiquadFilterType BiquadFilterNode::type() const void BiquadFilterNode::setType(BiquadFilterType type) { + ASSERT(isMainThread()); + + // Synchronize with any graph changes or changes to channel configuration since + // BiquadProcessor::setType() may iterate the processor's kernels via reset(). + Locker contextLocker { context().graphLock() }; protect(biquadProcessor())->setType(type); } diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsAudioData.cpp b/Source/WebCore/Modules/webcodecs/WebCodecsAudioData.cpp index 8f6e315aeed6..d9d428f08a7e 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsAudioData.cpp +++ b/Source/WebCore/Modules/webcodecs/WebCodecsAudioData.cpp @@ -65,6 +65,7 @@ WebCodecsAudioData::WebCodecsAudioData(ScriptExecutionContext& context) WebCodecsAudioData::WebCodecsAudioData(ScriptExecutionContext& context, WebCodecsAudioInternalData&& data) : ContextDestructionObserver(&context) , m_data(WTF::move(data)) + , m_memoryCost(m_data.memoryCost()) { } @@ -151,6 +152,7 @@ ExceptionOr> WebCodecsAudioData::clone(ScriptExecutionCo // https://www.w3.org/TR/webcodecs/#dom-audiodata-close void WebCodecsAudioData::close() { + m_memoryCost.store(0, std::memory_order_relaxed); m_data.audioData = nullptr; m_isDetached = true; diff --git a/Source/WebCore/Modules/webcodecs/WebCodecsAudioData.h b/Source/WebCore/Modules/webcodecs/WebCodecsAudioData.h index 97b6e9b3244c..46cd29c6295e 100644 --- a/Source/WebCore/Modules/webcodecs/WebCodecsAudioData.h +++ b/Source/WebCore/Modules/webcodecs/WebCodecsAudioData.h @@ -84,13 +84,16 @@ class WebCodecsAudioData : public RefCounted, public Context const WebCodecsAudioInternalData& data() const LIFETIME_BOUND { return m_data; } - size_t memoryCost() const { return m_data.memoryCost(); } + // memoryCost() may be called from a GC thread by the JS wrapper's visitChildren, so it must + // not touch m_data.audioData (which close() may concurrently null on the main thread). + size_t memoryCost() const { return m_memoryCost.load(std::memory_order_relaxed); } private: explicit WebCodecsAudioData(ScriptExecutionContext&); WebCodecsAudioData(ScriptExecutionContext&, WebCodecsAudioInternalData&&); WebCodecsAudioInternalData m_data; + std::atomic m_memoryCost { 0 }; bool m_isDetached { false }; }; diff --git a/Source/WebCore/bindings/js/JSDOMOperationReturningPromise.h b/Source/WebCore/bindings/js/JSDOMOperationReturningPromise.h index 78c4c04cfed0..ec257c3f3264 100644 --- a/Source/WebCore/bindings/js/JSDOMOperationReturningPromise.h +++ b/Source/WebCore/bindings/js/JSDOMOperationReturningPromise.h @@ -56,10 +56,10 @@ class IDLOperationReturningPromise { } using Operation2 = JSC::EncodedJSValue(JSC::JSGlobalObject*, JSC::CallFrame*, ClassParameter, Ref&&, Ref&&); - template + template static JSC::EncodedJSValue callReturningPromisePair(JSC::JSGlobalObject& lexicalGlobalObject, JSC::CallFrame& callFrame, const char* operationName) { - return callPromisePairFunction(lexicalGlobalObject, callFrame, [&operationName] (JSC::JSGlobalObject& lexicalGlobalObject, JSC::CallFrame& callFrame, Ref&& promise, Ref&& promise2) { + return callPromisePairFunction(lexicalGlobalObject, callFrame, [&operationName] (JSC::JSGlobalObject& lexicalGlobalObject, JSC::CallFrame& callFrame, Ref&& promise, Ref&& promise2) { auto* thisObject = IDLOperation::cast(lexicalGlobalObject, callFrame); if constexpr (shouldThrow != CastedThisErrorBehavior::Assert) { if (!thisObject) [[unlikely]] diff --git a/Source/WebCore/bindings/js/JSDOMPromiseDeferred.cpp b/Source/WebCore/bindings/js/JSDOMPromiseDeferred.cpp index d35cecd44b73..04a0e024c3c4 100644 --- a/Source/WebCore/bindings/js/JSDOMPromiseDeferred.cpp +++ b/Source/WebCore/bindings/js/JSDOMPromiseDeferred.cpp @@ -250,18 +250,30 @@ void DeferredPromise::reject(ExceptionCode ec, const String& message, RejectAsHa handleUncaughtException(scope, lexicalGlobalObject); } -void rejectPromiseWithExceptionIfAny(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject, JSPromise& promise, JSC::TopExceptionScope& catchScope) +static JSValue takeNonTerminationException(JSC::TopExceptionScope& catchScope) { - UNUSED_PARAM(lexicalGlobalObject); if (!catchScope.exception()) [[likely]] - return; + return { }; if (catchScope.vm().hasPendingTerminationException()) - return; + return { }; JSValue error = catchScope.exception()->value(); catchScope.clearException(); + return error; +} + +void rejectPromiseWithExceptionIfAny(JSC::JSGlobalObject&, JSDOMGlobalObject& globalObject, JSPromise& promise, JSC::TopExceptionScope& catchScope) +{ + if (auto error = takeNonTerminationException(catchScope)) [[unlikely]] + DeferredPromise::create(globalObject, promise)->reject(error); +} - DeferredPromise::create(globalObject, promise)->reject(error); +void rejectPromisesWithExceptionIfAny(JSC::JSGlobalObject&, JSDOMGlobalObject& globalObject, JSPromise& promise1, JSPromise& promise2, JSC::TopExceptionScope& catchScope) +{ + if (auto error = takeNonTerminationException(catchScope)) [[unlikely]] { + DeferredPromise::create(globalObject, promise1)->reject(error); + DeferredPromise::create(globalObject, promise2)->reject(error); + } } JSC::EncodedJSValue createRejectedPromiseWithTypeError(JSC::JSGlobalObject& lexicalGlobalObject, const String& errorMessage, RejectedPromiseWithTypeErrorCause cause) diff --git a/Source/WebCore/bindings/js/JSDOMPromiseDeferred.h b/Source/WebCore/bindings/js/JSDOMPromiseDeferred.h index 69ac0663fad3..d4b41e5aa3c4 100644 --- a/Source/WebCore/bindings/js/JSDOMPromiseDeferred.h +++ b/Source/WebCore/bindings/js/JSDOMPromiseDeferred.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -373,6 +374,7 @@ void fulfillPromiseWithArrayBufferFromSpan(Ref&&, std::span&&, Uint8Array*); void fulfillPromiseWithUint8ArrayFromSpan(Ref&&, std::span); WEBCORE_EXPORT void rejectPromiseWithExceptionIfAny(JSC::JSGlobalObject&, JSDOMGlobalObject&, JSC::JSPromise&, JSC::TopExceptionScope&); +WEBCORE_EXPORT void rejectPromisesWithExceptionIfAny(JSC::JSGlobalObject&, JSDOMGlobalObject&, JSC::JSPromise&, JSC::JSPromise&, JSC::TopExceptionScope&); enum class RejectedPromiseWithTypeErrorCause { NativeGetter, InvalidThis }; JSC::EncodedJSValue createRejectedPromiseWithTypeError(JSC::JSGlobalObject&, const String&, RejectedPromiseWithTypeErrorCause); @@ -422,26 +424,34 @@ inline JSC::JSValue callPromiseFunction(JSC::JSGlobalObject& lexicalGlobalObject using PromisePairFunction = JSC::EncodedJSValue(JSC::JSGlobalObject&, JSC::CallFrame&, Ref&&, Ref&&); -template +template inline JSC::EncodedJSValue callPromisePairFunction(JSC::JSGlobalObject& lexicalGlobalObject, JSC::CallFrame& callFrame, PromisePairFunctor functor) { JSC::VM& vm = JSC::getVM(&lexicalGlobalObject); auto catchScope = DECLARE_TOP_EXCEPTION_SCOPE(vm); auto& globalObject = downcast(lexicalGlobalObject); - auto* promise = JSC::JSPromise::create(vm, globalObject.promiseStructure()); - ASSERT(promise); + auto* promise1 = JSC::JSPromise::create(vm, globalObject.promiseStructure()); + ASSERT(promise1); auto* promise2 = JSC::JSPromise::create(vm, globalObject.promiseStructure()); ASSERT(promise2); - auto result = functor(globalObject, callFrame, DeferredPromise::create(globalObject, *promise, DeferredPromise::Mode::RetainPromiseOnResolve), DeferredPromise::create(globalObject, *promise2, DeferredPromise::Mode::RetainPromiseOnResolve)); + auto result = functor(globalObject, callFrame, DeferredPromise::create(globalObject, *promise1, DeferredPromise::Mode::RetainPromiseOnResolve), DeferredPromise::create(globalObject, *promise2, DeferredPromise::Mode::RetainPromiseOnResolve)); + + rejectPromisesWithExceptionIfAny(lexicalGlobalObject, globalObject, *promise1, *promise2, catchScope); - rejectPromiseWithExceptionIfAny(lexicalGlobalObject, globalObject, *promise, catchScope); - rejectPromiseWithExceptionIfAny(lexicalGlobalObject, globalObject, *promise2, catchScope); // FIXME: We could have error since any JS call can throw stack-overflow errors. // https://bugs.webkit.org/show_bug.cgi?id=203402 RETURN_IF_EXCEPTION(catchScope, JSC::encodedJSValue()); + // When the functor threw (e.g. during IDL argument conversion), its return + // value is an empty JSValue sentinel. Rebuild a valid result dictionary from + // the (now rejected) promises via convertDictionaryToJS. + if (!JSC::JSValue::decode(result)) [[unlikely]] { + result = JSC::JSValue::encode(convertDictionaryToJS(lexicalGlobalObject, globalObject, DictionaryType { DOMPromise::create(globalObject, *promise1), DOMPromise::create(globalObject, *promise2) })); + RETURN_IF_EXCEPTION(catchScope, JSC::encodedJSValue()); + } + return result; } diff --git a/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm b/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm index cc5689700415..a8f0269ae14c 100644 --- a/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm +++ b/Source/WebCore/bindings/scripts/CodeGeneratorJS.pm @@ -6369,6 +6369,11 @@ sub GenerateOperationTrampolineDefinition my @callFunctionTemplateArguments = (); push(@callFunctionTemplateArguments, $functionBodyName); + if ($operation->extendedAttributes->{ReturnsPromisePair}) { + my $dictClassName = GetDictionaryClassName($operation->type, $interface); + push(@callFunctionTemplateArguments, $dictClassName); + AddToImplIncludes("JSDOMPromise.h", $operation->extendedAttributes->{Conditional}); + } push(@callFunctionTemplateArguments, "CastedThisErrorBehavior::Assert") if ($operation->extendedAttributes->{PrivateIdentifier} and not $operation->extendedAttributes->{PublicIdentifier}); push(@$outputArray, "JSC_DEFINE_HOST_FUNCTION(${functionName}, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame))\n"); diff --git a/Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp b/Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp index a7d0cf0d81cc..b4acba80feee 100644 --- a/Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp +++ b/Source/WebCore/bindings/scripts/test/JS/JSTestObj.cpp @@ -65,6 +65,7 @@ #include "JSDOMIterator.h" #include "JSDOMOperation.h" #include "JSDOMOperationReturningPromise.h" +#include "JSDOMPromise.h" #include "JSDOMStringList.h" #include "JSDOMWindowBase.h" #include "JSDOMWrapperCache.h" @@ -7824,7 +7825,7 @@ static inline JSC::EncodedJSValue jsTestObjPrototypeFunction_returnsPromisePairB JSC_DEFINE_HOST_FUNCTION(jsTestObjPrototypeFunction_returnsPromisePair, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)) { - return IDLOperationReturningPromise::callReturningPromisePair(*lexicalGlobalObject, *callFrame, "returnsPromisePair"); + return IDLOperationReturningPromise::callReturningPromisePair(*lexicalGlobalObject, *callFrame, "returnsPromisePair"); } #if ENABLE(TEST_FEATURE) diff --git a/Source/WebCore/css/CSSFunctionRule.cpp b/Source/WebCore/css/CSSFunctionRule.cpp index 72f8f6eaec7f..85cc8f439fbe 100644 --- a/Source/WebCore/css/CSSFunctionRule.cpp +++ b/Source/WebCore/css/CSSFunctionRule.cpp @@ -50,9 +50,11 @@ auto CSSFunctionRule::getParameters() const -> Vector { return WTF::map(styleRuleFunction().parameters(), [](const auto& parameter) { RefPtr defaultValue = parameter.defaultValue; + StringBuilder type; + serializeCustomPropertySyntax(type, parameter.type); return FunctionParameter { .name = parameter.name, - .type = "*"_s, // FIXME: Implement. + .type = type.toString(), // FIXME: The spec says: // "The default value of the function parameter, or `null` if the argument does not have a default". // But WPT tests currently expect the value to missing/undefined, not `null`, so we are using @@ -64,7 +66,9 @@ auto CSSFunctionRule::getParameters() const -> Vector String CSSFunctionRule::returnType() const { - return "*"_s; + StringBuilder builder; + serializeCustomPropertySyntax(builder, styleRuleFunction().returnType()); + return builder.toString(); } String CSSFunctionRule::cssText() const @@ -78,14 +82,25 @@ String CSSFunctionRule::cssText() const for (auto& parameter : styleRuleFunction().parameters()) { builder.append(separator); serializeIdentifier(builder, parameter.name); - // FIXME: Serialize the type. + + if (!parameter.type.isUniversal()) { + builder.append(' '); + serializeCustomPropertySyntaxAsCSSType(builder, parameter.type); + } if (RefPtr defaultValue = parameter.defaultValue) builder.append(": "_s, defaultValue->serialize()); separator = ", "_s; } - builder.append(") { "_s); + builder.append(')'); + + if (auto& returnType = styleRuleFunction().returnType(); !returnType.isUniversal()) { + builder.append(" returns "_s); + serializeCustomPropertySyntaxAsCSSType(builder, returnType); + } + + builder.append(" { "_s); for (unsigned index = 0; index < length(); ++index) { Ref rule = *item(index); diff --git a/Source/WebCore/css/StyleProperties.cpp b/Source/WebCore/css/StyleProperties.cpp index 2d1877a3e852..0ade9334464c 100644 --- a/Source/WebCore/css/StyleProperties.cpp +++ b/Source/WebCore/css/StyleProperties.cpp @@ -25,6 +25,7 @@ #include "CSSColorValue.h" #include "CSSCustomPropertyValue.h" +#include "CSSMarkup.h" #include "CSSPrimitiveValue.h" #include "CSSPropertyInitialValues.h" #include "CSSPropertyNames.h" @@ -305,7 +306,7 @@ StringBuilder StyleProperties::asTextInternal(const CSS::SerializationContext& c result.append(' '); if (propertyID == CSSPropertyCustom) - result.append(downcast(*property.value()).name()); + serializeIdentifier(result, downcast(*property.value()).name()); else result.append(nameLiteral(propertyID)); diff --git a/Source/WebCore/css/parser/CSSCustomPropertySyntax.cpp b/Source/WebCore/css/parser/CSSCustomPropertySyntax.cpp index 0cd7f30ce86c..f8088f756f1e 100644 --- a/Source/WebCore/css/parser/CSSCustomPropertySyntax.cpp +++ b/Source/WebCore/css/parser/CSSCustomPropertySyntax.cpp @@ -25,12 +25,14 @@ #include "config.h" #include "CSSCustomPropertySyntax.h" +#include "CSSMarkup.h" #include "CSSParserIdioms.h" #include "CSSParserTokenRange.h" #include "CSSPropertyParserConsumer+Primitives.h" #include "CSSTokenizer.h" #include #include +#include namespace WebCore { @@ -205,4 +207,74 @@ auto CSSCustomPropertySyntax::typeForTypeName(StringView dataTypeName) -> Type return typeMap.get(dataTypeName, Type::Unknown); } +static ASCIILiteral typeNameForType(CSSCustomPropertySyntax::Type type) +{ + using Type = CSSCustomPropertySyntax::Type; + switch (type) { + case Type::Length: return "length"_s; + case Type::LengthPercentage: return "length-percentage"_s; + case Type::Percentage: return "percentage"_s; + case Type::Integer: return "integer"_s; + case Type::Number: return "number"_s; + case Type::Angle: return "angle"_s; + case Type::Time: return "time"_s; + case Type::Resolution: return "resolution"_s; + case Type::Color: return "color"_s; + case Type::Image: return "image"_s; + case Type::URL: return "url"_s; + case Type::CustomIdent: return "custom-ident"_s; + case Type::String: return "string"_s; + case Type::TransformFunction: return "transform-function"_s; + case Type::TransformList: return "transform-list"_s; + case Type::Ident: + case Type::Unknown: + break; + } + return { }; +} + +void serializeCustomPropertySyntax(StringBuilder& builder, const CSSCustomPropertySyntax& syntax) +{ + if (syntax.isUniversal()) { + builder.append('*'); + return; + } + + auto separator = ""_s; + for (auto& component : syntax.definition) { + builder.append(separator); + separator = " | "_s; + + // The Ident type is a literal custom identifier rather than a named data type. + if (component.type == CSSCustomPropertySyntax::Type::Ident) + serializeIdentifier(builder, component.ident); + else + builder.append('<', typeNameForType(component.type), '>'); + + switch (component.multiplier) { + case CSSCustomPropertySyntax::Multiplier::Single: + break; + case CSSCustomPropertySyntax::Multiplier::SpaceList: + builder.append('+'); + break; + case CSSCustomPropertySyntax::Multiplier::CommaList: + builder.append('#'); + break; + } + } +} + +void serializeCustomPropertySyntaxAsCSSType(StringBuilder& builder, const CSSCustomPropertySyntax& syntax) +{ + ASSERT(!syntax.isUniversal()); + // A single syntax component is written bare. Anything else must be wrapped in type(). + if (syntax.definition.size() == 1) { + serializeCustomPropertySyntax(builder, syntax); + return; + } + builder.append("type("_s); + serializeCustomPropertySyntax(builder, syntax); + builder.append(')'); +} + } diff --git a/Source/WebCore/css/parser/CSSCustomPropertySyntax.h b/Source/WebCore/css/parser/CSSCustomPropertySyntax.h index 2c3e43f69a9f..f4af5605b3d5 100644 --- a/Source/WebCore/css/parser/CSSCustomPropertySyntax.h +++ b/Source/WebCore/css/parser/CSSCustomPropertySyntax.h @@ -27,6 +27,10 @@ #include #include +namespace WTF { +class StringBuilder; +} + namespace WebCore { class CSSParserTokenRange; @@ -73,7 +77,6 @@ struct CSSCustomPropertySyntax { static std::optional parse(StringView); static std::optional consumeType(CSSParserTokenRange&); - static CSSCustomPropertySyntax universal() { return { }; } bool NODELETE containsUnknownType() const; @@ -83,4 +86,11 @@ struct CSSCustomPropertySyntax { static Type typeForTypeName(StringView); }; +// Serializes to the string form, e.g. "*", "", "+", " | ". +void serializeCustomPropertySyntax(StringBuilder&, const CSSCustomPropertySyntax&); + +// Serializes to the form used in @function preludes: a single component is bare, while a +// multi-component syntax is wrapped in type(). Must not be called on the universal syntax. +void serializeCustomPropertySyntaxAsCSSType(StringBuilder&, const CSSCustomPropertySyntax&); + } diff --git a/Source/WebCore/dom/DocumentStorageAccess.cpp b/Source/WebCore/dom/DocumentStorageAccess.cpp index 3ba8a7e499a5..54adf96d04c6 100644 --- a/Source/WebCore/dom/DocumentStorageAccess.cpp +++ b/Source/WebCore/dom/DocumentStorageAccess.cpp @@ -238,8 +238,8 @@ void DocumentStorageAccess::requestStorageAccess(Ref&& promise) if (!page->settings().storageAccessAPIPerPageScopeEnabled()) m_storageAccessScope = StorageAccessScope::PerFrame; - auto hasOrShouldIgnoreUserGesture = frame->requestSkipUserActivationCheckForStorageAccess(RegistrableDomain { document->url() }) || UserGestureIndicator::processingUserGesture() ? HasOrShouldIgnoreUserGesture::Yes : HasOrShouldIgnoreUserGesture::No; - page->chrome().client().requestStorageAccess(RegistrableDomain::uncheckedCreateFromHost(protect(document->securityOrigin())->host()), RegistrableDomain::uncheckedCreateFromHost(protect(document->topOrigin())->host()), *frame, m_storageAccessScope, hasOrShouldIgnoreUserGesture, [weakThis = WeakPtr { *this }, promise = WTF::move(promise)] (RequestStorageAccessResult result) mutable { + auto hasUserGestureOrNoUserGestureRequired = frame->requestSkipUserActivationCheckForStorageAccess(RegistrableDomain { document->url() }) || UserGestureIndicator::processingUserGesture() ? HasUserGestureOrNoUserGestureRequired::Yes : HasUserGestureOrNoUserGestureRequired::No; + page->chrome().client().requestStorageAccess(RegistrableDomain::uncheckedCreateFromHost(protect(document->securityOrigin())->host()), RegistrableDomain::uncheckedCreateFromHost(protect(document->topOrigin())->host()), *frame, m_storageAccessScope, hasUserGestureOrNoUserGestureRequired, [weakThis = WeakPtr { *this }, promise = WTF::move(promise), hasUserGestureOrNoUserGestureRequired] (RequestStorageAccessResult result) mutable { RefPtr protectedThis = weakThis.get(); if (!protectedThis) return; @@ -252,7 +252,8 @@ void DocumentStorageAccess::requestStorageAccess(Ref&& promise) shouldPreserveUserGesture = true; break; case StorageAccessWasGranted::No: - shouldPreserveUserGesture = result.promptWasShown == StorageAccessPromptWasShown::No; + const bool promptNotShownButMayHaveUserGesture = result.promptWasShown == StorageAccessPromptWasShown::No && hasUserGestureOrNoUserGestureRequired == HasUserGestureOrNoUserGestureRequired::Yes; + shouldPreserveUserGesture = promptNotShownButMayHaveUserGesture; } Ref document = protectedThis->m_document.get(); @@ -349,7 +350,7 @@ void DocumentStorageAccess::requestStorageAccessQuirk(RegistrableDomain&& reques auto topFrameDomain = RegistrableDomain(page->mainFrameURL()); RefPtr frame = document->frame(); - page->chrome().client().requestStorageAccess(WTF::move(requestingDomain), WTF::move(topFrameDomain), *frame, m_storageAccessScope, HasOrShouldIgnoreUserGesture::Yes, [weakThis = WeakPtr { *this }, completionHandler = WTF::move(completionHandler)] (RequestStorageAccessResult result) mutable { + page->chrome().client().requestStorageAccess(WTF::move(requestingDomain), WTF::move(topFrameDomain), *frame, m_storageAccessScope, HasUserGestureOrNoUserGestureRequired::Yes, [weakThis = WeakPtr { *this }, completionHandler = WTF::move(completionHandler)] (RequestStorageAccessResult result) mutable { RefPtr protectedThis = weakThis.get(); if (!protectedThis) return; diff --git a/Source/WebCore/dom/DocumentStorageAccess.h b/Source/WebCore/dom/DocumentStorageAccess.h index 65417557af76..6b46f8105710 100644 --- a/Source/WebCore/dom/DocumentStorageAccess.h +++ b/Source/WebCore/dom/DocumentStorageAccess.h @@ -41,7 +41,7 @@ enum class StorageAccessWasGranted : uint8_t { No, Yes, YesWithException }; enum class StorageAccessPromptWasShown : bool { No, Yes }; -enum class HasOrShouldIgnoreUserGesture : bool { No, Yes }; +enum class HasUserGestureOrNoUserGestureRequired : bool { No, Yes }; enum class StorageAccessScope : bool { PerFrame, diff --git a/Source/WebCore/history/HistoryItem.h b/Source/WebCore/history/HistoryItem.h index 835a3d4a199d..dde8deea1cee 100644 --- a/Source/WebCore/history/HistoryItem.h +++ b/Source/WebCore/history/HistoryItem.h @@ -227,6 +227,9 @@ class HistoryItem : public RefCountedAndCanMakeWeakPtr { WEBCORE_EXPORT void setWasCreatedByJSWithoutUserInteraction(bool); bool wasCreatedByJSWithoutUserInteraction() const { return m_wasCreatedByJSWithoutUserInteraction; } + void setIsInitialAboutBlank(bool isInitialAboutBlank) { m_isInitialAboutBlank = isInitialAboutBlank; } + bool isInitialAboutBlank() const { return m_isInitialAboutBlank; } + #if !LOG_DISABLED String logString() const; #endif @@ -260,6 +263,7 @@ class HistoryItem : public RefCountedAndCanMakeWeakPtr { bool m_lastVisitWasFailure { false }; bool m_wasRestoredFromSession { false }; bool m_wasCreatedByJSWithoutUserInteraction { false }; + bool m_isInitialAboutBlank { false }; bool m_shouldRestoreScrollPosition { true }; bool m_isTargetItem { false }; diff --git a/Source/WebCore/layout/integration/flex/LayoutIntegrationFlexLayout.cpp b/Source/WebCore/layout/integration/flex/LayoutIntegrationFlexLayout.cpp index 669200ca1b30..143145db0f6d 100644 --- a/Source/WebCore/layout/integration/flex/LayoutIntegrationFlexLayout.cpp +++ b/Source/WebCore/layout/integration/flex/LayoutIntegrationFlexLayout.cpp @@ -153,8 +153,8 @@ void FlexLayout::layout() auto isOrthogonal = flexContainerIsHorizontal != renderer->writingMode().isHorizontal(); auto borderBox = Layout::BoxGeometry::borderBoxRect(layoutState().geometryForBox(layoutBox)); - renderer->setWidth(LayoutUnit { }); - renderer->setHeight(LayoutUnit { }); + renderer->setBorderBoxWidth(LayoutUnit { }); + renderer->setBorderBoxHeight(LayoutUnit { }); // logical here means width and height constraints for the _content_ of the flex items not the flex items' own dimension inside the flex container. renderer->setOverridingBorderBoxLogicalWidth(isOrthogonal ? borderBox.height() : borderBox.width()); renderer->setOverridingBorderBoxLogicalHeight(isOrthogonal ? borderBox.width() : borderBox.height()); @@ -163,8 +163,8 @@ void FlexLayout::layout() renderer->layoutIfNeeded(); renderer->clearOverridingSize(); - renderer->setWidth(flexContainerIsHorizontal ? borderBox.width() : borderBox.height()); - renderer->setHeight(flexContainerIsHorizontal ? borderBox.height() : borderBox.width()); + renderer->setBorderBoxWidth(flexContainerIsHorizontal ? borderBox.width() : borderBox.height()); + renderer->setBorderBoxHeight(flexContainerIsHorizontal ? borderBox.height() : borderBox.width()); } }; relayoutFlexItems(); @@ -178,8 +178,8 @@ void FlexLayout::updateRenderers() auto& flexItemGeometry = layoutState().geometryForBox(layoutBox); auto borderBox = Layout::BoxGeometry::borderBoxRect(flexItemGeometry); renderer->setLocation(flexContainerIsHorizontal ? borderBox.topLeft() : borderBox.topLeft().transposedPoint()); - renderer->setWidth(flexContainerIsHorizontal ? borderBox.width() : borderBox.height()); - renderer->setHeight(flexContainerIsHorizontal ? borderBox.height() : borderBox.width()); + renderer->setBorderBoxWidth(flexContainerIsHorizontal ? borderBox.width() : borderBox.height()); + renderer->setBorderBoxHeight(flexContainerIsHorizontal ? borderBox.height() : borderBox.width()); renderer->setMarginStart(flexItemGeometry.marginStart()); renderer->setMarginEnd(flexItemGeometry.marginEnd()); diff --git a/Source/WebCore/layout/integration/grid/LayoutIntegrationGridLayout.cpp b/Source/WebCore/layout/integration/grid/LayoutIntegrationGridLayout.cpp index 4e084be3cb48..721feae82185 100644 --- a/Source/WebCore/layout/integration/grid/LayoutIntegrationGridLayout.cpp +++ b/Source/WebCore/layout/integration/grid/LayoutIntegrationGridLayout.cpp @@ -161,8 +161,8 @@ void GridLayout::updateGridItemRenderers() auto borderBoxRect = Layout::BoxGeometry::borderBoxRect(gridItemGeometry); renderer->setLocation(contentBoxOffset + borderBoxRect.topLeft()); - renderer->setWidth(borderBoxRect.width()); - renderer->setHeight(borderBoxRect.height()); + renderer->setBorderBoxWidth(borderBoxRect.width()); + renderer->setBorderBoxHeight(borderBoxRect.height()); renderer->setMarginBefore(gridItemGeometry.marginBefore()); renderer->setMarginAfter(gridItemGeometry.marginAfter()); @@ -182,9 +182,9 @@ void GridLayout::updateFormattingContextRootRenderer(const Layout::GridLayoutCon auto& rowSizes = usedTrackSizes.rowSizes; auto usedRowGutter = Layout::GridFormattingContext::usedGapValue(renderGrid->style().rowGap()); auto blockContentSize = std::reduce(rowSizes.begin(), rowSizes.end()) + Layout::GridLayoutUtils::totalGuttersSize(rowSizes.size(), usedRowGutter); - renderGrid->setHeight(blockContentSize + renderGrid->borderAndPaddingLogicalHeight()); + renderGrid->setBorderBoxHeight(blockContentSize + renderGrid->borderAndPaddingLogicalHeight()); } else - renderGrid->setHeight(layoutConstraints.blockAxis.availableSpace() + renderGrid->borderAndPaddingLogicalHeight()); + renderGrid->setBorderBoxHeight(layoutConstraints.blockAxis.availableSpace() + renderGrid->borderAndPaddingLogicalHeight()); for (CheckedRef layoutBox : formattingContextBoxes(gridBox())) orderIteratorPopulator.collectChild(CheckedRef { downcast(*layoutBox->rendererForIntegration()) }); diff --git a/Source/WebCore/loader/HistoryController.cpp b/Source/WebCore/loader/HistoryController.cpp index 47a8284c3b77..cca0ca00923d 100644 --- a/Source/WebCore/loader/HistoryController.cpp +++ b/Source/WebCore/loader/HistoryController.cpp @@ -886,6 +886,8 @@ void HistoryController::initializeItem(HistoryItem& item, RefPtr item.setShouldOpenExternalURLsPolicy(documentLoader->shouldOpenExternalURLsPolicyToPropagate()); + item.setIsInitialAboutBlank(documentLoader->isInitialAboutBlank()); + // Save form state if this is a POST item.setFormInfoFromRequest(documentLoader->request()); } diff --git a/Source/WebCore/page/ChromeClient.h b/Source/WebCore/page/ChromeClient.h index ed67709d049b..8bd12ba1ab6a 100644 --- a/Source/WebCore/page/ChromeClient.h +++ b/Source/WebCore/page/ChromeClient.h @@ -683,7 +683,7 @@ class ChromeClient { virtual RefPtr createIconForFiles(const Vector& /* filenames */) = 0; virtual void hasStorageAccess(RegistrableDomain&& /*subFrameDomain*/, RegistrableDomain&& /*topFrameDomain*/, LocalFrame&, CompletionHandler&& completionHandler) { completionHandler(false); } - virtual void requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, LocalFrame&, StorageAccessScope scope, HasOrShouldIgnoreUserGesture, CompletionHandler&& completionHandler) { completionHandler({ StorageAccessWasGranted::No, StorageAccessPromptWasShown::No, scope, WTF::move(topFrameDomain), WTF::move(subFrameDomain) }); } + virtual void requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, LocalFrame&, StorageAccessScope scope, HasUserGestureOrNoUserGestureRequired, CompletionHandler&& completionHandler) { completionHandler({ StorageAccessWasGranted::No, StorageAccessPromptWasShown::No, scope, WTF::move(topFrameDomain), WTF::move(subFrameDomain) }); } virtual bool hasPageLevelStorageAccess(const RegistrableDomain& /*topLevelDomain*/, const RegistrableDomain& /*resourceDomain*/) const { return false; } virtual void setLoginStatus(RegistrableDomain&&, IsLoggedIn, CompletionHandler&&) { } diff --git a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp index 9e7c72a7e6ff..5656e3e7cc4a 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp +++ b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp @@ -911,7 +911,7 @@ std::optional ContentSecurityPolicy::getCurrentCodePosition() void ContentSecurityPolicy::reportViolation(const ContentSecurityPolicyDirective& violatedDirective, const String& blockedURL, const String& consoleMessage, JSC::JSGlobalObject* state, StringView sourceContent) const { // FIXME: Extract source file, and position from JSC::ExecState. - return reportViolation(violatedDirective.nameForReporting().convertToASCIILowercase(), violatedDirective.directiveList(), blockedURL, consoleMessage, String(), sourceContent.left(40), { }, state); + return reportViolation(violatedDirective.nameForReporting(), violatedDirective.directiveList(), blockedURL, consoleMessage, String(), sourceContent.left(40), { }, state); } void ContentSecurityPolicy::reportViolation(const String& violatedDirective, const ContentSecurityPolicyDirectiveList& violatedDirectiveList, const String& blockedURL, const String& consoleMessage, JSC::JSGlobalObject* state) const @@ -922,7 +922,7 @@ void ContentSecurityPolicy::reportViolation(const String& violatedDirective, con void ContentSecurityPolicy::reportViolation(const ContentSecurityPolicyDirective& violatedDirective, const String& blockedURL, const String& consoleMessage, const String& sourceURL, StringView sourceContent, std::optional&& sourcePosition, const URL& preRedirectURL, JSC::JSGlobalObject* state, Element* element) const { - return reportViolation(violatedDirective.nameForReporting().convertToASCIILowercase(), violatedDirective.directiveList(), blockedURL, consoleMessage, sourceURL, sourceContent.left(40), WTF::move(sourcePosition), state, preRedirectURL, element); + return reportViolation(violatedDirective.nameForReporting(), violatedDirective.directiveList(), blockedURL, consoleMessage, sourceURL, sourceContent.left(40), WTF::move(sourcePosition), state, preRedirectURL, element); } void ContentSecurityPolicy::reportViolation(const String& effectiveViolatedDirective, const ContentSecurityPolicyDirectiveList& violatedDirectiveList, const String& blockedURLString, const String& consoleMessage, const String& sourceURL, StringView sourceContent, std::optional&& maybeSourcePosition, JSC::JSGlobalObject* state, const URL& preRedirectURL, Element* element) const diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp index b2ead0efba50..806ff2b00542 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp +++ b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp @@ -487,20 +487,20 @@ void ContentSecurityPolicyDirectiveList::parse(const String& policy, ContentSecu if (auto directive = parseDirective(std::span { directiveBegin, buffer.position() })) { ASSERT(!directive->name.isEmpty()); if (policyFrom == ContentSecurityPolicy::PolicyFrom::Inherited) { - if (equalIgnoringASCIICase(directive->name, ContentSecurityPolicyDirectiveNames::upgradeInsecureRequests) - || equalIgnoringASCIICase(directive->name, ContentSecurityPolicyDirectiveNames::sandbox)) + if (directive->name == ContentSecurityPolicyDirectiveNames::upgradeInsecureRequests + || directive->name == ContentSecurityPolicyDirectiveNames::sandbox) continue; } else if (policyFrom == ContentSecurityPolicy::PolicyFrom::HTTPEquivMeta) { - if (equalIgnoringASCIICase(directive->name, ContentSecurityPolicyDirectiveNames::sandbox) - || equalIgnoringASCIICase(directive->name, ContentSecurityPolicyDirectiveNames::reportURI) - || equalIgnoringASCIICase(directive->name, ContentSecurityPolicyDirectiveNames::frameAncestors)) { + if (directive->name == ContentSecurityPolicyDirectiveNames::sandbox + || directive->name == ContentSecurityPolicyDirectiveNames::reportURI + || directive->name == ContentSecurityPolicyDirectiveNames::frameAncestors) { m_policy->reportInvalidDirectiveInHTTPEquivMeta(directive->name); continue; } } else if (policyFrom == ContentSecurityPolicy::PolicyFrom::InheritedForPluginDocument) { - if (!equalIgnoringASCIICase(directive->name, ContentSecurityPolicyDirectiveNames::pluginTypes) - && !equalIgnoringASCIICase(directive->name, ContentSecurityPolicyDirectiveNames::reportURI) - && !equalIgnoringASCIICase(directive->name, ContentSecurityPolicyDirectiveNames::reportTo)) + if (directive->name != ContentSecurityPolicyDirectiveNames::pluginTypes + && directive->name != ContentSecurityPolicyDirectiveNames::reportURI + && directive->name != ContentSecurityPolicyDirectiveNames::reportTo) continue; } addDirective(WTF::move(*directive)); @@ -535,7 +535,8 @@ template auto ContentSecurityPolicyDirectiveList::parseD return std::nullopt; } - String name { nameBegin.first(buffer.position() - nameBegin.data()) }; + // Lowercase the directive name eagerly so downstream code can use case-sensitive comparisons. + String name = StringView { nameBegin.first(buffer.position() - nameBegin.data()) }.convertToASCIILowercase(); if (buffer.atEnd()) return ParsedDirective { WTF::move(name), { } }; @@ -689,75 +690,75 @@ void ContentSecurityPolicyDirectiveList::addDirective(ParsedDirective&& directiv { ASSERT(!directive.name.isEmpty()); - if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::defaultSrc)) { + if (directive.name == ContentSecurityPolicyDirectiveNames::defaultSrc) { setCSPDirective(WTF::move(directive), m_defaultSrc); m_policy->addHashAlgorithmsForInlineScripts(m_defaultSrc->hashAlgorithmsUsed()); m_policy->addHashAlgorithmsForInlineStylesheets(m_defaultSrc->hashAlgorithmsUsed()); - } else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::scriptSrc)) { + } else if (directive.name == ContentSecurityPolicyDirectiveNames::scriptSrc) { setCSPDirective(WTF::move(directive), m_scriptSrc); m_policy->addHashAlgorithmsForInlineScripts(m_scriptSrc->hashAlgorithmsUsed()); - } else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::scriptSrcElem)) { + } else if (directive.name == ContentSecurityPolicyDirectiveNames::scriptSrcElem) { setCSPDirective(WTF::move(directive), m_scriptSrcElem); m_policy->addHashAlgorithmsForInlineScripts(m_scriptSrcElem->hashAlgorithmsUsed()); - } else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::scriptSrcAttr)) { + } else if (directive.name == ContentSecurityPolicyDirectiveNames::scriptSrcAttr) { setCSPDirective(WTF::move(directive), m_scriptSrcAttr); m_policy->addHashAlgorithmsForInlineScripts(m_scriptSrcAttr->hashAlgorithmsUsed()); - } else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::styleSrc)) { + } else if (directive.name == ContentSecurityPolicyDirectiveNames::styleSrc) { setCSPDirective(WTF::move(directive), m_styleSrc); m_policy->addHashAlgorithmsForInlineStylesheets(m_styleSrc->hashAlgorithmsUsed()); - } else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::styleSrcElem)) { + } else if (directive.name == ContentSecurityPolicyDirectiveNames::styleSrcElem) { setCSPDirective(WTF::move(directive), m_styleSrcElem); m_policy->addHashAlgorithmsForInlineStylesheets(m_styleSrcElem->hashAlgorithmsUsed()); - } else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::styleSrcAttr)) { + } else if (directive.name == ContentSecurityPolicyDirectiveNames::styleSrcAttr) { setCSPDirective(WTF::move(directive), m_styleSrcAttr); m_policy->addHashAlgorithmsForInlineStylesheets(m_styleSrcAttr->hashAlgorithmsUsed()); - } else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::objectSrc)) + } else if (directive.name == ContentSecurityPolicyDirectiveNames::objectSrc) setCSPDirective(WTF::move(directive), m_objectSrc); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::workerSrc)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::workerSrc) setCSPDirective(WTF::move(directive), m_workerSrc); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::frameSrc)) { + else if (directive.name == ContentSecurityPolicyDirectiveNames::frameSrc) { // FIXME: Log to console "The frame-src directive is deprecated. Use the child-src directive instead." // See . setCSPDirective(WTF::move(directive), m_frameSrc); - } else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::imgSrc)) + } else if (directive.name == ContentSecurityPolicyDirectiveNames::imgSrc) setCSPDirective(WTF::move(directive), m_imgSrc); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::fontSrc)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::fontSrc) setCSPDirective(WTF::move(directive), m_fontSrc); #if ENABLE(APPLICATION_MANIFEST) - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::manifestSrc)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::manifestSrc) setCSPDirective(WTF::move(directive), m_manifestSrc); #endif - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::mediaSrc)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::mediaSrc) setCSPDirective(WTF::move(directive), m_mediaSrc); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::connectSrc)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::connectSrc) setCSPDirective(WTF::move(directive), m_connectSrc); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::childSrc)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::childSrc) setCSPDirective(WTF::move(directive), m_childSrc); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::formAction)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::formAction) setCSPDirective(WTF::move(directive), m_formAction); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::baseURI)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::baseURI) setCSPDirective(WTF::move(directive), m_baseURI); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::frameAncestors)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::frameAncestors) setCSPDirective(WTF::move(directive), m_frameAncestors); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::pluginTypes)) { + else if (directive.name == ContentSecurityPolicyDirectiveNames::pluginTypes) { auto name = directive.name; setCSPDirective(WTF::move(directive), m_pluginTypes); m_policy->reportDeprecatedDirectiveToConsole(name); - } else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::prefetchSrc)) + } else if (directive.name == ContentSecurityPolicyDirectiveNames::prefetchSrc) setCSPDirective(WTF::move(directive), m_prefetchSrc); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::sandbox)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::sandbox) applySandboxPolicy(WTF::move(directive)); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::reportTo)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::reportTo) parseReportTo(WTF::move(directive)); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::reportURI)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::reportURI) parseReportURI(WTF::move(directive)); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::upgradeInsecureRequests)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::upgradeInsecureRequests) setUpgradeInsecureRequests(WTF::move(directive)); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::blockAllMixedContent)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::blockAllMixedContent) setBlockAllMixedContentEnabled(WTF::move(directive)); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::trustedTypes)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::trustedTypes) setCSPDirective(WTF::move(directive), m_trustedTypes); - else if (equalIgnoringASCIICase(directive.name, ContentSecurityPolicyDirectiveNames::requireTrustedTypesFor)) + else if (directive.name == ContentSecurityPolicyDirectiveNames::requireTrustedTypesFor) parseRequireTrustedTypesFor(WTF::move(directive)); else m_policy->reportUnsupportedDirective(WTF::move(directive.name)); diff --git a/Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp b/Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp index 142083d1e8c1..2f84719ec286 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp +++ b/Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp @@ -43,6 +43,7 @@ namespace WebCore { static bool NODELETE isCSPDirectiveName(StringView name) { + // Called with a source-expression token, not a parsed directive name, so it is not lowercased. return equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::baseURI) || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::connectSrc) || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::defaultSrc) @@ -120,9 +121,9 @@ bool ContentSecurityPolicySourceList::isProtocolAllowedByStar(const URL& url) co bool isAllowed = url.protocolIsInHTTPFamily() || url.protocolIs("ws"_s) || url.protocolIs("wss"_s) || url.protocolIs(m_policy->selfProtocol()); // Also not allowed by the Content Security Policy Level 3 spec., we allow a data URL to match // "img-src *" and either a data URL or blob URL to match "media-src *" for web compatibility. - if (equalIgnoringASCIICase(m_directiveName, ContentSecurityPolicyDirectiveNames::imgSrc)) + if (m_directiveName == ContentSecurityPolicyDirectiveNames::imgSrc) isAllowed |= url.protocolIsData(); - else if (equalIgnoringASCIICase(m_directiveName, ContentSecurityPolicyDirectiveNames::mediaSrc)) + else if (m_directiveName == ContentSecurityPolicyDirectiveNames::mediaSrc) isAllowed |= url.protocolIsData() || url.protocolIsBlob(); return isAllowed; } diff --git a/Source/WebCore/platform/audio/cocoa/AudioEncoderCocoa.cpp b/Source/WebCore/platform/audio/cocoa/AudioEncoderCocoa.cpp index f8c04443535b..fa49a0e92d4e 100644 --- a/Source/WebCore/platform/audio/cocoa/AudioEncoderCocoa.cpp +++ b/Source/WebCore/platform/audio/cocoa/AudioEncoderCocoa.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Apple Inc. All rights reserved. + * Copyright (C) 2024-2026 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -314,7 +314,7 @@ Ref InternalAudioEncoderCocoa::encode(AudioEncoder: if (*asbd != m_inputDescription) { if (RefPtr converter = std::exchange(m_converter, { })) - converter->finish(); + converter->finish()->whenSettled(queueSingleton(), [protectedThis = Ref { *this }] { }); m_inputDescription = *asbd; AudioSampleBufferConverter::Options options = { diff --git a/Source/WebCore/platform/audio/ios/MediaDeviceRoute.h b/Source/WebCore/platform/audio/ios/MediaDeviceRoute.h index e08f7c16faa1..639b033f5272 100644 --- a/Source/WebCore/platform/audio/ios/MediaDeviceRoute.h +++ b/Source/WebCore/platform/audio/ios/MediaDeviceRoute.h @@ -39,7 +39,7 @@ #include #include -OBJC_CLASS WebMediaSourceObserver; +OBJC_CLASS WebPlaybackControlObserver; namespace WebCore { @@ -91,9 +91,9 @@ class MediaDeviceRouteClient : public AbstractRefCountedAndCanMakeWeakPtr playbackError() const; + std::optional error() const; Vector audioOptions() const; - MediaTime currentPlaybackPosition() const; + MediaTime playbackPosition() const; bool playing() const; float playbackSpeed() const; float scanSpeed() const; bool muted() const; float volume() const; - void setCurrentPlaybackPosition(MediaTime); + void setPlaybackPosition(MediaTime); void setPlaying(bool); void setPlaybackSpeed(float); void setScanSpeed(float); @@ -141,7 +141,7 @@ class MediaDeviceRoute final : public RefCountedAndCanMakeWeakPtr m_platformRoute; - RetainPtr m_mediaSourceObserver; + RetainPtr m_playbackControlObserver; WeakPtr m_client; #if HAVE(AVROUTING_FRAMEWORK) RetainPtr m_routeSession; diff --git a/Source/WebCore/platform/audio/ios/MediaDeviceRoute.mm b/Source/WebCore/platform/audio/ios/MediaDeviceRoute.mm index 13230fa39b2d..16bab672f212 100644 --- a/Source/WebCore/platform/audio/ios/MediaDeviceRoute.mm +++ b/Source/WebCore/platform/audio/ios/MediaDeviceRoute.mm @@ -36,14 +36,16 @@ #import -#define FOR_EACH_COMMON_READONLY_KEY_PATH(Macro) \ +#define FOR_EACH_READONLY_KEY_PATH(Macro) \ Macro(timeRange, TimeRange, MediaTimeRange) \ Macro(ready, Ready, bool) \ Macro(buffering, Buffering, bool) \ Macro(audioOptions, AudioOptions, Vector) \ + Macro(error, Error, std::optional) \ + Macro(playbackPosition, PlaybackPosition, MediaTime) \ \ -#define FOR_EACH_COMMON_READWRITE_KEY_PATH(Macro) \ +#define FOR_EACH_READWRITE_KEY_PATH(Macro) \ Macro(playing, Playing, bool) \ Macro(playbackSpeed, PlaybackSpeed, float) \ Macro(scanSpeed, ScanSpeed, float) \ @@ -51,43 +53,16 @@ Macro(volume, Volume, float) \ \ -#define FOR_EACH_COMMON_KEY_PATH(Macro) \ - FOR_EACH_COMMON_READONLY_KEY_PATH(Macro) \ - FOR_EACH_COMMON_READWRITE_KEY_PATH(Macro) \ +#define FOR_EACH_KEY_PATH(Macro) \ + FOR_EACH_READONLY_KEY_PATH(Macro) \ + FOR_EACH_READWRITE_KEY_PATH(Macro) \ \ -#define FOR_EACH_MEDIA_SOURCE_READONLY_KEY_PATH(Macro) \ - FOR_EACH_COMMON_READONLY_KEY_PATH(Macro) \ - Macro(playbackError, PlaybackError, std::optional) \ -\ - -#define FOR_EACH_MEDIA_SOURCE_READWRITE_KEY_PATH(Macro) \ - FOR_EACH_COMMON_READWRITE_KEY_PATH(Macro) \ - Macro(currentPlaybackPosition, CurrentPlaybackPosition, MediaTime) \ -\ - -#define FOR_EACH_MEDIA_SOURCE_KEY_PATH(Macro) \ - FOR_EACH_MEDIA_SOURCE_READONLY_KEY_PATH(Macro) \ - FOR_EACH_MEDIA_SOURCE_READWRITE_KEY_PATH(Macro) \ -\ - -#define FOR_EACH_PLAYBACK_CONTROL_KEY_PATH(Macro) \ - FOR_EACH_COMMON_KEY_PATH(Macro) \ -\ - -#define ADD_MEDIA_SOURCE_OBSERVER(KeyPath, SetterSuffix, Type) \ - [_mediaSource addObserver:self forKeyPath:@#KeyPath options:NSKeyValueObservingOptionInitial context:WebMediaSourceObserverContext]; \ -\ - -#define REMOVE_MEDIA_SOURCE_OBSERVER(KeyPath, SetterSuffix, Type) \ - [_mediaSource removeObserver:self forKeyPath:@#KeyPath context:WebMediaSourceObserverContext]; \ -\ - -#define ADD_PLAYBACK_CONTROL_OBSERVER(KeyPath, SetterSuffix, Type) \ +#define ADD_OBSERVER(KeyPath, SetterSuffix, Type) \ [_playbackControl addObserver:self forKeyPath:@#KeyPath options:NSKeyValueObservingOptionInitial context:WebPlaybackControlObserverContext]; \ \ -#define REMOVE_PLAYBACK_CONTROL_OBSERVER(KeyPath, SetterSuffix, Type) \ +#define REMOVE_OBSERVER(KeyPath, SetterSuffix, Type) \ [_playbackControl removeObserver:self forKeyPath:@#KeyPath context:WebPlaybackControlObserverContext]; \ \ @@ -108,37 +83,30 @@ #define DEFINE_GETTER(KeyPath, SetterSuffix, Type) \ Type MediaDeviceRoute::KeyPath() const \ { \ - if (RetainPtr playbackControl = [m_mediaSourceObserver playbackControl]) \ - return convert(playbackControl.get().KeyPath); \ - return convert([m_mediaSourceObserver mediaSource].KeyPath); \ + return convert([m_playbackControlObserver playbackControl].KeyPath); \ } \ \ #define DEFINE_SETTER(KeyPath, SetterSuffix, Type) \ void MediaDeviceRoute::set##SetterSuffix(Type KeyPath) \ { \ - if (RetainPtr playbackControl = [m_mediaSourceObserver playbackControl]) \ - return [playbackControl set##SetterSuffix:convert(WTF::move(KeyPath))]; \ - [[m_mediaSourceObserver mediaSource] set##SetterSuffix:convert(WTF::move(KeyPath))]; \ + [[m_playbackControlObserver playbackControl] set##SetterSuffix:convert(WTF::move(KeyPath))]; \ } \ \ NS_ASSUME_NONNULL_BEGIN -static void* WebMediaSourceObserverContext = &WebMediaSourceObserverContext; static void* WebPlaybackControlObserverContext = &WebPlaybackControlObserverContext; -@interface WebMediaSourceObserver : NSObject +@interface WebPlaybackControlObserver : NSObject + (instancetype)new NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithRoute:(WebCore::MediaDeviceRoute&)route NS_DESIGNATED_INITIALIZER; -@property (nonatomic, nullable, strong) AVMediaSource *mediaSource; @property (nonatomic, nullable, strong) AVPlaybackControl *playbackControl; @end -@implementation WebMediaSourceObserver { +@implementation WebPlaybackControlObserver { WeakPtr _route; - RetainPtr _mediaSource; RetainPtr _playbackControl; } @@ -151,23 +119,6 @@ - (instancetype)initWithRoute:(WebCore::MediaDeviceRoute&)route return self; } -- (AVMediaSource * _Nullable)mediaSource -{ - return _mediaSource.get(); -} - -- (void)setMediaSource:(AVMediaSource * _Nullable)mediaSource -{ - if (mediaSource) - self.playbackControl = nil; - - FOR_EACH_MEDIA_SOURCE_KEY_PATH(REMOVE_MEDIA_SOURCE_OBSERVER) - - _mediaSource = mediaSource; - - FOR_EACH_MEDIA_SOURCE_KEY_PATH(ADD_MEDIA_SOURCE_OBSERVER) -} - - (AVPlaybackControl * _Nullable)playbackControl { return _playbackControl.get(); @@ -175,60 +126,29 @@ - (AVPlaybackControl * _Nullable)playbackControl - (void)setPlaybackControl:(AVPlaybackControl * _Nullable)playbackControl { - if (playbackControl) - self.mediaSource = nil; - - FOR_EACH_PLAYBACK_CONTROL_KEY_PATH(REMOVE_PLAYBACK_CONTROL_OBSERVER) - REMOVE_PLAYBACK_CONTROL_OBSERVER(error, Error, std::optional) - REMOVE_PLAYBACK_CONTROL_OBSERVER(playbackPosition, PlaybackPosition, AVPlaybackUserInterfacePlaybackPosition *) + FOR_EACH_KEY_PATH(REMOVE_OBSERVER) _playbackControl = playbackControl; - FOR_EACH_PLAYBACK_CONTROL_KEY_PATH(ADD_PLAYBACK_CONTROL_OBSERVER) - ADD_PLAYBACK_CONTROL_OBSERVER(error, Error, std::optional) - ADD_PLAYBACK_CONTROL_OBSERVER(playbackPosition, PlaybackPosition, AVPlaybackUserInterfacePlaybackPosition *) + FOR_EACH_KEY_PATH(ADD_OBSERVER) } - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(nullable void*)context { - if (context != WebMediaSourceObserverContext && context != WebPlaybackControlObserverContext) { + if (context != WebPlaybackControlObserverContext) { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; return; } dispatch_async(mainDispatchQueueSingleton(), ^{ - if (context == WebMediaSourceObserverContext) { - FOR_EACH_MEDIA_SOURCE_KEY_PATH(OBSERVE_VALUE) - ASSERT_NOT_REACHED(); - return; - } - - if ([keyPath isEqualToString:@"error"]) { - if (RefPtr route = _route.get()) { - if (RefPtr client = route->client()) - client->playbackErrorDidChange(*route); - } - return; - } - if ([keyPath isEqualToString:@"playbackPosition"]) { - if (RefPtr route = _route.get()) { - if (RefPtr client = route->client()) - client->currentPlaybackPositionDidChange(*route); - } - return; - } - - FOR_EACH_PLAYBACK_CONTROL_KEY_PATH(OBSERVE_VALUE) + FOR_EACH_KEY_PATH(OBSERVE_VALUE) ASSERT_NOT_REACHED(); }); } - (void)dealloc { - FOR_EACH_MEDIA_SOURCE_KEY_PATH(REMOVE_MEDIA_SOURCE_OBSERVER) - FOR_EACH_PLAYBACK_CONTROL_KEY_PATH(REMOVE_PLAYBACK_CONTROL_OBSERVER) - REMOVE_PLAYBACK_CONTROL_OBSERVER(error, Error, std::optional) - REMOVE_PLAYBACK_CONTROL_OBSERVER(playbackPosition, PlaybackPosition, AVPlaybackUserInterfacePlaybackPosition *) + FOR_EACH_KEY_PATH(REMOVE_OBSERVER) [super dealloc]; } @@ -260,6 +180,11 @@ static MediaTime convert(CMTime time) return PAL::toMediaTime(time); } +static MediaTime convert(AVPlaybackUserInterfacePlaybackPosition *playbackPosition) +{ + return convert(playbackPosition.position); +} + static MediaTimeRange convert(CMTimeRange timeRange) { MediaTime start = PAL::toMediaTime(timeRange.start); @@ -301,7 +226,7 @@ static MediaTimeRange convert(CMTimeRange timeRange) MediaDeviceRoute::MediaDeviceRoute(WebMediaDevicePlatformRoute *platformRoute) : m_identifier { WTF::UUID::createVersion4() } , m_platformRoute { platformRoute } - , m_mediaSourceObserver { adoptNS([[WebMediaSourceObserver alloc] initWithRoute:*this]) } + , m_playbackControlObserver { adoptNS([[WebPlaybackControlObserver alloc] initWithRoute:*this]) } { } @@ -315,29 +240,10 @@ static MediaTimeRange convert(CMTimeRange timeRange) return m_platformRoute.get(); } -std::optional MediaDeviceRoute::playbackError() const +void MediaDeviceRoute::setPlaybackPosition(MediaTime playbackPosition) { - if (RetainPtr playbackControl = [m_mediaSourceObserver playbackControl]) - return convert(playbackControl.get().error); - return convert([m_mediaSourceObserver mediaSource].playbackError); -} - -MediaTime MediaDeviceRoute::currentPlaybackPosition() const -{ - if (RetainPtr playbackControl = [m_mediaSourceObserver playbackControl]) - return convert(playbackControl.get().playbackPosition.position); - return convert([m_mediaSourceObserver mediaSource].currentPlaybackPosition); -} - -void MediaDeviceRoute::setCurrentPlaybackPosition(MediaTime currentPlaybackPosition) -{ - if (RetainPtr playbackControl = [m_mediaSourceObserver playbackControl]) { - // FIXME: We should introduce a proper seek-with-tolerance function on MediaDeviceRoute rather than assuming a zero tolerance here. - [playbackControl seekToPosition:convert(WTF::move(currentPlaybackPosition)) tolerance:PAL::kCMTimeZero]; - return; - } - - [m_mediaSourceObserver mediaSource].currentPlaybackPosition = convert(WTF::move(currentPlaybackPosition)); + // FIXME: We should introduce a proper seek-with-tolerance function on MediaDeviceRoute rather than assuming a zero tolerance here. + [[m_playbackControlObserver playbackControl] seekToPosition:convert(WTF::move(playbackPosition)) tolerance:PAL::kCMTimeZero]; } MediaDeviceRoute::~MediaDeviceRoute() @@ -347,22 +253,16 @@ static MediaTimeRange convert(CMTimeRange timeRange) #endif } -FOR_EACH_COMMON_KEY_PATH(DEFINE_GETTER) -FOR_EACH_COMMON_READWRITE_KEY_PATH(DEFINE_SETTER) +FOR_EACH_KEY_PATH(DEFINE_GETTER) +FOR_EACH_READWRITE_KEY_PATH(DEFINE_SETTER) } // namespace WebCore -#undef FOR_EACH_COMMON_READONLY_KEY_PATH -#undef FOR_EACH_COMMON_READWRITE_KEY_PATH -#undef FOR_EACH_COMMON_KEY_PATH -#undef FOR_EACH_MEDIA_SOURCE_READONLY_KEY_PATH -#undef FOR_EACH_MEDIA_SOURCE_READWRITE_KEY_PATH -#undef FOR_EACH_MEDIA_SOURCE_KEY_PATH -#undef FOR_EACH_PLAYBACK_CONTROL_KEY_PATH -#undef ADD_MEDIA_SOURCE_OBSERVER -#undef REMOVE_MEDIA_SOURCE_OBSERVER -#undef ADD_PLAYBACK_CONTROL_OBSERVER -#undef REMOVE_PLAYBACK_CONTROL_OBSERVER +#undef FOR_EACH_READONLY_KEY_PATH +#undef FOR_EACH_READWRITE_KEY_PATH +#undef FOR_EACH_KEY_PATH +#undef ADD_OBSERVER +#undef REMOVE_OBSERVER #undef OBSERVE_VALUE #undef DEFINE_GETTER #undef DEFINE_SETTER diff --git a/Source/WebCore/platform/graphics/BitmapImageSource.cpp b/Source/WebCore/platform/graphics/BitmapImageSource.cpp index 276ac65b83eb..e4fd04575808 100644 --- a/Source/WebCore/platform/graphics/BitmapImageSource.cpp +++ b/Source/WebCore/platform/graphics/BitmapImageSource.cpp @@ -513,7 +513,7 @@ void BitmapImageSource::cacheNativeImageAtIndex(unsigned index, SubsamplingLevel destination.headroom = nativeImage->headroom(); cacheMetadataAtIndex(index, subsamplingLevel, options); - decodedSizeIncreased(frame.sizeInBytes()); + decodedSizeIncreased(destination.sizeInBytes()); } const ImageFrame& BitmapImageSource::frameAtIndex(unsigned index) const diff --git a/Source/WebCore/platform/graphics/DestinationColorSpace.cpp b/Source/WebCore/platform/graphics/DestinationColorSpace.cpp index c74fb6fe6dc5..f99b66ec4130 100644 --- a/Source/WebCore/platform/graphics/DestinationColorSpace.cpp +++ b/Source/WebCore/platform/graphics/DestinationColorSpace.cpp @@ -208,6 +208,16 @@ bool DestinationColorSpace::supportsOutput() const #endif } +bool DestinationColorSpace::usesRGBColorModel() const +{ +#if USE(CG) + // Avoid refing color space here as this is performance-sensitive. + SUPPRESS_UNRETAINED_ARG return CGColorSpaceGetModel(platformColorSpace()) == kCGColorSpaceModelRGB; +#else + return true; +#endif +} + bool DestinationColorSpace::usesExtendedRange() const { #if USE(CG) diff --git a/Source/WebCore/platform/graphics/DestinationColorSpace.h b/Source/WebCore/platform/graphics/DestinationColorSpace.h index d567d73f15b5..eb08a70b37c2 100644 --- a/Source/WebCore/platform/graphics/DestinationColorSpace.h +++ b/Source/WebCore/platform/graphics/DestinationColorSpace.h @@ -73,6 +73,7 @@ class DestinationColorSpace { WEBCORE_EXPORT bool supportsOutput() const; + WEBCORE_EXPORT bool usesRGBColorModel() const; WEBCORE_EXPORT bool usesExtendedRange() const; bool usesITUR_2100TF() const; diff --git a/Source/WebCore/platform/graphics/MediaPlayerPrivateWirelessPlayback.cpp b/Source/WebCore/platform/graphics/MediaPlayerPrivateWirelessPlayback.cpp index 115d8254dc3f..b4b547813277 100644 --- a/Source/WebCore/platform/graphics/MediaPlayerPrivateWirelessPlayback.cpp +++ b/Source/WebCore/platform/graphics/MediaPlayerPrivateWirelessPlayback.cpp @@ -280,7 +280,7 @@ void MediaPlayerPrivateWirelessPlayback::seekToTarget(const SeekTarget& seekTarg return; ALWAYS_LOG(LOGIDENTIFIER, seekTarget); - route->setCurrentPlaybackPosition(seekTarget.time); + route->setPlaybackPosition(seekTarget.time); } bool MediaPlayerPrivateWirelessPlayback::paused() const @@ -430,12 +430,12 @@ void MediaPlayerPrivateWirelessPlayback::readyDidChange(MediaDeviceRoute& route) setReadyState(MediaPlayerReadyState::HaveEnoughData); } -void MediaPlayerPrivateWirelessPlayback::playbackErrorDidChange(MediaDeviceRoute& route) +void MediaPlayerPrivateWirelessPlayback::errorDidChange(MediaDeviceRoute& route) { ASSERT(&route == this->route()); - ALWAYS_LOG(LOGIDENTIFIER, !!route.playbackError()); + ALWAYS_LOG(LOGIDENTIFIER, !!route.error()); - if (route.playbackError()) + if (route.error()) setNetworkState(route.ready() ? MediaPlayer::NetworkState::DecodeError : MediaPlayer::NetworkState::FormatError); } @@ -448,14 +448,14 @@ void MediaPlayerPrivateWirelessPlayback::audioOptionsDidChange(MediaDeviceRoute& player->characteristicChanged(); } -void MediaPlayerPrivateWirelessPlayback::currentPlaybackPositionDidChange(MediaDeviceRoute& route) +void MediaPlayerPrivateWirelessPlayback::playbackPositionDidChange(MediaDeviceRoute& route) { ASSERT(&route == this->route()); - auto currentPlaybackPosition = route.currentPlaybackPosition(); - ALWAYS_LOG(LOGIDENTIFIER, currentPlaybackPosition); + auto playbackPosition = route.playbackPosition(); + ALWAYS_LOG(LOGIDENTIFIER, playbackPosition); - updateTimebaseTimeAndRate(currentPlaybackPosition, route.playing() ? route.playbackSpeed() : 0); + updateTimebaseTimeAndRate(playbackPosition, route.playing() ? route.playbackSpeed() : 0); auto currentTime = this->currentTime(); @@ -492,7 +492,7 @@ CMTimebaseRef MediaPlayerPrivateWirelessPlayback::ensureTimebase() dispatch_activate(m_timerSource.get()); if (RefPtr route = this->route()) - updateTimebaseTimeAndRate(route->currentPlaybackPosition() ?: MediaTime::zeroTime(), route->playing() ? route->playbackSpeed() : 0); + updateTimebaseTimeAndRate(route->playbackPosition() ?: MediaTime::zeroTime(), route->playing() ? route->playbackSpeed() : 0); return m_timebase.get(); } diff --git a/Source/WebCore/platform/graphics/MediaPlayerPrivateWirelessPlayback.h b/Source/WebCore/platform/graphics/MediaPlayerPrivateWirelessPlayback.h index 8f599b6d1d01..89d553fa6f03 100644 --- a/Source/WebCore/platform/graphics/MediaPlayerPrivateWirelessPlayback.h +++ b/Source/WebCore/platform/graphics/MediaPlayerPrivateWirelessPlayback.h @@ -129,9 +129,9 @@ class MediaPlayerPrivateWirelessPlayback final // MediaDeviceRouteClient void timeRangeDidChange(MediaDeviceRoute&) final; void readyDidChange(MediaDeviceRoute&) final; - void playbackErrorDidChange(MediaDeviceRoute&) final; + void errorDidChange(MediaDeviceRoute&) final; void audioOptionsDidChange(MediaDeviceRoute&) final; - void currentPlaybackPositionDidChange(MediaDeviceRoute&) final; + void playbackPositionDidChange(MediaDeviceRoute&) final; CMTimebaseRef ensureTimebase(); void destroyTimebase(); diff --git a/Source/WebCore/platform/graphics/MediaSourcePrivate.cpp b/Source/WebCore/platform/graphics/MediaSourcePrivate.cpp index 44e79f12968e..e13cf3229600 100644 --- a/Source/WebCore/platform/graphics/MediaSourcePrivate.cpp +++ b/Source/WebCore/platform/graphics/MediaSourcePrivate.cpp @@ -58,7 +58,7 @@ bool MediaSourcePrivate::hasFutureTime(const MediaTime& currentTime, const Media auto ranges = buffered(); MediaTime nearest = ranges.nearest(currentTime); - if (abs(nearest - currentTime) > timeFudgeFactor()) + if (abs(nearest - currentTime) > gapToleranceAtTime(currentTime)) return false; size_t found = ranges.find(nearest); @@ -94,12 +94,20 @@ bool MediaSourcePrivate::hasBufferedTime(const MediaTime& time) const if (!ranges.length()) return false; - return abs(ranges.nearest(time) - time) <= timeFudgeFactor(); + return abs(ranges.nearest(time) - time) <= gapToleranceAtTime(time); } bool MediaSourcePrivate::hasCurrentTime() const { - return hasBufferedTime(currentTime()); + auto time = currentTime(); + if (hasBufferedTime(time)) + return true; + // Per MSE 2 §presentation-start-time: the user agent may treat a current + // playback position at or after time 0 and before the first buffered + // range as covered, provided the first range starts within a small + // window after presentation start. Affects readyState only; + // HTMLMediaElement.buffered reported to JS is unchanged. + return m_readyState.load() != MediaSourceReadyState::Closed && isWithinStartGapAllowance(time); } bool MediaSourcePrivate::isBuffered(const PlatformTimeRanges& ranges) const @@ -107,7 +115,9 @@ bool MediaSourcePrivate::isBuffered(const PlatformTimeRanges& ranges) const if (m_readyState.load() == MediaSourceReadyState::Closed) return false; - return buffered().containWithEpsilon(ranges, timeFudgeFactor()); + return buffered().containWithEpsilon(ranges, [&](const MediaTime& time) { + return gapToleranceAtTime(time); + }); } MediaSourcePrivate::MediaSourcePrivate(MediaSourcePrivateClient& client) @@ -173,7 +183,6 @@ void MediaSourcePrivate::cancelPendingWaitForTarget() protectedThis->m_pendingSeekTarget.reset(); } protectedThis->m_waitForTargetPromise.reset(); - protectedThis->m_reenqueuePending = false; }); } @@ -376,6 +385,20 @@ void MediaSourcePrivate::updateBufferedRanges() }); auto newBuffered = MediaSourcePrivate::computeBufferedRanges(activeRanges, ended); + + // Aggregate audio-only buffered ranges across active SourceBuffers so + // the canplaythrough gap policy can ask "does any audio bridge this + // gap?" — see MediaSourcePrivate::gapToleranceAtTime. + PlatformTimeRanges newAudioBuffered; + for (RefPtr sourceBuffer : m_activeSourceBuffers) { + if (sourceBuffer) + newAudioBuffered.unionWith(sourceBuffer->audioBufferedRanges()); + } + { + Locker locker { m_lock }; + m_audioBuffered = WTF::move(newAudioBuffered); + } + if (isBufferedEqual(newBuffered)) return; bufferedChanged(WTF::move(newBuffered)); @@ -601,6 +624,83 @@ void MediaSourcePrivate::shutdown() { } +MediaTime MediaSourcePrivate::nextStallTime(const MediaTime& currentTime) const +{ + auto stallAtTime = duration(); + auto ranges = buffered(); + size_t index = ranges.find(currentTime); + if (index == notFound) + return stallAtTime; + + // Find the next gap (or end of media). + auto remaining = ranges.span().subspan(index); + for (size_t i = 0; i < remaining.size(); i++) { + auto rangeEnd = remaining[i].end; + if (i + 1 < remaining.size()) { + if (remaining[i + 1].start - rangeEnd > gapToleranceAtTime(rangeEnd)) { + stallAtTime = rangeEnd; + break; + } + continue; + } + // Final range. + if (rangeEnd > currentTime) + stallAtTime = rangeEnd; + break; + } + return stallAtTime; +} + +MediaTime MediaSourcePrivate::gapToleranceAtTime(const MediaTime& time, std::optional excluded) const +{ + // Start-of-stream allowance: gap is before the first buffered range and + // close to presentation start time. + if (isWithinStartGapAllowance(time)) + return startGapAllowance(); + + // Mid-stream: widen to audioCoveredGapTolerance() when an audio track + // bridges the gap, otherwise midStreamGapTolerance(). + // + // When called with an `excluded` track the caller is on the dispatcher + // (TrackBuffer's IsCoveredByOtherTracks lambda runs from + // SourceBufferPrivate methods that assert dispatcher); walk + // m_activeSourceBuffers directly. + if (excluded) { + assertIsCurrent(m_dispatcher.get()); + for (RefPtr sourceBuffer : m_activeSourceBuffers) { + if (sourceBuffer && sourceBuffer->isAudioBufferedAt(time, *excluded)) + return audioCoveredGapTolerance(); + } + return midStreamGapTolerance(); + } + + // Other callers read the cached audio union. + Locker locker { m_lock }; + return m_audioBuffered.contain(time) ? audioCoveredGapTolerance() : midStreamGapTolerance(); +} + + + +bool MediaSourcePrivate::isWithinStartGapAllowance(const MediaTime& time) const +{ + // MSE 2 §presentation-start-time: + // "For the purposes of determining if HTMLMediaElement's buffered contains + // a TimeRanges that includes the current playback position, implementations + // MAY choose to allow a current playback position at or after presentation + // start time and before the first TimeRanges to play the first TimeRanges + // if that TimeRanges starts within a reasonably short time, like 1 second, + // after presentation start time. This allowance accommodates the reality + // that muxed streams commonly do not begin all tracks precisely at + // presentation start time. Implementations MUST report the actual buffered + // range, regardless of this allowance." + if (time < MediaTime::zeroTime() || time >= startGapAllowance()) + return false; + auto ranges = buffered(); + if (!ranges.length()) + return false; + return ranges.start(0) <= startGapAllowance() && time < ranges.start(0); +} + } // namespace WebCore #endif diff --git a/Source/WebCore/platform/graphics/MediaSourcePrivate.h b/Source/WebCore/platform/graphics/MediaSourcePrivate.h index 5211a885cc9f..46ea2b792c79 100644 --- a/Source/WebCore/platform/graphics/MediaSourcePrivate.h +++ b/Source/WebCore/platform/graphics/MediaSourcePrivate.h @@ -127,8 +127,33 @@ class WEBCORE_EXPORT MediaSourcePrivate void clearReenqueuePending() { m_reenqueuePending = false; } void cancelPendingWaitForTarget(); - void setTimeFudgeFactor(const MediaTime& fudgeFactor) { m_timeFudgeFactor = fudgeFactor; } - MediaTime timeFudgeFactor() const { return m_timeFudgeFactor; } + // Single source of truth for canplaythrough / gap-handling policy across + // the MSE pipeline. All values are durations. + // - startGapAllowance: per MSE 2 §presentation-start-time, allow up to + // 1s gap from time 0 to the first buffered range to count as + // "buffered" for HAVE_FUTURE_DATA / canplay; HTMLMediaElement.buffered + // reported to JS is unaffected. + // - midStreamGapTolerance: default tolerance for gaps mid-stream; + // gaps below this are skipped. + // - audioCoveredGapTolerance: widened tolerance applied when an audio + // track bridges a gap (typically a video-only gap with audio + // continuous through it). + static constexpr MediaTime startGapAllowance() { return { 1, 1 }; } + static constexpr MediaTime midStreamGapTolerance() { return { 125, 1000 }; } + static constexpr MediaTime audioCoveredGapTolerance() { return { 250, 1000 }; } + + // Returns the gap tolerance that should apply at `time`: + // - startGapAllowance if `time` is in the start-of-stream window; + // - audioCoveredGapTolerance if some active audio track other than + // `excluded` has `time` inside its buffered range; + // - midStreamGapTolerance otherwise. + // + // When `excluded` is unset the implementation reads a lock-protected + // audio-buffered cache and may be called from any thread. When + // `excluded` is set the caller MUST be on the dispatcher (used by + // TrackBuffer's IsCoveredByOtherTracks callback). + MediaTime gapToleranceAtTime(const MediaTime&, std::optional excluded = std::nullopt) const; + bool isWithinStartGapAllowance(const MediaTime&) const; MediaTime duration() const; PlatformTimeRanges buffered() const; @@ -139,6 +164,7 @@ class WEBCORE_EXPORT MediaSourcePrivate bool isBuffered(const PlatformTimeRanges&) const; PlatformTimeRanges seekable() const; + MediaTime nextStallTime(const MediaTime& currentTime) const; bool hasBufferedData() const; bool hasCurrentTime() const; bool hasFutureTime() const; @@ -181,13 +207,13 @@ class WEBCORE_EXPORT MediaSourcePrivate MediaTime m_duration WTF_GUARDED_BY_LOCK(m_lock) { MediaTime::invalidTime() }; PlatformTimeRanges m_buffered WTF_GUARDED_BY_LOCK(m_lock); + PlatformTimeRanges m_audioBuffered WTF_GUARDED_BY_LOCK(m_lock); std::optional m_pendingSeekTarget WTF_GUARDED_BY_LOCK(m_lock); std::optional m_waitForTargetPromise WTF_GUARDED_BY_CAPABILITY(m_dispatcher.get()); HashMap> m_bufferedRanges; PlatformTimeRanges m_liveSeekable WTF_GUARDED_BY_LOCK(m_lock); std::atomic m_streaming { false }; std::atomic m_streamingAllowed { false }; - MediaTime m_timeFudgeFactor; HashMap m_tracksTypes WTF_GUARDED_BY_CAPABILITY(m_dispatcher.get()); std::atomic m_tracksCombinedTypes; const ThreadSafeWeakPtr m_client; diff --git a/Source/WebCore/platform/graphics/PixelBufferConversion.cpp b/Source/WebCore/platform/graphics/PixelBufferConversion.cpp index c9974199cf08..0ff1a6eba25e 100644 --- a/Source/WebCore/platform/graphics/PixelBufferConversion.cpp +++ b/Source/WebCore/platform/graphics/PixelBufferConversion.cpp @@ -113,6 +113,12 @@ static void convertImagePixelsAccelerated(const ConstPixelBufferConversionView& auto sourceVImageBuffer = makeVImageBuffer(source, destinationSize); auto destinationVImageBuffer = makeVImageBuffer(destination, destinationSize); + auto zeroFillDestination = [&] { + size_t rowFillBytes = static_cast(destinationSize.width()) * 4; + for (int y = 0; y < destinationSize.height(); ++y) + zeroSpan(destination.rows.subspan(static_cast(y) * destination.bytesPerRow, rowFillBytes)); + }; + if (source.format.colorSpace != destination.format.colorSpace) { // FIXME: Consider using vImageConvert_AnyToAny for all conversions, not just ones that need a color space conversion, // after judiciously performance testing them against each other. @@ -122,11 +128,20 @@ static void convertImagePixelsAccelerated(const ConstPixelBufferConversionView& vImage_Error converterCreateError = kvImageNoError; auto converter = adoptCF(vImageConverter_CreateWithCGImageFormat(&sourceCGImageFormat, &destinationCGImageFormat, nullptr, kvImageNoFlags, &converterCreateError)); - if (converterCreateError != kvImageNoError) + if (converterCreateError != kvImageNoError) { + RELEASE_LOG_ERROR(Images, "%s: vImageConverter_CreateWithCGImageFormat() failed with error: %zd", __FUNCTION__, converterCreateError); + // The destination may be uninitialized; ensure no stale heap is exposed to callers. + zeroFillDestination(); return; + } vImage_Error converterConvertError = vImageConvert_AnyToAny(converter.get(), &sourceVImageBuffer, &destinationVImageBuffer, nullptr, kvImageNoFlags); - ASSERT_WITH_MESSAGE_UNUSED(converterConvertError, converterConvertError == kvImageNoError, "vImageConvert_AnyToAny failed conversion with error: %zd", converterConvertError); + if (converterConvertError != kvImageNoError) { + RELEASE_LOG_ERROR(Images, "%s: vImageConvert_AnyToAny() failed with error: %zd", __FUNCTION__, converterConvertError); + // The destination may be uninitialized; ensure no stale heap is exposed to callers. + zeroFillDestination(); + } + return; } @@ -343,8 +358,9 @@ static void writeFloat16(Float16 f16, const std::span& spanFloat16, siz static void convertImagePixelsFromFloat16ToFloat16(const ConstPixelBufferConversionView& source, const PixelBufferConversionView& destination, const IntSize& destinationSize) { - if (source.format.colorSpace != destination.format.colorSpace) - return; + // FIXME: Float16-to-Float16 color-space conversion is unimplemented; fall through and copy + // verbatim. Do not early-return on a color-space mismatch: the destination is allocated + // uninitialized, so skipping the write would leak heap bytes through getPixelBuffer(). auto sourceBytes = source.rows.size_bytes(); auto sourcePixelComponents = sourceBytes / 2; diff --git a/Source/WebCore/platform/graphics/PlatformTimeRanges.cpp b/Source/WebCore/platform/graphics/PlatformTimeRanges.cpp index 6ae78ecceab4..611c72131b26 100644 --- a/Source/WebCore/platform/graphics/PlatformTimeRanges.cpp +++ b/Source/WebCore/platform/graphics/PlatformTimeRanges.cpp @@ -270,7 +270,19 @@ bool PlatformTimeRanges::containWithEpsilon(const MediaTime& time, const MediaTi return findWithEpsilon(time, epsilon) != notFound; } +bool PlatformTimeRanges::containWithEpsilon(const MediaTime& time, NOESCAPE const Function& epsilonAtTime) const +{ + return findWithEpsilon(time, epsilonAtTime(time)) != notFound; +} + bool PlatformTimeRanges::containWithEpsilon(const PlatformTimeRanges& ranges, const MediaTime& epsilon) const +{ + return containWithEpsilon(ranges, [&](const MediaTime&) { + return epsilon; + }); +} + +bool PlatformTimeRanges::containWithEpsilon(const PlatformTimeRanges& ranges, NOESCAPE const Function& epsilonAtTime) const { if (ranges.length() < 1) return true; @@ -285,7 +297,7 @@ bool PlatformTimeRanges::containWithEpsilon(const PlatformTimeRanges& ranges, co return false; auto hasBufferedTime = [&] (const MediaTime& time) { - return abs(bufferedRanges.nearest(time) - time) <= epsilon; + return abs(bufferedRanges.nearest(time) - time) <= epsilonAtTime(time); }; if (!hasBufferedTime(ranges.minimumBufferedTime()) || !hasBufferedTime(ranges.maximumBufferedTime())) @@ -294,9 +306,9 @@ bool PlatformTimeRanges::containWithEpsilon(const PlatformTimeRanges& ranges, co if (bufferedRanges.length() == 1) return true; - // Ensure that if we have a gap in the buffered range, it is smaller than the fudge factor; + // Ensure that if we have a gap in the buffered range, it is smaller than the epsilon tolerance at the gap's start; for (unsigned i = 1; i < bufferedRanges.length(); i++) { - if (bufferedRanges.start(i) - bufferedRanges.end(i - 1) > epsilon) + if (bufferedRanges.start(i) - bufferedRanges.end(i - 1) > epsilonAtTime(bufferedRanges.end(i - 1))) return false; } diff --git a/Source/WebCore/platform/graphics/PlatformTimeRanges.h b/Source/WebCore/platform/graphics/PlatformTimeRanges.h index a1fbb4d0acb7..2bb8c4528f9b 100644 --- a/Source/WebCore/platform/graphics/PlatformTimeRanges.h +++ b/Source/WebCore/platform/graphics/PlatformTimeRanges.h @@ -78,6 +78,12 @@ class WEBCORE_EXPORT PlatformTimeRanges final { bool contain(const MediaTime&) const; bool containWithEpsilon(const MediaTime&, const MediaTime& epsilon) const; bool containWithEpsilon(const PlatformTimeRanges&, const MediaTime& epsilon) const; + // Variable-epsilon overloads: `epsilonAtTime(t)` is consulted at every + // boundary / gap location (rangeMin, rangeMax, and between adjacent + // sub-ranges). Used by the MSE gap-skipping policy where the + // tolerance varies by stream position. + bool containWithEpsilon(const MediaTime&, NOESCAPE const Function& epsilonAtTime) const; + bool containWithEpsilon(const PlatformTimeRanges&, NOESCAPE const Function& epsilonAtTime) const; size_t find(const MediaTime&) const; size_t findWithEpsilon(const MediaTime&, const MediaTime& epsilon) const; @@ -132,6 +138,8 @@ class WEBCORE_EXPORT PlatformTimeRanges final { friend bool operator==(const Range&, const Range&) = default; }; + std::span span() const LIFETIME_BOUND { return m_ranges.span(); } + friend bool operator==(const PlatformTimeRanges&, const PlatformTimeRanges&) = default; private: diff --git a/Source/WebCore/platform/graphics/SourceBufferPrivate.cpp b/Source/WebCore/platform/graphics/SourceBufferPrivate.cpp index cc5438a6b44d..9ca485e9bf19 100644 --- a/Source/WebCore/platform/graphics/SourceBufferPrivate.cpp +++ b/Source/WebCore/platform/graphics/SourceBufferPrivate.cpp @@ -488,7 +488,7 @@ void SourceBufferPrivate::reenqueueMediaForTime(TrackBuffer& trackBuffer, TrackI bool isEnded = false; if (RefPtr mediaSource = m_mediaSource.get()) isEnded = mediaSource->isEnded(); - if (trackBuffer.reenqueueMediaForTime(time, timeFudgeFactor(), isEnded)) + if (trackBuffer.reenqueueMediaForTime(time, isEnded)) provideMediaData(trackBuffer, trackID); } @@ -821,7 +821,13 @@ void SourceBufferPrivate::addTrackBuffer(TrackID trackId, RefPtrtimeFudgeFactor() : PlatformTimeRanges::timeFudgeFactor()); + UniqueRef trackBuffer = mediaSource + ? TrackBuffer::create(WTF::move(description), + [weakMediaSource = ThreadSafeWeakPtr { *mediaSource }, trackId](const MediaTime& fromTime, const MediaTime& toTime) -> bool { + RefPtr ms = weakMediaSource.get(); + return ms && (toTime - fromTime) <= ms->gapToleranceAtTime(fromTime, trackId); + }) + : TrackBuffer::create(WTF::move(description)); #if !RELEASE_LOG_DISABLED // False positive see webkit.org/b/302520 SUPPRESS_UNCOUNTED_ARG trackBuffer->setLogger(protect(buffer.logger()), buffer.logIdentifier()); @@ -1767,6 +1773,34 @@ void SourceBufferPrivate::iterateTrackBuffers(NOESCAPE const Function excluded) const +{ + assertIsCurrent(m_dispatcher.get()); + for (auto& [trackID, trackBuffer] : m_trackBufferMap) { + if (excluded && *excluded == trackID) + continue; + const auto& description = trackBuffer->description(); + if (!description || !description->isAudio()) + continue; + if (trackBuffer->buffered().contain(time)) + return true; + } + return false; +} + +PlatformTimeRanges SourceBufferPrivate::audioBufferedRanges() const +{ + assertIsCurrent(m_dispatcher.get()); + PlatformTimeRanges ranges; + for (auto& [_, trackBuffer] : m_trackBufferMap) { + const auto& description = trackBuffer->description(); + if (!description || !description->isAudio()) + continue; + ranges.unionWith(trackBuffer->buffered()); + } + return ranges; +} + // Issue flushTrack IPCs now (still on m_dispatcher) for any track whose // renderer holds samples about to be re-enqueued. Done at the end of an // append (or removal) operation so the flush IPC reaches the renderer diff --git a/Source/WebCore/platform/graphics/SourceBufferPrivate.h b/Source/WebCore/platform/graphics/SourceBufferPrivate.h index 38d54957dc89..216950c42cc4 100644 --- a/Source/WebCore/platform/graphics/SourceBufferPrivate.h +++ b/Source/WebCore/platform/graphics/SourceBufferPrivate.h @@ -143,6 +143,18 @@ class SourceBufferPrivate // Methods used by MediaSourcePrivate bool NODELETE hasReceivedFirstInitializationSegment() const; + // Returns true if this SourceBuffer has an audio track whose buffered + // ranges include `time`. If `excluded` is set, the track with that ID is + // skipped (used so an audio TrackBuffer doesn't claim self-coverage when + // querying the unified gap policy). + bool isAudioBufferedAt(const MediaTime&, std::optional excluded) const; + + // Union of all audio TrackBuffers' buffered ranges in this + // SourceBuffer. Caller must be on the dispatcher; the result is a + // copy that's safe to merge into MediaSourcePrivate's lock-protected + // audio-buffered cache. + PlatformTimeRanges audioBufferedRanges() const; + virtual size_t platformMaximumBufferSize() const { return 0; } Ref setMaximumBufferSize(size_t); @@ -184,7 +196,6 @@ class SourceBufferPrivate virtual Ref appendInternal(Ref&&) = 0; virtual void resetParserStateInternal() = 0; - virtual MediaTime timeFudgeFactor() const { return PlatformTimeRanges::timeFudgeFactor(); } virtual void flush(TrackID) { } virtual void enqueueSample(Ref&&, TrackID) { } virtual void allSamplesInTrackEnqueued(TrackID) { } diff --git a/Source/WebCore/platform/graphics/TrackBuffer.cpp b/Source/WebCore/platform/graphics/TrackBuffer.cpp index 09b89ed56a42..95184b305ad0 100644 --- a/Source/WebCore/platform/graphics/TrackBuffer.cpp +++ b/Source/WebCore/platform/graphics/TrackBuffer.cpp @@ -55,21 +55,23 @@ static inline MediaTime roundTowardsTimeScaleWithRoundingMargin(const MediaTime& } }; -UniqueRef TrackBuffer::create(RefPtr&& description) +UniqueRef TrackBuffer::create(RefPtr&& description, IsAcceptableEnqueueGapFn&& isAcceptableEnqueueGap) { - return create(WTF::move(description), MediaTime::zeroTime()); + return makeUniqueRef(WTF::move(description), WTF::move(isAcceptableEnqueueGap)); } -UniqueRef TrackBuffer::create(RefPtr&& description, const MediaTime& discontinuityTolerance) +TrackBuffer::TrackBuffer(RefPtr&& description, IsAcceptableEnqueueGapFn&& isAcceptableEnqueueGap) + : m_description(WTF::move(description)) + , m_enqueueDiscontinuityBoundary(PlatformTimeRanges::timeFudgeFactor()) + , m_isAcceptableEnqueueGap(WTF::move(isAcceptableEnqueueGap)) { - return makeUniqueRef(WTF::move(description), discontinuityTolerance); } -TrackBuffer::TrackBuffer(RefPtr&& description, const MediaTime& discontinuityTolerance) - : m_description(WTF::move(description)) - , m_enqueueDiscontinuityBoundary(discontinuityTolerance) - , m_discontinuityTolerance(discontinuityTolerance) +bool TrackBuffer::isAcceptableEnqueueGap(const MediaTime& fromTime, const MediaTime& toTime) const { + if (toTime - fromTime <= PlatformTimeRanges::timeFudgeFactor()) + return true; + return m_isAcceptableEnqueueGap && m_isAcceptableEnqueueGap(fromTime, toTime); } MediaTime TrackBuffer::maximumBufferedTime() const @@ -202,7 +204,8 @@ RefPtr TrackBuffer::nextSample() Ref sample = decodeQueue().begin()->second; - if (sample->decodeTime() > enqueueDiscontinuityBoundary()) { + if (sample->decodeTime() > enqueueDiscontinuityBoundary() + && (!m_isAcceptableEnqueueGap || !m_isAcceptableEnqueueGap(m_lastEnqueueDecodeEnd, sample->decodeTime()))) { WARNING_LOG(LOGIDENTIFIER, "bailing early because of unbuffered gap, new sample DTS: ", sample->decodeTime(), " >= the current discontinuity boundary: ", enqueueDiscontinuityBoundary()); return { }; } @@ -216,7 +219,8 @@ RefPtr TrackBuffer::nextSample() setLastEnqueuedDecodeKey({ sample->decodeTime(), sample->presentationTime() }); auto decodeEnd = std::max(sample->decodeTime() + sample->duration(), samplePresentationEnd); - setEnqueueDiscontinuityBoundary(decodeEnd + m_discontinuityTolerance); + m_lastEnqueueDecodeEnd = decodeEnd; + setEnqueueDiscontinuityBoundary(decodeEnd + PlatformTimeRanges::timeFudgeFactor()); m_minimumEnqueuedPresentationTime = MediaTime::invalidTime(); if (m_hasOutOfOrderFrames) @@ -263,10 +267,11 @@ void TrackBuffer::updateMinimumUpcomingPresentationTime() m_minimumEnqueuedPresentationTime = MediaTime::invalidTime(); } -bool TrackBuffer::reenqueueMediaForTime(const MediaTime& time, const MediaTime& timeFudgeFactor, bool isEnded) +bool TrackBuffer::reenqueueMediaForTime(const MediaTime& time, bool isEnded) { clearDecodeQueue(); - m_enqueueDiscontinuityBoundary = time + m_discontinuityTolerance; + m_lastEnqueueDecodeEnd = time; + m_enqueueDiscontinuityBoundary = time + PlatformTimeRanges::timeFudgeFactor(); m_needsReenqueueing = false; @@ -276,10 +281,11 @@ bool TrackBuffer::reenqueueMediaForTime(const MediaTime& time, const MediaTime& // Find the sample which contains the current presentation time. auto currentSamplePTSIterator = m_samples.presentationOrder().findSampleContainingPresentationTime(time); - // Find the next sample, so long as its presentation start time is within the timeFudgeFactor. + // Find the next sample, so long as its presentation start time is within + // the gap-skipping policy from the seek target. if (currentSamplePTSIterator == m_samples.presentationOrder().end()) { auto nextSampleIterator = m_samples.presentationOrder().findSampleStartingOnOrAfterPresentationTime(time); - if ((nextSampleIterator->first - time) <= timeFudgeFactor) + if (nextSampleIterator != m_samples.presentationOrder().end() && isAcceptableEnqueueGap(time, nextSampleIterator->first)) currentSamplePTSIterator = nextSampleIterator; } diff --git a/Source/WebCore/platform/graphics/TrackBuffer.h b/Source/WebCore/platform/graphics/TrackBuffer.h index b0f47595b02f..7f15a16a7055 100644 --- a/Source/WebCore/platform/graphics/TrackBuffer.h +++ b/Source/WebCore/platform/graphics/TrackBuffer.h @@ -46,8 +46,17 @@ class TrackBuffer final { WTF_MAKE_TZONE_ALLOCATED(TrackBuffer); public: - static UniqueRef NODELETE create(RefPtr&&); - static UniqueRef create(RefPtr&&, const MediaTime&); + // Returns true if the timestamp gap between `fromTime` and `toTime` + // is acceptable per the gap-skipping policy. Used both with DTS gaps + // (between adjacent samples in nextSample) and with PTS gaps + // (between a seek target and the next available sample in + // reenqueueMediaForTime). Only consulted when the gap exceeds + // PlatformTimeRanges::timeFudgeFactor() — gaps below that threshold + // are treated as contiguous (matching how the buffered range itself + // is reported to clients) and never reach the lambda. + using IsAcceptableEnqueueGapFn = Function; + + static UniqueRef create(RefPtr&&, IsAcceptableEnqueueGapFn&& = nullptr); MediaTime NODELETE maximumBufferedTime() const; void addBufferedRange(const MediaTime& start, const MediaTime& end, AddTimeRangeOption = AddTimeRangeOption::None); @@ -58,7 +67,7 @@ class TrackBuffer final // SampleMap and m_decodeQueue, and adjusts m_buffered to match. void adjustSampleStartTime(MediaSample& original, const MediaTime& offset); - bool reenqueueMediaForTime(const MediaTime&, const MediaTime& timeFudgeFactor, bool isEnded = false); + bool reenqueueMediaForTime(const MediaTime&, bool isEnded = false); MediaTime findSeekTimeForTargetTime(const MediaTime& targetTime, const MediaTime& negativeThreshold, const MediaTime& positiveThreshold); int64_t removeCodedFrames(const MediaTime& start, const MediaTime& end, const MediaTime& currentTime); PlatformTimeRanges removeSamples(const DecodeOrderSampleMap::MapType&, ASCIILiteral); @@ -125,8 +134,16 @@ class TrackBuffer final #endif private: - friend UniqueRef WTF::makeUniqueRefWithoutFastMallocCheck(RefPtr&&, const WTF::MediaTime&); - TrackBuffer(RefPtr&&, const MediaTime&); + friend UniqueRef WTF::makeUniqueRefWithoutFastMallocCheck(RefPtr&&, IsAcceptableEnqueueGapFn&&); + TrackBuffer(RefPtr&&, IsAcceptableEnqueueGapFn&&); + + // Returns true if the DTS gap from `fromTime` to `toTime` is small + // enough to enqueue across. Gaps within + // PlatformTimeRanges::timeFudgeFactor() are always accepted (the + // buffered range itself would be reported as contiguous); larger + // gaps consult the constructor-supplied callback (when set — + // currently MSE only). + bool isAcceptableEnqueueGap(const MediaTime& fromTime, const MediaTime& toTime) const; const DecodeOrderSampleMap::MapType& decodeQueue() const LIFETIME_BOUND { return m_decodeQueue; } DecodeOrderSampleMap::MapType& decodeQueue() LIFETIME_BOUND { return m_decodeQueue; } @@ -173,7 +190,8 @@ class TrackBuffer final DecodeOrderSampleMap::KeyType m_lastEnqueuedDecodeKey { MediaTime::invalidTime(), MediaTime::invalidTime() }; MediaTime m_enqueueDiscontinuityBoundary; - MediaTime m_discontinuityTolerance; + MediaTime m_lastEnqueueDecodeEnd; + IsAcceptableEnqueueGapFn m_isAcceptableEnqueueGap; MediaTime m_roundedTimestampOffset { MediaTime::invalidTime() }; diff --git a/Source/WebCore/platform/graphics/angle/GraphicsContextGLANGLE.cpp b/Source/WebCore/platform/graphics/angle/GraphicsContextGLANGLE.cpp index 14a725d37127..25fefe171fa8 100644 --- a/Source/WebCore/platform/graphics/angle/GraphicsContextGLANGLE.cpp +++ b/Source/WebCore/platform/graphics/angle/GraphicsContextGLANGLE.cpp @@ -1678,7 +1678,19 @@ void GraphicsContextGLANGLE::pixelStorei(GCGLenum pname, GCGLint param) { if (!makeContextCurrent()) return; - + switch (pname) { + case UNPACK_ALIGNMENT: + case UNPACK_ROW_LENGTH: + case UNPACK_IMAGE_HEIGHT: + case UNPACK_SKIP_PIXELS: + case UNPACK_SKIP_ROWS: + case UNPACK_SKIP_IMAGES: + break; + default: + // Should be never set, rather passed to the commands that need these. + addError(GCGLErrorCode::InvalidOperation); + return; + } GL_PixelStorei(pname, param); } diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm index 3e2cee5e6408..7b42a13ea95e 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm @@ -808,25 +808,12 @@ MediaPlayerScope supportedScope() const final return; } - auto ranges = protect(m_mediaSourcePrivate)->buffered(); if (!protect(m_mediaSourcePrivate)->hasFutureTime(time) && shouldBePlaying()) { ALWAYS_LOG(LOGIDENTIFIER, "Not having data to play at time: ", time, " stalling"); stall(); } - auto stallAtTime = duration(); - size_t index = ranges.find(time); - if (index != notFound) { - // Find the next gap (or end of media) - for (; index < ranges.length(); index++) { - if ((index < ranges.length() - 1 && ranges.start(index + 1) - ranges.end(index) > m_mediaSourcePrivate->timeFudgeFactor()) - || (index == ranges.length() - 1 && ranges.end(index) > time)) { - stallAtTime = ranges.end(index); - break; - } - } - } - + auto stallAtTime = protect(m_mediaSourcePrivate)->nextStallTime(time); ALWAYS_LOG(LOGIDENTIFIER, "will stall playback at time: ", stallAtTime); m_renderer->notifyTimeReachedAndStall(stallAtTime)->whenSettled(RunLoop::mainSingleton(), WTF::move(onStallReached))->track(m_stallRequest); } diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h b/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h index 9105b89fd38a..8542ae93fceb 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h +++ b/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.h @@ -139,7 +139,6 @@ class SourceBufferPrivateAVFObjC final void flush(TrackID) final; void enqueueSample(Ref&&, TrackID) final; bool isReadyForMoreSamples(TrackID) final; - MediaTime timeFudgeFactor() const final; void notifyClientWhenReadyForMoreSamples(TrackID) final; bool canSetMinimumUpcomingPresentationTime(TrackID) const override; void setMinimumUpcomingPresentationTime(TrackID, const MediaTime&) override; diff --git a/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm b/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm index 6500ebdaff46..ded8eaaac10a 100644 --- a/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm +++ b/Source/WebCore/platform/graphics/avfoundation/objc/SourceBufferPrivateAVFObjC.mm @@ -695,14 +695,6 @@ return false; } -MediaTime SourceBufferPrivateAVFObjC::timeFudgeFactor() const -{ - if (RefPtr mediaSource = m_mediaSource.get()) - return mediaSource->timeFudgeFactor(); - - return SourceBufferPrivate::timeFudgeFactor(); -} - FloatSize SourceBufferPrivateAVFObjC::naturalSize() { assertIsCurrent(m_dispatcher.get()); diff --git a/Source/WebCore/platform/graphics/cocoa/MediaPlayerPrivateWebM.mm b/Source/WebCore/platform/graphics/cocoa/MediaPlayerPrivateWebM.mm index 218713b87c34..0b5d42d0f86f 100644 --- a/Source/WebCore/platform/graphics/cocoa/MediaPlayerPrivateWebM.mm +++ b/Source/WebCore/platform/graphics/cocoa/MediaPlayerPrivateWebM.mm @@ -1276,7 +1276,7 @@ if (needsFlush == NeedsFlush::Yes) m_renderer->flushTrack(*trackIdentifier); - if (trackBuffer.reenqueueMediaForTime(time, timeFudgeFactor(), m_loadFinished)) + if (trackBuffer.reenqueueMediaForTime(time, m_loadFinished)) provideMediaData(trackBuffer, trackId); } @@ -1643,7 +1643,10 @@ setHasAudio(m_hasAudio || description->isAudio()); setHasVideo(m_hasVideo || description->isVideo()); - auto trackBuffer = TrackBuffer::create(WTF::move(description), discontinuityTolerance); + auto trackBuffer = TrackBuffer::create(WTF::move(description), + [](const MediaTime& fromTime, const MediaTime& toTime) { + return (toTime - fromTime) <= discontinuityTolerance; + }); trackBuffer->setLogger(protect(logger()), logIdentifier()); m_trackBufferMap.try_emplace(trackId, WTF::move(trackBuffer)); m_requestReadyForMoreSamplesSetMap[trackId] = false; @@ -1963,7 +1966,7 @@ MediaPlayerScope supportedScope() const final auto currentTime = this->currentTime(); MediaTime aheadTime = std::min(durationOnRunningQueue(), currentTime + MediaTime::createWithDouble(kHaveEnoughDataThreshold)); PlatformTimeRanges neededBufferedRange { currentTime, std::max(currentTime, aheadTime) }; - auto newState = m_buffered.containWithEpsilon(neededBufferedRange, MediaTime(2002, 24000)) ? MediaPlayer::ReadyState::HaveEnoughData : MediaPlayer::ReadyState::HaveFutureData; + auto newState = m_buffered.containWithEpsilon(neededBufferedRange, timeFudgeFactor()) ? MediaPlayer::ReadyState::HaveEnoughData : MediaPlayer::ReadyState::HaveFutureData; ensureOnMainThread([weakThis = ThreadSafeWeakPtr { *this }, newState] { if (RefPtr protectedThis = weakThis.get()) protectedThis->setReadyState(newState); diff --git a/Source/WebCore/platform/graphics/cpu/arm/filters/FEBlendNeonApplier.cpp b/Source/WebCore/platform/graphics/cpu/arm/filters/FEBlendNeonApplier.cpp index 94c2f76eeb73..b504e89c3d20 100644 --- a/Source/WebCore/platform/graphics/cpu/arm/filters/FEBlendNeonApplier.cpp +++ b/Source/WebCore/platform/graphics/cpu/arm/filters/FEBlendNeonApplier.cpp @@ -187,6 +187,9 @@ bool FEBlendNeonApplier::apply(const Filter&, std::span> auto effectBDrawingRect = result.absoluteImageRectRelativeTo(input2); auto sourcePixelArrayB = input2.getPixelBuffer(AlphaPremultiplication::Premultiplied, effectBDrawingRect); + if (!sourcePixelArrayA || !sourcePixelArrayB) + return false; + unsigned sourcePixelArrayLength = sourcePixelArrayA->bytes().size(); ASSERT(sourcePixelArrayLength == sourcePixelArrayB->bytes().size()); diff --git a/Source/WebCore/platform/graphics/filters/FilterImage.cpp b/Source/WebCore/platform/graphics/filters/FilterImage.cpp index cfdafbeef8ab..e78634c2b7df 100644 --- a/Source/WebCore/platform/graphics/filters/FilterImage.cpp +++ b/Source/WebCore/platform/graphics/filters/FilterImage.cpp @@ -149,25 +149,26 @@ ImageBuffer* FilterImage::imageBufferFromPixelBuffer() return m_imageBuffer.get(); } -static void copyImageBytes(const PixelBuffer& sourcePixelBuffer, PixelBuffer& destinationPixelBuffer) +static bool copyImageBytes(const PixelBuffer& sourcePixelBuffer, PixelBuffer& destinationPixelBuffer) { ASSERT(sourcePixelBuffer.size() == destinationPixelBuffer.size()); auto destinationSize = destinationPixelBuffer.size(); auto rowBytes = CheckedUint32(destinationSize.width()) * 4; if (rowBytes.hasOverflowed()) [[unlikely]] - return; + return false; ConstPixelBufferConversionView source { sourcePixelBuffer.format(), rowBytes, sourcePixelBuffer.bytes() }; PixelBufferConversionView destination { destinationPixelBuffer.format(), rowBytes, destinationPixelBuffer.bytes() }; convertImagePixels(source, destination, destinationSize); + return true; } -static void copyImageBytes(const PixelBuffer& sourcePixelBuffer, PixelBuffer& destinationPixelBuffer, const IntRect& sourceRect) +static bool copyImageBytes(const PixelBuffer& sourcePixelBuffer, PixelBuffer& destinationPixelBuffer, const IntRect& sourceRect) { - auto sourcePixelBufferRect = IntRect { { }, sourcePixelBuffer.size() }; - auto destinationPixelBufferRect = IntRect { { }, destinationPixelBuffer.size() }; + const auto sourcePixelBufferRect = IntRect { { }, sourcePixelBuffer.size() }; + const auto destinationPixelBufferRect = IntRect { { }, destinationPixelBuffer.size() }; auto sourceRectClipped = intersection(sourcePixelBufferRect, sourceRect); auto destinationRect = IntRect { { }, sourceRectClipped.size() }; @@ -181,13 +182,9 @@ static void copyImageBytes(const PixelBuffer& sourcePixelBuffer, PixelBuffer& de destinationRect.intersect(destinationPixelBufferRect); sourceRectClipped.setSize(destinationRect.size()); - // Initialize the destination to transparent black, if not entirely covered by the source. - if (destinationRect.size() != destinationPixelBufferRect.size()) - destinationPixelBuffer.zeroFill(); - // Early return if the rect does not intersect with the source. if (destinationRect.isEmpty()) - return; + return false; auto size = CheckedUint32(sourceRectClipped.width()) * 4; auto destinationBytesPerRow = CheckedUint32(destinationPixelBufferRect.width()) * 4; @@ -196,7 +193,11 @@ static void copyImageBytes(const PixelBuffer& sourcePixelBuffer, PixelBuffer& de auto sourceOffset = sourceRectClipped.y() * sourceBytesPerRow + CheckedUint32(sourceRectClipped.x()) * 4; if (size.hasOverflowed() || destinationBytesPerRow.hasOverflowed() || sourceBytesPerRow.hasOverflowed() || destinationOffset.hasOverflowed() || sourceOffset.hasOverflowed()) [[unlikely]] - return; + return false; + + // Initialize the destination to transparent black, if not entirely covered by the source. + if (destinationRect.size() != destinationPixelBufferRect.size()) + destinationPixelBuffer.zeroFill(); auto destinationPixel = destinationPixelBuffer.bytes().subspan(destinationOffset.value()); auto sourcePixel = sourcePixelBuffer.bytes().subspan(sourceOffset.value()); @@ -208,6 +209,8 @@ static void copyImageBytes(const PixelBuffer& sourcePixelBuffer, PixelBuffer& de } memcpySpan(destinationPixel, sourcePixel.first(size)); } + + return true; } static RefPtr getConvertedPixelBuffer(ImageBuffer& imageBuffer, AlphaPremultiplication alphaFormat, const IntRect& sourceRect, DestinationColorSpace colorSpace, ImageBufferAllocator& allocator) @@ -280,11 +283,15 @@ PixelBuffer* FilterImage::pixelBuffer(AlphaPremultiplication alphaFormat) return nullptr; if (alphaFormat == AlphaPremultiplication::Unpremultiplied) { - if (auto& sourcePixelBuffer = pixelBufferSlot(AlphaPremultiplication::Premultiplied)) - copyImageBytes(*sourcePixelBuffer, *pixelBuffer); + if (auto& sourcePixelBuffer = pixelBufferSlot(AlphaPremultiplication::Premultiplied)) { + if (!copyImageBytes(*sourcePixelBuffer, *pixelBuffer)) + return nullptr; + } } else { - if (auto& sourcePixelBuffer = pixelBufferSlot(AlphaPremultiplication::Unpremultiplied)) - copyImageBytes(*sourcePixelBuffer, *pixelBuffer); + if (auto& sourcePixelBuffer = pixelBufferSlot(AlphaPremultiplication::Unpremultiplied)) { + if (!copyImageBytes(*sourcePixelBuffer, *pixelBuffer)) + return nullptr; + } } return pixelBuffer.get(); @@ -300,11 +307,13 @@ RefPtr FilterImage::getPixelBuffer(AlphaPremultiplication alphaForm if (!pixelBuffer) return nullptr; - copyPixelBuffer(*pixelBuffer, sourceRect); + if (!copyPixelBuffer(*pixelBuffer, sourceRect)) + return nullptr; + return pixelBuffer; } -void FilterImage::copyPixelBuffer(PixelBuffer& destinationPixelBuffer, const IntRect& sourceRect) +bool FilterImage::copyPixelBuffer(PixelBuffer& destinationPixelBuffer, const IntRect& sourceRect) { auto alphaFormat = destinationPixelBuffer.format().alphaFormat; auto& colorSpace = destinationPixelBuffer.format().colorSpace; @@ -317,8 +326,8 @@ void FilterImage::copyPixelBuffer(PixelBuffer& destinationPixelBuffer, const Int if (m_imageBuffer) { IntRect rect { { }, m_absoluteImageRect.size() }; if (auto convertedPixelBuffer = getConvertedPixelBuffer(Ref { *m_imageBuffer }, alphaFormat, rect, colorSpace, m_allocator)) - copyImageBytes(*convertedPixelBuffer, destinationPixelBuffer, sourceRect); - return; + return copyImageBytes(*convertedPixelBuffer, destinationPixelBuffer, sourceRect); + return false; } } @@ -326,15 +335,15 @@ void FilterImage::copyPixelBuffer(PixelBuffer& destinationPixelBuffer, const Int } if (!sourcePixelBuffer) - return; + return false; if (requiresPixelBufferColorSpaceConversion(colorSpace)) { if (auto convertedPixelBuffer = getConvertedPixelBuffer(*sourcePixelBuffer, alphaFormat, colorSpace, m_allocator)) - copyImageBytes(*convertedPixelBuffer, destinationPixelBuffer, sourceRect); - return; + return copyImageBytes(*convertedPixelBuffer, destinationPixelBuffer, sourceRect); + return false; } - copyImageBytes(*sourcePixelBuffer, destinationPixelBuffer, sourceRect); + return copyImageBytes(*sourcePixelBuffer, destinationPixelBuffer, sourceRect); } void FilterImage::correctPremultipliedPixelBuffer() diff --git a/Source/WebCore/platform/graphics/filters/FilterImage.h b/Source/WebCore/platform/graphics/filters/FilterImage.h index d8793955a65d..af8a531ddcf2 100644 --- a/Source/WebCore/platform/graphics/filters/FilterImage.h +++ b/Source/WebCore/platform/graphics/filters/FilterImage.h @@ -75,7 +75,7 @@ class FilterImage : public RefCounted { PixelBuffer* pixelBuffer(AlphaPremultiplication); RefPtr getPixelBuffer(AlphaPremultiplication, const IntRect& sourceRect, std::optional = std::nullopt); - void copyPixelBuffer(PixelBuffer& destinationPixelBuffer, const IntRect& sourceRect); + bool copyPixelBuffer(PixelBuffer& destinationPixelBuffer, const IntRect& sourceRect); void NODELETE correctPremultipliedPixelBuffer(); void NODELETE transformToColorSpace(const DestinationColorSpace&); diff --git a/Source/WebCore/platform/graphics/filters/software/FEGaussianBlurSoftwareApplier.cpp b/Source/WebCore/platform/graphics/filters/software/FEGaussianBlurSoftwareApplier.cpp index 10df4e6adcbb..dfca48c32320 100644 --- a/Source/WebCore/platform/graphics/filters/software/FEGaussianBlurSoftwareApplier.cpp +++ b/Source/WebCore/platform/graphics/filters/software/FEGaussianBlurSoftwareApplier.cpp @@ -345,6 +345,11 @@ inline void FEGaussianBlurSoftwareApplier::boxBlurGeneric(PixelBuffer& ioBuffer, } #endif + // boxBlurAlphaOnly() fills the alpha channel only. Make + // sure the other channels are initialized in this case. + if (isAlphaImage) + tempBuffer.zeroFill(); + boxBlurUnaccelerated(ioBuffer, tempBuffer, kernelSizeX, kernelSizeY, stride, paintSize, isAlphaImage, edgeMode); } diff --git a/Source/WebCore/platform/mock/mediasource/MockBox.cpp b/Source/WebCore/platform/mock/mediasource/MockBox.cpp index cf0f1847dbd9..e40fc4a6208c 100644 --- a/Source/WebCore/platform/mock/mediasource/MockBox.cpp +++ b/Source/WebCore/platform/mock/mediasource/MockBox.cpp @@ -92,7 +92,7 @@ MockInitializationBox::MockInitializationBox(ArrayBuffer* data) auto view = JSC::DataView::create(data, 0, data->byteLength()); int32_t timeValue = view->get(8, true); - int32_t timeScale = view->get(12, true); + uint32_t timeScale = view->get(12, true); m_duration = MediaTime(timeValue, timeScale); size_t offset = 16; @@ -120,7 +120,7 @@ MockSampleBox::MockSampleBox(ArrayBuffer* data) ASSERT(m_length == 30); auto view = JSC::DataView::create(data, 0, data->byteLength()); - int32_t timeScale = view->get(8, true); + uint32_t timeScale = view->get(8, true); int32_t timeValue = view->get(12, true); m_presentationTimestamp = MediaTime(timeValue, timeScale); @@ -128,8 +128,8 @@ MockSampleBox::MockSampleBox(ArrayBuffer* data) timeValue = view->get(16, true); m_decodeTimestamp = MediaTime(timeValue, timeScale); - timeValue = view->get(20, true); - m_duration = MediaTime(timeValue, timeScale); + uint32_t durationValue = view->get(20, true); + m_duration = MediaTime(durationValue, timeScale); m_trackID = view->get(24, true); m_flags = view->get(28, true); diff --git a/Source/WebCore/rendering/RenderBox.cpp b/Source/WebCore/rendering/RenderBox.cpp index 3deb92520f3f..9ed73427f6fc 100644 --- a/Source/WebCore/rendering/RenderBox.cpp +++ b/Source/WebCore/rendering/RenderBox.cpp @@ -1020,8 +1020,8 @@ int RenderBox::reflectionOffset() const if (!reflection) return 0; if (reflection->direction == ReflectionDirection::Left || reflection->direction == ReflectionDirection::Right) - return Style::evaluate(reflection->offset, borderBoxRect().width(), Style::ZoomNeeded { }); - return Style::evaluate(reflection->offset, borderBoxRect().height(), Style::ZoomNeeded { }); + return Style::evaluate(reflection->offset, borderBoxWidth(), Style::ZoomNeeded { }); + return Style::evaluate(reflection->offset, borderBoxHeight(), Style::ZoomNeeded { }); } LayoutRect RenderBox::reflectedRect(const LayoutRect& r) const diff --git a/Source/WebCore/rendering/RenderBox.h b/Source/WebCore/rendering/RenderBox.h index 94a9bec2c5fb..d75c9eef40b9 100644 --- a/Source/WebCore/rendering/RenderBox.h +++ b/Source/WebCore/rendering/RenderBox.h @@ -74,8 +74,8 @@ class RenderBox : public RenderBoxModelObject { template void setX(T x) { m_frameRect.setX(x); } template void setY(T y) { m_frameRect.setY(y); } - template void setWidth(T width) { m_frameRect.setWidth(width); } - template void setHeight(T height) { m_frameRect.setHeight(height); } + template void setBorderBoxWidth(T width) { m_frameRect.setWidth(width); } + template void setBorderBoxHeight(T height) { m_frameRect.setHeight(height); } inline LayoutUnit logicalLeft() const; inline LayoutUnit logicalRight() const; diff --git a/Source/WebCore/rendering/RenderBoxInlines.h b/Source/WebCore/rendering/RenderBoxInlines.h index a01fdff806c7..198f53d163ce 100644 --- a/Source/WebCore/rendering/RenderBoxInlines.h +++ b/Source/WebCore/rendering/RenderBoxInlines.h @@ -210,9 +210,9 @@ inline const LayoutRect RenderBox::scrollablePaddingAreaOverflowRect() const inline void RenderBox::setLogicalHeight(LayoutUnit size) { if (writingMode().isHorizontal()) - setHeight(size); + setBorderBoxHeight(size); else - setWidth(size); + setBorderBoxWidth(size); } inline void RenderBox::setLogicalLeft(LayoutUnit left) @@ -234,9 +234,9 @@ inline void RenderBox::setLogicalTop(LayoutUnit top) inline void RenderBox::setLogicalWidth(LayoutUnit size) { if (writingMode().isHorizontal()) - setWidth(size); + setBorderBoxWidth(size); else - setHeight(size); + setBorderBoxHeight(size); } inline bool RenderBox::hasStretchedLogicalHeight(StretchingMode mode) const diff --git a/Source/WebCore/rendering/RenderDeprecatedFlexibleBox.cpp b/Source/WebCore/rendering/RenderDeprecatedFlexibleBox.cpp index 51d16e3e2b09..f5e38eec1fb6 100644 --- a/Source/WebCore/rendering/RenderDeprecatedFlexibleBox.cpp +++ b/Source/WebCore/rendering/RenderDeprecatedFlexibleBox.cpp @@ -370,7 +370,7 @@ void RenderDeprecatedFlexibleBox::layoutBlock(RelayoutChildren relayoutChildren, && parent()->style().boxAlign() == BoxAlignment::Stretch)) relayoutChildren = RelayoutChildren::Yes; - setHeight(0); + setBorderBoxHeight(0); m_stretchingChildren = false; @@ -487,7 +487,7 @@ void RenderDeprecatedFlexibleBox::layoutHorizontalBox(RelayoutChildren relayoutC // The first pass skips flexible objects completely. do { // Reset our height. - setHeight(yPos); + setBorderBoxHeight(yPos); xPos = borderLeft() + paddingLeft(); @@ -527,17 +527,17 @@ void RenderDeprecatedFlexibleBox::layoutHorizontalBox(RelayoutChildren relayoutC maxDescent = std::max(maxDescent, descent); // Now update our height. - setHeight(std::max(yPos + maxAscent + maxDescent, borderBoxHeight())); + setBorderBoxHeight(std::max(yPos + maxAscent + maxDescent, borderBoxHeight())); } else - setHeight(std::max(borderBoxHeight(), yPos + child->borderBoxHeight() + child->verticalMarginExtent())); + setBorderBoxHeight(std::max(borderBoxHeight(), yPos + child->borderBoxHeight() + child->verticalMarginExtent())); } ASSERT(childIndex == childLayoutDeltas.size()); if (!iterator.first() && hasLineIfEmpty()) - setHeight(borderBoxHeight() + lineHeight()); + setBorderBoxHeight(borderBoxHeight() + lineHeight()); - setHeight(borderBoxHeight() + toAdd); + setBorderBoxHeight(borderBoxHeight() + toAdd); oldHeight = borderBoxHeight(); updateLogicalHeight(); @@ -746,7 +746,7 @@ void RenderDeprecatedFlexibleBox::layoutHorizontalBox(RelayoutChildren relayoutC // So that the computeLogicalHeight in layoutBlock() knows to relayout positioned objects because of // a height change, we revert our height back to the intrinsic height before returning. if (heightSpecified) - setHeight(oldHeight); + setBorderBoxHeight(oldHeight); } void RenderDeprecatedFlexibleBox::layoutSingleClampedFlexItem() @@ -774,7 +774,7 @@ void RenderDeprecatedFlexibleBox::layoutSingleClampedFlexItem() } else childBoxBottom += clampedRendererCandidate.contentBoxRect().height() + clampedRendererCandidate.marginBottom(); - setHeight(childBoxBottom + paddingBottom() + borderBottom()); + setBorderBoxHeight(childBoxBottom + paddingBottom() + borderBottom()); updateLogicalHeight(); computeInFlowOverflow(flippedContentBoxRect()); @@ -815,7 +815,7 @@ void RenderDeprecatedFlexibleBox::layoutVerticalBox(RelayoutChildren relayoutChi // Our first pass is done without flexing. We simply lay the children // out within the box. do { - setHeight(borderTop() + paddingTop()); + setBorderBoxHeight(borderTop() + paddingTop()); LayoutUnit minHeight = borderBoxHeight() + toAdd; for (RenderBox* child = iterator.first(); child; child = iterator.next()) { @@ -839,7 +839,7 @@ void RenderDeprecatedFlexibleBox::layoutVerticalBox(RelayoutChildren relayoutChi child->computeAndSetBlockDirectionMargins(*this); // Add in the child's marginTop to our height. - setHeight(borderBoxHeight() + child->marginTop()); + setBorderBoxHeight(borderBoxHeight() + child->marginTop()); if (!haveLineClamp) child->markForPaginationRelayoutIfNeeded(); @@ -872,20 +872,20 @@ void RenderDeprecatedFlexibleBox::layoutVerticalBox(RelayoutChildren relayoutChi // Place the child. placeChild(child, LayoutPoint(childX, borderBoxHeight())); - setHeight(borderBoxHeight() + child->borderBoxHeight() + child->marginBottom()); + setBorderBoxHeight(borderBoxHeight() + child->borderBoxHeight() + child->marginBottom()); } yPos = borderBoxHeight(); if (!iterator.first() && hasLineIfEmpty()) - setHeight(borderBoxHeight() + lineHeight()); + setBorderBoxHeight(borderBoxHeight() + lineHeight()); - setHeight(borderBoxHeight() + toAdd); + setBorderBoxHeight(borderBoxHeight() + toAdd); // Negative margins can cause our height to shrink below our minimal height (border/padding). // If this happens, ensure that the computed height is increased to the minimal height. if (borderBoxHeight() < minHeight) - setHeight(minHeight); + setBorderBoxHeight(minHeight); // Now we have to calc our height, so we know how much space we have remaining. oldHeight = borderBoxHeight(); @@ -1041,12 +1041,12 @@ void RenderDeprecatedFlexibleBox::layoutVerticalBox(RelayoutChildren relayoutChi }; auto usedHeight = borderBoxHeight(); auto clampedHeight = contentOffset() + clampedContent.contentHeight + borderBottom() + paddingBottom(); - setHeight(clampedHeight); + setBorderBoxHeight(clampedHeight); updateLogicalHeight(); if (clampedHeight != borderBoxHeight()) - setHeight(heightSpecified ? oldHeight : usedHeight); + setBorderBoxHeight(heightSpecified ? oldHeight : usedHeight); } else if (heightSpecified) - setHeight(oldHeight); + setBorderBoxHeight(oldHeight); } static size_t lineCountFor(const RenderBlockFlow& blockFlow) diff --git a/Source/WebCore/rendering/RenderFlexibleBox.cpp b/Source/WebCore/rendering/RenderFlexibleBox.cpp index 219ae8a20407..6e603f3d7913 100644 --- a/Source/WebCore/rendering/RenderFlexibleBox.cpp +++ b/Source/WebCore/rendering/RenderFlexibleBox.cpp @@ -115,7 +115,7 @@ struct RenderFlexibleBox::LineState { : crossAxisOffset(crossAxisOffset) , crossAxisExtent(crossAxisExtent) , baselineAlignmentState(baselineAlignmentState) - , flexLayoutItems(flexLayoutItems) + , flexLayoutItems(WTF::move(flexLayoutItems)) { } diff --git a/Source/WebCore/rendering/RenderFrameSet.cpp b/Source/WebCore/rendering/RenderFrameSet.cpp index 0b81ad43b249..e541c3263b82 100644 --- a/Source/WebCore/rendering/RenderFrameSet.cpp +++ b/Source/WebCore/rendering/RenderFrameSet.cpp @@ -440,8 +440,8 @@ void RenderFrameSet::layout() } if (!parent()->isRenderFrameSet() && !protect(document())->printing()) { - setWidth(view().viewWidth()); - setHeight(view().viewHeight()); + setBorderBoxWidth(view().viewWidth()); + setBorderBoxHeight(view().viewHeight()); } unsigned cols = frameSetElement().totalCols(); @@ -480,8 +480,8 @@ static void resetFrameRendererAndDescendants(RenderBox* frameSetChild, RenderFra return; for (auto* descendant = frameSetChild; descendant; descendant = downcast(RenderObjectTraversal::next(*descendant, &parentFrameSet))) { - descendant->setWidth(0); - descendant->setHeight(0); + descendant->setBorderBoxWidth(0); + descendant->setBorderBoxHeight(0); descendant->clearNeedsLayout(); } } @@ -505,8 +505,8 @@ void RenderFrameSet::positionFrames() int width = m_cols.m_sizes[c]; // has to be resized and itself resize its contents - child->setWidth(width); - child->setHeight(height); + child->setBorderBoxWidth(width); + child->setBorderBoxHeight(height); #if PLATFORM(IOS_FAMILY) // FIXME: Is this iOS-specific? child->setNeedsLayout(MarkingBehavior::MarkOnlyThis); diff --git a/Source/WebCore/rendering/RenderHighlight.cpp b/Source/WebCore/rendering/RenderHighlight.cpp index 6e0f15e254b0..b35199bcaef8 100644 --- a/Source/WebCore/rendering/RenderHighlight.cpp +++ b/Source/WebCore/rendering/RenderHighlight.cpp @@ -126,10 +126,9 @@ RenderObject::HighlightState RenderHighlight::highlightStateForRenderer(const Re return renderer.selectionState(); if (&renderer == m_renderRange.start()) { - if (m_renderRange.start() && m_renderRange.end() && m_renderRange.start() == m_renderRange.end()) + if (m_renderRange.end() && m_renderRange.start() == m_renderRange.end()) return RenderObject::HighlightState::Both; - if (m_renderRange.start()) - return RenderObject::HighlightState::Start; + return RenderObject::HighlightState::Start; } if (&renderer == m_renderRange.end()) return RenderObject::HighlightState::End; diff --git a/Source/WebCore/rendering/RenderListMarker.cpp b/Source/WebCore/rendering/RenderListMarker.cpp index d79f01842240..cdc0f074e1ab 100644 --- a/Source/WebCore/rendering/RenderListMarker.cpp +++ b/Source/WebCore/rendering/RenderListMarker.cpp @@ -339,8 +339,8 @@ void RenderListMarker::layout() if (isImage()) { updateInlineMarginsAndContent(); RefPtr image = m_image; - setWidth(image->imageSize(this, style().usedZoom()).width()); - setHeight(image->imageSize(this, style().usedZoom()).height()); + setBorderBoxWidth(image->imageSize(this, style().usedZoom()).width()); + setBorderBoxHeight(image->imageSize(this, style().usedZoom()).height()); m_layoutBounds = { borderBoxHeight(), 0 }; } else { setLogicalWidth(minContentLogicalWidthContribution()); diff --git a/Source/WebCore/rendering/RenderReplaced.cpp b/Source/WebCore/rendering/RenderReplaced.cpp index 3e6d59f00492..a2825434d72f 100644 --- a/Source/WebCore/rendering/RenderReplaced.cpp +++ b/Source/WebCore/rendering/RenderReplaced.cpp @@ -158,7 +158,7 @@ void RenderReplaced::layout() LayoutRect oldContentRect = replacedContentRect(); - setHeight(minimumReplacedHeight()); + setBorderBoxHeight(minimumReplacedHeight()); updateLogicalWidth(); updateLogicalHeight(); diff --git a/Source/WebCore/rendering/RenderScrollbarPart.cpp b/Source/WebCore/rendering/RenderScrollbarPart.cpp index 71d7c434a9d4..0d2bb4989c41 100644 --- a/Source/WebCore/rendering/RenderScrollbarPart.cpp +++ b/Source/WebCore/rendering/RenderScrollbarPart.cpp @@ -67,11 +67,11 @@ void RenderScrollbarPart::layout() void RenderScrollbarPart::layoutHorizontalPart() { if (m_part == ScrollbarBGPart) { - setWidth(protect(m_scrollbar.get())->width()); + setBorderBoxWidth(protect(m_scrollbar.get())->width()); computeScrollbarHeight(); } else { computeScrollbarWidth(); - setHeight(protect(m_scrollbar.get())->height()); + setBorderBoxHeight(protect(m_scrollbar.get())->height()); } } @@ -79,9 +79,9 @@ void RenderScrollbarPart::layoutVerticalPart() { if (m_part == ScrollbarBGPart) { computeScrollbarWidth(); - setHeight(protect(m_scrollbar.get())->height()); + setBorderBoxHeight(protect(m_scrollbar.get())->height()); } else { - setWidth(protect(m_scrollbar.get())->width()); + setBorderBoxWidth(protect(m_scrollbar.get())->width()); computeScrollbarHeight(); } } @@ -115,7 +115,7 @@ void RenderScrollbarPart::computeScrollbarWidth() auto width = calcScrollbarThicknessUsing(style().width(), zoomFactor); auto minWidth = calcScrollbarThicknessUsing(style().minWidth(), zoomFactor); auto maxWidth = style().maxWidth().isNone() ? width : calcScrollbarThicknessUsing(style().maxWidth(), zoomFactor); - setWidth(std::max(minWidth, std::min(maxWidth, width))); + setBorderBoxWidth(std::max(minWidth, std::min(maxWidth, width))); // Buttons and track pieces can all have margins along the axis of the scrollbar. m_marginBox.setLeft(Style::evaluateMinimum(style().marginLeft(), 0_lu, style().usedZoomForLength())); @@ -130,7 +130,7 @@ void RenderScrollbarPart::computeScrollbarHeight() auto height = calcScrollbarThicknessUsing(style().height(), zoomFactor); auto minHeight = calcScrollbarThicknessUsing(style().minHeight(), zoomFactor); auto maxHeight = style().maxHeight().isNone() ? height : calcScrollbarThicknessUsing(style().maxHeight(), zoomFactor); - setHeight(std::max(minHeight, std::min(maxHeight, height))); + setBorderBoxHeight(std::max(minHeight, std::min(maxHeight, height))); // Buttons and track pieces can all have margins along the axis of the scrollbar. m_marginBox.setTop(Style::evaluateMinimum(style().marginTop(), 0_lu, style().usedZoomForLength())); @@ -167,8 +167,8 @@ void RenderScrollbarPart::paintIntoRect(GraphicsContext& graphicsContext, const { // Make sure our dimensions match the rect. setLocation(rect.location() - toLayoutSize(paintOffset)); - setWidth(rect.width()); - setHeight(rect.height()); + setBorderBoxWidth(rect.width()); + setBorderBoxHeight(rect.height()); if (graphicsContext.paintingDisabled() || style().opacity().isTransparent()) return; diff --git a/Source/WebCore/rendering/RenderTableCell.cpp b/Source/WebCore/rendering/RenderTableCell.cpp index 7f6ca50a8fc4..b0e7ff624d06 100644 --- a/Source/WebCore/rendering/RenderTableCell.cpp +++ b/Source/WebCore/rendering/RenderTableCell.cpp @@ -1423,7 +1423,7 @@ void RenderTableCell::paintCollapsedBorders(PaintInfo& paintInfo, const LayoutPo return; LayoutRect localRepaintRect = paintInfo.rect; - LayoutRect paintRect = LayoutRect(paintOffset + location(), frameRect().size()); + LayoutRect paintRect = LayoutRect(paintOffset + location(), borderBoxSize()); if (paintRect.y() - table()->outerBorderTop() >= localRepaintRect.maxY()) return; @@ -1643,7 +1643,7 @@ void RenderTableCell::paintBoxDecorations(PaintInfo& paintInfo, const LayoutPoin if (!table->collapseBorders() && style().emptyCells() == EmptyCell::Hide && !firstChild()) return; - LayoutRect paintRect = LayoutRect(paintOffset, frameRect().size()); + LayoutRect paintRect = LayoutRect(paintOffset, borderBoxSize()); adjustBorderBoxRectForPainting(paintRect); BackgroundPainter backgroundPainter { *this, paintInfo }; @@ -1675,7 +1675,7 @@ void RenderTableCell::paintMask(PaintInfo& paintInfo, const LayoutPoint& paintOf if (!tableElt->collapseBorders() && style().emptyCells() == EmptyCell::Hide && !firstChild()) return; - LayoutRect paintRect = LayoutRect(paintOffset, frameRect().size()); + LayoutRect paintRect = LayoutRect(paintOffset, borderBoxSize()); adjustBorderBoxRectForPainting(paintRect); paintMaskImages(paintInfo, paintRect); diff --git a/Source/WebCore/rendering/StyledMarkedText.cpp b/Source/WebCore/rendering/StyledMarkedText.cpp index 63d986798170..8569bc014ed8 100644 --- a/Source/WebCore/rendering/StyledMarkedText.cpp +++ b/Source/WebCore/rendering/StyledMarkedText.cpp @@ -42,7 +42,7 @@ static void computeStyleForPseudoElementStyle(StyledMarkedText::Style& style, co return; style.backgroundColor = pseudoElementStyle->visitedDependentBackgroundColorApplyingColorFilter(paintInfo.paintBehavior); - style.textStyles.fillColor = pseudoElementStyle->usedStrokeColor(); + style.textStyles.fillColor = pseudoElementStyle->visitedDependentTextFillColorApplyingColorFilter(paintInfo.paintBehavior); style.textStyles.strokeColor = pseudoElementStyle->usedStrokeColor(); style.textStyles.hasExplicitlySetFillColor = pseudoElementStyle->hasExplicitlySetColor(); diff --git a/Source/WebCore/rendering/svg/RenderSVGForeignObject.cpp b/Source/WebCore/rendering/svg/RenderSVGForeignObject.cpp index 51804a367b56..8a105200698d 100644 --- a/Source/WebCore/rendering/svg/RenderSVGForeignObject.cpp +++ b/Source/WebCore/rendering/svg/RenderSVGForeignObject.cpp @@ -80,7 +80,7 @@ void RenderSVGForeignObject::paint(PaintInfo& paintInfo, const LayoutPoint& pain void RenderSVGForeignObject::updateLogicalWidth() { - setWidth(enclosingLayoutRect(m_viewport).width()); + setBorderBoxWidth(enclosingLayoutRect(m_viewport).width()); } RenderBox::LogicalExtentComputedValues RenderSVGForeignObject::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop) const diff --git a/Source/WebCore/rendering/svg/RenderSVGRoot.cpp b/Source/WebCore/rendering/svg/RenderSVGRoot.cpp index e23d67968271..4f43cebc1f1f 100644 --- a/Source/WebCore/rendering/svg/RenderSVGRoot.cpp +++ b/Source/WebCore/rendering/svg/RenderSVGRoot.cpp @@ -329,7 +329,7 @@ void RenderSVGRoot::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) return; // An empty viewport disables rendering. - if (borderBoxRect().isEmpty()) + if (borderBoxSize().isEmpty()) return; auto adjustedPaintOffset = paintOffset + location(); @@ -653,7 +653,7 @@ LayoutRect RenderSVGRoot::overflowClipRect(const LayoutPoint& location, OverlayS void RenderSVGRoot::boundingRects(Vector& rects, const LayoutPoint& accumulatedOffset) const { - rects.append({ accumulatedOffset, borderBoxRect().size() }); + rects.append({ accumulatedOffset, borderBoxSize() }); } void RenderSVGRoot::absoluteQuads(Vector& quads, bool* wasFixed) const diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp index 74c2799243af..69b8ec1f3b55 100644 --- a/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp @@ -33,6 +33,8 @@ #include "SVGTextLayoutEngineBaseline.h" #include "SVGTextLayoutEngineSpacing.h" #include "StyleComputedStyle+GettersInlines.h" +#include +#include // Set to a value > 0 to dump the text fragments #define DUMP_SVG_TEXT_LAYOUT_FRAGMENTS 0 @@ -434,6 +436,9 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(InlineIterator::SVGTextBoxItera float baselineShift = baselineLayout.calculateBaselineShift(style); baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, text); + // Grapheme-cluster boundaries, keyed by the same code-unit offset the loop walks (m_visualCharacterOffset). + NonSharedCharacterBreakIterator clusterIterator(StringView(text.text())); + // Main layout algorithm. while (true) { // Find the start of the current text box in this list, respecting ligatures. @@ -629,9 +634,12 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(InlineIterator::SVGTextBoxItera m_lastChunkHasTextLength = definesTextLength; } + // Only a cluster start may open a new fragment, so a combining mark stays with its base rather than being shaped alone (dotted circle). Bug 266832. + bool isClusterStart = ubrk_isBoundary(clusterIterator, m_visualCharacterOffset); + // Determine whether we have to start a new fragment. - bool shouldStartNewFragment = hasXOrY || m_dx || m_dy || m_isVerticalText || m_inPathLayout || angle || angle != lastAngle - || orientationAngle || applySpacingToNextCharacter || definesTextLength; + bool shouldStartNewFragment = isClusterStart && (hasXOrY || m_dx || m_dy || m_isVerticalText || m_inPathLayout || angle || angle != lastAngle + || orientationAngle || applySpacingToNextCharacter || definesTextLength); // If we already started a fragment, close it now. if (didStartTextFragment && shouldStartNewFragment) { diff --git a/Source/WebCore/style/StyleBuilder.cpp b/Source/WebCore/style/StyleBuilder.cpp index 8e490f1f9c31..367b615cc6a3 100644 --- a/Source/WebCore/style/StyleBuilder.cpp +++ b/Source/WebCore/style/StyleBuilder.cpp @@ -226,23 +226,33 @@ void Builder::applyCustomProperty(const AtomString& name) auto iterator = m_cascade.customProperties().find(name); if (iterator == m_cascade.customProperties().end()) { - // The property is not in this cascade, but a custom function body inherits the calling - // context's custom properties, which are resolved lazily. Resolve it there and copy in the - // computed value so var() references can find it. - if (auto* callingContextBuilder = m_state->callingContextBuilder()) { - callingContextBuilder->applyCustomProperty(name); - if (RefPtr value = callingContextBuilder->state().style().customPropertyValue(name)) { - bool isInherited = isInheritedCustomProperty(m_state->registeredProperty(name)); - m_state->style().setCustomPropertyValue(value.releaseNonNull(), isInherited); - } - m_state->m_appliedCustomProperties.add(name); - } + // A property missing from this cascade is resolved against the function evaluation context, if any. + if (m_state->callingContextBuilder()) + applyCustomPropertyFromCallingContext(name); return; } applyCustomPropertyImpl(name, iterator->value); } +// A custom property absent from a function's cascade is either a parameter (locally registered: it +// shadows inheritance and resolves to its registered value) or one inherited from the calling context +// (resolved lazily). https://drafts.csswg.org/css-mixins/#evaluating-custom-functions +void Builder::applyCustomPropertyFromCallingContext(const AtomString& name) +{ + auto* callingContextBuilder = m_state->callingContextBuilder(); + ASSERT(callingContextBuilder); + + if (m_state->registeredProperty(name)) + applyCustomProperty(name, CSSWideKeyword::Initial); + else { + callingContextBuilder->applyCustomProperty(name); + if (RefPtr value = callingContextBuilder->state().style().customPropertyValue(name)) + m_state->style().setCustomPropertyValue(value.releaseNonNull(), isInheritedCustomProperty(m_state->registeredProperty(name))); + } + m_state->m_appliedCustomProperties.add(name); +} + void Builder::applyCustomPropertyImpl(const AtomString& name, const PropertyCascade::Property& property) { if (!property.cssValue[SelectorChecker::MatchDefault]) @@ -264,6 +274,11 @@ void Builder::applyCustomPropertyImpl(const AtomString& name, const PropertyCasc // The computed value is the guaranteed-invalid value. if (!registered || registered->syntax.isUniversal()) return CustomProperty::createForGuaranteedInvalid(name); + // For a custom function's hypothetical element, an invalid value resolves to the guaranteed-invalid + // value (the parameter overrides inheritance), not unset. + // https://drafts.csswg.org/css-mixins/#evaluating-custom-functions + if (m_state->callingContextBuilder()) + return CustomProperty::createForGuaranteedInvalid(name); // Otherwise: // ...as if the property’s value had been specified as the unset keyword. return CSSWideKeyword::Unset; @@ -692,7 +707,7 @@ std::optional Builder::resolveCustomPropertyVa auto resolvedData = switchOn(value.value(), [&](const Ref& substitutionValue) -> RefPtr { - SubstitutionResolver substitutionResolver(*this); + SubstitutionResolver substitutionResolver(*this, registered); return substitutionResolver.substitute(substitutionValue.get()); }, [&](const Ref& data) -> RefPtr { diff --git a/Source/WebCore/style/StyleBuilder.h b/Source/WebCore/style/StyleBuilder.h index 3e10aa20a684..3612247790e9 100644 --- a/Source/WebCore/style/StyleBuilder.h +++ b/Source/WebCore/style/StyleBuilder.h @@ -69,6 +69,7 @@ class Builder { void applyLogicalGroupProperties(); void applyCustomProperties(); void applyCustomPropertyImpl(const AtomString&, const PropertyCascade::Property&); + void applyCustomPropertyFromCallingContext(const AtomString&); enum CustomPropertyCycleTracking { Enabled = 0, Disabled }; template diff --git a/Source/WebCore/style/StyleSubstitutionResolver.cpp b/Source/WebCore/style/StyleSubstitutionResolver.cpp index 2adde8fdbf79..8ad876adb9db 100644 --- a/Source/WebCore/style/StyleSubstitutionResolver.cpp +++ b/Source/WebCore/style/StyleSubstitutionResolver.cpp @@ -60,6 +60,7 @@ #include "StyleLocalPropertyRegistry.h" #include "StyleResolver.h" #include "StyleScope.h" +#include namespace WebCore { namespace Style { @@ -75,6 +76,24 @@ static bool containsURLTokens(std::span tokens) return false; } +static Ref createFirstValidVariableData(std::span> candidates, const CSSParserContext& context) +{ + // Uses the internal -internal-first-valid name rather than the public first-valid(). The public + // function must validate against any property's grammar, which is not implemented yet. See the + // FIXME on substituteFirstValid(). + Vector tokens; + tokens.append(CSSParserToken(FunctionToken, "-internal-first-valid"_s, CSSParserToken::BlockStart)); + bool isFirst = true; + for (auto& candidate : candidates) { + if (!isFirst) + tokens.append(CSSParserToken(CommaToken)); + isFirst = false; + tokens.append(candidate); + } + tokens.append(CSSParserToken(RightParenthesisToken, CSSParserToken::BlockEnd)); + return CSSVariableData::create(CSSParserTokenRange { tokens }, context); +} + void SubstitutionResolver::propagateAttrTaint(IsAttrTainted isAttrTainted, std::span tokens) { if (isAttrTainted != IsAttrTainted::Yes) @@ -84,8 +103,9 @@ void SubstitutionResolver::propagateAttrTaint(IsAttrTainted isAttrTainted, std:: m_hasTaintedURL = true; } -SubstitutionResolver::SubstitutionResolver(Builder& builder) +SubstitutionResolver::SubstitutionResolver(Builder& builder, const CSSRegisteredCustomProperty* registration) : m_styleBuilder(builder) + , m_registration(registration) { } @@ -173,12 +193,44 @@ bool SubstitutionResolver::substituteVariableFunction(CSSParserTokenRange range, return true; } +// https://drafts.csswg.org/css-values-5/#first-valid +// FIXME: This only validates against a custom property's registered syntax. The real, author-exposed +// first-valid() is a usable on any property, so candidates must be validated against the +// target property's grammar (e.g. by feeding them to the property parser) rather than only a registration. +bool SubstitutionResolver::substituteFirstValid(CSSParserTokenRange range, Vector& tokens, const CSSParserContext& context) +{ + for (unsigned i = 0; !range.atEnd(); ++i) { + auto candidateRange = CSSPropertyParserHelpers::consumeArgument(range, i); + if (!candidateRange) + break; + + auto substituted = substituteTokenRange(*candidateRange, context); + if (!substituted || substituted->isEmpty()) + continue; + + if (m_registration && !m_registration->syntax.isUniversal() + && !CSSPropertyParser::isValidCustomPropertyValueForSyntax(m_registration->syntax, CSSParserTokenRange { *substituted }, context)) + continue; + + tokens.appendVector(*substituted); + return true; + } + return false; +} + // https://drafts.csswg.org/css-mixins/#evaluate-a-custom-function // Registers each parameter with its type, resolves argument styles, then updates registrations // to universal syntax with resolved values as initial values. // Returns resolved argument properties to prepend to the body rule, or nullptr on failure. RefPtr SubstitutionResolver::resolveAndRegisterDashedFunctionArguments(const Vector& parameters, const Vector>& arguments, LocalPropertyRegistry& registrations) { + // A parameter without a default requires a corresponding argument. A missing one makes the whole + // invocation guaranteed-invalid (unlike a supplied-but-invalid argument, which defaults below). + for (auto [i, parameter] : indexedRange(parameters)) { + if (!parameter.defaultValue && i >= arguments.size()) + return nullptr; + } + // "For each function parameter, create a custom property registration with the parameter's type." auto argumentRegistrations = LocalPropertyRegistry { }; for (auto& parameter : parameters) { @@ -191,26 +243,42 @@ RefPtr SubstitutionResolver::resolveAndRegisterDashedFun // "Let argument rule be an initially empty style rule" with first-valid(arg value, default value) for each parameter. auto argumentRule = MutableStyleProperties::create(); - for (unsigned i = 0; i < parameters.size(); ++i) { - auto& parameter = parameters[i]; - auto argumentData = [&] -> RefPtr { - if (i < arguments.size() && !arguments[i].isEmpty()) - return CSSVariableData::create(CSSParserTokenRange { arguments[i] }, m_substitutionValue->context()); - return parameter.defaultValue; + for (auto [i, parameter] : indexedRange(parameters)) { + bool hasArgument = i < arguments.size() && !arguments[i].isEmpty(); + + // first-valid(arg value, default value). A parameter with neither is omitted from the rule, + // resolving to the guaranteed-invalid value via its (valueless) registration. + auto candidates = [&] { + Vector, 2> candidates; + if (hasArgument) + candidates.append(arguments[i].span()); + if (parameter.defaultValue) + candidates.append(parameter.defaultValue->tokens().span()); + return candidates; }(); - if (!argumentData) - return nullptr; + if (candidates.isEmpty()) + continue; + + // A bare CSS-wide keyword (e.g. a parameter defaulting to `inherit`) keeps its keyword semantics + // rather than becoming a literal value. https://drafts.csswg.org/css-mixins/#evaluating-custom-functions + auto primaryTokens = CSSParserTokenRange { candidates.first() }; + primaryTokens.consumeWhitespace(); + if (auto keyword = CSSPropertyParserHelpers::consumeCSSWideKeyword(primaryTokens); keyword && primaryTokens.atEnd()) { + argumentRule->addParsedProperty({ CSSPropertyCustom, CSSCustomPropertyValue::createWithCSSWideKeyword(parameter.name, *keyword) }); + continue; + } - // A bare CSS-wide keyword argument/default (e.g. a parameter defaulting to `inherit`) must keep - // its keyword semantics so it resolves against the calling context, rather than being treated as - // a literal universal value. https://drafts.csswg.org/css-mixins/#evaluating-custom-functions - auto value = [&] -> Ref { - auto tokens = argumentData->tokenRange(); - tokens.consumeWhitespace(); - if (auto keyword = CSSPropertyParserHelpers::consumeCSSWideKeyword(tokens); keyword && tokens.atEnd()) - return CSSCustomPropertyValue::createWithCSSWideKeyword(parameter.name, *keyword); - return CSSCustomPropertyValue::createSyntaxAll(parameter.name, argumentData.releaseNonNull()); - }(); + // Fast path: an untyped parameter with an argument needs no first-valid(). The argument is + // already substituted and, being non-empty, is always valid for the universal syntax, so it + // wins outright and the default is irrelevant. + if (hasArgument && parameter.type.isUniversal()) { + auto value = CSSCustomPropertyValue::createSyntaxAll(parameter.name, CSSVariableData::create(arguments[i], m_substitutionValue->context())); + argumentRule->addParsedProperty({ CSSPropertyCustom, WTF::move(value) }); + continue; + } + + auto firstValidData = createFirstValidVariableData(candidates.span(), m_substitutionValue->context()); + auto value = CSSCustomPropertyValue::createUnresolved(parameter.name, CSSSubstitutionValue::create(WTF::move(firstValidData))); argumentRule->addParsedProperty({ CSSPropertyCustom, WTF::move(value) }); } @@ -247,7 +315,7 @@ RefPtr SubstitutionResolver::resolveAndRegisterDashedFun }); if (resolvedValue && !resolvedValue->isGuaranteedInvalid()) { - auto tokenData = CSSVariableData::create(CSSParserTokenRange { resolvedValue->tokens() }); + auto tokenData = CSSVariableData::create(CSSParserTokenRange { resolvedValue->tokens() }, resolvedValue->isAttrTainted(), m_substitutionValue->context()); auto value = CSSCustomPropertyValue::createSyntaxAll(parameter.name, WTF::move(tokenData)); resolvedArgumentProperties->addParsedProperty({ CSSPropertyCustom, WTF::move(value) }); } @@ -290,9 +358,9 @@ bool SubstitutionResolver::substituteDashedFunction(StringView functionName, CSS if (!argumentRange) break; auto substituted = substituteTokenRange(*argumentRange, m_substitutionValue->context()); - if (!substituted) - return { }; - result.append(WTF::move(*substituted)); + // A failed substitution leaves the argument guaranteed-invalid (empty) so it defaults via + // first-valid(), rather than aborting. https://drafts.csswg.org/css-mixins/#replace-a-dashed-function + result.append(substituted.value_or(Vector { })); } if (result.size() > parameters.size()) return { }; @@ -701,6 +769,11 @@ std::optional> SubstitutionResolver::substituteTokenRange success = false; continue; } + if (token.value() == "-internal-first-valid"_s) { + if (!substituteFirstValid(range.consumeBlock(), tokens, context)) + success = false; + continue; + } if (isCustomPropertyName(token.value())) { // if (!substituteDashedFunction(token.value(), range.consumeBlock(), tokens)) diff --git a/Source/WebCore/style/StyleSubstitutionResolver.h b/Source/WebCore/style/StyleSubstitutionResolver.h index 7d2622cafde0..618b845709fe 100644 --- a/Source/WebCore/style/StyleSubstitutionResolver.h +++ b/Source/WebCore/style/StyleSubstitutionResolver.h @@ -36,6 +36,7 @@ class CSSValue; class CSSVariableData; class CSSSubstitutionValue; struct CSSParserContext; +struct CSSRegisteredCustomProperty; enum CSSPropertyID : uint16_t; enum CSSValueID : uint16_t; @@ -51,7 +52,9 @@ class LocalPropertyRegistry; // https://drafts.csswg.org/css-values-5/#arbitrary-substitution class SubstitutionResolver { public: - explicit SubstitutionResolver(Builder&); + // The registration is that of the custom property whose value is being resolved, if any. It is + // used to validate first-valid() candidates against the target syntax. + explicit SubstitutionResolver(Builder&, const CSSRegisteredCustomProperty* = nullptr); RefPtr substituteAndParse(const CSSSubstitutionValue&, CSSPropertyID); RefPtr substituteAndParseShorthand(const CSSShorthandSubstitutionValue&, CSSPropertyID); @@ -61,6 +64,7 @@ class SubstitutionResolver { std::optional> substituteTokenRange(CSSParserTokenRange, const CSSParserContext&); bool substituteVariableFunction(CSSParserTokenRange, CSSValueID, Vector&, const CSSParserContext&); + bool substituteFirstValid(CSSParserTokenRange, Vector&, const CSSParserContext&); bool substituteDashedFunction(StringView functionName, CSSParserTokenRange, Vector&); RefPtr resolveAndRegisterDashedFunctionArguments(const Vector&, const Vector>&, LocalPropertyRegistry&); bool substituteAttrFunction(CSSParserTokenRange, Vector&, const CSSParserContext&); @@ -83,6 +87,7 @@ class SubstitutionResolver { void propagateAttrTaint(IsAttrTainted, std::span); Builder& m_styleBuilder; + const CSSRegisteredCustomProperty* m_registration { nullptr }; RefPtr m_substitutionValue; Vector m_intermediateTokenStrings; Vector> m_intermediateCustomProperties; diff --git a/Source/WebCore/style/values/transforms/StyleTransformList.cpp b/Source/WebCore/style/values/transforms/StyleTransformList.cpp index 0022f83a5dcc..7307f1bc7370 100644 --- a/Source/WebCore/style/values/transforms/StyleTransformList.cpp +++ b/Source/WebCore/style/values/transforms/StyleTransformList.cpp @@ -112,7 +112,7 @@ auto Blending::blend(const TransformList& from, const TransformLi } CheckedPtr renderBox = dynamicDowncast(context.client.renderer()); - auto boxSize = renderBox ? renderBox->borderBoxRect().size() : LayoutSize(); + auto boxSize = renderBox ? renderBox->borderBoxSize() : LayoutSize(); bool shouldFallBackToDiscrete = shouldFallBackToDiscreteInterpolation(from, to, boxSize); diff --git a/Source/WebCore/svg/SVGAnimationElement.cpp b/Source/WebCore/svg/SVGAnimationElement.cpp index b2c4992328ea..442b628f8664 100644 --- a/Source/WebCore/svg/SVGAnimationElement.cpp +++ b/Source/WebCore/svg/SVGAnimationElement.cpp @@ -552,7 +552,8 @@ void SVGAnimationElement::startedActiveInterval() if (!splinesCount || (hasAttributeWithoutSynchronization(SVGNames::keyPointsAttr) && m_keyPoints.size() - 1 != splinesCount) || (animationMode == AnimationMode::Values && m_values.size() - 1 != splinesCount) - || (hasAttributeWithoutSynchronization(SVGNames::keyTimesAttr) && keyTimes.size() - 1 != splinesCount)) + || (hasAttributeWithoutSynchronization(SVGNames::keyTimesAttr) && keyTimes.size() - 1 != splinesCount) + || (!keyTimes.isEmpty() && keyTimes.last() != 1)) return; } diff --git a/Source/WebCore/svg/SVGGeometryElement.cpp b/Source/WebCore/svg/SVGGeometryElement.cpp index 385f70c215a9..13c44665f002 100644 --- a/Source/WebCore/svg/SVGGeometryElement.cpp +++ b/Source/WebCore/svg/SVGGeometryElement.cpp @@ -49,47 +49,56 @@ SVGGeometryElement::SVGGeometryElement(const QualifiedName& tagName, Document& d } } -float SVGGeometryElement::getTotalLength() const +float SVGGeometryElement::calculateTotalLength() const { - protect(document())->updateLayoutIgnorePendingStylesheets({ LayoutOptions::TreatContentVisibilityHiddenAsVisible, LayoutOptions::TreatContentVisibilityAutoAsVisible }, this); - - auto* renderer = this->renderer(); + CheckedPtr renderer = this->renderer(); if (!renderer) return 0; - if (CheckedPtr renderSVGShape = dynamicDowncast(renderer)) + if (CheckedPtr renderSVGShape = dynamicDowncast(*renderer)) return renderSVGShape->getTotalLength(); - if (CheckedPtr renderSVGShape = dynamicDowncast(renderer)) + if (CheckedPtr renderSVGShape = dynamicDowncast(*renderer)) return renderSVGShape->getTotalLength(); ASSERT_NOT_REACHED(); return 0; } -ExceptionOr> SVGGeometryElement::getPointAtLength(float distance) const +float SVGGeometryElement::getTotalLength() const { protect(document())->updateLayoutIgnorePendingStylesheets({ LayoutOptions::TreatContentVisibilityHiddenAsVisible, LayoutOptions::TreatContentVisibilityAutoAsVisible }, this); + return calculateTotalLength(); +} - auto* renderer = this->renderer(); +ExceptionOr> SVGGeometryElement::calculatePointAtLength(float distance) const +{ + CheckedPtr renderer = this->renderer(); // Spec: If current element is a non-rendered element, throw an InvalidStateError. if (!renderer) return Exception { ExceptionCode::InvalidStateError }; - // Spec: Clamp distance to [0, length]. - distance = clampTo(distance, 0, getTotalLength()); - // Spec: Return a newly created, detached SVGPoint object. - if (CheckedPtr renderSVGShape = dynamicDowncast(renderer)) + if (CheckedPtr renderSVGShape = dynamicDowncast(*renderer)) return SVGPoint::create(renderSVGShape->getPointAtLength(distance)); - if (CheckedPtr renderSVGShape = dynamicDowncast(renderer)) + if (CheckedPtr renderSVGShape = dynamicDowncast(*renderer)) return SVGPoint::create(renderSVGShape->getPointAtLength(distance)); ASSERT_NOT_REACHED(); return Exception { ExceptionCode::InvalidStateError }; } +ExceptionOr> SVGGeometryElement::getPointAtLength(float distance) const +{ + protect(document())->updateLayoutIgnorePendingStylesheets({ LayoutOptions::TreatContentVisibilityHiddenAsVisible, LayoutOptions::TreatContentVisibilityAutoAsVisible }, this); + + // Spec: Clamp distance to [0, length]. + distance = clampTo(distance, 0, calculateTotalLength()); + + return calculatePointAtLength(distance); +} + bool SVGGeometryElement::isPointInFill(DOMPointInit&& pointInit) { protect(document())->updateLayoutIgnorePendingStylesheets({ LayoutOptions::TreatContentVisibilityHiddenAsVisible, LayoutOptions::TreatContentVisibilityAutoAsVisible }, this); diff --git a/Source/WebCore/svg/SVGGeometryElement.h b/Source/WebCore/svg/SVGGeometryElement.h index dd21c40964a3..41847b9318df 100644 --- a/Source/WebCore/svg/SVGGeometryElement.h +++ b/Source/WebCore/svg/SVGGeometryElement.h @@ -56,6 +56,9 @@ class SVGGeometryElement : public SVGGraphicsElement { private: bool isSVGGeometryElement() const override { return true; } + float calculateTotalLength() const; + ExceptionOr> calculatePointAtLength(float distance) const; + const Ref m_pathLength { SVGAnimatedNumber::create(this) }; }; diff --git a/Source/WebCore/svg/SVGStyleElement.cpp b/Source/WebCore/svg/SVGStyleElement.cpp index cc6ef216ab25..88de20dabf45 100644 --- a/Source/WebCore/svg/SVGStyleElement.cpp +++ b/Source/WebCore/svg/SVGStyleElement.cpp @@ -26,6 +26,7 @@ #include "CSSStyleSheet.h" #include "CommonAtomStrings.h" #include "Document.h" +#include "MediaQueryParser.h" #include "NodeName.h" #include "SVGElementInlines.h" #include "SVGNames.h" @@ -79,9 +80,18 @@ void SVGStyleElement::attributeChanged(const QualifiedName& name, const AtomStri break; case AttributeNames::typeAttr: m_styleSheetOwner.setContentType(newValue); + m_styleSheetOwner.childrenChanged(*this); + if (CheckedPtr scope = m_styleSheetOwner.styleScope()) + scope->didChangeStyleSheetContents(); break; case AttributeNames::mediaAttr: m_styleSheetOwner.setMedia(newValue); + if (RefPtr sheet = this->sheet()) { + sheet->setMediaQueries(MQ::MediaQueryParser::parse(newValue, protect(document())->cssParserContext())); + if (CheckedPtr scope = m_styleSheetOwner.styleScope()) + scope->didChangeStyleSheetContents(); + } else + m_styleSheetOwner.childrenChanged(*this); break; default: break; diff --git a/Source/WebCore/xml/parser/XMLDocumentParserLibxml2.cpp b/Source/WebCore/xml/parser/XMLDocumentParserLibxml2.cpp index dfb9640ea3c1..ff8f3b47f528 100644 --- a/Source/WebCore/xml/parser/XMLDocumentParserLibxml2.cpp +++ b/Source/WebCore/xml/parser/XMLDocumentParserLibxml2.cpp @@ -910,6 +910,23 @@ void XMLDocumentParser::startElementNs(const xmlChar* xmlLocalName, const xmlCha if (willConstructCustomElement) [[unlikely]] { customElementReactionStack.reset(); markupInsertionCountIncrementer.reset(); + // Draining the reaction stack runs the custom element constructor, which may + // have re-parented newElement, adopted it into another document, or detached + // the current parser node. parserAppendChild requires its argument to have no + // parent and to share our document. + // FIXME: We are misusing the upgrade-an-element machinery here to emulate + // synchronous custom element construction. Per HTML's "create an element for + // a token" with willExecuteScript=true we should run the constructor inside + // Document::createElement (via constructElementWithFallback), which post- + // validates parent/children/attributes/document and falls back to + // HTMLUnknownElement on violation, like HTMLDocumentParser does in + // runScriptsForPausedTreeBuilder. Switching to that would also fix the + // spec-incorrect attribute-ordering above (we currently set attributes before + // the constructor runs). + if (!m_currentNode || newElement->parentNode() || &newElement->document() != &m_currentNode->document()) { + stopParsing(); + return; + } } newElement->beginParsingChildren(); diff --git a/Source/WebGPU/WebGPU/BindGroupLayout.mm b/Source/WebGPU/WebGPU/BindGroupLayout.mm index 1b805841d217..c555be162c34 100644 --- a/Source/WebGPU/WebGPU/BindGroupLayout.mm +++ b/Source/WebGPU/WebGPU/BindGroupLayout.mm @@ -572,7 +572,7 @@ static uint64_t NODELETE makeBindGroupLayoutPairIdentifier(uint32_t bindGroupIde if (device->isCachedCompatibile(*this, otherLayout)) return nil; - if (autogeneratedPipelineLayout() != otherLayout.autogeneratedPipelineLayout()) + if (isAutoGenerated() != otherLayout.isAutoGenerated() || autogeneratedPipelineLayout() != otherLayout.autogeneratedPipelineLayout()) return @"Auto-generated layouts mismatch"; auto& entries = m_sortedEntries; diff --git a/Source/WebGPU/WebGPU/ComputePipeline.mm b/Source/WebGPU/WebGPU/ComputePipeline.mm index 0d5378fb51c5..d3923a0a687e 100644 --- a/Source/WebGPU/WebGPU/ComputePipeline.mm +++ b/Source/WebGPU/WebGPU/ComputePipeline.mm @@ -128,7 +128,7 @@ if (!size.width || size.width > deviceLimits.maxComputeWorkgroupSizeX || !size.height || size.height > deviceLimits.maxComputeWorkgroupSizeY || !size.depth || size.depth > deviceLimits.maxComputeWorkgroupSizeZ || size.width * size.height * size.depth > deviceLimits.maxComputeInvocationsPerWorkgroup) return returnInvalidComputePipeline(*this, isAsync); - if (m_computePipelineId == Device::maxPipelines) { + if (m_pipelineId == Device::maxPipelines) { loseTheDevice(WGPUDeviceLostReason_Undefined); return returnInvalidComputePipeline(*this, isAsync, @"too many compute pipelines"); } @@ -149,14 +149,14 @@ if (!computePipelineState) return returnFailedPSOCreation(); - return std::make_pair(ComputePipeline::create(computePipelineState, WTF::move(generatedPipelineLayout), size, WTF::move(minimumBufferSizes), ++m_computePipelineId, *this), nil); + return std::make_pair(ComputePipeline::create(computePipelineState, WTF::move(generatedPipelineLayout), size, WTF::move(minimumBufferSizes), ++m_pipelineId, *this), nil); } auto computePipelineState = createComputePipelineState(m_device, function, pipelineLayout, size, label.get(), shaderValidationState(), WTF::move(shaderSource)); if (!computePipelineState) return returnFailedPSOCreation(); - return std::make_pair(ComputePipeline::create(computePipelineState, WTF::move(pipelineLayout), size, WTF::move(minimumBufferSizes), ++m_computePipelineId, *this), nil); + return std::make_pair(ComputePipeline::create(computePipelineState, WTF::move(pipelineLayout), size, WTF::move(minimumBufferSizes), ++m_pipelineId, *this), nil); } void Device::createComputePipelineAsync(const WGPUComputePipelineDescriptor& descriptor, CompletionHandler&&, String&& message)>&& callback) diff --git a/Source/WebGPU/WebGPU/Device.h b/Source/WebGPU/WebGPU/Device.h index 8f87a4639a56..66aa24e815d5 100644 --- a/Source/WebGPU/WebGPU/Device.h +++ b/Source/WebGPU/WebGPU/Device.h @@ -335,8 +335,7 @@ class Device : public WGPUDeviceImpl, public ThreadSafeRefCountedAndCanMakeThrea mutable HashSet, WTF::UnsignedWithZeroKeyHashTraits> m_bindGroupCompatibilityCache; uint64_t m_commandEncoderId { 0 }; uint64_t m_pipelineLayoutId { 0 }; - uint64_t m_renderPipelineId { 0 }; - uint64_t m_computePipelineId { 0 }; + uint64_t m_pipelineId { 0 }; uint32_t m_bindGroupLayoutId { 0 }; uint32_t m_bindGroupId { 0 }; uint32_t m_appleGPUFamily { 0 }; diff --git a/Source/WebGPU/WebGPU/RenderPassEncoder.mm b/Source/WebGPU/WebGPU/RenderPassEncoder.mm index 12eef5366bc3..620b6d36813b 100644 --- a/Source/WebGPU/WebGPU/RenderPassEncoder.mm +++ b/Source/WebGPU/WebGPU/RenderPassEncoder.mm @@ -771,7 +771,8 @@ static void setViewportMinMaxDepthIntoBuffer(auto& fragmentDynamicOffsets, float uint32_t indexSizeInBytes = indexType == MTLIndexTypeUInt16 ? sizeof(uint16_t) : sizeof(uint32_t); uint32_t firstIndex = indexBufferOffsetInBytes / indexSizeInBytes; - if (!minVertexCount || !minInstanceCount || indexBufferOffsetInBytes >= indexBuffer.length || -baseVertex > static_cast(minVertexCount) || baseVertex > static_cast(minVertexCount)) + int64_t baseVertex64 = baseVertex; + if (!minVertexCount || !minInstanceCount || indexBufferOffsetInBytes >= indexBuffer.length || -baseVertex64 > static_cast(minVertexCount) || baseVertex64 > static_cast(minVertexCount)) return DrawIndexResult { IndexCall::Skip, nil, 0 }; auto primitiveOffset = primitiveType == MTLPrimitiveTypeLineStrip || primitiveType == MTLPrimitiveTypeTriangleStrip ? 1u : 0u; diff --git a/Source/WebGPU/WebGPU/RenderPipeline.mm b/Source/WebGPU/WebGPU/RenderPipeline.mm index 599d0f0efbc9..8e3e83bcae6c 100644 --- a/Source/WebGPU/WebGPU/RenderPipeline.mm +++ b/Source/WebGPU/WebGPU/RenderPipeline.mm @@ -1753,7 +1753,7 @@ static uint32_t NODELETE componentsForDataType(MTLDataType dataType) if (error) return returnInvalidRenderPipeline(*this, isAsync, error.localizedDescription); - if (m_renderPipelineId == Device::maxPipelines) { + if (m_pipelineId == Device::maxPipelines) { loseTheDevice(WGPUDeviceLostReason_Undefined); return returnInvalidRenderPipeline(*this, isAsync, @"too many render pipelines"); } @@ -1762,10 +1762,10 @@ static uint32_t NODELETE componentsForDataType(MTLDataType dataType) if (!generatedPipelineLayout->isValid()) return returnInvalidRenderPipeline(*this, isAsync, "Generated pipeline layout is not valid"_s); - return std::make_pair(RenderPipeline::create(mtlPrimitiveType, mtlIndexType, mtlFrontFace, mtlCullMode, mtlDepthClipMode, depthStencilDescriptor, WTF::move(generatedPipelineLayout), depthBias, depthBiasSlopeScale, depthBiasClamp, sampleMask, mtlRenderPipelineDescriptor, colorAttachmentCount, descriptor, WTF::move(requiredBufferIndices), WTF::move(minimumBufferSizes), ++m_renderPipelineId, vertexShaderBindingCount, *this), nil); + return std::make_pair(RenderPipeline::create(mtlPrimitiveType, mtlIndexType, mtlFrontFace, mtlCullMode, mtlDepthClipMode, depthStencilDescriptor, WTF::move(generatedPipelineLayout), depthBias, depthBiasSlopeScale, depthBiasClamp, sampleMask, mtlRenderPipelineDescriptor, colorAttachmentCount, descriptor, WTF::move(requiredBufferIndices), WTF::move(minimumBufferSizes), ++m_pipelineId, vertexShaderBindingCount, *this), nil); } - return std::make_pair(RenderPipeline::create(mtlPrimitiveType, mtlIndexType, mtlFrontFace, mtlCullMode, mtlDepthClipMode, depthStencilDescriptor, const_cast(*pipelineLayout), depthBias, depthBiasSlopeScale, depthBiasClamp, sampleMask, mtlRenderPipelineDescriptor, colorAttachmentCount, descriptor, WTF::move(requiredBufferIndices), WTF::move(minimumBufferSizes), ++m_renderPipelineId, vertexShaderBindingCount, *this), nil); + return std::make_pair(RenderPipeline::create(mtlPrimitiveType, mtlIndexType, mtlFrontFace, mtlCullMode, mtlDepthClipMode, depthStencilDescriptor, const_cast(*pipelineLayout), depthBias, depthBiasSlopeScale, depthBiasClamp, sampleMask, mtlRenderPipelineDescriptor, colorAttachmentCount, descriptor, WTF::move(requiredBufferIndices), WTF::move(minimumBufferSizes), ++m_pipelineId, vertexShaderBindingCount, *this), nil); } void Device::createRenderPipelineAsync(const WGPURenderPipelineDescriptor& descriptor, CompletionHandler&&, String&& message)>&& callback) diff --git a/Source/WebKit/GPUProcess/graphics/RemoteGraphicsContextGL.cpp b/Source/WebKit/GPUProcess/graphics/RemoteGraphicsContextGL.cpp index 6ace2f95a57e..3aeee1a0ea91 100644 --- a/Source/WebKit/GPUProcess/graphics/RemoteGraphicsContextGL.cpp +++ b/Source/WebKit/GPUProcess/graphics/RemoteGraphicsContextGL.cpp @@ -293,17 +293,14 @@ void RemoteGraphicsContextGL::getBufferSubDataInline(uint32_t target, uint64_t o return; } - MallocSpan bufferStore; - std::span bufferData; - bufferStore = MallocSpan::tryMalloc(dataSize); - if (bufferStore) { - bufferData = bufferStore.mutableSpan(); - if (!context->getBufferSubDataWithStatus(target, offset, bufferData)) - bufferData = { }; + MallocSpan buffer = MallocSpan::tryMalloc(dataSize); + if (buffer) { + if (!context->getBufferSubDataWithStatus(target, offset, buffer.mutableSpan())) + buffer = { }; } else context->addError(GCGLErrorCode::OutOfMemory); - completionHandler(bufferData); + completionHandler(buffer.span()); } void RemoteGraphicsContextGL::getBufferSubDataSharedMemory(uint32_t target, uint64_t offset, uint64_t dataSize, WebCore::SharedMemory::Handle handle, CompletionHandler&& completionHandler) @@ -340,25 +337,19 @@ void RemoteGraphicsContextGL::readPixelsInline(WebCore::IntRect rect, uint32_t f completionHandler(std::nullopt, { }); return; } - MallocSpan pixelsStore; - std::span pixels; - if (replyImageBytes && replyImageBytes <= readPixelsInlineSizeLimit) { - pixelsStore = MallocSpan::tryMalloc(replyImageBytes); - if (pixelsStore) - pixels = pixelsStore.mutableSpan(); - } + MallocSpan pixels; + if (replyImageBytes && replyImageBytes <= readPixelsInlineSizeLimit) + pixels = MallocSpan::tryZeroedMalloc(replyImageBytes); RefPtr context = m_context; std::optional readArea; - if (pixels.size() == replyImageBytes) - readArea = context->readPixelsWithStatus(rect, format, type, packReverseRowOrder, pixels); + if (pixels.sizeInBytes() == replyImageBytes) + readArea = context->readPixelsWithStatus(rect, format, type, packReverseRowOrder, pixels.mutableSpan()); else context->addError(GCGLErrorCode::OutOfMemory); - if (!readArea) { + if (!readArea) pixels = { }; - pixelsStore = { }; - } - completionHandler(readArea, pixels); + completionHandler(readArea, pixels.span()); } diff --git a/Source/WebKit/GPUProcess/webrtc/UserMediaCaptureManagerProxy.cpp b/Source/WebKit/GPUProcess/webrtc/UserMediaCaptureManagerProxy.cpp index 074baf3e09aa..9d2a2f433b78 100644 --- a/Source/WebKit/GPUProcess/webrtc/UserMediaCaptureManagerProxy.cpp +++ b/Source/WebKit/GPUProcess/webrtc/UserMediaCaptureManagerProxy.cpp @@ -234,7 +234,7 @@ class UserMediaCaptureManagerProxySourceProxy final bool isObservingMedia = protectedThis->isObservingMedia(); protectedThis->unobserveMedia(); - source->applyConstraints(WTF::move(constraints), [weakThis = WTF::move(weakThis), &constraints, isObservingMedia, callback = WTF::move(callback)](auto&& error) mutable { + source->applyConstraints(constraints, [weakThis = WTF::move(weakThis), constraints, isObservingMedia, callback = WTF::move(callback)](auto&& error) mutable { RefPtr protectedThis = weakThis.get(); if (!protectedThis) { callback(RealtimeMediaSource::ApplyConstraintsError { { }, { } }); diff --git a/Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.cpp b/Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.cpp index df275bb0c5ac..96157f4ae9f9 100644 --- a/Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.cpp +++ b/Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.cpp @@ -416,7 +416,7 @@ void WebResourceLoadStatisticsStore::callHasStorageAccessForFrameHandler(const R callback(false); } -void WebResourceLoadStatisticsStore::requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, FrameIdentifier frameID, PageIdentifier webPageID, WebPageProxyIdentifier webPageProxyID, StorageAccessScope scope, HasOrShouldIgnoreUserGesture hasOrShouldIgnoreUserGesture, CompletionHandler&& completionHandler) +void WebResourceLoadStatisticsStore::requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, FrameIdentifier frameID, PageIdentifier webPageID, WebPageProxyIdentifier webPageProxyID, StorageAccessScope scope, HasUserGestureOrNoUserGestureRequired hasUserGestureOrNoUserGestureRequired, CompletionHandler&& completionHandler) { ASSERT(RunLoop::isMain()); @@ -425,7 +425,7 @@ void WebResourceLoadStatisticsStore::requestStorageAccess(RegistrableDomain&& su return; } - if (hasOrShouldIgnoreUserGesture == HasOrShouldIgnoreUserGesture::No) { + if (hasUserGestureOrNoUserGestureRequired == HasUserGestureOrNoUserGestureRequired::No) { auto it = m_domainsGrantedStorageAccessPermissionInPage.find(webPageProxyID); if (it == m_domainsGrantedStorageAccessPermissionInPage.end() || !it->value.contains({ topFrameDomain, subFrameDomain })) return completionHandler({ StorageAccessWasGranted::No, StorageAccessPromptWasShown::No, scope, topFrameDomain, subFrameDomain }); diff --git a/Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.h b/Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.h index 27144ad291df..f8622ac1885f 100644 --- a/Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.h +++ b/Source/WebKit/NetworkProcess/Classifier/WebResourceLoadStatisticsStore.h @@ -149,7 +149,7 @@ class WebResourceLoadStatisticsStore final : public ThreadSafeRefCountedAndCanMa void hasHadUserInteraction(RegistrableDomain&&, CompletionHandler&&); void hasStorageAccess(SubFrameDomain&&, TopFrameDomain&&, std::optional, WebCore::PageIdentifier, CompletionHandler&&); bool hasStorageAccessForFrame(const SubFrameDomain&, const TopFrameDomain&, WebCore::FrameIdentifier, WebCore::PageIdentifier); - void requestStorageAccess(SubFrameDomain&&, TopFrameDomain&&, WebCore::FrameIdentifier, WebCore::PageIdentifier, WebPageProxyIdentifier, StorageAccessScope, WebCore::HasOrShouldIgnoreUserGesture, CompletionHandler&&); + void requestStorageAccess(SubFrameDomain&&, TopFrameDomain&&, WebCore::FrameIdentifier, WebCore::PageIdentifier, WebPageProxyIdentifier, StorageAccessScope, WebCore::HasUserGestureOrNoUserGestureRequired, CompletionHandler&&); void queryStorageAccessPermission(SubFrameDomain&&, TopFrameDomain&&, std::optional, CompletionHandler&&); void setLoginStatus(RegistrableDomain&&, IsLoggedIn, std::optional&&, CompletionHandler&&); void isLoggedIn(RegistrableDomain&&, CompletionHandler&&); diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp index 17eb67e55342..df064b1aadf4 100644 --- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp +++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp @@ -669,6 +669,7 @@ void NetworkConnectionToWebProcess::testProcessIncomingSyncMessagesWhenWaitingFo void NetworkConnectionToWebProcess::loadPing(NetworkResourceLoadParameters&& loadParameters) { + MESSAGE_CHECK(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, loadParameters.request.firstPartyForCookies()) == NetworkProcess::AllowCookieAccess::Allow); CONNECTION_RELEASE_LOG(Loading, "loadPing: (parentPID=%d, pageProxyID=%" PRIu64 ", webPageID=%" PRIu64 ", frameID=%" PRIu64 ", resourceID=%" PRIu64 ")", loadParameters.parentPID, loadParameters.webPageProxyID.toUInt64(), loadParameters.webPageID.toUInt64(), loadParameters.webFrameID.toUInt64(), loadParameters.identifier ? loadParameters.identifier->toUInt64() : 0); auto completionHandler = [connection = m_connection, identifier = *loadParameters.identifier] (const ResourceError& error, const ResourceResponse& response) { @@ -1151,6 +1152,7 @@ static bool shouldCheckBlobFileAccess() #endif } +// FIXME: (rdar://176402219) The web process should not send file paths to the network process. File paths should come from the UI process. void NetworkConnectionToWebProcess::registerInternalFileBlobURL(const URL& url, const String& path, const String& replacementPath, SandboxExtension::Handle&& extensionHandle, const String& contentType) { MESSAGE_CHECK(!url.isEmpty()); @@ -1168,14 +1170,12 @@ void NetworkConnectionToWebProcess::registerInternalFileBlobURL(const URL& url, // For transcoded files, check if the WebProcess has actual sandbox access // via the extension granted for the original file, rather than checking // our internal allowed paths list (which won't include temporary transcoded files). - if (sandboxExtension) { - // sandbox_check returns 0 on success (has access), non-zero on failure - if (sandbox_check(m_connection->remoteProcessID(), "file-read-data", static_cast(SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT), FileSystem::fileSystemRepresentation(replacementPath).data())) { - CONNECTION_RELEASE_LOG_ERROR(Sandbox, "registerInternalFileBlobURL: WebProcess does not have sandbox access to replacementPath"); - MESSAGE_CHECK(false); - } - } else // No sandbox extension provided, fall back to path allowlist check - MESSAGE_CHECK(isFilePathAllowed(*session, replacementPath)); + MESSAGE_CHECK(sandboxExtension); + // sandbox_check returns 0 on success (has access), non-zero on failure + if (sandbox_check(m_connection->remoteProcessID(), "file-read-data", static_cast(SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT), FileSystem::fileSystemRepresentation(replacementPath).data())) { + CONNECTION_RELEASE_LOG_ERROR(Sandbox, "registerInternalFileBlobURL: WebProcess does not have sandbox access to replacementPath"); + MESSAGE_CHECK(false); + } #else MESSAGE_CHECK(isFilePathAllowed(*session, replacementPath)); #endif @@ -1341,14 +1341,32 @@ void NetworkConnectionToWebProcess::removeStorageAccessForFrame(FrameIdentifier void NetworkConnectionToWebProcess::logUserInteraction(RegistrableDomain&& domain) { + MESSAGE_CHECK(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, domain) == NetworkProcess::AllowCookieAccess::Allow); + if (CheckedPtr networkSession = this->networkSession()) { if (RefPtr resourceLoadStatistics = networkSession->resourceLoadStatistics()) resourceLoadStatistics->logUserInteraction(WTF::move(domain), [] { }); } } +// Validate that the WebContent process is not setting fields it has no authority over. The WCP-side ResourceLoadObserver +// never populates these; only the network grocess grant/classification paths should write them. +static bool resourceLoadStatisticsContainsOnlyObservableFields(const ResourceLoadStatistics& statistics) +{ + return statistics.storageAccessUnderTopFrameDomains.isEmpty() + && !statistics.grandfathered + && !statistics.isPrevalentResource + && !statistics.isVeryPrevalentResource + && !statistics.dataRecordsRemoved + && !statistics.timesAccessedAsFirstPartyDueToUserInteraction + && !statistics.timesAccessedAsFirstPartyDueToStorageAccessAPI; +} + void NetworkConnectionToWebProcess::resourceLoadStatisticsUpdated(Vector&& statistics, CompletionHandler&& completionHandler) { + for (auto& statistic : statistics) + MESSAGE_CHECK_COMPLETION(resourceLoadStatisticsContainsOnlyObservableFields(statistic), completionHandler()); + if (CheckedPtr networkSession = this->networkSession()) { if (networkSession->sessionID().isEphemeral()) { completionHandler(); @@ -1376,11 +1394,11 @@ void NetworkConnectionToWebProcess::hasStorageAccess(RegistrableDomain&& subFram completionHandler(false); } -void NetworkConnectionToWebProcess::requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, FrameIdentifier frameID, PageIdentifier webPageID, WebPageProxyIdentifier webPageProxyID, StorageAccessScope scope, HasOrShouldIgnoreUserGesture hasOrShouldIgnoreUserGesture, CompletionHandler&& completionHandler) +void NetworkConnectionToWebProcess::requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, FrameIdentifier frameID, PageIdentifier webPageID, WebPageProxyIdentifier webPageProxyID, StorageAccessScope scope, HasUserGestureOrNoUserGestureRequired hasUserGestureOrNoUserGestureRequired, CompletionHandler&& completionHandler) { if (CheckedPtr networkSession = this->networkSession()) { if (RefPtr resourceLoadStatistics = networkSession->resourceLoadStatistics()) { - resourceLoadStatistics->requestStorageAccess(WTF::move(subFrameDomain), WTF::move(topFrameDomain), frameID, webPageID, webPageProxyID, scope, hasOrShouldIgnoreUserGesture, WTF::move(completionHandler)); + resourceLoadStatistics->requestStorageAccess(WTF::move(subFrameDomain), WTF::move(topFrameDomain), frameID, webPageID, webPageProxyID, scope, hasUserGestureOrNoUserGestureRequired, WTF::move(completionHandler)); return; } } @@ -1436,6 +1454,9 @@ void NetworkConnectionToWebProcess::storageAccessQuirkForTopFrameDomain(URL&& to void NetworkConnectionToWebProcess::requestStorageAccessUnderOpener(WebCore::RegistrableDomain&& domainInNeedOfStorageAccess, PageIdentifier openerPageID, WebCore::RegistrableDomain&& openerDomain) { + MESSAGE_CHECK(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, domainInNeedOfStorageAccess) == NetworkProcess::AllowCookieAccess::Allow); + MESSAGE_CHECK(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, openerDomain) == NetworkProcess::AllowCookieAccess::Allow); + if (CheckedPtr networkSession = this->networkSession()) { if (RefPtr resourceLoadStatistics = networkSession->resourceLoadStatistics()) resourceLoadStatistics->requestStorageAccessUnderOpener(WTF::move(domainInNeedOfStorageAccess), openerPageID, WTF::move(openerDomain)); @@ -1578,9 +1599,12 @@ size_t NetworkConnectionToWebProcess::findNetworkActivityTracker(WebCore::Resour void NetworkConnectionToWebProcess::establishSharedWorkerContextConnection(WebPageProxyIdentifier, WebCore::Site&& site, WebCore::CrossOriginEmbedderPolicyValue crossOriginEmbedderPolicy, CompletionHandler&& completionHandler) { CONNECTION_RELEASE_LOG(SharedWorker, "establishSharedWorkerContextConnection:"); - CheckedPtr session = networkSession(); - if (CheckedPtr swServer = session ? session->sharedWorkerServer() : nullptr) - m_sharedWorkerContextConnection = WebSharedWorkerServerToContextConnection::create(*this, WTF::move(site), *swServer, crossOriginEmbedderPolicy); + if (CheckedPtr session = networkSession()) { + auto allowCookieAccess = session->networkProcess().allowsFirstPartyForCookies(webProcessIdentifier(), site.domain()); + MESSAGE_CHECK_COMPLETION(allowCookieAccess != NetworkProcess::AllowCookieAccess::Terminate, completionHandler()); + if (CheckedPtr swServer = session->sharedWorkerServer()) + m_sharedWorkerContextConnection = WebSharedWorkerServerToContextConnection::create(*this, WTF::move(site), *swServer, crossOriginEmbedderPolicy); + } completionHandler(); } @@ -1829,8 +1853,12 @@ void NetworkConnectionToWebProcess::installMockContentFilter(WebCore::MockConten void NetworkConnectionToWebProcess::useRedirectionForCurrentNavigation(WebCore::ResourceLoaderIdentifier identifier, WebCore::ResourceResponse&& response) { - if (RefPtr loader = m_networkResourceLoaders.get(identifier)) - loader->useRedirectionForCurrentNavigation(WTF::move(response)); + MESSAGE_CHECK(response.isRedirection()); + RefPtr loader = m_networkResourceLoaders.get(identifier); + if (!loader) + return; + MESSAGE_CHECK(loader->isMainFrameLoad()); + loader->useRedirectionForCurrentNavigation(WTF::move(response)); } #if ENABLE(DECLARATIVE_WEB_PUSH) diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h index 20ab2d499c49..fda15c3bcac9 100644 --- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h +++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h @@ -407,7 +407,7 @@ class NetworkConnectionToWebProcess final void logUserInteraction(RegistrableDomain&&); void resourceLoadStatisticsUpdated(Vector&&, CompletionHandler&&); void hasStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, WebCore::FrameIdentifier, WebCore::PageIdentifier, CompletionHandler&&); - void requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, WebCore::FrameIdentifier, WebCore::PageIdentifier, WebPageProxyIdentifier, WebCore::StorageAccessScope, WebCore::HasOrShouldIgnoreUserGesture, CompletionHandler&&); + void requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, WebCore::FrameIdentifier, WebCore::PageIdentifier, WebPageProxyIdentifier, WebCore::StorageAccessScope, WebCore::HasUserGestureOrNoUserGestureRequired, CompletionHandler&&); void queryStorageAccessPermission(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, std::optional, CompletionHandler&&); void storageAccessQuirkForTopFrameDomain(URL&& topFrameURL, CompletionHandler)>&&); void requestStorageAccessUnderOpener(WebCore::RegistrableDomain&& domainInNeedOfStorageAccess, WebCore::PageIdentifier openerPageID, WebCore::RegistrableDomain&& openerDomain); diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in index 8228094b0fab..cc292e637bc5 100644 --- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in +++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in @@ -89,7 +89,7 @@ messages -> NetworkConnectionToWebProcess WantsDispatchMessage { LogUserInteraction(WebCore::RegistrableDomain domain) ResourceLoadStatisticsUpdated(Vector statistics) -> () [EnabledBy=StorageAccessAPIEnabled] HasStorageAccess(WebCore::RegistrableDomain subFrameDomain, WebCore::RegistrableDomain topFrameDomain, WebCore::FrameIdentifier frameID, WebCore::PageIdentifier pageID) -> (bool hasStorageAccess) - [EnabledBy=StorageAccessAPIEnabled] RequestStorageAccess(WebCore::RegistrableDomain subFrameDomain, WebCore::RegistrableDomain topFrameDomain, WebCore::FrameIdentifier frameID, WebCore::PageIdentifier webPageID, WebKit::WebPageProxyIdentifier webPageProxyID, enum:bool WebCore::StorageAccessScope scope, enum:bool WebCore::HasOrShouldIgnoreUserGesture hasOrShouldIgnoreUserGesture) -> (struct WebCore::RequestStorageAccessResult result) + [EnabledBy=StorageAccessAPIEnabled] RequestStorageAccess(WebCore::RegistrableDomain subFrameDomain, WebCore::RegistrableDomain topFrameDomain, WebCore::FrameIdentifier frameID, WebCore::PageIdentifier webPageID, WebKit::WebPageProxyIdentifier webPageProxyID, enum:bool WebCore::StorageAccessScope scope, enum:bool WebCore::HasUserGestureOrNoUserGestureRequired hasUserGestureOrNoUserGestureRequired) -> (struct WebCore::RequestStorageAccessResult result) [EnabledBy=StorageAccessAPIEnabled] QueryStorageAccessPermission(WebCore::RegistrableDomain subFrameDomain, WebCore::RegistrableDomain topFrameDomain, std::optional webPageProxyID) -> (enum:uint8_t WebCore::PermissionState permissionState) [EnabledBy=StorageAccessAPIEnabled] StorageAccessQuirkForTopFrameDomain(URL topFrameURL) -> (Vector domains) [EnabledBy=StorageAccessAPIEnabled] RequestStorageAccessUnderOpener(WebCore::RegistrableDomain domainInNeedOfStorageAccess, WebCore::PageIdentifier openerPageID, WebCore::RegistrableDomain openerDomain) diff --git a/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.h b/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.h index accdf25d87f0..a03e3d77f978 100644 --- a/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.h +++ b/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.h @@ -90,7 +90,6 @@ struct NetworkResourceLoadParameters { WebCore::CrossOriginEmbedderPolicy parentCrossOriginEmbedderPolicy { }; WebCore::CrossOriginEmbedderPolicy crossOriginEmbedderPolicy { }; WebCore::HTTPHeaderMap originalRequestHeaders { }; - bool shouldRestrictHTTPResponseAccess { false }; WebCore::PreflightPolicy preflightPolicy { WebCore::PreflightPolicy::Consider }; bool shouldEnableCrossOriginResourcePolicy { false }; Vector> frameAncestorOrigins { }; diff --git a/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.serialization.in b/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.serialization.in index 25cfa9505188..a06e4fd47de2 100644 --- a/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.serialization.in +++ b/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.serialization.in @@ -66,8 +66,6 @@ enum class WebKit::NavigatingToAppBoundDomain : bool; WebCore::CrossOriginEmbedderPolicy crossOriginEmbedderPolicy; WebCore::HTTPHeaderMap originalRequestHeaders; - bool shouldRestrictHTTPResponseAccess; - WebCore::PreflightPolicy preflightPolicy; bool shouldEnableCrossOriginResourcePolicy; diff --git a/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp b/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp index e4055d4dadb0..fcfbb195411b 100644 --- a/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp +++ b/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp @@ -156,23 +156,21 @@ NetworkResourceLoader::NetworkResourceLoader(NetworkResourceLoadParameters&& par if (CheckedPtr session = connection.networkProcess().networkSession(sessionID())) m_cache = session->cache(); - if (synchronousReply || m_parameters.shouldRestrictHTTPResponseAccess || m_parameters.options.keepAlive) { - NetworkLoadChecker::LoadType requestLoadType = isMainFrameLoad() ? NetworkLoadChecker::LoadType::MainFrame : NetworkLoadChecker::LoadType::Other; - m_networkLoadChecker = NetworkLoadChecker::create(Ref { connection.networkProcess() }.get(), this, &connection.schemeRegistry(), FetchOptions { m_parameters.options }, - sessionID(), webPageProxyID(), HTTPHeaderMap { m_parameters.originalRequestHeaders }, URL { m_parameters.request.url() }, - URL { m_parameters.documentURL }, m_parameters.sourceOrigin.copyRef(), m_parameters.topOrigin.copyRef(), m_parameters.parentOrigin(), - m_parameters.preflightPolicy, originalRequest().httpReferrer(), m_parameters.allowPrivacyProxy, m_parameters.advancedPrivacyProtections, - shouldCaptureExtraNetworkLoadMetrics(), requestLoadType); - - RefPtr networkLoadChecker = m_networkLoadChecker; - if (m_parameters.cspResponseHeaders) - networkLoadChecker->setCSPResponseHeaders(ContentSecurityPolicyResponseHeaders { m_parameters.cspResponseHeaders.value() }); - networkLoadChecker->setParentCrossOriginEmbedderPolicy(m_parameters.parentCrossOriginEmbedderPolicy); - networkLoadChecker->setCrossOriginEmbedderPolicy(m_parameters.crossOriginEmbedderPolicy); + NetworkLoadChecker::LoadType requestLoadType = isMainFrameLoad() ? NetworkLoadChecker::LoadType::MainFrame : NetworkLoadChecker::LoadType::Other; + m_networkLoadChecker = NetworkLoadChecker::create(Ref { connection.networkProcess() }.get(), this, &connection.schemeRegistry(), FetchOptions { m_parameters.options }, + sessionID(), webPageProxyID(), HTTPHeaderMap { m_parameters.originalRequestHeaders }, URL { m_parameters.request.url() }, + URL { m_parameters.documentURL }, m_parameters.sourceOrigin.copyRef(), m_parameters.topOrigin.copyRef(), m_parameters.parentOrigin(), + m_parameters.preflightPolicy, originalRequest().httpReferrer(), m_parameters.allowPrivacyProxy, m_parameters.advancedPrivacyProtections, + shouldCaptureExtraNetworkLoadMetrics(), requestLoadType); + + RefPtr networkLoadChecker = m_networkLoadChecker; + if (m_parameters.cspResponseHeaders) + networkLoadChecker->setCSPResponseHeaders(ContentSecurityPolicyResponseHeaders { m_parameters.cspResponseHeaders.value() }); + networkLoadChecker->setParentCrossOriginEmbedderPolicy(m_parameters.parentCrossOriginEmbedderPolicy); + networkLoadChecker->setCrossOriginEmbedderPolicy(m_parameters.crossOriginEmbedderPolicy); #if ENABLE(CONTENT_EXTENSIONS) - networkLoadChecker->setContentExtensionController(URL { m_parameters.mainDocumentURL }, URL { m_parameters.frameURL }, m_parameters.userContentControllerIdentifier); + networkLoadChecker->setContentExtensionController(URL { m_parameters.mainDocumentURL }, URL { m_parameters.frameURL }, m_parameters.userContentControllerIdentifier); #endif - } if (synchronousReply) m_synchronousLoadData = makeUnique(WTF::move(synchronousReply)); } @@ -1507,9 +1505,6 @@ static bool shouldSanitizeResponse(const NetworkProcess& process, std::optional< ResourceResponse NetworkResourceLoader::sanitizeResponseIfPossible(ResourceResponse&& response, ResourceResponse::SanitizationType type) { - if (!m_parameters.shouldRestrictHTTPResponseAccess) - return WTF::move(response); - if (shouldSanitizeResponse(Ref { m_connection->networkProcess() }.get(), pageID(), parameters().options, originalRequest().url())) response.sanitizeHTTPHeaderFields(type); @@ -1544,6 +1539,12 @@ void NetworkResourceLoader::continueWillSendRequest(ResourceRequest&& newRequest { LOADER_RELEASE_LOG("continueWillSendRequest: (isAllowedToAskUserForCredentials=%d)", isAllowedToAskUserForCredentials); + // If there is a match in the network cache, we need to reuse the original cache policy and partition. + // This must happen before any branch below that may store a redirect in the disk cache, otherwise a + // compromised WebContent process could poison the cache for an arbitrary partition. + newRequest.setCachePolicy(originalRequest().cachePolicy()); + newRequest.setShouldBlockThirdPartyStorage(originalRequest().shouldBlockThirdPartyStorage()); + if (m_redirectionForCurrentNavigation) { LOADER_RELEASE_LOG("continueWillSendRequest: using stored redirect response"); auto redirection = std::exchange(m_redirectionForCurrentNavigation, { }); @@ -1605,10 +1606,6 @@ void NetworkResourceLoader::continueWillSendRequest(ResourceRequest&& newRequest m_isAllowedToAskUserForCredentials = isAllowedToAskUserForCredentials; - // If there is a match in the network cache, we need to reuse the original cache policy and partition. - newRequest.setCachePolicy(originalRequest().cachePolicy()); - newRequest.setShouldBlockThirdPartyStorage(originalRequest().shouldBlockThirdPartyStorage()); - if (m_isWaitingContinueWillSendRequestForCachedRedirect) { m_isWaitingContinueWillSendRequestForCachedRedirect = false; diff --git a/Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServer.cpp b/Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServer.cpp index c3bbbdc0c738..cf085c237ffd 100644 --- a/Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServer.cpp +++ b/Source/WebKit/NetworkProcess/SharedWorker/WebSharedWorkerServer.cpp @@ -183,9 +183,9 @@ void WebSharedWorkerServer::addContextConnection(WebSharedWorkerServerToContextC RELEASE_LOG(SharedWorker, "WebSharedWorkerServer::addContextConnection(%p) webProcessIdentifier=%" PRIu64, &contextConnection, contextConnection.webProcessIdentifier() ? contextConnection.webProcessIdentifier()->toUInt64() : 0); ContextConnectionKey key { contextConnection.registrableDomain(), contextConnection.crossOriginEmbedderPolicyValue() }; - ASSERT(!m_contextConnections.contains(key)); - - m_contextConnections.add(key, contextConnection); + auto result = m_contextConnections.add(key, contextConnection); + if (!result.isNewEntry) + return; contextConnectionCreated(contextConnection); } diff --git a/Source/WebKit/NetworkProcess/storage/NetworkStorageManager.cpp b/Source/WebKit/NetworkProcess/storage/NetworkStorageManager.cpp index 635c30272863..25ee7e11ce95 100644 --- a/Source/WebKit/NetworkProcess/storage/NetworkStorageManager.cpp +++ b/Source/WebKit/NetworkProcess/storage/NetworkStorageManager.cpp @@ -2045,8 +2045,16 @@ void NetworkStorageManager::deleteDatabase(IPC::Connection& connection, const We void NetworkStorageManager::establishTransaction(IPC::Connection& ipcConnection, WebCore::IDBDatabaseConnectionIdentifier databaseConnectionIdentifier, const WebCore::IDBTransactionInfo& transactionInfo) { - if (RefPtr connection = m_idbStorageRegistry->connection(databaseConnectionIdentifier, ipcConnection)) - connection->establishTransaction(transactionInfo); + RefPtr databaseConnection = m_idbStorageRegistry->connection(databaseConnectionIdentifier, ipcConnection); + if (!databaseConnection) + return; + + // FIXME: consider converting this early return to MESSAGE_CHECK. + CheckedPtr database = databaseConnection->database(); + if (database && database->isVersionChangeTransactionActive(*databaseConnection)) + return; + + databaseConnection->establishTransaction(transactionInfo); } void NetworkStorageManager::databaseConnectionPendingClose(IPC::Connection& ipcConnection, WebCore::IDBDatabaseConnectionIdentifier databaseConnectionIdentifier) diff --git a/Source/WebKit/Scripts/process-entitlements.sh b/Source/WebKit/Scripts/process-entitlements.sh index 3c47c4d486e0..1dd610b479b5 100755 --- a/Source/WebKit/Scripts/process-entitlements.sh +++ b/Source/WebKit/Scripts/process-entitlements.sh @@ -90,7 +90,6 @@ function mac_process_gpu_entitlements() then plistbuddy Add :com.apple.security.hardened-process.checked-allocations.no-tagged-receive bool YES fi - plistbuddy Add :com.apple.security.hardened-process.checked-allocations.soft-mode bool YES # FIXME: Should be removed before release fi } @@ -140,7 +139,6 @@ function mac_process_network_entitlements() then plistbuddy Add :com.apple.security.hardened-process.checked-allocations.no-tagged-receive bool YES fi - plistbuddy Add :com.apple.security.hardened-process.checked-allocations.soft-mode bool YES # FIXME: Should be removed before release # FIXME: This should be removed after crash investigation as part of plistbuddy Add :com.apple.private.get-system-corpse bool YES @@ -232,7 +230,6 @@ function mac_process_webcontent_shared_entitlements() then plistbuddy Add :com.apple.security.hardened-process.checked-allocations.no-tagged-receive bool YES fi - plistbuddy Add :com.apple.security.hardened-process.checked-allocations.soft-mode bool YES # FIXME: Should be removed before release plistbuddy Add :com.apple.private.webkit.use-xpc-endpoint bool YES plistbuddy Add :com.apple.rootless.storage.WebKitWebContentSandbox bool YES @@ -292,11 +289,6 @@ function maccatalyst_process_webcontent_shared_entitlements() plistbuddy Add :com.apple.private.webkit.use-xpc-endpoint bool YES plistbuddy Add :com.apple.runningboard.assertions.webkit bool YES - if (( "${TARGET_MAC_OS_X_VERSION_MAJOR}" >= 260000 )) - then - plistbuddy Add :com.apple.security.hardened-process.checked-allocations.soft-mode bool YES # FIXME: Should be removed before release - fi - if [[ "${WK_USE_FATAL_EXCEPTIONS}" == YES ]] then plistbuddy Add :com.apple.security.fatal-exceptions array @@ -365,7 +357,6 @@ function maccatalyst_process_gpu_entitlements() plistbuddy Add :com.apple.QuartzCore.webkit-limited-types bool YES plistbuddy Add :com.apple.private.coremedia.allow-fps-attachment bool YES plistbuddy Add :com.apple.developer.hardened-process bool YES - plistbuddy Add :com.apple.security.hardened-process.checked-allocations.soft-mode bool YES # FIXME: Should be removed before release if [[ "${WK_USE_FATAL_EXCEPTIONS}" == YES ]] then @@ -391,7 +382,6 @@ function maccatalyst_process_network_entitlements() plistbuddy Add :com.apple.runningboard.assertions.webkit bool YES plistbuddy Add :com.apple.private.webkit.use-xpc-endpoint bool YES plistbuddy Add :com.apple.developer.hardened-process bool YES - plistbuddy Add :com.apple.security.hardened-process.checked-allocations.soft-mode bool YES # FIXME: Should be removed before release plistbuddy Add :com.apple.private.tcc.manager.check-by-audit-token array plistbuddy Add :com.apple.private.tcc.manager.check-by-audit-token:0 string kTCCServiceWebKitIntelligentTrackingPrevention @@ -451,7 +441,6 @@ function ios_family_process_webcontent_shared_entitlements() plistbuddy add :com.apple.coreaudio.allow-vorbis-decode bool YES plistbuddy Add :com.apple.developer.hardened-process bool YES plistbuddy Add :com.apple.security.hardened-process.checked-allocations.no-tagged-receive bool YES - plistbuddy Add :com.apple.security.hardened-process.checked-allocations.soft-mode bool YES # FIXME: Should be removed before release if [[ "${WK_USE_FATAL_EXCEPTIONS}" == YES ]] then @@ -569,7 +558,6 @@ function ios_family_process_gpu_entitlements() plistbuddy Add :com.apple.developer.hardened-process bool YES plistbuddy Add :com.apple.security.hardened-process.checked-allocations.no-tagged-receive bool YES - plistbuddy Add :com.apple.security.hardened-process.checked-allocations.soft-mode bool YES # FIXME: Should be removed before release plistbuddy Add :com.apple.developer.kernel.extended-virtual-addressing bool YES } @@ -590,7 +578,6 @@ function ios_family_process_model_entitlements() plistbuddy Add :com.apple.private.pac.exception bool YES fi plistbuddy Add :com.apple.security.hardened-process.checked-allocations.no-tagged-receive bool YES - plistbuddy Add :com.apple.security.hardened-process.checked-allocations.soft-mode bool YES # FIXME: Should be removed before release } function ios_family_process_adattributiond_entitlements() @@ -663,7 +650,6 @@ function ios_family_process_network_entitlements() plistbuddy Add :com.apple.private.assets.accessible-asset-types:0 string com.apple.MobileAsset.WebContentRestrictions plistbuddy Add :com.apple.developer.hardened-process bool YES plistbuddy Add :com.apple.security.hardened-process.checked-allocations.no-tagged-receive bool YES - plistbuddy Add :com.apple.security.hardened-process.checked-allocations.soft-mode bool YES # FIXME: Should be removed before release plistbuddy Add :com.apple.private.security.mutable-state-flags array plistbuddy Add :com.apple.private.security.mutable-state-flags:0 string BlockNetworkAccess diff --git a/Source/WebKit/Scripts/webkit/messages.py b/Source/WebKit/Scripts/webkit/messages.py index bc1b7244a963..3d997b234e25 100644 --- a/Source/WebKit/Scripts/webkit/messages.py +++ b/Source/WebKit/Scripts/webkit/messages.py @@ -1278,7 +1278,7 @@ def headers_for_type(type, for_implementation_file=False): 'WebCore::GraphicsLayerKeyframeValueList': [''], 'WebCore::HasAvailableTargets': [''], 'WebCore::HasInsecureContent': [''], - 'WebCore::HasOrShouldIgnoreUserGesture': [''], + 'WebCore::HasUserGestureOrNoUserGestureRequired': [''], 'WebCore::Headroom': [''], 'WebCore::HighlightRequestOriginatedInApp': [''], 'WebCore::HighlightVisibility': [''], diff --git a/Source/WebKit/Shared/SessionState.cpp b/Source/WebKit/Shared/SessionState.cpp index 1bc50bc95a01..2d2320a6ce55 100644 --- a/Source/WebKit/Shared/SessionState.cpp +++ b/Source/WebKit/Shared/SessionState.cpp @@ -43,7 +43,7 @@ FrameState::FrameState() RELEASE_ASSERT(RunLoop::isMain()); } -FrameState::FrameState(String&& urlString, String&& originalURLString, String&& referrer, AtomString&& target, std::optional frameID, std::optional>&& stateObjectData, int64_t documentSequenceNumber, int64_t itemSequenceNumber, std::optional navigationAPIKey, WebCore::IntPoint scrollPosition, bool shouldRestoreScrollPosition, float pageScaleFactor, std::optional&& httpBody, std::optional itemID, std::optional frameItemID, String&& title, WebCore::ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, RefPtr&& sessionStateObject, bool wasCreatedByJSWithoutUserInteraction, bool wasRestoredFromSession, std::optional&& policyContainer, +FrameState::FrameState(String&& urlString, String&& originalURLString, String&& referrer, AtomString&& target, std::optional frameID, std::optional>&& stateObjectData, int64_t documentSequenceNumber, int64_t itemSequenceNumber, std::optional navigationAPIKey, WebCore::IntPoint scrollPosition, bool shouldRestoreScrollPosition, float pageScaleFactor, std::optional&& httpBody, std::optional itemID, std::optional frameItemID, String&& title, WebCore::ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, RefPtr&& sessionStateObject, bool wasCreatedByJSWithoutUserInteraction, bool wasRestoredFromSession, bool isInitialAboutBlank, std::optional&& policyContainer, #if PLATFORM(IOS_FAMILY) WebCore::FloatRect exposedContentRect, WebCore::IntRect unobscuredContentRect, WebCore::FloatSize minimumLayoutSizeInScrollViewCoordinates, WebCore::IntSize contentSize, bool scaleIsInitial, WebCore::FloatBoxExtent obscuredInsets, #endif @@ -69,6 +69,7 @@ FrameState::FrameState(String&& urlString, String&& originalURLString, String&& , sessionStateObject(WTF::move(sessionStateObject)) , wasCreatedByJSWithoutUserInteraction(wasCreatedByJSWithoutUserInteraction) , wasRestoredFromSession(wasRestoredFromSession) + , isInitialAboutBlank(isInitialAboutBlank) , policyContainer(WTF::move(policyContainer)) #if PLATFORM(IOS_FAMILY) , exposedContentRect(exposedContentRect) @@ -83,7 +84,7 @@ FrameState::FrameState(String&& urlString, String&& originalURLString, String&& { } -FrameState::FrameState(const String& urlString, const String& originalURLString, const String& referrer, const AtomString& target, std::optional frameID, std::optional> stateObjectData, int64_t documentSequenceNumber, int64_t itemSequenceNumber, std::optional navigationAPIKey, WebCore::IntPoint scrollPosition, bool shouldRestoreScrollPosition, float pageScaleFactor, const std::optional& httpBody, std::optional itemID, std::optional frameItemID, const String& title, WebCore::ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, RefPtr&& sessionStateObject, bool wasCreatedByJSWithoutUserInteraction, bool wasRestoredFromSession, const std::optional& policyContainer, +FrameState::FrameState(const String& urlString, const String& originalURLString, const String& referrer, const AtomString& target, std::optional frameID, std::optional> stateObjectData, int64_t documentSequenceNumber, int64_t itemSequenceNumber, std::optional navigationAPIKey, WebCore::IntPoint scrollPosition, bool shouldRestoreScrollPosition, float pageScaleFactor, const std::optional& httpBody, std::optional itemID, std::optional frameItemID, const String& title, WebCore::ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, RefPtr&& sessionStateObject, bool wasCreatedByJSWithoutUserInteraction, bool wasRestoredFromSession, bool isInitialAboutBlank, const std::optional& policyContainer, #if PLATFORM(IOS_FAMILY) WebCore::FloatRect exposedContentRect, WebCore::IntRect unobscuredContentRect, WebCore::FloatSize minimumLayoutSizeInScrollViewCoordinates, WebCore::IntSize contentSize, bool scaleIsInitial, WebCore::FloatBoxExtent obscuredInsets, #endif @@ -109,6 +110,7 @@ FrameState::FrameState(const String& urlString, const String& originalURLString, , sessionStateObject(WTF::move(sessionStateObject)) , wasCreatedByJSWithoutUserInteraction(wasCreatedByJSWithoutUserInteraction) , wasRestoredFromSession(wasRestoredFromSession) + , isInitialAboutBlank(isInitialAboutBlank) , policyContainer(policyContainer) #if PLATFORM(IOS_FAMILY) , exposedContentRect(exposedContentRect) @@ -146,6 +148,7 @@ Ref FrameState::copy() sessionStateObject.copyRef(), wasCreatedByJSWithoutUserInteraction, wasRestoredFromSession, + isInitialAboutBlank, policyContainer, #if PLATFORM(IOS_FAMILY) exposedContentRect, @@ -184,6 +187,7 @@ void FrameState::replacePayloadFrom(Ref&& other) sessionStateObject = WTF::move(other->sessionStateObject); wasCreatedByJSWithoutUserInteraction = other->wasCreatedByJSWithoutUserInteraction; wasRestoredFromSession = other->wasRestoredFromSession; + isInitialAboutBlank = other->isInitialAboutBlank; policyContainer = WTF::move(other->policyContainer); #if PLATFORM(IOS_FAMILY) exposedContentRect = other->exposedContentRect; diff --git a/Source/WebKit/Shared/SessionState.h b/Source/WebKit/Shared/SessionState.h index 351054a6e110..85baa0555cb2 100644 --- a/Source/WebKit/Shared/SessionState.h +++ b/Source/WebKit/Shared/SessionState.h @@ -117,6 +117,7 @@ class FrameState : public RefCounted { RefPtr sessionStateObject; bool wasCreatedByJSWithoutUserInteraction { false }; bool wasRestoredFromSession { false }; + bool isInitialAboutBlank { false }; std::optional policyContainer; // FIXME: These should not be per frame. @@ -137,14 +138,14 @@ class FrameState : public RefCounted { // This is used to help debug . FrameState(); - FrameState(String&& urlString, String&& originalURLString, String&& referrer, AtomString&& target, std::optional, std::optional>&& stateObjectData, int64_t documentSequenceNumber, int64_t itemSequenceNumber, std::optional navigationAPIKey, WebCore::IntPoint scrollPosition, bool shouldRestoreScrollPosition, float pageScaleFactor, std::optional&&, std::optional, std::optional, String&& title, WebCore::ShouldOpenExternalURLsPolicy, RefPtr&& sessionStateObject, bool wasCreatedByJSWithoutUserInteraction, bool wasRestoredFromSession, std::optional&&, + FrameState(String&& urlString, String&& originalURLString, String&& referrer, AtomString&& target, std::optional, std::optional>&& stateObjectData, int64_t documentSequenceNumber, int64_t itemSequenceNumber, std::optional navigationAPIKey, WebCore::IntPoint scrollPosition, bool shouldRestoreScrollPosition, float pageScaleFactor, std::optional&&, std::optional, std::optional, String&& title, WebCore::ShouldOpenExternalURLsPolicy, RefPtr&& sessionStateObject, bool wasCreatedByJSWithoutUserInteraction, bool wasRestoredFromSession, bool isInitialAboutBlank, std::optional&&, #if PLATFORM(IOS_FAMILY) WebCore::FloatRect exposedContentRect, WebCore::IntRect unobscuredContentRect, WebCore::FloatSize minimumLayoutSizeInScrollViewCoordinates, WebCore::IntSize contentSize, bool scaleIsInitial, WebCore::FloatBoxExtent obscuredInsets, #endif Vector>&& children, Vector&& documentState ); - FrameState(const String& urlString, const String& originalURLString, const String& referrer, const AtomString& target, std::optional, std::optional> stateObjectData, int64_t documentSequenceNumber, int64_t itemSequenceNumber, std::optional navigationAPIKey, WebCore::IntPoint scrollPosition, bool shouldRestoreScrollPosition, float pageScaleFactor, const std::optional&, std::optional, std::optional, const String& title, WebCore::ShouldOpenExternalURLsPolicy, RefPtr&& sessionStateObject, bool wasCreatedByJSWithoutUserInteraction, bool wasRestoredFromSession, const std::optional&, + FrameState(const String& urlString, const String& originalURLString, const String& referrer, const AtomString& target, std::optional, std::optional> stateObjectData, int64_t documentSequenceNumber, int64_t itemSequenceNumber, std::optional navigationAPIKey, WebCore::IntPoint scrollPosition, bool shouldRestoreScrollPosition, float pageScaleFactor, const std::optional&, std::optional, std::optional, const String& title, WebCore::ShouldOpenExternalURLsPolicy, RefPtr&& sessionStateObject, bool wasCreatedByJSWithoutUserInteraction, bool wasRestoredFromSession, bool isInitialAboutBlank, const std::optional&, #if PLATFORM(IOS_FAMILY) WebCore::FloatRect exposedContentRect, WebCore::IntRect unobscuredContentRect, WebCore::FloatSize minimumLayoutSizeInScrollViewCoordinates, WebCore::IntSize contentSize, bool scaleIsInitial, WebCore::FloatBoxExtent obscuredInsets, #endif diff --git a/Source/WebKit/Shared/SessionState.serialization.in b/Source/WebKit/Shared/SessionState.serialization.in index 3bd8fcf873ee..a3b6f3663760 100644 --- a/Source/WebKit/Shared/SessionState.serialization.in +++ b/Source/WebKit/Shared/SessionState.serialization.in @@ -64,6 +64,7 @@ header: "SessionState.h" RefPtr sessionStateObject; bool wasCreatedByJSWithoutUserInteraction; bool wasRestoredFromSession; + bool isInitialAboutBlank; std::optional policyContainer; #if PLATFORM(IOS_FAMILY) diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in index 50c3e74ecfbe..1bbb46028367 100644 --- a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in +++ b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in @@ -1066,8 +1066,6 @@ struct WebCore::ClientOrigin { enum class WebCore::AdjustViewSize : bool; -enum class WebCore::UseLosslessCompression : bool; - [RefCounted] class WebCore::TextIndicator { WebCore::TextIndicatorData data(); }; @@ -2267,7 +2265,7 @@ enum class WebCore::StorageAccessWasGranted : uint8_t { enum class WebCore::StorageAccessPromptWasShown : bool -enum class WebCore::HasOrShouldIgnoreUserGesture : bool +enum class WebCore::HasUserGestureOrNoUserGestureRequired : bool enum class WebCore::StorageAccessScope : bool diff --git a/Source/WebKit/Shared/WebCoreArgumentCodersPlatform.serialization.in b/Source/WebKit/Shared/WebCoreArgumentCodersPlatform.serialization.in index d5bfa414bc44..64bde797ba98 100644 --- a/Source/WebKit/Shared/WebCoreArgumentCodersPlatform.serialization.in +++ b/Source/WebKit/Shared/WebCoreArgumentCodersPlatform.serialization.in @@ -651,6 +651,8 @@ enum class WebCore::PixelFormat : uint8_t { #endif }; +enum class WebCore::UseLosslessCompression : bool; + [AdditionalEncoder=StreamConnectionEncoder] struct WebCore::ImageBufferFormat { WebCore::PixelFormat pixelFormat; WebCore::UseLosslessCompression useLosslessCompression; @@ -659,7 +661,7 @@ enum class WebCore::PixelFormat : uint8_t { [AdditionalEncoder=StreamConnectionEncoder] struct WebCore::PixelBufferFormat { WebCore::AlphaPremultiplication alphaFormat; WebCore::PixelFormat pixelFormat; - WebCore::DestinationColorSpace colorSpace; + [Validator='colorSpace->usesRGBColorModel()'] WebCore::DestinationColorSpace colorSpace; }; #if USE(SOUP) diff --git a/Source/WebKit/Shared/WebPageCreationParameters.h b/Source/WebKit/Shared/WebPageCreationParameters.h index 9558194e444c..f912f9204579 100644 --- a/Source/WebKit/Shared/WebPageCreationParameters.h +++ b/Source/WebKit/Shared/WebPageCreationParameters.h @@ -332,6 +332,9 @@ struct WebPageCreationParameters { #if PLATFORM(MAC) double overflowHeightForTopScrollEdgeEffect { 0 }; +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + bool fullScreenTitlebarOverlayIsDisplayed { false }; +#endif #if HAVE(NSVIEW_CORNER_CONFIGURATION) WebCore::CornerRadii scrollbarAvoidanceCornerRadii; #endif diff --git a/Source/WebKit/Shared/WebPageCreationParameters.serialization.in b/Source/WebKit/Shared/WebPageCreationParameters.serialization.in index 57d7197ca6c9..8db5473446e6 100644 --- a/Source/WebKit/Shared/WebPageCreationParameters.serialization.in +++ b/Source/WebKit/Shared/WebPageCreationParameters.serialization.in @@ -246,6 +246,9 @@ enum class WebCore::UserInterfaceLayoutDirection : bool; #if PLATFORM(MAC) double overflowHeightForTopScrollEdgeEffect; +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + bool fullScreenTitlebarOverlayIsDisplayed; +#endif #if HAVE(NSVIEW_CORNER_CONFIGURATION) WebCore::CornerRadii scrollbarAvoidanceCornerRadii; #endif diff --git a/Source/WebKit/Shared/cf/CoreIPCSecTrust.mm b/Source/WebKit/Shared/cf/CoreIPCSecTrust.mm index 25685b72a064..aa36ce6422d5 100644 --- a/Source/WebKit/Shared/cf/CoreIPCSecTrust.mm +++ b/Source/WebKit/Shared/cf/CoreIPCSecTrust.mm @@ -96,7 +96,7 @@ static String updatePolicyVector(NSDictionary *policyOption, CoreIPCSecTrustData case CoreIPCSecTrust::PolicyOptionValueShape::Bool: { if (![optionValue isKindOfClass:NSNumber.class]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::Bool unexpected type for key "_s, (String)optionKey); - NSNumber *value = optionValue; + RetainPtr value = optionValue; CoreIPCSecTrustData::PolicyVariant v = static_cast([value boolValue]); policyVector.append(std::make_pair(WTF::move(k), WTF::move(v))); break; @@ -104,22 +104,22 @@ static String updatePolicyVector(NSDictionary *policyOption, CoreIPCSecTrustData case CoreIPCSecTrust::PolicyOptionValueShape::String: { if (![optionValue isKindOfClass:NSString.class]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::String unexpected type for key "_s, (String)optionKey); - NSString *value = optionValue; - CoreIPCSecTrustData::PolicyVariant v = CoreIPCString(value); + RetainPtr value = optionValue; + CoreIPCSecTrustData::PolicyVariant v = CoreIPCString(value.get()); policyVector.append(std::make_pair(WTF::move(k), WTF::move(v))); break; } case CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfNumbers: { if (![optionValue isKindOfClass:NSArray.class]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfNumbers unexpected type for key "_s, (String)optionKey, " (expecting NSArray)"_s); - NSArray* value = optionValue; - if (!value.count) + RetainPtr value = optionValue; + if (![value count]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfNumbers array length 0 for key "_s, (String)optionKey); - if (!arrayElementsTheSameType(value, NSNumber.class)) + if (!arrayElementsTheSameType(value.get(), NSNumber.class)) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfNumbers unexpected type for key "_s, (String)optionKey, " (expecting NSNumber)"_s); Vector vector; - vector.reserveCapacity(value.count); - for (NSNumber *element in value) { + vector.reserveCapacity([value count]); + for (NSNumber *element in value.get()) { CoreIPCNumber n { element }; vector.append(WTF::move(n)); } @@ -130,14 +130,14 @@ static String updatePolicyVector(NSDictionary *policyOption, CoreIPCSecTrustData case CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfStrings: { if (![optionValue isKindOfClass:NSArray.class]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfStrings unexpected type for key "_s, (String)optionKey, " (expecting NSArray)"_s); - NSArray* value = optionValue; - if (!value.count) + RetainPtr value = optionValue; + if (![value count]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfStrings array length 0 for key "_s, (String)optionKey); - if (!arrayElementsTheSameType(value, NSString.class)) + if (!arrayElementsTheSameType(value.get(), NSString.class)) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfStrings unexpected type for key "_s, (String)optionKey, " (expecting NSString)"_s); Vector vector; - vector.reserveCapacity(value.count); - for (NSString *element in value) { + vector.reserveCapacity([value count]); + for (NSString *element in value.get()) { CoreIPCString s { element }; vector.append(WTF::move(s)); } @@ -148,14 +148,14 @@ static String updatePolicyVector(NSDictionary *policyOption, CoreIPCSecTrustData case CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfData: { if (![optionValue isKindOfClass:NSArray.class]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfData unexpected type for key %@ "_s, (String)optionKey, " (expecting NSArray)"_s); - NSArray* value = optionValue; - if (!value.count) + RetainPtr value = optionValue; + if (![value count]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfData array length 0 for key "_s, (String)optionKey); - if (!arrayElementsTheSameType(value, NSData.class)) + if (!arrayElementsTheSameType(value.get(), NSData.class)) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfData unexpected type for key "_s, (String)optionKey, " (expecting NSData)"_s); Vector vector; - vector.reserveCapacity(value.count); - for (NSData *element in value) { + vector.reserveCapacity([value count]); + for (NSData *element in value.get()) { CoreIPCData d { element }; vector.append(WTF::move(d)); } @@ -166,16 +166,16 @@ static String updatePolicyVector(NSDictionary *policyOption, CoreIPCSecTrustData case CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfArrayContainingDateOrNumber: { if (![optionValue isKindOfClass:NSArray.class]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfArrayContainingDateOrNumber unexpected type for key "_s, (String)optionKey, " (expecting NSArray)"_s); - NSArray *value = optionValue; - if (!value.count) + RetainPtr value = optionValue; + if (![value count]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfArrayContainingDateOrNumber array length 0 for key "_s, (String)optionKey); - if (!arrayElementsTheSameType(value, NSArray.class)) + if (!arrayElementsTheSameType(value.get(), NSArray.class)) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfArrayContainingDateOrNumber unexpected type for key "_s, (String)optionKey, " (expecting NSArray)"_s); CoreIPCSecTrustData::PolicyArrayOfArrayContainingDateOrNumbers outerVector; - outerVector.reserveCapacity(value.count); + outerVector.reserveCapacity([value count]); - for (NSArray *secondLevelArray in value) { + for (NSArray *secondLevelArray in value.get()) { if (![secondLevelArray isKindOfClass:NSArray.class]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfArrayContainingDateOrNumber second level array unexpected type for key "_s, (String)optionKey); @@ -184,12 +184,12 @@ static String updatePolicyVector(NSDictionary *policyOption, CoreIPCSecTrustData for (id element in secondLevelArray) { if ([element isKindOfClass:NSNumber.class]) { - NSNumber *e = element; - Variant v = CoreIPCNumber(e); + RetainPtr e = element; + Variant v = CoreIPCNumber(e.get()); innerVector.append(WTF::move(v)); } else if ([element isKindOfClass:NSDate.class]) { - NSDate *d = element; - Variant v = CoreIPCDate(d); + RetainPtr d = element; + Variant v = CoreIPCDate(d.get()); innerVector.append(WTF::move(v)); } else return makeString("CoreIPCSecTrust::PolicyOptionValueShape::ArrayOfArrayContainingDateOrNumber second level array contents unexpected type for key "_s, (String)optionKey); @@ -203,10 +203,10 @@ static String updatePolicyVector(NSDictionary *policyOption, CoreIPCSecTrustData case CoreIPCSecTrust::PolicyOptionValueShape::DictionaryValueIsNumber: { if (![optionValue isKindOfClass:NSDictionary.class]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::DictionaryValueIsNumber unexpected type for key "_s, (String)optionKey, " (expecting NSDictionary)"_s); - NSDictionary *d = optionValue; + RetainPtr d = optionValue; CoreIPCSecTrustData::PolicyDictionaryValueIsNumber vector; - vector.reserveCapacity(d.count); - for (NSString* key in d) { + vector.reserveCapacity([d count]); + for (NSString* key in d.get()) { if (![key isKindOfClass:NSString.class]) return makeString("CoreIPCSecTrust::PolicyOptionValueShape::DictionaryValueIsNumber unexpected dictionary key type for key "_s, (String)optionKey, " (expecting NSString)"_s); NSNumber *value = [d objectForKey:key]; @@ -408,7 +408,7 @@ static String optionalArrayOfDataHelper(std::optional>& toSe CoreIPCSecTrustData::InfoOption v = WTF::move(s); vector.append(std::make_pair(WTF::move(k), WTF::move(v))); } else if ([value isKindOfClass:NSNumber.class]) { - NSNumber *candidateBool = value; + RetainPtr candidateBool = value; if ([candidateBool isEqualToNumber:@YES] || [candidateBool isEqualToNumber:@NO]) { bool v = [candidateBool boolValue]; vector.append(std::make_pair(WTF::move(k), v)); @@ -418,11 +418,11 @@ static String optionalArrayOfDataHelper(std::optional>& toSe return; } } else if ([value isKindOfClass:NSArray.class]) { - NSArray *revocationInfoArray = value; + RetainPtr revocationInfoArray = value; CoreIPCSecTrustData::RevocationInfoArray revocationInfo; revocationInfo.reserveCapacity([revocationInfoArray count]); - for (NSDictionary *entry in revocationInfoArray) { + for (NSDictionary *entry in revocationInfoArray.get()) { if (![entry isKindOfClass:NSDictionary.class]) { RELEASE_LOG_ERROR(IPC, "CoreIPCSecTrust 'RevocationInfo' array contains non-dictionary element"); ASSERT_NOT_REACHED(); @@ -460,12 +460,12 @@ static String optionalArrayOfDataHelper(std::optional>& toSe id subValue = [subDict objectForKey:subKey]; if ([subValue isKindOfClass:NSNumber.class]) { - NSNumber *number = subValue; + RetainPtr number = subValue; if ([number isEqualToNumber:@YES] || [number isEqualToNumber:@NO]) { CoreIPCSecTrustData::RevocationInfoSubDictValue v = [number boolValue]; revocationSubDict.append(std::make_pair(WTF::move(subKeyString), WTF::move(v))); } else { - CoreIPCNumber n { number }; + CoreIPCNumber n { number.get() }; CoreIPCSecTrustData::RevocationInfoSubDictValue v = WTF::move(n); revocationSubDict.append(std::make_pair(WTF::move(subKeyString), WTF::move(v))); } @@ -494,10 +494,10 @@ static String optionalArrayOfDataHelper(std::optional>& toSe CoreIPCSecTrustData::InfoOption v = WTF::move(revocationInfo); vector.append(std::make_pair(WTF::move(k), WTF::move(v))); } else if ([value isKindOfClass:NSDictionary.class]) { - NSDictionary *subDict = value; + RetainPtr subDict = value; CoreIPCSecTrustData::InfoSubDict infoSubDict; infoSubDict.reserveCapacity([subDict count]); - for (NSString *subKey in subDict) { + for (NSString *subKey in subDict.get()) { if (![subKey isKindOfClass:NSString.class]) { RELEASE_LOG_ERROR(IPC, "CoreIPCSecTrust 'info' sub-dictionary key is not a string"); ASSERT_NOT_REACHED(); @@ -616,13 +616,13 @@ static String optionalArrayOfDataHelper(std::optional>& toSe auto p = std::make_pair(WTF::move(k), WTF::move(v)); innerVector.append(WTF::move(p)); } else if ([value isKindOfClass:NSNumber.class]) { - NSNumber *number = value; + RetainPtr number = value; if ([number isEqualToNumber:@YES] || [number isEqualToNumber:@NO]) { bool v = [number boolValue]; auto p = std::make_pair(WTF::move(k), v); innerVector.append(WTF::move(p)); } else { - CoreIPCNumber n { number }; + CoreIPCNumber n { number.get() }; auto p = std::make_pair(WTF::move(k), n); innerVector.append(WTF::move(p)); } diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKUserContentController.h b/Source/WebKit/UIProcess/API/Cocoa/WKUserContentController.h index f72ac538ef0c..3ff9231dd841 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKUserContentController.h +++ b/Source/WebKit/UIProcess/API/Cocoa/WKUserContentController.h @@ -150,7 +150,7 @@ WK_CLASS_AVAILABLE(macos(10.10), ios(8.0)) @param contentWorld The WKContentWorld to add the buffer to. The buffer will only be visible to JavaScript executing in that content world. */ -- (void)addBuffer:(WKJSScriptingBuffer *)buffer name:(NSString *)name contentWorld:(WKContentWorld *)world WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA)); +- (void)addBuffer:(id)buffer name:(NSString *)name contentWorld:(WKContentWorld *)world WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA)); /*! @abstract Removes a previously added data buffer from the given `WKContentWorld @param name The name of the buffer to remove. diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKUserContentController.mm b/Source/WebKit/UIProcess/API/Cocoa/WKUserContentController.mm index 9d28b56cf0de..bb7abd8be445 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKUserContentController.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/WKUserContentController.mm @@ -231,9 +231,15 @@ - (void)removeAllScriptMessageHandlers protect(*_userContentControllerProxy)->removeAllUserMessageHandlers(); } -- (void)addBuffer:(WKJSScriptingBuffer *)buffer name:(NSString *)name contentWorld:(WKContentWorld *)world +- (void)addBuffer:(id)buffer name:(NSString *)name contentWorld:(WKContentWorld *)world { - protect(*_userContentControllerProxy)->addJSBuffer(Ref { *buffer->_buffer }, Ref { *world->_contentWorld }, name); + RetainPtr bufferToAdd; + if (RetainPtr data = dynamic_objc_cast(buffer)) + bufferToAdd = adoptNS([[WKJSScriptingBuffer alloc] initWithData:data.get()]); + else + bufferToAdd = dynamic_objc_cast(buffer); + + protect(*_userContentControllerProxy)->addJSBuffer(Ref { *bufferToAdd->_buffer }, Ref { *world->_contentWorld }, name); } - (void)removeBufferWithName:(NSString *)name contentWorld:(WKContentWorld *)world diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm b/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm index a8e0213b3ed2..93b8a32fed18 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm @@ -7709,6 +7709,11 @@ - (void)_getSelectorPathDataForNode:(_WKJSHandle *)node completionHandler:(void }); } +- (void)_getSelectorPathData:(WKJSHandle *)node completionHandler:(void (^)(NSData *))completionHandler +{ + [self _getSelectorPathDataForNode:(_WKJSHandle *)node completionHandler:completionHandler]; +} + - (void)_getNodeForSelectorPathData:(NSData *)data completionHandler:(void (^)(_WKJSHandle *))completion { RefPtr frame = _page->mainFrame(); diff --git a/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h b/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h index d11a871c89c7..c39525a76b3a 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h +++ b/Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h @@ -660,6 +660,7 @@ typedef NS_OPTIONS(NSUInteger, _WKWebViewDataType) { #endif - (void)_getSelectorPathDataForNode:(_WKJSHandle *)node completionHandler:(WK_SWIFT_UI_ACTOR void (^)(NSData *))completionHandler WK_SWIFT_ASYNC_NAME(_getSelectorPathDataForNode(_:)) WK_API_AVAILABLE(macos(26.4), ios(26.4), visionos(26.4)); +- (void)_getSelectorPathData:(WKJSHandle *)node completionHandler:(WK_SWIFT_UI_ACTOR void (^)(NSData *))completionHandler WK_SWIFT_ASYNC_NAME(_getSelectorPathData(_:)) WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA)); - (void)_getNodeForSelectorPathData:(NSData *)data completionHandler:(WK_SWIFT_UI_ACTOR void (^)(_WKJSHandle *))completionHandler WK_SWIFT_ASYNC_NAME(_getNodeForSelectorPathData(_:)) WK_API_AVAILABLE(macos(26.4), ios(26.4), visionos(26.4)); - (void)_debugTextWithConfiguration:(_WKTextExtractionConfiguration *)configuration completionHandler:(WK_SWIFT_UI_ACTOR void(^)(NSString *))completionHandler WK_API_AVAILABLE(macos(26.4), ios(26.4), visionos(26.4)) NS_SWIFT_NAME(_debugText(with:completionHandler:)); diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextExtraction.h b/Source/WebKit/UIProcess/API/Cocoa/_WKTextExtraction.h index af5b807d0680..1ad868d35d36 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/_WKTextExtraction.h +++ b/Source/WebKit/UIProcess/API/Cocoa/_WKTextExtraction.h @@ -30,6 +30,7 @@ NS_HEADER_AUDIT_BEGIN(nullability, sendability) @class WKFrameInfo; +@class WKJSHandle; @class WKSecurityOrigin; @class WKWebView; @class _WKJSHandle; @@ -93,7 +94,7 @@ WK_CLASS_AVAILABLE(macos(26.4), ios(26.4), visionos(26.4)) /*! Disables all optional metadata in the extraction output: URLs, bounding rects, node identifiers, event listeners, and accessibility attributes. - The output format and other structural configuration (e.g. `targetRect`, `targetNode`) + The output format and other structural configuration (e.g. `targetRect`, `targetNodeHandle`) are left unchanged. Individual flags can still be re-enabled after calling this method. */ - (void)configureForMinimalOutput; @@ -190,6 +191,7 @@ WK_CLASS_AVAILABLE(macos(26.4), ios(26.4), visionos(26.4)) The default value is `nil`. */ @property (nonatomic, copy, nullable) _WKJSHandle *targetNode; +@property (nonatomic, copy, nullable) WKJSHandle *targetNodeHandle WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA)); /*! If specified, these DOM nodes and their subtrees will be skipped during extraction. @@ -270,6 +272,7 @@ WK_CLASS_AVAILABLE(macos(26.4), ios(26.4), visionos(26.4)) At least one of `nodeIdentifier` or `searchText` must be specified. */ - (void)requestJSHandleForNodeIdentifier:(nullable NSString *)nodeIdentifier searchText:(nullable NSString *)searchText completionHandler:(void (^)(_WKJSHandle * _Nullable))completionHandler; +- (void)requestHandleForNodeIdentifier:(nullable NSString *)nodeIdentifier searchText:(nullable NSString *)searchText completionHandler:(void (^)(WKJSHandle * _Nullable))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA)); /*! Asynchronously map a node identifier string (corresponding to a `uid` in @@ -282,6 +285,7 @@ WK_CLASS_AVAILABLE(macos(26.4), ios(26.4), visionos(26.4)) At least one of `nodeIdentifier` or `searchText` must be specified. */ - (void)requestContainerJSHandleForNodeIdentifier:(nullable NSString *)nodeIdentifier searchText:(nullable NSString *)searchText completionHandler:(void (^)(_WKJSHandle * _Nullable))completionHandler; +- (void)requestContainerHandleForNodeIdentifier:(nullable NSString *)nodeIdentifier searchText:(nullable NSString *)searchText completionHandler:(void (^)(WKJSHandle * _Nullable))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA)); /*! Asynchronously find the smallest appropriately-sized container element that @@ -294,6 +298,7 @@ WK_CLASS_AVAILABLE(macos(26.4), ios(26.4), visionos(26.4)) At least one search text or a non-null node identifier must be specified. */ - (void)requestContainerJSHandleForSearchTexts:(NSArray *)searchTexts nodeIdentifier:(nullable NSString *)nodeIdentifier completionHandler:(void (^)(_WKJSHandle * _Nullable))completionHandler; +- (void)requestContainerHandleForSearchTexts:(NSArray *)searchTexts nodeIdentifier:(nullable NSString *)nodeIdentifier completionHandler:(void (^)(WKJSHandle * _Nullable))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA), visionos(WK_XROS_TBA)); @end diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextExtraction.mm b/Source/WebKit/UIProcess/API/Cocoa/_WKTextExtraction.mm index 594105662089..577febe000ff 100644 --- a/Source/WebKit/UIProcess/API/Cocoa/_WKTextExtraction.mm +++ b/Source/WebKit/UIProcess/API/Cocoa/_WKTextExtraction.mm @@ -94,6 +94,16 @@ - (void)setTargetNode:(_WKJSHandle *)targetNode _targetNode = adoptNS([targetNode copy]); } +- (WKJSHandle *)targetNodeHandle +{ + return _targetNode.get(); +} + +- (void)setTargetNodeHandle:(WKJSHandle *)targetNode +{ + _targetNode = adoptNS([targetNode copy]); +} + - (NSArray<_WKJSHandle *> *)nodesToSkip { return _nodesToSkip.get(); @@ -229,6 +239,13 @@ - (void)requestJSHandleForNodeIdentifier:(NSString *)nodeIdentifier searchText:( [webView _requestJSHandleForNodeIdentifier:nodeIdentifier searchText:searchText completionHandler:completionHandler]; } +- (void)requestHandleForNodeIdentifier:(nullable NSString *)nodeIdentifier searchText:(nullable NSString *)searchText completionHandler:(void (^)(WKJSHandle * _Nullable))completionHandler +{ + [self requestJSHandleForNodeIdentifier:nodeIdentifier searchText:searchText completionHandler:[completionHandler = makeBlockPtr(completionHandler)] (_WKJSHandle *handle) { + completionHandler(handle); + }]; +} + - (void)requestContainerJSHandleForNodeIdentifier:(NSString *)nodeIdentifier searchText:(NSString *)searchText completionHandler:(void (^)(_WKJSHandle *))completionHandler { RetainPtr webView = _webView; @@ -238,6 +255,13 @@ - (void)requestContainerJSHandleForNodeIdentifier:(NSString *)nodeIdentifier sea [webView _requestContainerJSHandleForNodeIdentifier:nodeIdentifier searchText:searchText completionHandler:completionHandler]; } +- (void)requestContainerHandleForNodeIdentifier:(NSString *)nodeIdentifier searchText:(NSString *)searchText completionHandler:(void (^)(WKJSHandle *))completionHandler +{ + [self requestContainerJSHandleForNodeIdentifier:nodeIdentifier searchText:searchText completionHandler:[completionHandler = makeBlockPtr(completionHandler)] (_WKJSHandle *handle) { + completionHandler(handle); + }]; +} + - (void)requestContainerJSHandleForSearchTexts:(NSArray *)searchTexts nodeIdentifier:(NSString *)nodeIdentifier completionHandler:(void (^)(_WKJSHandle *))completionHandler { RetainPtr webView = _webView; @@ -247,6 +271,13 @@ - (void)requestContainerJSHandleForSearchTexts:(NSArray *)searchText [webView _requestContainerJSHandleForSearchTexts:searchTexts nodeIdentifier:nodeIdentifier completionHandler:completionHandler]; } +- (void)requestContainerHandleForSearchTexts:(NSArray *)searchTexts nodeIdentifier:(nullable NSString *)nodeIdentifier completionHandler:(void (^)(WKJSHandle * _Nullable))completionHandler +{ + [self requestContainerJSHandleForSearchTexts:searchTexts nodeIdentifier:nodeIdentifier completionHandler:[completionHandler = makeBlockPtr(completionHandler)] (_WKJSHandle *handle) { + completionHandler(handle); + }]; +} + @end @implementation _WKTextExtractionInteraction { diff --git a/Source/WebKit/UIProcess/Extensions/API/WebExtensionContextAPIStorage.cpp b/Source/WebKit/UIProcess/Extensions/API/WebExtensionContextAPIStorage.cpp index ca3d362301a5..601870a9a2c4 100644 --- a/Source/WebKit/UIProcess/Extensions/API/WebExtensionContextAPIStorage.cpp +++ b/Source/WebKit/UIProcess/Extensions/API/WebExtensionContextAPIStorage.cpp @@ -149,12 +149,8 @@ void WebExtensionContext::storageSet(WebPageProxyIdentifier webPageProxyIdentifi if (!keysSuccessfullySet.size()) return; - if (keysSuccessfullySet.size() != data.size()) { - for (const auto& key : data.keys()) { - if (!keysSuccessfullySet.contains(key)) - data.remove(key); - } - } + if (keysSuccessfullySet.size() != data.size()) + data.removeIf([&](auto& entry) { return !keysSuccessfullySet.contains(entry.key); }); fireStorageChangedEventIfNeeded(existingKeysAndValues, data, dataType); }); diff --git a/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIRuntimeCocoa.mm b/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIRuntimeCocoa.mm index f92c726da35a..04a8c1379eb9 100644 --- a/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIRuntimeCocoa.mm +++ b/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPIRuntimeCocoa.mm @@ -44,6 +44,7 @@ #import "WebExtensionMessageTargetParameters.h" #import "WebExtensionUtilities.h" #import +#import #import #import @@ -210,11 +211,11 @@ return; } - size_t handledCount = 0; + auto handledCount = Box::create(0); size_t totalExpected = mainWorldProcesses.size(); for (auto& process : mainWorldProcesses) { - process->sendWithAsyncReply(Messages::WebExtensionContextProxy::DispatchRuntimeConnectEvent(targetContentWorldType, channelIdentifier, name, std::nullopt, completeSenderParameters, resolvedUserGesture), [=, this, protectedThis = Ref { *this }, &handledCount](HashCountedSet&& addedPortCounts) mutable { + process->sendWithAsyncReply(Messages::WebExtensionContextProxy::DispatchRuntimeConnectEvent(targetContentWorldType, channelIdentifier, name, std::nullopt, completeSenderParameters, resolvedUserGesture), [=, this, protectedThis = Ref { *this }](HashCountedSet&& addedPortCounts) mutable { // Flip target and source worlds since we're adding the opposite side of the port connection, sending from target back to source. addPorts(targetContentWorldType, sourceContentWorldType, channelIdentifier, WTF::move(addedPortCounts)); @@ -223,7 +224,7 @@ firePortDisconnectEventIfNeeded(sourceContentWorldType, targetContentWorldType, channelIdentifier); - if (++handledCount < totalExpected) + if (++*handledCount < totalExpected) return; clearQueuedPortMessages(targetContentWorldType, channelIdentifier); @@ -549,11 +550,11 @@ return; } - size_t handledCount = 0; + auto handledCount = Box::create(0); size_t totalExpected = mainWorldProcesses.size(); for (auto& process : mainWorldProcesses) { - process->sendWithAsyncReply(Messages::WebExtensionContextProxy::DispatchRuntimeConnectEvent(targetContentWorldType, channelIdentifier, name, std::nullopt, completeSenderParameters, resolvedUserGesture), [=, this, protectedThis = Ref { *this }, &handledCount](HashCountedSet&& addedPortCounts) mutable { + process->sendWithAsyncReply(Messages::WebExtensionContextProxy::DispatchRuntimeConnectEvent(targetContentWorldType, channelIdentifier, name, std::nullopt, completeSenderParameters, resolvedUserGesture), [=, this, protectedThis = Ref { *this }](HashCountedSet&& addedPortCounts) mutable { // Flip target and source worlds since we're adding the opposite side of the port connection, sending from target back to source. addPorts(targetContentWorldType, sourceContentWorldType, channelIdentifier, WTF::move(addedPortCounts)); @@ -562,7 +563,7 @@ firePortDisconnectEventIfNeeded(sourceContentWorldType, targetContentWorldType, channelIdentifier); - if (++handledCount < totalExpected) + if (++*handledCount < totalExpected) return; clearQueuedPortMessages(targetContentWorldType, channelIdentifier); diff --git a/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPITabsCocoa.mm b/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPITabsCocoa.mm index 41b2a71a2197..36191a9f20f1 100644 --- a/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPITabsCocoa.mm +++ b/Source/WebKit/UIProcess/Extensions/Cocoa/API/WebExtensionContextAPITabsCocoa.mm @@ -47,6 +47,7 @@ #import "WebExtensionWindowIdentifier.h" #import "WebPageProxy.h" #import +#import #import #import #import @@ -528,11 +529,11 @@ static inline String toMIMEType(WebExtensionTab::ImageFormat format) return; } - size_t handledCount = 0; + auto handledCount = Box::create(0); size_t totalExpected = processes.size(); for (Ref process : processes) { - process->sendWithAsyncReply(Messages::WebExtensionContextProxy::DispatchRuntimeConnectEvent(targetContentWorldType, channelIdentifier, name, targetParameters, senderParameters, userGesture), [=, this, protectedThis = Ref { *this }, &handledCount](HashCountedSet&& addedPortCounts) mutable { + process->sendWithAsyncReply(Messages::WebExtensionContextProxy::DispatchRuntimeConnectEvent(targetContentWorldType, channelIdentifier, name, targetParameters, senderParameters, userGesture), [=, this, protectedThis = Ref { *this }](HashCountedSet&& addedPortCounts) mutable { // Flip target and source worlds since we're adding the opposite side of the port connection, sending from target back to source. addPorts(targetContentWorldType, sourceContentWorldType, channelIdentifier, WTF::move(addedPortCounts)); @@ -541,7 +542,7 @@ static inline String toMIMEType(WebExtensionTab::ImageFormat format) firePortDisconnectEventIfNeeded(sourceContentWorldType, targetContentWorldType, channelIdentifier); - if (++handledCount < totalExpected) + if (++*handledCount < totalExpected) return; clearQueuedPortMessages(targetContentWorldType, channelIdentifier); diff --git a/Source/WebKit/UIProcess/WebBackForwardList.cpp b/Source/WebKit/UIProcess/WebBackForwardList.cpp index 26273ceeede3..73419fbf4994 100644 --- a/Source/WebKit/UIProcess/WebBackForwardList.cpp +++ b/Source/WebKit/UIProcess/WebBackForwardList.cpp @@ -751,6 +751,7 @@ Ref WebBackForwardList::completeFrameStateForNavigation(Refconnection()) #define MESSAGE_CHECK_COMPLETION(process, assertion, completion) MESSAGE_CHECK_COMPLETION_BASE(assertion, process->connection(), completion) +#define MESSAGE_CHECK_WITH_RETURN_VALUE(process, assertion, returnValue) MESSAGE_CHECK_WITH_RETURN_VALUE_BASE(assertion, process->connection(), returnValue) void WebBackForwardList::backForwardAddItem(IPC::Connection& connection, Ref&& navigatedFrameState) { @@ -758,7 +759,7 @@ void WebBackForwardList::backForwardAddItem(IPC::Connection& connection, RefdidLoadWebArchive() ? LoadedWebArchive::Yes : LoadedWebArchive::No); } -static void messageCheckItemURLs(Ref& frameState, Ref& process) +static bool messageCheckItemURLs(Ref& frameState, Ref& process) { URL itemURL { frameState->urlString }; URL itemOriginalURL { frameState->originalURLString }; @@ -769,11 +770,12 @@ static void messageCheckItemURLs(Ref& frameState, RefwasPreviouslyApprovedFileURL(itemURL)); - MESSAGE_CHECK(process, !itemOriginalURL.protocolIsFile() || process->wasPreviouslyApprovedFileURL(itemOriginalURL)); + MESSAGE_CHECK_WITH_RETURN_VALUE(process, !itemURL.protocolIsFile() || process->wasPreviouslyApprovedFileURL(itemURL), false); + MESSAGE_CHECK_WITH_RETURN_VALUE(process, !itemOriginalURL.protocolIsFile() || process->wasPreviouslyApprovedFileURL(itemOriginalURL), false); #if PLATFORM(COCOA) } #endif + return true; } void WebBackForwardList::backForwardAddItemShared(IPC::Connection& connection, Ref&& navigatedFrameState, LoadedWebArchive loadedWebArchive) @@ -783,7 +785,8 @@ void WebBackForwardList::backForwardAddItemShared(IPC::Connection& connection, R MESSAGE_CHECK(process, !navigatedFrameState->itemID || navigatedFrameState->itemID->processIdentifier() == process->coreProcessIdentifier()); MESSAGE_CHECK(process, !navigatedFrameState->frameItemID || navigatedFrameState->frameItemID->processIdentifier() == process->coreProcessIdentifier()); - messageCheckItemURLs(navigatedFrameState, process); + if (!messageCheckItemURLs(navigatedFrameState, process)) + return; if (RefPtr targetFrame = WebFrameProxy::webFrame(navigatedFrameState->frameID)) { MESSAGE_CHECK(process, targetFrame->page() == m_page.get()); @@ -810,7 +813,8 @@ void WebBackForwardList::backForwardAddItemShared(IPC::Connection& connection, R void WebBackForwardList::backForwardSetChildItem(IPC::Connection& connection, BackForwardFrameItemIdentifier frameItemID, Ref&& frameState) { Ref process = WebProcessProxy::fromConnection(connection); - messageCheckItemURLs(frameState, process); + if (!messageCheckItemURLs(frameState, process)) + return; RefPtr item = currentItem(); if (!item) @@ -833,8 +837,8 @@ void WebBackForwardList::backForwardUpdateItem(IPC::Connection& connection, Ref< // In the case of a process swap, the `backForwardUpdateItem` message can be received from the old process, // and therefore present an unexpected file: URL. // We can safely skip the message check in these cases. - if (!m_handlingProvisionalMessage) - messageCheckItemURLs(frameState, process); + if (!m_handlingProvisionalMessage && !messageCheckItemURLs(frameState, process)) + return; RefPtr frameItem = frameState->itemID && frameState->frameItemID ? WebBackForwardListFrameItem::itemForID(*frameState->itemID, *frameState->frameItemID) : nullptr; if (!frameItem) diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp index 9ff190ec986d..de365b14593d 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.cpp +++ b/Source/WebKit/UIProcess/WebPageProxy.cpp @@ -2874,10 +2874,28 @@ RefPtr WebPageProxy::goToBackForwardItem(WebBackForwardListFram return RefPtr { WTF::move(navigation) }; } +// The HTML spec replaces an iframe's initial about:blank entry on the frame's first real +// navigation; WebKit's BF list keeps the stale state in older entries, so dispatching a +// traversal into one would regress the live iframe. The target entry is identified by the +// authoritative isInitialAboutBlank flag (set from DocumentLoader::isInitialAboutBlank when the +// history item is created), not by a URL-string match, so an intentional iframe.src="about:blank" +// navigation — which is not the initial empty document — is correctly dispatched. +static bool isStaleInitialAboutBlankIframeTarget(WebBackForwardListFrameItem& toFrame) +{ + if (!toFrame.frameState().isInitialAboutBlank) + return false; + auto toFrameID = toFrame.frameID(); + if (!toFrameID) + return false; + RefPtr toLiveFrame = WebFrameProxy::webFrame(*toFrameID); + return toLiveFrame && !toLiveFrame->isMainFrame(); +} + bool WebPageProxy::dispatchPerFrameTraversals(WebBackForwardListFrameItem& fromFrame, WebBackForwardListFrameItem& toFrame, NavigationIdentifier navigationID, FrameLoadType frameLoadType, ShouldRestoreFromBackForwardCache shouldRestore, const WebCore::PublicSuffix& publicSuffix) { bool anySent = false; - if (fromFrame.frameState().itemSequenceNumber != toFrame.frameState().itemSequenceNumber) + if (fromFrame.frameState().itemSequenceNumber != toFrame.frameState().itemSequenceNumber + && !isStaleInitialAboutBlankIframeTarget(toFrame)) anySent = sendGoToBackForwardItemForFrame(toFrame, navigationID, frameLoadType, shouldRestore, publicSuffix); bool sameDocument = fromFrame.frameState().documentSequenceNumber == toFrame.frameState().documentSequenceNumber; @@ -13841,6 +13859,9 @@ WebPageCreationParameters WebPageProxy::creationParameters(WebProcessProxy& proc if (m_viewWindowCoordinates) parameters.viewWindowCoordinates = *m_viewWindowCoordinates; parameters.overflowHeightForTopScrollEdgeEffect = m_overflowHeightForTopScrollEdgeEffect; +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + parameters.fullScreenTitlebarOverlayIsDisplayed = fullScreenTitlebarOverlayIsDisplayed(); +#endif #if HAVE(NSVIEW_CORNER_CONFIGURATION) parameters.scrollbarAvoidanceCornerRadii = internals().scrollbarAvoidanceCornerRadii; #endif diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h index 4dc6cd5f94cd..fd9ea7fb4b33 100644 --- a/Source/WebKit/UIProcess/WebPageProxy.h +++ b/Source/WebKit/UIProcess/WebPageProxy.h @@ -1040,6 +1040,15 @@ class WebPageProxy final : public API::ObjectImpl, publ double overflowHeightForTopScrollEdgeEffect() const { return m_overflowHeightForTopScrollEdgeEffect; } void setOverflowHeightForTopScrollEdgeEffect(double); + +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + enum class WindowIsInNativeFullScreen : bool { No, Yes }; + void setWindowIsInNativeFullScreen(WindowIsInNativeFullScreen); + void setFullScreenTitlebarOverlayIsRevealed(bool); + + bool fullScreenTitlebarOverlayIsDisplayed() const { return m_windowIsInNativeFullScreen && m_fullScreenTitlebarOverlayIsRevealed; } +#endif + #if HAVE(NSVIEW_CORNER_CONFIGURATION) void setScrollbarAvoidanceCornerRadii(WebCore::CornerRadii&&); #endif @@ -3832,6 +3841,10 @@ class WebPageProxy final : public API::ObjectImpl, publ #if PLATFORM(MAC) bool m_acceptsFirstMouse { false }; double m_overflowHeightForTopScrollEdgeEffect { 0 }; +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + bool m_windowIsInNativeFullScreen { false }; + bool m_fullScreenTitlebarOverlayIsRevealed { false }; +#endif #endif #if USE(SYSTEM_PREVIEW) diff --git a/Source/WebKit/UIProcess/WebProcessProxy.cpp b/Source/WebKit/UIProcess/WebProcessProxy.cpp index 3abd9215128a..2afb8e81ec5e 100644 --- a/Source/WebKit/UIProcess/WebProcessProxy.cpp +++ b/Source/WebKit/UIProcess/WebProcessProxy.cpp @@ -1177,9 +1177,12 @@ bool WebProcessProxy::checkURLReceivedFromWebProcess(const URL& url, CheckBackFo // Items in back/forward list have been already checked. // One case where we don't have sandbox extensions for file URLs in b/f list is if the list has been reinstated after a crash or a browser restart. + // Only consider items belonging to a page hosted by this WebProcessProxy. if (checkBackForwardList == CheckBackForwardList::Yes) { String path = url.fileSystemPath(); for (auto& item : WebBackForwardListItem::allItems().values()) { + if (!m_pageMap.contains(item->pageID())) + continue; URL itemURL { item->url() }; if (itemURL.protocolIsFile() && itemURL.fileSystemPath() == path) return true; diff --git a/Source/WebKit/UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm b/Source/WebKit/UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm index fc7a4caef8be..d2cf28cfb7a2 100644 --- a/Source/WebKit/UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm +++ b/Source/WebKit/UIProcess/WebsiteData/Cocoa/WebsiteDataStoreCocoa.mm @@ -718,7 +718,7 @@ static NavigatingToAppBoundDomain schemeOrDomainIsAppBound(const String& host, c { ASSERT(RunLoop::isMain()); - ensureAppBoundDomains([&host, &protocol, listener = Ref { listener }] (auto& domains, auto& schemes) mutable { + ensureAppBoundDomains([host, protocol, listener = Ref { listener }] (auto& domains, auto& schemes) mutable { // Must check for both an empty app bound domains list and an empty key before returning nullopt // because test cases may have app bound domains but no key. bool hasAppBoundDomains = keyExists || !domains.isEmpty(); diff --git a/Source/WebKit/UIProcess/mac/WKAppKitGestureController.swift b/Source/WebKit/UIProcess/mac/WKAppKitGestureController.swift index 9a2633b878cd..1f6654c2a493 100644 --- a/Source/WebKit/UIProcess/mac/WKAppKitGestureController.swift +++ b/Source/WebKit/UIProcess/mac/WKAppKitGestureController.swift @@ -28,6 +28,7 @@ import WebKit_Internal import AppKit import AppKit_Private.NSPanGestureRecognizer_Private private import CxxStdlib +private import WebCore_Private final class WKPanGestureRecognizer: NSPanGestureRecognizer { private weak var webView: WKWebView? diff --git a/Source/WebKit/UIProcess/mac/WebPageProxyMac.mm b/Source/WebKit/UIProcess/mac/WebPageProxyMac.mm index 456ef335e3f0..0a85fdcd4b66 100644 --- a/Source/WebKit/UIProcess/mac/WebPageProxyMac.mm +++ b/Source/WebKit/UIProcess/mac/WebPageProxyMac.mm @@ -35,6 +35,7 @@ #import "FrameInfoData.h" #import "ImageAnalysisUtilities.h" #import "InsertTextOptions.h" +#import "Logging.h" #import "MenuUtilities.h" #import "MessageSenderInlines.h" #import "NativeWebKeyboardEvent.h" @@ -479,6 +480,37 @@ static inline bool expectsLegacyImplicitRubberBandControl() protect(legacyMainFrameProcess())->send(Messages::WebPage::SetOverflowHeightForTopScrollEdgeEffect(value), webPageIDInMainFrameProcess()); } +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + +void WebPageProxy::setWindowIsInNativeFullScreen(WindowIsInNativeFullScreen windowIsInNativeFullScreen) +{ + auto isInFullScreen = windowIsInNativeFullScreen == WindowIsInNativeFullScreen::Yes; + if (m_windowIsInNativeFullScreen == isInFullScreen) + return; + + m_windowIsInNativeFullScreen = isInFullScreen; + + if (!hasRunningProcess()) + return; + + protect(legacyMainFrameProcess())->send(Messages::WebPage::SetFullScreenTitlebarOverlayIsDisplayed(fullScreenTitlebarOverlayIsDisplayed()), webPageIDInMainFrameProcess()); +} + +void WebPageProxy::setFullScreenTitlebarOverlayIsRevealed(bool fullScreenTitlebarOverlayIsRevealed) +{ + if (m_fullScreenTitlebarOverlayIsRevealed == fullScreenTitlebarOverlayIsRevealed) + return; + + m_fullScreenTitlebarOverlayIsRevealed = fullScreenTitlebarOverlayIsRevealed; + + if (!hasRunningProcess()) + return; + + protect(legacyMainFrameProcess())->send(Messages::WebPage::SetFullScreenTitlebarOverlayIsDisplayed(fullScreenTitlebarOverlayIsDisplayed()), webPageIDInMainFrameProcess()); +} + +#endif + void WebPageProxy::setObscuredContentInsetsAsync(const FloatBoxExtent& obscuredContentInsets) { m_internals->pendingObscuredContentInsets = obscuredContentInsets; @@ -588,9 +620,7 @@ static inline bool expectsLegacyImplicitRubberBandControl() return nil; } - // The NSFileManager expects a path string, while NSWorkspace uses file URLs, and will decode any percent encoding - // in its passed URLs before loading from disk. Create the files using decoded file paths so they match up. - RetainPtr path = [[pdfDirectoryPath stringByAppendingPathComponent:suggestedFilename.createNSString().get()] stringByRemovingPercentEncoding]; + RetainPtr path = [pdfDirectoryPath stringByAppendingPathComponent:suggestedFilename.createNSString().get()]; RetainPtr fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:path.get()]) { @@ -604,6 +634,10 @@ static inline bool expectsLegacyImplicitRubberBandControl() path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()]; } + // Reject any path that resolves outside the temporary PDF directory. + if (![[path stringByStandardizingPath] hasPrefix:[pdfDirectoryPath stringByStandardizingPath]]) + return nil; + return path; } @@ -614,11 +648,23 @@ static inline bool expectsLegacyImplicitRubberBandControl() return; } - auto sanitizedFilename = ResourceResponseBase::sanitizeSuggestedFilename(suggestedFilename); + // Encoded path separator should get stripped rather than decoded into the assembled path after sanitisation. + // Otherwise, any encoded slash produces a path traversal on post-join decodes. (rdar://174079512) + RetainPtr nsSuggestedFilename = suggestedFilename.createNSString(); + if (RetainPtr decoded = [nsSuggestedFilename stringByRemovingPercentEncoding]) + nsSuggestedFilename = WTF::move(decoded); + + auto sanitizedFilename = ResourceResponseBase::sanitizeSuggestedFilename(nsSuggestedFilename.get()); if (!sanitizedFilename.endsWithIgnoringASCIICase(".pdf"_s)) { WTFLogAlways("Cannot save file without .pdf extension to the temporary directory."); return; } + + if (sanitizedFilename != FileSystem::lastComponentOfPathIgnoringTrailingSlash(sanitizedFilename)) { + RELEASE_LOG(PDF, "Cannot save PDF whose sanitized filename is not a single path component."); + return; + } + RetainPtr nsPath = pathToPDFOnDisk(sanitizedFilename); if (!nsPath) diff --git a/Source/WebKit/UIProcess/mac/WebViewImpl.h b/Source/WebKit/UIProcess/mac/WebViewImpl.h index 4e6601bd2421..f0f5fd52ab90 100644 --- a/Source/WebKit/UIProcess/mac/WebViewImpl.h +++ b/Source/WebKit/UIProcess/mac/WebViewImpl.h @@ -362,7 +362,7 @@ class WebViewImpl final : public CanMakeWeakPtr, public CanMakeChec void windowDidChangeOcclusionState(); void windowWillClose(); void NODELETE windowWillEnterOrExitFullScreen(); - void windowDidEnterOrExitFullScreen(); + void windowDidEnterOrExitFullScreen(bool windowIsInFullScreen); void screenDidChangeColorSpace(); bool shouldDelayWindowOrderingForEvent(NSEvent *); bool windowResizeMouseLocationIsInVisibleScrollerThumb(CGPoint); diff --git a/Source/WebKit/UIProcess/mac/WebViewImpl.mm b/Source/WebKit/UIProcess/mac/WebViewImpl.mm index 619c97bee425..f8317488d10b 100644 --- a/Source/WebKit/UIProcess/mac/WebViewImpl.mm +++ b/Source/WebKit/UIProcess/mac/WebViewImpl.mm @@ -398,9 +398,9 @@ - (void)startObserving:(NSWindow *)window [defaultNotificationCenter addObserver:self selector:@selector(_windowDidChangeOcclusionState:) name:NSWindowDidChangeOcclusionStateNotification object:window]; [defaultNotificationCenter addObserver:self selector:@selector(_windowWillClose:) name:NSWindowWillCloseNotification object:window]; [defaultNotificationCenter addObserver:self selector:@selector(_windowWillEnterOrExitFullScreen:) name:NSWindowWillEnterFullScreenNotification object:window]; - [defaultNotificationCenter addObserver:self selector:@selector(_windowDidEnterOrExitFullScreen:) name:NSWindowDidEnterFullScreenNotification object:window]; + [defaultNotificationCenter addObserver:self selector:@selector(_windowDidEnterFullScreen:) name:NSWindowDidEnterFullScreenNotification object:window]; [defaultNotificationCenter addObserver:self selector:@selector(_windowWillEnterOrExitFullScreen:) name:NSWindowWillExitFullScreenNotification object:window]; - [defaultNotificationCenter addObserver:self selector:@selector(_windowDidEnterOrExitFullScreen:) name:NSWindowDidExitFullScreenNotification object:window]; + [defaultNotificationCenter addObserver:self selector:@selector(_windowDidExitFullScreen:) name:NSWindowDidExitFullScreenNotification object:window]; [defaultNotificationCenter addObserver:self selector:@selector(_screenDidChangeColorSpace:) name:NSScreenColorSpaceDidChangeNotification object:nil]; #if HAVE(SUPPORT_HDR_DISPLAY_APIS) @@ -621,10 +621,16 @@ - (void)_dictionaryLookupPopoverWillClose:(NSNotification *)notification } #endif -- (void)_windowDidEnterOrExitFullScreen:(NSNotification *)notification +- (void)_windowDidEnterFullScreen:(NSNotification *)notification { if (CheckedPtr impl = _impl.get()) - impl->windowDidEnterOrExitFullScreen(); + impl->windowDidEnterOrExitFullScreen(true); +} + +- (void)_windowDidExitFullScreen:(NSNotification *)notification +{ + if (CheckedPtr impl = _impl.get()) + impl->windowDidEnterOrExitFullScreen(false); } - (void)_windowWillEnterOrExitFullScreen:(NSNotification *)notification @@ -2221,10 +2227,14 @@ static NSTrackingAreaOptions NODELETE flagsChangedEventMonitorTrackingAreaOption m_windowIsEnteringOrExitingFullScreen = true; } -void WebViewImpl::windowDidEnterOrExitFullScreen() +void WebViewImpl::windowDidEnterOrExitFullScreen(bool windowIsInFullScreen) { m_windowIsEnteringOrExitingFullScreen = false; +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + m_page->setWindowIsInNativeFullScreen(windowIsInFullScreen ? WebPageProxy::WindowIsInNativeFullScreen::Yes : WebPageProxy::WindowIsInNativeFullScreen::No); +#endif + #if ENABLE(CONTENT_INSET_BACKGROUND_FILL) updateScrollPocket(); #endif @@ -2478,6 +2488,11 @@ static NSTrackingAreaOptions NODELETE flagsChangedEventMonitorTrackingAreaOption m_page->setIntrinsicDeviceScaleFactor(intrinsicDeviceScaleFactor()); m_page->webViewDidMoveToWindow(); + +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + auto isInNativeFullScreen = window && (window.get().styleMask & NSWindowStyleMaskFullScreen); + m_page->setWindowIsInNativeFullScreen(isInNativeFullScreen ? WebPageProxy::WindowIsInNativeFullScreen::Yes : WebPageProxy::WindowIsInNativeFullScreen::No); +#endif } void WebViewImpl::viewDidChangeBackingProperties() @@ -2609,7 +2624,11 @@ static NSTrackingAreaOptions NODELETE flagsChangedEventMonitorTrackingAreaOption if (m_fullScreenTitlebarOverlayHeight == fullScreenTitlebarOverlayHeight) return; + auto wasRevealed = m_fullScreenTitlebarOverlayHeight > 0; m_fullScreenTitlebarOverlayHeight = fullScreenTitlebarOverlayHeight; + auto isRevealed = m_fullScreenTitlebarOverlayHeight > 0; + if (wasRevealed != isRevealed) + m_page->setFullScreenTitlebarOverlayIsRevealed(isRevealed); updateScrollPocket(); } diff --git a/Source/WebKit/WebProcess/Model/WebModelPlayer.mm b/Source/WebKit/WebProcess/Model/WebModelPlayer.mm index ee242bcee36b..44302f05d99e 100644 --- a/Source/WebKit/WebProcess/Model/WebModelPlayer.mm +++ b/Source/WebKit/WebProcess/Model/WebModelPlayer.mm @@ -88,7 +88,7 @@ void display(WebCore::PlatformCALayer& layer) final } else layer.clearContents(); - if (RefPtr player = m_modelPlayer.get()) + if (RefPtr player = m_modelPlayer) player->scheduleUpdateIfNeeded(); } WebCore::GraphicsLayer::CompositingCoordinatesOrientation orientation() const final @@ -142,7 +142,7 @@ void setContentsFormat(WebCore::ContentsFormat contentsFormat) if (RefPtr document = page.localTopDocument()) { m_screenPropertiesChangedObserver = ScreenPropertiesChangedObserver::create([weakThis = ThreadSafeWeakPtr { *this }](WebCore::PlatformDisplayID displayID) { - RefPtr protectedThis = weakThis.get(); + RefPtr protectedThis { weakThis }; if (!protectedThis) return; auto platformScreen = WebCore::PlatformScreen::singleton(); @@ -192,7 +192,7 @@ void setContentsFormat(WebCore::ContentsFormat contentsFormat) void WebModelPlayer::load(WebCore::Model& modelSource, WebCore::LayoutSize size, bool) { - RefPtr corePage = m_page.get(); + RefPtr corePage { m_page }; if (!corePage) return; m_modelLoader = nil; @@ -262,7 +262,7 @@ void setContentsFormat(WebCore::ContentsFormat contentsFormat) if (!model) return; - if (RefPtr client = protectedThis->m_client.get(); client && !protectedThis->m_didFinishLoading) { + if (RefPtr client = protectedThis->m_client; client && !protectedThis->m_didFinishLoading) { protectedThis->m_didFinishLoading = true; [protectedThis->m_modelLoader setLoop:protectedThis->m_isLooping]; protectedThis->m_cachedAnimationState = protectedThis->currentAnimationState(); @@ -312,14 +312,14 @@ void setContentsFormat(WebCore::ContentsFormat contentsFormat) m_retainedData = modelSource.data()->createNSData(); if ([m_modelLoader loadModel:m_retainedData.get() mimeType:modelSource.mimeType().createNSString().get()]) startUpdateLoopIfNeeded(); - else if (RefPtr client = m_client.get()) + else if (RefPtr client = m_client) client->didFailLoading(protectedThis.get(), { }); } void WebModelPlayer::notifyEntityTransformUpdated() { RefPtr model = m_currentModel; - RefPtr client = m_client.get(); + RefPtr client { m_client }; if (!model || !client || !model->entityTransform()) return; @@ -333,7 +333,7 @@ void setContentsFormat(WebCore::ContentsFormat contentsFormat) if (!currentModel) return; - RefPtr corePage = m_page.get(); + RefPtr corePage { m_page }; if (!corePage) return; RefPtr document = corePage->localTopDocument(); @@ -506,7 +506,7 @@ void setContentsFormat(WebCore::ContentsFormat contentsFormat) if (!currentModel || !m_hasRenderedFrame || m_displayTextureIndex >= m_displayBuffers.size()) return nullptr; - RefPtr corePage { m_page.get() }; + RefPtr corePage { m_page }; if (!corePage) return nullptr; @@ -580,7 +580,7 @@ void setContentsFormat(WebCore::ContentsFormat contentsFormat) if (!m_isUpdateLoopRunning || m_isUpdateScheduled) return; - RefPtr corePage = m_page.get(); + RefPtr corePage { m_page }; if (!corePage) return; @@ -676,7 +676,7 @@ void setContentsFormat(WebCore::ContentsFormat contentsFormat) void WebModelPlayer::scheduleDisplayUpdate() { - if (RefPtr graphicsLayer = m_graphicsLayer.get()) + if (RefPtr graphicsLayer = m_graphicsLayer) graphicsLayer->setContentsNeedsDisplay(); } @@ -817,7 +817,7 @@ void setContentsFormat(WebCore::ContentsFormat contentsFormat) } startUpdateLoopIfNeeded(); - if (RefPtr client = m_client.get()) + if (RefPtr client = m_client) client->didFinishEnvironmentMapLoading(*this, success); } @@ -831,7 +831,7 @@ static bool disableReloading() { // When the model becomes invisible, release memory-intensive resources. // When it becomes visible again, HTMLModelElement will trigger a reload through startLoadModelTimer(). - RefPtr client = m_client.get(); + RefPtr client { m_client }; if (!client || disableReloading()) return; @@ -966,7 +966,7 @@ static float interpolateHeadroom(float headroomForLow, float headroomForHigh, fl void WebModelPlayer::updateScreenHeadroomFromPage() { - RefPtr page = m_page.get(); + RefPtr page { m_page }; if (!page) return; diff --git a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp index 71d29d8cf97f..41126289c29f 100644 --- a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp +++ b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp @@ -573,8 +573,6 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL } } - loadParameters.shouldRestrictHTTPResponseAccess = shouldPerformSecurityChecks(); - loadParameters.isMainFrameNavigation = isMainFrameNavigation; if (loadParameters.isMainFrameNavigation && document) { // Fall back to use opener's cross-origin opener policy like in Document::initSecurityContext. @@ -891,7 +889,6 @@ void WebLoaderStrategy::loadResourceSynchronously(FrameLoader& frameLoader, WebC loadParameters.storedCredentialsPolicy = options.credentials == FetchOptions::Credentials::Omit ? StoredCredentialsPolicy::DoNotUse : StoredCredentialsPolicy::Use; loadParameters.clientCredentialPolicy = clientCredentialPolicy; loadParameters.shouldClearReferrerOnHTTPSToHTTPRedirect = shouldClearReferrerOnHTTPSToHTTPRedirect(webFrame ? protect(webFrame->coreLocalFrame()).get() : nullptr); - loadParameters.shouldRestrictHTTPResponseAccess = shouldPerformSecurityChecks(); loadParameters.options = options; loadParameters.sourceOrigin = document->securityOrigin(); @@ -978,7 +975,7 @@ void WebLoaderStrategy::startPingLoad(LocalFrame& frame, ResourceRequest& reques loadParameters.options = options; loadParameters.originalRequestHeaders = originalRequestHeaders; loadParameters.shouldClearReferrerOnHTTPSToHTTPRedirect = shouldClearReferrerOnHTTPSToHTTPRedirect(&frame); - loadParameters.shouldRestrictHTTPResponseAccess = shouldPerformSecurityChecks(); + if (policyCheck == ContentSecurityPolicyImposition::DoPolicyCheck && !document->shouldBypassMainWorldContentSecurityPolicy()) { if (CheckedPtr contentSecurityPolicy = document->contentSecurityPolicy()) loadParameters.cspResponseHeaders = contentSecurityPolicy->responseHeaders(); @@ -1060,7 +1057,7 @@ void WebLoaderStrategy::preconnectTo(WebCore::ResourceRequest&& request, WebPage parameters.parentPID = legacyPresentingApplicationPID(); parameters.storedCredentialsPolicy = storedCredentialsPolicy; parameters.shouldPreconnectOnly = PreconnectOnly::Yes; - parameters.shouldRestrictHTTPResponseAccess = shouldPerformSecurityChecks(); + // FIXME: Use the proper destination once all fetch options are passed. parameters.options.destination = FetchOptions::Destination::EmptyString; #if ENABLE(APP_BOUND_DOMAINS) diff --git a/Source/WebKit/WebProcess/WebCoreSupport/SessionStateConversion.cpp b/Source/WebKit/WebProcess/WebCoreSupport/SessionStateConversion.cpp index 7f1e6a6645a8..77816e2b98e5 100644 --- a/Source/WebKit/WebProcess/WebCoreSupport/SessionStateConversion.cpp +++ b/Source/WebKit/WebProcess/WebCoreSupport/SessionStateConversion.cpp @@ -101,6 +101,7 @@ Ref toFrameState(const HistoryItem& historyItem) frameState->sessionStateObject = historyItem.stateObject(); frameState->wasCreatedByJSWithoutUserInteraction = historyItem.wasCreatedByJSWithoutUserInteraction(); frameState->wasRestoredFromSession = historyItem.wasRestoredFromSession(); + frameState->isInitialAboutBlank = historyItem.isInitialAboutBlank(); frameState->policyContainer = historyItem.policyContainer(); static constexpr auto maxTitleLength = 1000u; // Closest power of 10 above the W3C recommendation for Title length. @@ -174,6 +175,7 @@ static void applyFrameState(HistoryItemClient& client, HistoryItem& historyItem, historyItem.setStateObject(frameState.sessionStateObject.get()); historyItem.setWasCreatedByJSWithoutUserInteraction(frameState.wasCreatedByJSWithoutUserInteraction); historyItem.setWasRestoredFromSession(frameState.wasRestoredFromSession); + historyItem.setIsInitialAboutBlank(frameState.isInitialAboutBlank); if (auto policyContainer = frameState.policyContainer) historyItem.setPolicyContainer(*policyContainer); diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp index 1d1af9ff7dd6..a6f1b13095b9 100644 --- a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp +++ b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp @@ -2074,12 +2074,12 @@ void WebChromeClient::hasStorageAccess(RegistrableDomain&& subFrameDomain, Regis completionHandler(false); } -void WebChromeClient::requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, LocalFrame& frame, StorageAccessScope scope, HasOrShouldIgnoreUserGesture hasOrShouldIgnoreUserGesture, CompletionHandler&& completionHandler) +void WebChromeClient::requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, LocalFrame& frame, StorageAccessScope scope, HasUserGestureOrNoUserGestureRequired hasUserGestureOrNoUserGestureRequired, CompletionHandler&& completionHandler) { RefPtr webFrame = WebFrame::fromCoreFrame(frame); ASSERT(webFrame); if (RefPtr page = m_page.get()) - page->requestStorageAccess(WTF::move(subFrameDomain), WTF::move(topFrameDomain), *webFrame, scope, hasOrShouldIgnoreUserGesture, WTF::move(completionHandler)); + page->requestStorageAccess(WTF::move(subFrameDomain), WTF::move(topFrameDomain), *webFrame, scope, hasUserGestureOrNoUserGestureRequired, WTF::move(completionHandler)); else completionHandler({ }); } diff --git a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h index 19896b0cd0d5..e47d57813d3b 100644 --- a/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h +++ b/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h @@ -458,7 +458,7 @@ class WebChromeClient final : public WebCore::ChromeClient { void didInvalidateDocumentMarkerRects() final; void hasStorageAccess(WebCore::RegistrableDomain&& subFrameDomain, WebCore::RegistrableDomain&& topFrameDomain, WebCore::LocalFrame&, WTF::CompletionHandler&&) final; - void requestStorageAccess(WebCore::RegistrableDomain&& subFrameDomain, WebCore::RegistrableDomain&& topFrameDomain, WebCore::LocalFrame&, WebCore::StorageAccessScope, WebCore::HasOrShouldIgnoreUserGesture, WTF::CompletionHandler&&) final; + void requestStorageAccess(WebCore::RegistrableDomain&& subFrameDomain, WebCore::RegistrableDomain&& topFrameDomain, WebCore::LocalFrame&, WebCore::StorageAccessScope, WebCore::HasUserGestureOrNoUserGestureRequired, WTF::CompletionHandler&&) final; bool hasPageLevelStorageAccess(const WebCore::RegistrableDomain& topLevelDomain, const WebCore::RegistrableDomain& resourceDomain) const final; void setLoginStatus(WebCore::RegistrableDomain&&, WebCore::IsLoggedIn, WTF::CompletionHandler&&) final; diff --git a/Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm b/Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm index 109a39d7e936..b1a5044d7a5b 100644 --- a/Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm +++ b/Source/WebKit/WebProcess/WebPage/Cocoa/WebPageCocoa.mm @@ -1696,6 +1696,11 @@ static void drawPDFPage(PDFDocument *pdfDocument, CFIndex pageIndex, CGContextRe auto sides = m_page->fixedContainerEdges().fixedEdges(); +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + if (m_fullScreenTitlebarOverlayIsDisplayed) + sides.add(BoxSide::Top); +#endif + if ((additionalHeight + obscuredInsets.top()) > 0) sides.add(BoxSide::Top); diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp index f1043a817fad..0a4e49cfb18a 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp +++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp @@ -693,6 +693,9 @@ WebPage::WebPage(PageIdentifier pageID, WebPageCreationParameters&& parameters) #endif #if PLATFORM(MAC) , m_overflowHeightForTopScrollEdgeEffect(parameters.overflowHeightForTopScrollEdgeEffect) +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + , m_fullScreenTitlebarOverlayIsDisplayed(parameters.fullScreenTitlebarOverlayIsDisplayed) +#endif #endif #if ENABLE(META_VIEWPORT) , m_forceAlwaysUserScalable(parameters.ignoresViewportScaleLimits) @@ -8962,9 +8965,9 @@ void WebPage::hasStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDo WebProcess::singleton().ensureNetworkProcessConnection().connection().sendWithAsyncReply(Messages::NetworkConnectionToWebProcess::HasStorageAccess(WTF::move(subFrameDomain), WTF::move(topFrameDomain), frame.frameID(), m_identifier), WTF::move(completionHandler)); } -void WebPage::requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, WebFrame& frame, StorageAccessScope scope, HasOrShouldIgnoreUserGesture hasOrShouldIgnoreUserGesture, CompletionHandler&& completionHandler) +void WebPage::requestStorageAccess(RegistrableDomain&& subFrameDomain, RegistrableDomain&& topFrameDomain, WebFrame& frame, StorageAccessScope scope, HasUserGestureOrNoUserGestureRequired hasUserGestureOrNoUserGestureRequired, CompletionHandler&& completionHandler) { - WebProcess::singleton().ensureNetworkProcessConnection().connection().sendWithAsyncReply(Messages::NetworkConnectionToWebProcess::RequestStorageAccess(WTF::move(subFrameDomain), WTF::move(topFrameDomain), frame.frameID(), m_identifier, m_webPageProxyIdentifier, scope, hasOrShouldIgnoreUserGesture), [this, protectedThis = Ref { *this }, completionHandler = WTF::move(completionHandler), frame = Ref { frame }, pageID = m_identifier, frameID = frame.frameID()](RequestStorageAccessResult result) mutable { + WebProcess::singleton().ensureNetworkProcessConnection().connection().sendWithAsyncReply(Messages::NetworkConnectionToWebProcess::RequestStorageAccess(WTF::move(subFrameDomain), WTF::move(topFrameDomain), frame.frameID(), m_identifier, m_webPageProxyIdentifier, scope, hasUserGestureOrNoUserGestureRequired), [this, protectedThis = Ref { *this }, completionHandler = WTF::move(completionHandler), frame = Ref { frame }, pageID = m_identifier, frameID = frame.frameID()](RequestStorageAccessResult result) mutable { if (result.wasGranted == StorageAccessWasGranted::Yes) { switch (result.scope) { case StorageAccessScope::PerFrame: diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h index 6a01b62af0c9..d019f9230bab 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.h +++ b/Source/WebKit/WebProcess/WebPage/WebPage.h @@ -254,7 +254,7 @@ enum class EventHandling : uint8_t; enum class EventMakesGamepadsVisible : bool; enum class ExceptionCode : uint8_t; enum class FinalizeRenderingUpdateFlags : uint8_t; -enum class HasOrShouldIgnoreUserGesture : bool; +enum class HasUserGestureOrNoUserGestureRequired : bool; enum class HighlightRequestOriginatedInApp : bool; enum class IFrameUnloadReason : bool; enum class ImageDecodingError : uint8_t; @@ -1787,7 +1787,7 @@ class WebPage final : public API::ObjectImpl, pub #endif void hasStorageAccess(WebCore::RegistrableDomain&& subFrameDomain, WebCore::RegistrableDomain&& topFrameDomain, WebFrame&, CompletionHandler&&); - void requestStorageAccess(WebCore::RegistrableDomain&& subFrameDomain, WebCore::RegistrableDomain&& topFrameDomain, WebFrame&, WebCore::StorageAccessScope, WebCore::HasOrShouldIgnoreUserGesture, CompletionHandler&&); + void requestStorageAccess(WebCore::RegistrableDomain&& subFrameDomain, WebCore::RegistrableDomain&& topFrameDomain, WebFrame&, WebCore::StorageAccessScope, WebCore::HasUserGestureOrNoUserGestureRequired, CompletionHandler&&); void setLoginStatus(WebCore::RegistrableDomain&&, WebCore::IsLoggedIn, CompletionHandler&&); void isLoggedIn(WebCore::RegistrableDomain&&, CompletionHandler&&); bool hasPageLevelStorageAccess(const WebCore::RegistrableDomain& topLevelDomain, const WebCore::RegistrableDomain& resourceDomain) const; @@ -2196,6 +2196,9 @@ class WebPage final : public API::ObjectImpl, pub #if PLATFORM(MAC) void setOverflowHeightForTopScrollEdgeEffect(double value) { m_overflowHeightForTopScrollEdgeEffect = value; } +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + void setFullScreenTitlebarOverlayIsDisplayed(bool fullScreenTitlebarOverlayIsDisplayed) { m_fullScreenTitlebarOverlayIsDisplayed = fullScreenTitlebarOverlayIsDisplayed; } +#endif #endif RefPtr shareableBitmapSnapshotForNode(WebCore::Node&); @@ -3090,12 +3093,14 @@ class WebPage final : public API::ObjectImpl, pub #if PLATFORM(MAC) double m_overflowHeightForTopScrollEdgeEffect { 0 }; - // Root-view origin of the in-flight selection-extend drag: captured on the first extent update and // cleared by `cancelAutoscroll` (which the UI process calls at gesture begin/end). Lets the edge check // require a minimum drag toward an edge before selection autoscroll engages, so a selection that merely // originates near an edge doesn't scroll. Persists across hot-zone enter/exit within a single drag. std::optional m_selectionAutoscrollDragOrigin; +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + bool m_fullScreenTitlebarOverlayIsDisplayed { false }; +#endif #endif bool m_needsScrollGeometryUpdates { false }; diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in index 25e1d4943c97..2b3711f32f3c 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in +++ b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in @@ -677,6 +677,9 @@ messages -> WebPage WantsAsyncDispatchMessage { DidEndMagnificationGesture() SetOverflowHeightForTopScrollEdgeEffect(double value) +#if ENABLE(SCROLL_POCKET_IN_FULLSCREEN) + SetFullScreenTitlebarOverlayIsDisplayed(bool fullScreenTitlebarOverlayIsDisplayed) +#endif #endif StartDeferringResizeEvents(); diff --git a/Tools/Scripts/measure-build-time b/Tools/Scripts/measure-build-time index f6dcff645797..e26250e6eb1e 100755 --- a/Tools/Scripts/measure-build-time +++ b/Tools/Scripts/measure-build-time @@ -224,23 +224,23 @@ class IncrementalBuild6(PatchIncrementalBuild): description = 'Apply a small patch that changes a Swift-C++ class\'s public initializer and rebuild.' patch = '''\ diff --git a/Source/WebKit/UIProcess/WebBackForwardList.cpp b/Source/WebKit/UIProcess/WebBackForwardList.cpp -index 5fbcb767a0f5..b251202181fd 100644 +index 73419fbf4994..12e9c6ebda40 100644 --- a/Source/WebKit/UIProcess/WebBackForwardList.cpp +++ b/Source/WebKit/UIProcess/WebBackForwardList.cpp -@@ -968,7 +968,7 @@ void WebBackForwardList::didReceiveProvisionalMessage(IPC::Connection& connectio +@@ -990,7 +990,7 @@ void WebBackForwardList::didReceiveProvisionalMessage(IPC::Connection& connectio #else // ENABLE(BACK_FORWARD_LIST_SWIFT) WebBackForwardListWrapper::WebBackForwardListWrapper(WebPageProxy& webPageProxy) - : m_impl(WTF::makeUniqueWithoutFastMallocCheck(WebBackForwardList::init(webPageProxy))) + : m_impl(WTF::makeUniqueWithoutFastMallocCheck(WebBackForwardList::init(webPageProxy, true))) + , m_messageForwarder(m_impl->getMessageReceiver()) { } - diff --git a/Source/WebKit/UIProcess/WebBackForwardList.swift b/Source/WebKit/UIProcess/WebBackForwardList.swift -index 3e1247fdf3d3..8b243cd79c1e 100644 +index 7556b716bef9..24e1cf8098e1 100644 --- a/Source/WebKit/UIProcess/WebBackForwardList.swift +++ b/Source/WebKit/UIProcess/WebBackForwardList.swift -@@ -137,7 +137,8 @@ final class WebBackForwardList { +@@ -141,7 +141,8 @@ final class WebBackForwardList { // @used ensures these are retained even under -O -wmo: rdar://179098545 @used diff --git a/Tools/Scripts/webkitpy/browserperfdash/plans/browser_binary_size.py b/Tools/Scripts/webkitpy/browserperfdash/plans/browser_binary_size.py index 10a493019211..0e68ac17ed73 100644 --- a/Tools/Scripts/webkitpy/browserperfdash/plans/browser_binary_size.py +++ b/Tools/Scripts/webkitpy/browserperfdash/plans/browser_binary_size.py @@ -22,6 +22,8 @@ import json import os +import subprocess +import tempfile from webkitpy.common.host import Host from webkitpy.port import configuration_options, platform_options, factory from webkitpy.binary_bundling.ldd import SharedObjectResolver @@ -38,20 +40,35 @@ def generate_json_for_benchmark(basename_sizes): return benchmark_json +def get_stripped_object_size(object_path): + fd, tmp_path = tempfile.mkstemp(prefix=f'{PLUGIN_NAME}_', suffix='.stripped') + os.close(fd) + try: + result = subprocess.run(['strip', '--strip-all', '-o', tmp_path, object_path], capture_output=True, text=True) + if result.returncode != 0: + raise RuntimeError(f"'strip --strip-all' failed for '{object_path}' (exit {result.returncode}): {result.stderr.strip()}") + return os.path.getsize(tmp_path) + finally: + try: + os.remove(tmp_path) + except OSError: + pass + + def get_basenames_and_sizes(path_list): basename_sizes = {} for object_path in path_list: object_base = os.path.basename(object_path) if object_base in basename_sizes: raise RuntimeError(f'There are repeated objects on the list. Object {object_path} is at least twice') - basename_sizes[object_base] = os.path.getsize(object_path) + basename_sizes[object_base] = get_stripped_object_size(object_path) return basename_sizes def get_browser_relevant_objects_glib(browser_driver): required_binary = 'Tools/Scripts/run-minibrowser' if not browser_driver.process_name.endswith(required_binary): - raise NotImplementedError(f'Getting the browser size data for browser "{browser_name}" is only supported when running the browser via "{required_binary}" and the driver was going to run "{browser_driver.process_name}"') + raise NotImplementedError(f'Getting the browser size data for browser "{browser_driver.browser_name}" is only supported when running the browser via "{required_binary}" and the driver was going to run "{browser_driver.process_name}"') browser_relevant_objects = [] port_name = 'gtk' if browser_driver.browser_name == 'minibrowser-gtk' else 'wpe' port_driver = factory.PortFactory(Host()).get(port_name) diff --git a/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py b/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py index cd10a8cf064d..98f86fcf6076 100644 --- a/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py +++ b/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py @@ -955,6 +955,8 @@ def print_expectations(self, args): aggregate_tests = aggregate_tests_to_run | aggregate_tests_to_skip self._printer.print_found(len(aggregate_tests), len(aggregate_tests_to_run), self._options.repeat_each, self._options.iterations) + if not aggregate_tests: + continue test_col_width = max(len(test.test_path) for test in aggregate_tests) + 1 self._print_expectations_for_subset(device_type_list[0], test_col_width, tests_to_run_by_device[device_type_list[0]], aggregate_tests_to_skip) diff --git a/Tools/Scripts/webkitpy/layout_tests/controllers/manager_unittest.py b/Tools/Scripts/webkitpy/layout_tests/controllers/manager_unittest.py index b799df2b5722..da1b3d475c84 100644 --- a/Tools/Scripts/webkitpy/layout_tests/controllers/manager_unittest.py +++ b/Tools/Scripts/webkitpy/layout_tests/controllers/manager_unittest.py @@ -141,3 +141,14 @@ def parse_exp(test_names, expectations): # so if the output is not *exactly* as expected including whitespaces, this # could lead to unwanted effects, like blocking builds for a long time. self.assertEqual(get_printed_expectations(), out) + + def test_print_expectations_no_tests_found(self): + # Passing a non-existent path should not raise ValueError from max() on an + # empty sequence; it should return 0 cleanly. + manager = self._get_manager() + manager._options.update(repeat_each=1, iterations=1) + device_type_list = manager._port.supported_device_types() + manager._create_port_for_driver = Mock(return_value=manager._port) + manager._collect_tests = Mock(return_value=({dt: [] for dt in device_type_list}, set())) + exit_code = manager.print_expectations(['this/file/does/not/exist.html']) + self.assertEqual(exit_code, 0) diff --git a/Tools/TestWebKitAPI/CMakeLists.txt b/Tools/TestWebKitAPI/CMakeLists.txt index 1ea34713d9dc..24512eb7e1aa 100644 --- a/Tools/TestWebKitAPI/CMakeLists.txt +++ b/Tools/TestWebKitAPI/CMakeLists.txt @@ -136,6 +136,7 @@ set(TestWTF_SOURCES Tests/WTF/StringView.cpp Tests/WTF/SynchronizedFixedQueue.cpp Tests/WTF/TextBreakIterator.cpp + Tests/WTF/TinyLRUCache.cpp Tests/WTF/ThreadGroup.cpp Tests/WTF/ThreadMessages.cpp Tests/WTF/Threading.cpp diff --git a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj index 4b74c83f074d..f992cbbb57a8 100644 --- a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj +++ b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj @@ -669,10 +669,10 @@ WebCore/CBORReaderTest.cpp, WebCore/CBORValueTest.cpp, WebCore/CBORWriterTest.cpp, + WebCore/cocoa/AttributedStringFontCache.mm, WebCore/cocoa/AudioStreamDescriptionCocoa.mm, WebCore/cocoa/AudioVideoRendererAVFObjCTests.mm, WebCore/cocoa/AVFoundationSoftLinkTest.mm, - WebCore/cocoa/AttributedStringFontCache.mm, WebCore/cocoa/BifurcatedGraphicsContextTestsCG.cpp, WebCore/cocoa/CaptionPreferencesTests.mm, WebCore/cocoa/CoreMediaUtilities.mm, diff --git a/Tools/TestWebKitAPI/Tests/WTF/TinyLRUCache.cpp b/Tools/TestWebKitAPI/Tests/WTF/TinyLRUCache.cpp new file mode 100644 index 000000000000..fdfbbf0c7e6e --- /dev/null +++ b/Tools/TestWebKitAPI/Tests/WTF/TinyLRUCache.cpp @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2026 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include + +#include "Helpers/Test.h" +#include +#include + +namespace TestWebKitAPI { + +struct CountingPolicy : public WTF::TinyLRUCachePolicy { + static unsigned createCount; + static int createValueForKey(const int& key) + { + ++createCount; + return key * 10; + } +}; +unsigned CountingPolicy::createCount = 0; + +TEST(WTF_TinyLRUCache, GetCachesOnHit) +{ + CountingPolicy::createCount = 0; + WTF::TinyLRUCache cache; + EXPECT_EQ(0u, cache.size()); + + EXPECT_EQ(10, cache.get(1)); + EXPECT_EQ(1u, CountingPolicy::createCount); + EXPECT_EQ(1u, cache.size()); + + EXPECT_EQ(10, cache.get(1)); + EXPECT_EQ(1u, CountingPolicy::createCount); + EXPECT_EQ(1u, cache.size()); + + EXPECT_EQ(20, cache.get(2)); + EXPECT_EQ(2u, CountingPolicy::createCount); + EXPECT_EQ(2u, cache.size()); +} + +TEST(WTF_TinyLRUCache, GetEvictsLRU) +{ + CountingPolicy::createCount = 0; + WTF::TinyLRUCache cache; + + cache.get(1); + cache.get(2); + EXPECT_EQ(2u, cache.size()); + + cache.get(3); // evicts 1 + EXPECT_EQ(2u, cache.size()); + + cache.get(2); + EXPECT_EQ(3u, CountingPolicy::createCount); + + cache.get(1); // re-computed because evicted + EXPECT_EQ(4u, CountingPolicy::createCount); +} + +struct NullKeyPolicy : public WTF::TinyLRUCachePolicy { + static unsigned createCount; + static unsigned createNullCount; + static bool isKeyNull(const int& key) { return !key; } + static int createValueForNullKey() + { + ++createNullCount; + return -1; + } + static int createValueForKey(const int& key) + { + ++createCount; + return key * 10; + } +}; +unsigned NullKeyPolicy::createCount = 0; +unsigned NullKeyPolicy::createNullCount = 0; + +TEST(WTF_TinyLRUCache, GetWithNullKeyReturnsNullValueAndDoesNotStore) +{ + NullKeyPolicy::createCount = 0; + NullKeyPolicy::createNullCount = 0; + WTF::TinyLRUCache cache; + + EXPECT_EQ(-1, cache.get(0)); + EXPECT_EQ(0u, cache.size()); + EXPECT_EQ(0u, NullKeyPolicy::createCount); + + // Repeated null lookups must not grow the cache or re-create the null value. + EXPECT_EQ(-1, cache.get(0)); + EXPECT_EQ(0u, cache.size()); + EXPECT_EQ(1u, NullKeyPolicy::createNullCount); + + // Non-null keys still work and are unaffected by null-key lookups. + EXPECT_EQ(50, cache.get(5)); + EXPECT_EQ(1u, cache.size()); + EXPECT_EQ(1u, NullKeyPolicy::createCount); + + // findIfCached must miss for null keys regardless of prior get() calls. + EXPECT_FALSE(cache.findIfCached(0)); +} + +TEST(WTF_TinyLRUCache, FindIfCachedReturnsNullOnMiss) +{ + WTF::TinyLRUCache cache; + EXPECT_FALSE(cache.findIfCached(42)); +} + +TEST(WTF_TinyLRUCache, FindIfCachedHitsAfterInsert) +{ + WTF::TinyLRUCache cache; + cache.insert(1, 100); + auto hit = cache.findIfCached(1); + ASSERT_TRUE(hit); + EXPECT_EQ(100, *hit); + EXPECT_EQ(1u, cache.size()); +} + +TEST(WTF_TinyLRUCache, InsertEvictsLRU) +{ + WTF::TinyLRUCache cache; + cache.insert(1, 100); + cache.insert(2, 200); + EXPECT_EQ(2u, cache.size()); + + cache.insert(3, 300); // evicts 1 + EXPECT_EQ(2u, cache.size()); + EXPECT_FALSE(cache.findIfCached(1)); + auto two = cache.findIfCached(2); + ASSERT_TRUE(two); + EXPECT_EQ(200, *two); + auto three = cache.findIfCached(3); + ASSERT_TRUE(three); + EXPECT_EQ(300, *three); +} + +TEST(WTF_TinyLRUCache, FindIfCachedPromotesToMRU) +{ + WTF::TinyLRUCache cache; + cache.insert(1, 100); + cache.insert(2, 200); + cache.insert(3, 300); + + // Promote 1 to MRU; subsequent insert should evict 2 (now LRU), not 1. + EXPECT_TRUE(cache.findIfCached(1)); + + cache.insert(4, 400); + EXPECT_TRUE(cache.findIfCached(1)); + EXPECT_FALSE(cache.findIfCached(2)); + EXPECT_TRUE(cache.findIfCached(3)); + EXPECT_TRUE(cache.findIfCached(4)); +} + +TEST(WTF_TinyLRUCache, ClearReleasesEntriesAndResetsSize) +{ + WTF::TinyLRUCache cache; + cache.insert(1, 100); + cache.insert(2, 200); + EXPECT_EQ(2u, cache.size()); + cache.clear(); + EXPECT_EQ(0u, cache.size()); + EXPECT_FALSE(cache.findIfCached(1)); + EXPECT_FALSE(cache.findIfCached(2)); +} + +class TrackedRefCounted : public RefCounted { +public: + static unsigned aliveCount; + static Ref create(int value) { return adoptRef(*new TrackedRefCounted(value)); } + int value() const { return m_value; } + ~TrackedRefCounted() { --aliveCount; } +private: + TrackedRefCounted(int value) : m_value(value) { ++aliveCount; } + int m_value; +}; +unsigned TrackedRefCounted::aliveCount = 0; + +TEST(WTF_TinyLRUCache, ClearReleasesRefsImmediately) +{ + TrackedRefCounted::aliveCount = 0; + { + WTF::TinyLRUCache, 4> cache; + cache.insert(1, TrackedRefCounted::create(100).ptr()); + cache.insert(2, TrackedRefCounted::create(200).ptr()); + EXPECT_EQ(2u, TrackedRefCounted::aliveCount); + + cache.clear(); + EXPECT_EQ(0u, TrackedRefCounted::aliveCount); + } +} + +TEST(WTF_TinyLRUCache, EvictionViaInsertReleasesRef) +{ + TrackedRefCounted::aliveCount = 0; + { + WTF::TinyLRUCache, 2> cache; + cache.insert(1, TrackedRefCounted::create(100).ptr()); + cache.insert(2, TrackedRefCounted::create(200).ptr()); + EXPECT_EQ(2u, TrackedRefCounted::aliveCount); + + // Inserting a third entry into a capacity-2 cache must drop the LRU's ref. + cache.insert(3, TrackedRefCounted::create(300).ptr()); + EXPECT_EQ(2u, TrackedRefCounted::aliveCount); + } + EXPECT_EQ(0u, TrackedRefCounted::aliveCount); +} + +TEST(WTF_TinyLRUCache, FindIfCachedDoesNotConsumeEntry) +{ + WTF::TinyLRUCache cache; + cache.insert(1, 100); + { + auto hit = cache.findIfCached(1); + ASSERT_TRUE(hit); + EXPECT_EQ(100, *hit); + } + { + auto hit = cache.findIfCached(1); + ASSERT_TRUE(hit); + EXPECT_EQ(100, *hit); + } + EXPECT_EQ(1u, cache.size()); +} + +TEST(WTF_TinyLRUCache, FindIfCachedAfterClearMisses) +{ + WTF::TinyLRUCache cache; + cache.insert(1, 100); + cache.clear(); + EXPECT_FALSE(cache.findIfCached(1)); + cache.insert(1, 999); // re-insert with different value + auto hit = cache.findIfCached(1); + ASSERT_TRUE(hit); + EXPECT_EQ(999, *hit); +} + +TEST(WTF_TinyLRUCache, FindOrComputeWorkflow) +{ + WTF::TinyLRUCache cache; + int externalContext = 7; + + auto findOrInsert = [&](int key) -> int { + if (auto hit = cache.findIfCached(key)) + return *hit; + int value = key * externalContext; + cache.insert(key, WTF::move(value)); + auto hit = cache.findIfCached(key); + return hit ? *hit : -1; + }; + + EXPECT_EQ(7, findOrInsert(1)); + EXPECT_EQ(14, findOrInsert(2)); + EXPECT_EQ(7, findOrInsert(1)); + EXPECT_EQ(14, findOrInsert(2)); + EXPECT_EQ(2u, cache.size()); +} + +TEST(WTF_TinyLRUCache, FindResultSafeAfterCacheDestroyed) +{ + WTF::TinyLRUCache* cache = new WTF::TinyLRUCache(); + cache->insert(1, 100); + auto result = cache->findIfCached(1); + ASSERT_TRUE(result); + delete cache; + EXPECT_FALSE(result); +} + +TEST(WTF_TinyLRUCache, InsertInvalidatesFindResult) +{ + WTF::TinyLRUCache cache; + cache.insert(1, 100); + auto result = cache.findIfCached(1); + ASSERT_TRUE(result); + cache.insert(2, 200); + EXPECT_FALSE(result); +} + +TEST(WTF_TinyLRUCache, ClearInvalidatesFindResult) +{ + WTF::TinyLRUCache cache; + cache.insert(1, 100); + auto result = cache.findIfCached(1); + ASSERT_TRUE(result); + cache.clear(); + EXPECT_FALSE(result); +} + +TEST(WTF_TinyLRUCache, SubsequentFindInvalidatesPriorFindResult) +{ + WTF::TinyLRUCache cache; + cache.insert(1, 100); + cache.insert(2, 200); + auto result = cache.findIfCached(2); + ASSERT_TRUE(result); + cache.findIfCached(1); + EXPECT_FALSE(result); +} + +TEST(WTF_TinyLRUCacheDeathTest, UseAfterInsertDeathTest) +{ + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + WTF::TinyLRUCache cache; + cache.insert(1, 100); + auto result = cache.findIfCached(1); + ASSERT_TRUE(result); + cache.insert(2, 200); + ASSERT_DEATH_IF_SUPPORTED(*result, ""); +} + +TEST(WTF_TinyLRUCacheDeathTest, UseAfterClearDeathTest) +{ + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + WTF::TinyLRUCache cache; + cache.insert(1, 100); + auto result = cache.findIfCached(1); + ASSERT_TRUE(result); + cache.clear(); + ASSERT_DEATH_IF_SUPPORTED(*result, ""); +} + +TEST(WTF_TinyLRUCacheDeathTest, UseAfterSubsequentFindDeathTest) +{ + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + WTF::TinyLRUCache cache; + cache.insert(1, 100); + cache.insert(2, 200); + auto result = cache.findIfCached(2); + ASSERT_TRUE(result); + cache.findIfCached(1); + ASSERT_DEATH_IF_SUPPORTED(*result, ""); +} + +} // namespace TestWebKitAPI diff --git a/Tools/TestWebKitAPI/Tests/WebCore/ImageBufferTests.cpp b/Tools/TestWebKitAPI/Tests/WebCore/ImageBufferTests.cpp index 5217363bd3a6..e92116a3a69b 100644 --- a/Tools/TestWebKitAPI/Tests/WebCore/ImageBufferTests.cpp +++ b/Tools/TestWebKitAPI/Tests/WebCore/ImageBufferTests.cpp @@ -326,4 +326,41 @@ INSTANTIATE_TEST_SUITE_P(ImageBufferTests, testing::Values(RenderingMode::Unaccelerated, RenderingMode::Accelerated)), TestParametersToStringFormatter()); +#if USE(CG) + +TEST(ImageBufferTests, GetPixelBufferAllZeros) +{ + auto sourceColorSpace = DestinationColorSpace::SRGB(); + auto sourcePixelFormat = PixelFormat::BGRA8; + FloatSize size { 1000, 1000 }; + FloatRect fillRect = FloatRect { { }, size }; + float scale = 1.f; + + RefPtr imageBuffer = ImageBuffer::create(size, RenderingMode::Unaccelerated, RenderingPurpose::Unspecified, scale, sourceColorSpace, sourcePixelFormat); + EXPECT_NE(nullptr, imageBuffer); + + auto& context = imageBuffer->context(); + context.fillRect(fillRect, Color::green); + + auto getPixelBufferAllZeros = [&](const FloatRect& rect) { + RetainPtr platformColorSpace = adoptCF(CGColorSpaceCreateWithName(kCGColorSpaceGenericCMYK)); + auto destinationColorSpace = DestinationColorSpace(WTF::move(platformColorSpace)); + PixelBufferFormat destinationPixelFormat { AlphaPremultiplication::Unpremultiplied, PixelFormat::RGBA8, destinationColorSpace }; + + RefPtr pixelBuffer = imageBuffer->getPixelBuffer(destinationPixelFormat, enclosingIntRect(rect)); + EXPECT_NE(nullptr, pixelBuffer); + + auto bytes = pixelBuffer->bytes(); + return std::none_of(bytes.begin(), bytes.end(), [](auto byte) { + return byte; + }); + }; + + EXPECT_TRUE(getPixelBufferAllZeros({ { }, size })); + EXPECT_TRUE(getPixelBufferAllZeros({ { }, size / 10 })); + EXPECT_TRUE(getPixelBufferAllZeros({ FloatPoint { size - size / 10 }, size / 10 })); +} + +#endif + } diff --git a/Tools/TestWebKitAPI/Tests/WebKit/WKBackForwardListTests.mm b/Tools/TestWebKitAPI/Tests/WebKit/WKBackForwardListTests.mm new file mode 100644 index 000000000000..0c52f9aed0b6 --- /dev/null +++ b/Tools/TestWebKitAPI/Tests/WebKit/WKBackForwardListTests.mm @@ -0,0 +1,1188 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "config.h" + +#import "HTTPServer.h" +#import "PlatformUtilities.h" +#import "Test.h" +#import "TestNavigationDelegate.h" +#import "TestUIDelegate.h" +#import "TestWKWebView.h" +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + +static NSString *loadableURL1 = @"data:text/html,no%20error%20A"; +static NSString *loadableURL2 = @"data:text/html,no%20error%20B"; +static NSString *loadableURL3 = @"data:text/html,no%20error%20C"; + +TEST(WKBackForwardList, RemoveCurrentItem) +{ + auto webView = adoptNS([[WKWebView alloc] init]); + + [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadableURL1]]]; + [webView _test_waitForDidFinishNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadableURL2]]]; + [webView _test_waitForDidFinishNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadableURL3]]]; + [webView _test_waitForDidFinishNavigation]; + + WKBackForwardList *list = [webView backForwardList]; + EXPECT_EQ((NSUInteger)2, list.backList.count); + EXPECT_EQ((NSUInteger)0, list.forwardList.count); + EXPECT_STREQ([[list.currentItem URL] absoluteString].UTF8String, loadableURL3.UTF8String); + + _WKSessionState *sessionState = [webView _sessionStateWithFilter:^BOOL(WKBackForwardListItem *item) + { + return [item.URL isEqual:[NSURL URLWithString:loadableURL2]]; + }]; + + [webView _restoreSessionState:sessionState andNavigate:NO]; + + WKBackForwardList *newList = [webView backForwardList]; + + EXPECT_EQ((NSUInteger)0, newList.backList.count); + EXPECT_EQ((NSUInteger)0, newList.forwardList.count); + EXPECT_STREQ([[newList.currentItem URL] absoluteString].UTF8String, loadableURL2.UTF8String); +} + +TEST(WKBackForwardList, CanNotGoBackAfterRestoringEmptySessionState) +{ + auto webView = adoptNS([[WKWebView alloc] init]); + + [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadableURL1]]]; + [webView _test_waitForDidFinishNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadableURL2]]]; + [webView _test_waitForDidFinishNavigation]; + + WKBackForwardList *list = [webView backForwardList]; + EXPECT_EQ(YES, [webView canGoBack]); + EXPECT_EQ(NO, [webView canGoForward]); + EXPECT_EQ((NSUInteger)1, list.backList.count); + EXPECT_EQ((NSUInteger)0, list.forwardList.count); + + auto singlePageWebView = adoptNS([[WKWebView alloc] init]); + + [singlePageWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadableURL1]]]; + [singlePageWebView _test_waitForDidFinishNavigation]; + + [webView _restoreSessionState:[singlePageWebView _sessionState] andNavigate:NO]; + + WKBackForwardList *newList = [webView backForwardList]; + + EXPECT_EQ(NO, [webView canGoBack]); + EXPECT_EQ(NO, [webView canGoForward]); + EXPECT_EQ((NSUInteger)0, newList.backList.count); + EXPECT_EQ((NSUInteger)0, newList.forwardList.count); +} + +TEST(WKBackForwardList, RestoringNilSessionState) +{ + auto webView = adoptNS([[WKWebView alloc] init]); + + [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadableURL1]]]; + [webView _test_waitForDidFinishNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadableURL2]]]; + [webView _test_waitForDidFinishNavigation]; + + WKBackForwardList *list = [webView backForwardList]; + EXPECT_EQ(YES, [webView canGoBack]); + EXPECT_EQ(NO, [webView canGoForward]); + EXPECT_EQ((NSUInteger)1, list.backList.count); + EXPECT_EQ((NSUInteger)0, list.forwardList.count); + + auto singlePageWebView = adoptNS([[WKWebView alloc] init]); + + [singlePageWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadableURL1]]]; + [singlePageWebView _test_waitForDidFinishNavigation]; + + [webView _restoreSessionState:nil andNavigate:NO]; + + WKBackForwardList *newList = [webView backForwardList]; + + EXPECT_EQ(YES, [webView canGoBack]); + EXPECT_EQ(NO, [webView canGoForward]); + EXPECT_EQ((NSUInteger)1, newList.backList.count); + EXPECT_EQ((NSUInteger)0, newList.forwardList.count); +} + +static bool done; +static size_t navigations; + +@interface AsyncPolicyDecisionDelegate : NSObject +@end + +@implementation AsyncPolicyDecisionDelegate + +- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation +{ + if (navigations++) + done = true; +} + +- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler +{ + dispatch_async(mainDispatchQueueSingleton(), ^{ + decisionHandler(WKNavigationActionPolicyAllow); + }); +} + +@end + +TEST(WKBackForwardList, WindowLocationAsyncPolicyDecision) +{ + NSURL *simple = [NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]; + NSURL *simple2 = [NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]; + auto webView = adoptNS([[WKWebView alloc] init]); + auto delegate = adoptNS([[AsyncPolicyDecisionDelegate alloc] init]); + [webView setNavigationDelegate:delegate.get()]; + [webView loadHTMLString:@"" baseURL:simple2]; + TestWebKitAPI::Util::run(&done); + EXPECT_STREQ(webView.get().backForwardList.currentItem.URL.absoluteString.UTF8String, simple.absoluteString.UTF8String); +} + +TEST(WKBackForwardList, InteractionStateRestoration) +{ + auto webView = adoptNS([[WKWebView alloc] init]); + + RetainPtr url1 = [NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]; + RetainPtr url2 = [NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]; + RetainPtr url3 = [NSBundle.test_resourcesBundle URLForResource:@"simple3" withExtension:@"html"]; + + [webView loadRequest:[NSURLRequest requestWithURL:url1.get()]]; + [webView _test_waitForDidFinishNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:url2.get()]]; + [webView _test_waitForDidFinishNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:url3.get()]]; + [webView _test_waitForDidFinishNavigation]; + + WKBackForwardList *list = [webView backForwardList]; + EXPECT_EQ((NSUInteger)2, list.backList.count); + EXPECT_EQ((NSUInteger)0, list.forwardList.count); + EXPECT_STREQ([[list.currentItem URL] absoluteString].UTF8String, [url3 absoluteString].UTF8String); + + id interactionState = [webView interactionState]; + RetainPtr temporaryFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[NSUUID UUID].UUIDString] isDirectory:NO]; + NSError *error = nil; + RetainPtr archivedInteractionState = [NSKeyedArchiver archivedDataWithRootObject:interactionState requiringSecureCoding:YES error:&error]; + EXPECT_TRUE(!error); + interactionState = nil; + [archivedInteractionState writeToURL:temporaryFile.get() options:NSDataWritingAtomic error:&error]; + archivedInteractionState = nil; + EXPECT_TRUE(!error); + + webView = adoptNS([[WKWebView alloc] init]); + + archivedInteractionState = [NSData dataWithContentsOfURL:temporaryFile.get()]; + interactionState = [NSKeyedUnarchiver unarchivedObjectOfClass:[(id)[webView interactionState] class] fromData:archivedInteractionState.get() error:&error]; + EXPECT_TRUE(!error); + + [webView setInteractionState:interactionState]; + [webView _test_waitForDidFinishNavigation]; + + WKBackForwardList *newList = [webView backForwardList]; + + EXPECT_EQ((NSUInteger)2, newList.backList.count); + EXPECT_EQ((NSUInteger)0, newList.forwardList.count); + EXPECT_STREQ([[newList.currentItem URL] absoluteString].UTF8String, [url3 absoluteString].UTF8String); + + done = false; + [webView evaluateJavaScript:@"document.body.innerText" completionHandler:^(id result, NSError *error) { + EXPECT_TRUE(!error); + NSString* bodyText = result; + EXPECT_WK_STREQ(@"Third simple HTML file.", bodyText); + done = true; + }]; + TestWebKitAPI::Util::run(&done); + + [webView goBack]; + [webView _test_waitForDidFinishNavigation]; + + done = false; + [webView evaluateJavaScript:@"document.body.innerText" completionHandler:^(id result, NSError *error) { + EXPECT_TRUE(!error); + NSString* bodyText = result; + EXPECT_WK_STREQ(@"Second simple HTML file.", bodyText); + done = true; + }]; + TestWebKitAPI::Util::run(&done); + + [webView goBack]; + [webView _test_waitForDidFinishNavigation]; + + done = false; + [webView evaluateJavaScript:@"document.body.innerText" completionHandler:^(id result, NSError *error) { + EXPECT_TRUE(!error); + NSString* bodyText = result; + EXPECT_WK_STREQ(@"Simple HTML file.", bodyText); + done = true; + }]; + TestWebKitAPI::Util::run(&done); +} + +TEST(WKBackForwardList, InteractionStateRestorationNil) +{ + auto webView = adoptNS([[WKWebView alloc] init]); + + RetainPtr url1 = [NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]; + RetainPtr url2 = [NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]; + RetainPtr url3 = [NSBundle.test_resourcesBundle URLForResource:@"simple3" withExtension:@"html"]; + + [webView loadRequest:[NSURLRequest requestWithURL:url1.get()]]; + [webView _test_waitForDidFinishNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:url2.get()]]; + [webView _test_waitForDidFinishNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:url3.get()]]; + [webView _test_waitForDidFinishNavigation]; + + WKBackForwardList *list = [webView backForwardList]; + EXPECT_EQ((NSUInteger)2, list.backList.count); + EXPECT_EQ((NSUInteger)0, list.forwardList.count); + EXPECT_STREQ([[list.currentItem URL] absoluteString].UTF8String, [url3 absoluteString].UTF8String); + + [webView setInteractionState:nil]; + + list = [webView backForwardList]; + EXPECT_EQ((NSUInteger)2, list.backList.count); + EXPECT_EQ((NSUInteger)0, list.forwardList.count); + EXPECT_STREQ([[list.currentItem URL] absoluteString].UTF8String, [url3 absoluteString].UTF8String); +} + +TEST(WKBackForwardList, InteractionStateRestorationInvalid) +{ + auto webView = adoptNS([[WKWebView alloc] init]); + + RetainPtr url1 = [NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]; + RetainPtr url2 = [NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]; + RetainPtr url3 = [NSBundle.test_resourcesBundle URLForResource:@"simple3" withExtension:@"html"]; + + [webView loadRequest:[NSURLRequest requestWithURL:url1.get()]]; + [webView _test_waitForDidFinishNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:url2.get()]]; + [webView _test_waitForDidFinishNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:url3.get()]]; + [webView _test_waitForDidFinishNavigation]; + + WKBackForwardList *list = [webView backForwardList]; + EXPECT_EQ((NSUInteger)2, list.backList.count); + EXPECT_EQ((NSUInteger)0, list.forwardList.count); + EXPECT_STREQ([[list.currentItem URL] absoluteString].UTF8String, [url3 absoluteString].UTF8String); + + NSString *invalidState = @"foo"; + [webView setInteractionState:invalidState]; + + list = [webView backForwardList]; + EXPECT_EQ((NSUInteger)2, list.backList.count); + EXPECT_EQ((NSUInteger)0, list.forwardList.count); + EXPECT_STREQ([[list.currentItem URL] absoluteString].UTF8String, [url3 absoluteString].UTF8String); +} + +@interface WKBackForwardNavigationDelegate : NSObject +- (void)waitForDidFinishNavigationOrDidSameDocumentNavigation; +@end + +static RetainPtr lastNavigation; + +@implementation WKBackForwardNavigationDelegate { + bool _navigated; + bool _didFinishNavigation; +} + +- (instancetype) init +{ + self = [super init]; + return self; +} + +- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler +{ + EXPECT_WK_STREQ(challenge.protectionSpace.authenticationMethod, NSURLAuthenticationMethodServerTrust); + completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); +} + +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation +{ + _navigated = true; + _didFinishNavigation = true; + lastNavigation = navigation; +} + +- (void)_webView:(WKWebView *)webView navigation:(WKNavigation *)navigation didSameDocumentNavigation:(_WKSameDocumentNavigationType)navigationType +{ + if (navigationType == _WKSameDocumentNavigationTypeSessionStatePush || navigationType == _WKSameDocumentNavigationTypeSessionStatePop) { + _navigated = true; + lastNavigation = navigation; + } +} + +- (void)waitForDidFinishNavigationOrDidSameDocumentNavigation +{ + _navigated = false; + TestWebKitAPI::Util::run(&_navigated); +} + +- (void)waitForDidFinishNavigation +{ + _didFinishNavigation = false; + TestWebKitAPI::Util::run(&_didFinishNavigation); +} + +@end + +// _beginBackSwipeForTesting / _completeBackSwipeForTesting are not implemented on macOS. +#if !PLATFORM(MAC) + +TEST(WKBackForwardList, BackSwipeNavigationSkipsItemsWithoutUserGesture) +{ + auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]); + [webView setAllowsBackForwardNavigationGestures:YES]; + [webView becomeFirstResponder]; + + auto navigationDelegate = adoptNS([WKBackForwardNavigationDelegate new]); + webView.get().navigationDelegate = navigationDelegate.get(); + + RetainPtr url1 = [NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]; + RetainPtr url2 = [NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]; + + [webView loadRequest:[NSURLRequest requestWithURL:url1.get()]]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:url2.get()]]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + // Add back/forward list items without user gestures. + [webView _evaluateJavaScriptWithoutUserGesture:@"history.pushState(null, document.title, location.pathname + '#a');" completionHandler:nil]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + [webView _evaluateJavaScriptWithoutUserGesture:@"history.pushState(null, document.title, location.pathname + '#b');" completionHandler:nil]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + [webView _evaluateJavaScriptWithoutUserGesture:@"history.pushState(null, document.title, location.pathname + '#c');" completionHandler:nil]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + EXPECT_EQ([webView backForwardList].backList.count, 4U); + EXPECT_EQ([webView backForwardList].forwardList.count, 0U); + + // Navigating back via a swipe gesture should skip those back/forward list items without a user gesture. + [webView _beginBackSwipeForTesting]; + [webView _completeBackSwipeForTesting]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + EXPECT_STREQ([webView URL].absoluteString.UTF8String, [url1 absoluteString].UTF8String); + + EXPECT_EQ([webView backForwardList].backList.count, 0U); + EXPECT_EQ([webView backForwardList].forwardList.count, 4U); +} + +TEST(WKBackForwardList, BackSwipeNavigationDoesNotSkipItemsWithUserGesture) +{ + auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]); + [webView setAllowsBackForwardNavigationGestures:YES]; + [webView becomeFirstResponder]; + + auto navigationDelegate = adoptNS([WKBackForwardNavigationDelegate new]); + webView.get().navigationDelegate = navigationDelegate.get(); + + RetainPtr url1 = [NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]; + RetainPtr url2 = [NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]; + + [webView loadRequest:[NSURLRequest requestWithURL:url1.get()]]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:url2.get()]]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + // Add back/forward list item with a user gesture. + [webView evaluateJavaScript:@"history.pushState(null, document.title, location.pathname + '#a');" completionHandler:nil]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + EXPECT_EQ([webView backForwardList].backList.count, 2U); + EXPECT_EQ([webView backForwardList].forwardList.count, 0U); + + // Navigating back via a swipe gesture should skip those back/forward list items without a user gesture. + [webView _beginBackSwipeForTesting]; + [webView _completeBackSwipeForTesting]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + EXPECT_STREQ([webView URL].absoluteString.UTF8String, [url2 absoluteString].UTF8String); + + EXPECT_EQ([webView backForwardList].backList.count, 1U); + EXPECT_EQ([webView backForwardList].forwardList.count, 1U); +} + +#endif + +static void runBackForwardNavigationSkipsItemsWithoutUserGestureTest(Function&& navigate) +{ + auto webView = adoptNS([[WKWebView alloc] init]); + + auto navigationDelegate = adoptNS([WKBackForwardNavigationDelegate new]); + webView.get().navigationDelegate = navigationDelegate.get(); + + RetainPtr url1 = [NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]; + RetainPtr url2 = [NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]; + RetainPtr url3 = [NSBundle.test_resourcesBundle URLForResource:@"simple3" withExtension:@"html"]; + + [webView loadRequest:[NSURLRequest requestWithURL:url1.get()]]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:url2.get()]]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + // Test case: + // url1 -> url2 -> url2#a (no user gesture) -> url2#b (no user gesture) -> url2#c (no user gesture) -> url3. + + // Add back/forward list items without user gestures. + navigate(webView.get(), "location.pathname + '#a'"_s); + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + EXPECT_FALSE([lastNavigation _isUserInitiated]); + EXPECT_TRUE(webView.get().backForwardList.currentItem._wasCreatedByJSWithoutUserInteraction); + RetainPtr expectedURLString = makeString(String([url2 absoluteString]), "#a"_s).createNSString(); + EXPECT_WK_STREQ([lastNavigation _request].URL.absoluteString.UTF8String, expectedURLString.get().UTF8String); + + navigate(webView.get(), "location.pathname + '#b'"_s); + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + EXPECT_FALSE([lastNavigation _isUserInitiated]); + EXPECT_TRUE(webView.get().backForwardList.currentItem._wasCreatedByJSWithoutUserInteraction); + expectedURLString = makeString(String([url2 absoluteString]), "#b"_s).createNSString(); + EXPECT_WK_STREQ([lastNavigation _request].URL.absoluteString.UTF8String, expectedURLString.get().UTF8String); + + navigate(webView.get(), "location.pathname + '#c'"_s); + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + EXPECT_FALSE([lastNavigation _isUserInitiated]); + EXPECT_TRUE(webView.get().backForwardList.currentItem._wasCreatedByJSWithoutUserInteraction); + expectedURLString = makeString(String([url2 absoluteString]), "#c"_s).createNSString(); + EXPECT_WK_STREQ([lastNavigation _request].URL.absoluteString.UTF8String, expectedURLString.get().UTF8String); + + [webView loadRequest:[NSURLRequest requestWithURL:url3.get()]]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + EXPECT_FALSE(webView.get().backForwardList.currentItem._wasCreatedByJSWithoutUserInteraction); + + EXPECT_EQ([webView backForwardList].backList.count, 5U); + EXPECT_EQ([webView backForwardList].forwardList.count, 0U); + + // We are now on url3. Let's go back. + [webView goBack]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + // We should go back to url2#c. + expectedURLString = makeString(String([url2 absoluteString]), "#c"_s).createNSString(); + EXPECT_STREQ([webView URL].absoluteString.UTF8String, expectedURLString.get().UTF8String); + EXPECT_EQ([webView backForwardList].backList.count, 4U); + EXPECT_EQ([webView backForwardList].forwardList.count, 1U); + + // Let's go back again. + [webView goBack]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + // We should have skipped over url2#b, url2#a and url2, to end up on url1. + EXPECT_STREQ([webView URL].absoluteString.UTF8String, [url1 absoluteString].UTF8String); + EXPECT_EQ([webView backForwardList].backList.count, 0U); + EXPECT_EQ([webView backForwardList].forwardList.count, 5U); + + // Now let's go forward. + [webView goForward]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + // We should get to the latest url2 URL, that is url2#c. + expectedURLString = makeString(String([url2 absoluteString]), "#c"_s).createNSString(); + EXPECT_STREQ([webView URL].absoluteString.UTF8String, expectedURLString.get().UTF8String); + EXPECT_EQ([webView backForwardList].backList.count, 4U); + EXPECT_EQ([webView backForwardList].forwardList.count, 1U); + + // Let's go forward again. + [webView goForward]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + // We should now be on url3. + EXPECT_STREQ([webView URL].absoluteString.UTF8String, [url3 absoluteString].UTF8String); + EXPECT_EQ([webView backForwardList].backList.count, 5U); + EXPECT_EQ([webView backForwardList].forwardList.count, 0U); + + // Navigating via the JS API shouldn't skip those back/forward list items. + [webView _evaluateJavaScriptWithoutUserGesture:@"history.back();" completionHandler:^(id, NSError *) { }]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + expectedURLString = makeString(String([url2 absoluteString]), "#c"_s).createNSString(); + EXPECT_STREQ([webView URL].absoluteString.UTF8String, expectedURLString.get().UTF8String); + EXPECT_EQ([webView backForwardList].backList.count, 4U); + EXPECT_EQ([webView backForwardList].forwardList.count, 1U); + + [webView _evaluateJavaScriptWithoutUserGesture:@"history.back();" completionHandler:^(id, NSError *) { }]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + expectedURLString = makeString(String([url2 absoluteString]), "#b"_s).createNSString(); + EXPECT_STREQ([webView URL].absoluteString.UTF8String, expectedURLString.get().UTF8String); + EXPECT_EQ([webView backForwardList].backList.count, 3U); + EXPECT_EQ([webView backForwardList].forwardList.count, 2U); +} + +TEST(WKBackForwardList, BackForwardNavigationSkipsItemsWithoutUserGesturePushState) +{ + runBackForwardNavigationSkipsItemsWithoutUserGestureTest([](WKWebView* webView, ASCIILiteral destination) { + [webView _evaluateJavaScriptWithoutUserGesture:makeString("history.pushState(null, document.title, "_s, destination, ");"_s).createNSString().get() completionHandler:nil]; + }); +} + +TEST(WKBackForwardList, BackForwardNavigationSkipsItemsWithoutUserGestureFragment) +{ + runBackForwardNavigationSkipsItemsWithoutUserGestureTest([](WKWebView* webView, ASCIILiteral destination) { + [webView _evaluateJavaScriptWithoutUserGesture:makeString("location.href = "_s, destination, ";"_s).createNSString().get() completionHandler:nil]; + }); +} + +TEST(WKBackForwardList, BackForwardNavigationSkipsItemsWithoutUserGesturePushStateAfterEvaluateJS) +{ + runBackForwardNavigationSkipsItemsWithoutUserGestureTest([](WKWebView* webView, ASCIILiteral destination) { + // Do a call to evaluateJavaScript (with user gesture) *BEFORE* the pushState and make sure it doesn't count + // as a user gesture for the pushState(). + __block bool didRunScript = false; + [webView evaluateJavaScript:@"window.foo = 1;" completionHandler:^(id, NSError *) { + didRunScript = true; + }]; + TestWebKitAPI::Util::run(&didRunScript); + [webView _evaluateJavaScriptWithoutUserGesture:makeString("history.pushState(null, document.title, "_s, destination, ");"_s).createNSString().get() completionHandler:nil]; + }); +} + +TEST(WKBackForwardList, BackForwardNavigationSkipsItemsWithoutUserGestureSubframe) +{ + TestWebKitAPI::HTTPServer server({ + { "/source.html"_s, { "foo"_s } }, + { "/destination.html"_s, { ""_s } }, + { "/iframe.html"_s, { ""_s } }, + }, TestWebKitAPI::HTTPServer::Protocol::Http); + + auto webView = adoptNS([[WKWebView alloc] init]); + + auto navigationDelegate = adoptNS([WKBackForwardNavigationDelegate new]); + webView.get().navigationDelegate = navigationDelegate.get(); + + [webView loadRequest:server.request("/source.html"_s)]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + [webView loadRequest:server.request("/destination.html"_s)]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + // Wait for the subframe to call pushState(). + while ([webView backForwardList].backList.count != 2) + TestWebKitAPI::Util::spinRunLoop(); + + [webView goBack]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + // We should be back to source.html since we would have ignored the history item + // added by the subframe without user interaction. + EXPECT_EQ([webView backForwardList].backList.count, 0U); + EXPECT_STREQ([webView URL].absoluteString.UTF8String, server.request("/source.html"_s).URL.absoluteString.UTF8String); + + [webView goForward]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + EXPECT_EQ([webView backForwardList].backList.count, 2U); + EXPECT_EQ([webView backForwardList].forwardList.count, 0U); + EXPECT_STREQ([webView URL].absoluteString.UTF8String, server.request("/destination.html"_s).URL.absoluteString.UTF8String); +} + +TEST(WKBackForwardList, BackForwardNavigationSkipsClientSideRedirectWithCOOP) +{ + TestWebKitAPI::HTTPServer server({ + { "/source.html"_s, { "click me"_s } }, + { "/form.html"_s, { "
"_s } }, + { "/redirect.html"_s, { { { "Content-Type"_s, "text/html"_s }, { "cross-origin-opener-policy"_s, "same-origin"_s } }, ""_s } }, + { "/destination.html"_s, { "foo"_s } }, + }, TestWebKitAPI::HTTPServer::Protocol::Https); + + auto webView = adoptNS([[WKWebView alloc] init]); + + auto navigationDelegate = adoptNS([WKBackForwardNavigationDelegate new]); + webView.get().navigationDelegate = navigationDelegate.get(); + + [webView loadRequest:server.request("/source.html"_s)]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + [webView evaluateJavaScript:@"document.getElementById('testLink').click()" completionHandler:nil]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + EXPECT_EQ([webView backForwardList].backList.count, 1U); + EXPECT_EQ([webView backForwardList].forwardList.count, 0U); + EXPECT_STREQ([webView URL].absoluteString.UTF8String, server.request("/form.html"_s).URL.absoluteString.UTF8String); + + // Wait for form submission to happen. + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + EXPECT_EQ([webView backForwardList].backList.count, 1U); + EXPECT_EQ([webView backForwardList].forwardList.count, 0U); + EXPECT_STREQ([webView URL].absoluteString.UTF8String, server.request("/redirect.html"_s).URL.absoluteString.UTF8String); + + // Wait for redirect to finish. + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + EXPECT_EQ([webView backForwardList].backList.count, 1U); + EXPECT_EQ([webView backForwardList].forwardList.count, 0U); + EXPECT_STREQ([webView URL].absoluteString.UTF8String, server.request("/destination.html"_s).URL.absoluteString.UTF8String); + + [webView goBack]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + EXPECT_EQ([webView backForwardList].backList.count, 0U); + EXPECT_EQ([webView backForwardList].forwardList.count, 1U); + EXPECT_STREQ([webView URL].absoluteString.UTF8String, server.request("/source.html"_s).URL.absoluteString.UTF8String); +} + +static void runBackForwardNavigationDoesNotSkipItemsWithUserGestureTest(Function&& navigate) +{ + auto webView = adoptNS([[WKWebView alloc] init]); + + auto navigationDelegate = adoptNS([WKBackForwardNavigationDelegate new]); + webView.get().navigationDelegate = navigationDelegate.get(); + + // Test case: url1 -> url2 -> url2#a (with user gesture) + // No item should be skipped when navigating backwards or forwards. + + RetainPtr url1 = [NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]; + RetainPtr url2 = [NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]; + + [webView loadRequest:[NSURLRequest requestWithURL:url1.get()]]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:url2.get()]]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + // Add back/forward list items without user gestures. + navigate(webView.get(), "#a"_s); + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + RetainPtr expectedURLString = makeString(String([url2 absoluteString]), "#a"_s).createNSString(); + EXPECT_WK_STREQ([lastNavigation _request].URL.absoluteString.UTF8String, expectedURLString.get().UTF8String); + + RetainPtr lastURL = [webView URL]; + EXPECT_FALSE([lastURL isEqual:url2.get()]); + + EXPECT_FALSE(webView.get().backForwardList.backItem._wasCreatedByJSWithoutUserInteraction); + [webView goBack]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + EXPECT_WK_STREQ([lastNavigation _request].URL.absoluteString.UTF8String, [url2 absoluteString].UTF8String); + + EXPECT_STREQ([webView URL].absoluteString.UTF8String, [url2 absoluteString].UTF8String); + + EXPECT_FALSE(webView.get().backForwardList.backItem._wasCreatedByJSWithoutUserInteraction); + [webView goBack]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + EXPECT_STREQ([webView URL].absoluteString.UTF8String, [url1 absoluteString].UTF8String); + + [webView goForward]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + EXPECT_STREQ([webView URL].absoluteString.UTF8String, [url2 absoluteString].UTF8String); + + [webView goForward]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + expectedURLString = makeString(String([url2 absoluteString]), "#a"_s).createNSString(); + EXPECT_WK_STREQ([lastNavigation _request].URL.absoluteString.UTF8String, expectedURLString.get().UTF8String); + + EXPECT_STREQ([webView URL].absoluteString.UTF8String, [lastURL absoluteString].UTF8String); +} + +TEST(WKBackForwardList, BackForwardNavigationDoesNotSkipItemsWithUserGesturePushState) +{ + runBackForwardNavigationDoesNotSkipItemsWithUserGestureTest([](WKWebView *webView, ASCIILiteral fragment) { + [webView evaluateJavaScript:makeString("history.pushState(null, document.title, location.pathname + '"_s, fragment, "');"_s).createNSString().get() completionHandler:nil]; + }); +} + +TEST(WKBackForwardList, BackForwardNavigationDoesNotSkipItemsWithUserGestureFragment) +{ + runBackForwardNavigationDoesNotSkipItemsWithUserGestureTest([](WKWebView *webView, ASCIILiteral fragment) { + [webView evaluateJavaScript:makeString("location.href = location.pathname + '"_s, fragment, "';"_s).createNSString().get() completionHandler:nil]; + }); +} + +TEST(WKBackForwardList, BackForwardNavigationDoesNotSkipItemsFromLoadRequest) +{ + runBackForwardNavigationDoesNotSkipItemsWithUserGestureTest([](WKWebView *webView, ASCIILiteral fragment) { + auto newURLString = makeString(String([webView URL].absoluteString), fragment); + [webView loadRequest:adoptNS([[NSURLRequest alloc] initWithURL:adoptNS([[NSURL alloc] initWithString:newURLString.createNSString().get()]).get()]).get()]; + }); +} + +TEST(WKBackForwardList, BackForwardNavigationDoesNotSkipItemsWithRecentUserGesturePushState) +{ + runBackForwardNavigationDoesNotSkipItemsWithUserGestureTest([](WKWebView *webView, ASCIILiteral fragment) { + // Call pushState() in a setTimeout() so that it has a recent user gesture but not a current one. + [webView evaluateJavaScript:makeString("setTimeout(() => { history.pushState(null, document.title, location.pathname + '"_s, fragment, "'); }, 0);"_s).createNSString().get() completionHandler:nil]; + }); +} + +TEST(WKBackForwardList, BackForwardNavigationDoesNotSkipItemsWithRecentUserGestureFragment) +{ + runBackForwardNavigationDoesNotSkipItemsWithUserGestureTest([](WKWebView *webView, ASCIILiteral fragment) { + // Do fragment navigation in a setTimeout() so that it has a recent user gesture but not a current one. + [webView evaluateJavaScript:makeString("setTimeout(() => { location.href = location.pathname + '"_s, fragment, "'; }, 0);"_s).createNSString().get() completionHandler:nil]; + }); +} + +TEST(WKBackForwardList, BackForwardNavigationDoesNotSkipUpdatedItemWithRecentUserGesture) +{ + auto webView = adoptNS([[WKWebView alloc] init]); + + auto navigationDelegate = adoptNS([WKBackForwardNavigationDelegate new]); + webView.get().navigationDelegate = navigationDelegate.get(); + + RetainPtr url1 = [NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]; + RetainPtr url2 = [NSBundle.test_resourcesBundle URLForResource:@"fragment-navigation-before-load-event" withExtension:@"html"]; + + [webView loadRequest:[NSURLRequest requestWithURL:url1.get()]]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + [webView loadRequest:[NSURLRequest requestWithURL:url2.get()]]; + [navigationDelegate waitForDidFinishNavigation]; + + // Page navigated to #fragment before the load event. + RetainPtr expectedURLString = makeString(String([url2 absoluteString]), "#fragment"_s).createNSString(); + EXPECT_STREQ([webView URL].absoluteString.UTF8String, expectedURLString.get().UTF8String); + + // Navigate with a user gesture. + [webView evaluateJavaScript:@"location.href = location.pathname + '#otherFragment';" completionHandler:nil]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + // Should go back to #fragment. + [webView goBack]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + EXPECT_STREQ([webView URL].absoluteString.UTF8String, expectedURLString.get().UTF8String); +} + +TEST(WKBackForwardList, BackNavigationHijacking) +{ + auto webView = adoptNS([[WKWebView alloc] init]); + + auto navigationDelegate = adoptNS([WKBackForwardNavigationDelegate new]); + webView.get().navigationDelegate = navigationDelegate.get(); + + RetainPtr url1 = [NSBundle.test_resourcesBundle URLForResource:@"simple" withExtension:@"html"]; + RetainPtr url2 = [NSBundle.test_resourcesBundle URLForResource:@"simple2" withExtension:@"html"]; + + [webView loadRequest:[NSURLRequest requestWithURL:url1.get()]]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + + [webView _evaluateJavaScriptWithoutUserGesture:@"history.pushState(null, null, '');" completionHandler:nil]; + __block bool ranJS = false; + [webView _evaluateJavaScriptWithoutUserGesture:@"onpopstate = (e) => { history.forward(); };false" completionHandler:^(id, NSError *) { + ranJS = true; + }]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + TestWebKitAPI::Util::run(&ranJS); + + [webView loadRequest:[NSURLRequest requestWithURL:url2.get()]]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + EXPECT_STREQ([webView URL].absoluteString.UTF8String, [url2 absoluteString].UTF8String); + + EXPECT_TRUE(webView.get().backForwardList.backItem._wasCreatedByJSWithoutUserInteraction); + [webView goBack]; + [navigationDelegate waitForDidFinishNavigationOrDidSameDocumentNavigation]; + EXPECT_STREQ([webView URL].absoluteString.UTF8String, [url1 absoluteString].UTF8String); + + TestWebKitAPI::Util::spinRunLoop(10); + usleep(100000); + TestWebKitAPI::Util::spinRunLoop(10); + + EXPECT_STREQ([webView URL].absoluteString.UTF8String, [url1 absoluteString].UTF8String); +} + +TEST(WKBackForwardList, BackForwardListRemoveAndAddSubframes) +{ + auto indexHTML = "" + ""_s; + TestWebKitAPI::HTTPServer server({ + { "/index"_s, { indexHTML } }, + { "/frame1"_s, { ""_s } }, + { "/frame2"_s, { ""_s } }, + { "/frame3"_s, { ""_s } }, + }, TestWebKitAPI::HTTPServer::Protocol::Https); + auto webView = adoptNS([[WKWebView alloc] init]); + auto navigationDelegate = adoptNS([WKBackForwardNavigationDelegate new]); + webView.get().navigationDelegate = navigationDelegate.get(); + auto uiDelegate = adoptNS([TestUIDelegate new]); + webView.get().UIDelegate = uiDelegate.get(); + + [webView loadRequest:server.request("/index"_s)]; + EXPECT_WK_STREQ([uiDelegate waitForAlert], "frame2"); + + [webView _frames:^(_WKFrameTreeNode *mainFrame) { + [webView evaluateJavaScript:@"location.href = '/frame3'" inFrame:mainFrame.childFrames[1].info inContentWorld:WKContentWorld.pageWorld completionHandler:nil]; + }]; + EXPECT_WK_STREQ([uiDelegate waitForAlert], "frame3"); + + auto removeAndAddFrame = @"let frame = document.getElementById('1');" + "frame.parentNode.removeChild(frame);" + "let newFrame = document.createElement('iframe');" + "newFrame.src = '/frame1';" + "document.body.appendChild(newFrame);"; + __block bool done = false; + [webView evaluateJavaScript:removeAndAddFrame completionHandler:^(id, NSError *) { + done = true; + }]; + TestWebKitAPI::Util::run(&done); + done = false; + + [webView goBack]; + EXPECT_WK_STREQ([uiDelegate waitForAlert], "frame2"); + + __block auto expectedFrameURL = server.request("/frame2"_s).URL.absoluteString.UTF8String; + [webView evaluateJavaScript:@"document.getElementById('2').contentWindow.location.href" completionHandler:^(id result, NSError *) { + EXPECT_WK_STREQ(expectedFrameURL, [result UTF8String]); + done = true; + }]; + TestWebKitAPI::Util::run(&done); +} + +TEST(WKBackForwardList, SessionStateTitleTruncation) +{ + TestWebKitAPI::HTTPServer server({ + { "/"_s, { ""_s } } + }); + + auto webView = adoptNS([WKWebView new]); + [webView loadRequest:server.request()]; + while (!webView.get().canGoBack) + TestWebKitAPI::Util::spinRunLoop(); + while (webView.get()._sessionState.data.length < 1000u) + TestWebKitAPI::Util::spinRunLoop(); + _WKSessionState *sessionState = webView.get()._sessionState; + NSData *stateData = sessionState.data; + EXPECT_LT(stateData.length, 2000u); +} + +TEST(WKBackForwardList, RestoreSessionStateResetProvisionalItem) +{ + RetainPtr webView = adoptNS([[TestWKWebView alloc] init]); + [webView synchronouslyLoadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadableURL1]]]; + [webView synchronouslyLoadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:loadableURL2]]]; + [webView synchronouslyGoBack]; + [webView synchronouslyGoForward]; + + RetainPtr sessionState = [webView _sessionStateWithFilter:^BOOL(WKBackForwardListItem *item) { + return [item.URL isEqual:[NSURL URLWithString:loadableURL1]]; + }]; + [webView _restoreSessionState:sessionState.get() andNavigate:NO]; + [[webView backForwardList] currentItem]; +} + +TEST(WKBackForwardList, GoBackToPageAfterNavigatingIframeAndRestoringSession) +{ + TestWebKitAPI::HTTPServer server({ + { "/example"_s, { ""_s } }, + { "/a"_s, { ""_s } }, + { "/b"_s, { ""_s } }, + }); + RetainPtr webView = adoptNS([[WKWebView alloc] init]); + [webView loadRequest:server.request("/example"_s)]; + EXPECT_WK_STREQ([webView _test_waitForAlert], "a"); + + [webView evaluateJavaScript:@"document.querySelector('iframe').src = '/b';" completionHandler:nil]; + EXPECT_WK_STREQ([webView _test_waitForAlert], "b"); + + [webView loadRequest:server.requestWithLocalhost("/example"_s)]; + EXPECT_WK_STREQ([webView _test_waitForAlert], "a"); + + [webView _restoreSessionState:[webView _sessionState] andNavigate:NO]; + [webView goBack]; + EXPECT_WK_STREQ([webView _test_waitForAlert], "b"); + EXPECT_WK_STREQ([webView URL].absoluteString, server.request("/example"_s).URL.absoluteString.UTF8String); +} + +#if ENABLE(IPC_TESTING_API) + +static void enableIPCTestingAPI(WKWebViewConfiguration *configuration) +{ + for (_WKFeature *feature in [WKPreferences _features]) { + if ([feature.key isEqualToString:@"IPCTestingAPIEnabled"]) { + [[configuration preferences] _setEnabled:YES forFeature:feature]; + break; + } + } +} + +TEST(WKBackForwardList, BackForwardUpdateItemRejectsFileURL) +{ + TestWebKitAPI::HTTPServer server({ + { "/page"_s, { "page"_s } }, + }, TestWebKitAPI::HTTPServer::Protocol::Http); + + auto poolConfig = adoptNS([[_WKProcessPoolConfiguration alloc] init]); + [poolConfig setProcessSwapsOnNavigation:YES]; + auto pool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:poolConfig.get()]); + + auto configA = adoptNS([[WKWebViewConfiguration alloc] init]); + [configA setProcessPool:pool.get()]; + enableIPCTestingAPI(configA.get()); + + auto webViewA = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configA.get()]); + [webViewA synchronouslyLoadRequest:server.request("/page"_s)]; + + // B shares A's process via _relatedWebView. + auto configB = adoptNS([[WKWebViewConfiguration alloc] init]); + [configB setProcessPool:pool.get()]; + configB.get()._relatedWebView = webViewA.get(); + enableIPCTestingAPI(configB.get()); + + auto webViewB = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configB.get()]); + [webViewB synchronouslyLoadRequest:server.request("/page"_s)]; + + EXPECT_EQ([webViewA _webProcessIdentifier], [webViewB _webProcessIdentifier]); + + long long bPageID = [[webViewB stringByEvaluatingJavaScript:@"String(IPC.pageID)"] longLongValue]; + EXPECT_GT(bPageID, 0LL); + + // Using pushState on A, capture an `AddItem` IPC message for later use. + [webViewA stringByEvaluatingJavaScript: + @"(function() {" + " var addName = null, updateName = null;" + " var keys = Object.keys(IPC.messages);" + " for (var i = 0; i < keys.length; i++) {" + " if (keys[i].indexOf('BackForwardAddItem') !== -1 && keys[i].indexOf('InProcess') === -1)" + " addName = IPC.messages[keys[i]].name;" + " if (keys[i].indexOf('BackForwardUpdateItem') !== -1)" + " updateName = IPC.messages[keys[i]].name;" + " }" + " window._updateItemMsgName = updateName;" + " window._addBuf = null;" + " IPC.addOutgoingMessageListener('UI', function(msg) {" + " if (msg.name === addName && msg.buffer)" + " window._addBuf = new Uint8Array(msg.buffer.slice(0));" + " });" + " history.pushState({}, '', '?pad=' + 'X'.repeat(40));" + "})()" + ]; + int attempts = 0; + while (![[webViewA stringByEvaluatingJavaScript:@"window._addBuf ? 'ok' : ''"] isEqualToString:@"ok"] && ++attempts < 50) + TestWebKitAPI::Util::spinRunLoop(5); + EXPECT_LT(attempts, 50); + + // Modify the captured buffer to carry a file:// URL, then send it as + // BackForwardUpdateItem to page B's destination ID. B's handler + // resolves A's item via the global itemForID map — the ownership + // check (item->pageID() vs B's identifier) must reject it. + NSString *attackJS = [NSString stringWithFormat: + @"(function() {" + "var HDR = 0x10;" + "var args = new Uint8Array(window._addBuf.buffer.slice(HDR));" + "var origURL = location.href;" + "var targetBase = 'file:///etc/passwd';" + "var padTarget = targetBase + '/'.repeat(Math.max(0, origURL.length - targetBase.length));" + "if (padTarget.length !== origURL.length) return 'FAIL:len_mismatch';" + "var oldBytes = new TextEncoder().encode(origURL);" + "var newBytes = new TextEncoder().encode(padTarget);" + "var count = 0;" + "for (var i = 0; i <= args.length - oldBytes.length; i++) {" + " var match = true;" + " for (var j = 0; j < oldBytes.length; j++) {" + " if (args[i+j] !== oldBytes[j]) { match = false; break; }" + " }" + " if (match) {" + " args.set(newBytes, i);" + " count++;" + " i += oldBytes.length - 1;" + " }" + "}" + "if (!count) return 'FAIL:no_url_found';" + "IPC.sendMessage('UI', %lld, window._updateItemMsgName, args);" + "return 'sent:' + count;" + "})()", bPageID + ]; + NSString *attackResult = [webViewA stringByEvaluatingJavaScript:attackJS]; + EXPECT_TRUE([attackResult hasPrefix:@"sent:"]); + + for (int i = 0; i < 100; i++) + TestWebKitAPI::Util::spinRunLoop(); + + WKBackForwardList *list = [webViewA backForwardList]; + EXPECT_FALSE([list.currentItem.URL.absoluteString hasPrefix:@"file://"]); + for (WKBackForwardListItem *item in list.backList) + EXPECT_FALSE([item.URL.absoluteString hasPrefix:@"file://"]); +} + +static constexpr auto forgedFileURLTestMainBytes = R"TESTRESOURCE( + + + +)TESTRESOURCE"_s; + +TEST(WKBackForwardList, ForgedFileURLItemIsRejected) +{ + RetainPtr coreIPCURL = [NSBundle.test_resourcesBundle URLForResource:@"coreipc" withExtension:@"js"]; + RetainPtr coreIPCData = [NSData dataWithContentsOfURL:coreIPCURL.get()]; + ++ TestWebKitAPI::HTTPServer server({ ++ { "/"_s, { forgedFileURLTestMainBytes } }, ++ { "/coreipc.js"_s, { { { "Content-Type"_s, "text/javascript"_s } }, coreIPCData.get() } }, ++ }); ++ ++ RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]); ++ for (_WKFeature *feature in [WKPreferences _features]) { ++ if ([feature.key isEqualToString:@"IPCTestingAPIEnabled"]) { ++ [[configuration preferences] _setEnabled:YES forFeature:feature]; ++ break; ++ } ++ } ++ ++ RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 300, 300) configuration:configuration.get()]); ++ [webView loadRequest:server.request("/"_s)]; ++ ++ EXPECT_WK_STREQ([webView _test_waitForAlert], "PASS: forged file:// back-forward item was rejected by MESSAGE_CHECK"); ++} + +#endif // ENABLE(IPC_TESTING_API) diff --git a/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/JSBuffer.mm b/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/JSBuffer.mm index d970733312f1..c60b93cb79f2 100644 --- a/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/JSBuffer.mm +++ b/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/JSBuffer.mm @@ -38,12 +38,12 @@ static const char constantString[] = "Hello world!"; RetainPtr oddLength = adoptNS([[WKJSScriptingBuffer alloc] initWithData:[NSData dataWithBytes:"abc" length:3]]); - RetainPtr evenLength = adoptNS([[_WKJSBuffer alloc] initWithData:[NSData dataWithBytes:"abcd" length:4]]); + RetainPtr evenLength = adoptNS([NSData dataWithBytes:"abcd" length:4]); RetainPtr invalidSurrogatePair = adoptNS([[_WKJSBuffer alloc] initWithData:[NSData dataWithBytes:"\x3d\xd8\x27\x00\xff\xff\x00\x00" length:8]]); RetainPtr readOnlyBuffer = adoptNS([[_WKJSBuffer alloc] initWithData:[NSData dataWithBytesNoCopy:(void *)constantString length:sizeof(constantString)-1 freeWhenDone:NO]]); RetainPtr configuration = adoptNS([WKWebViewConfiguration new]); [configuration.get().userContentController addBuffer:oddLength.get() name:@"oddLength" contentWorld:WKContentWorld.pageWorld]; - [configuration.get().userContentController _addBuffer:evenLength.get() contentWorld:WKContentWorld.pageWorld name:@"evenLength"]; + [configuration.get().userContentController addBuffer:evenLength.get() name:@"evenLength" contentWorld:WKContentWorld.pageWorld]; [configuration.get().userContentController _addBuffer:invalidSurrogatePair.get() contentWorld:WKContentWorld.pageWorld name:@"invalidSurrogatePair"]; [configuration.get().userContentController _addBuffer:readOnlyBuffer.get() contentWorld:WKContentWorld.pageWorld name:@"readOnlyBuffer"]; diff --git a/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/ObscuredContentInsets.mm b/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/ObscuredContentInsets.mm index 2602e2e662b6..40bf842cfe16 100644 --- a/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/ObscuredContentInsets.mm +++ b/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/ObscuredContentInsets.mm @@ -1032,6 +1032,161 @@ static void runSubcases(TestWKWebView *webView, bool leftFixedEdge, bool rightFi [NSNotificationCenter.defaultCenter removeObserver:exitObserver.get()]; } +static RetainPtr createFullScreenCapableWindow() +{ + auto styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskFullSizeContentView; + RetainPtr toolbar = adoptNS([[NSToolbar alloc] initWithIdentifier:@"ScrollPocketTestToolbar"]); + RetainPtr window = adoptNS([[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]); + + [window setCollectionBehavior:[window collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary]; + [window setToolbar:toolbar.get()]; + + // Attach a titlebar accessory so AppKit does not autohide the fullscreen toolbar mid-test. + RetainPtr accessoryViewController = adoptNS([[NSTitlebarAccessoryViewController alloc] init]); + [accessoryViewController setView:adoptNS([[NSView alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)]).get()]; + [accessoryViewController setLayoutAttribute:NSLayoutAttributeRight]; + [window addTitlebarAccessoryViewController:accessoryViewController.get()]; + + return window; +} + +TEST(ObscuredContentInsets, ScrollPocketCoversFullScreenTitlebarAfterReload) +{ + constexpr CGFloat topInset = 20; + + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + [NSApp activateIgnoringOtherApps:YES]; + + RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]); + [webView _setAutomaticallyAdjustsContentInsets:NO]; + [webView setObscuredContentInsets:NSEdgeInsetsMake(topInset, 0, 0, 0)]; + [webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + + RetainPtr window = createFullScreenCapableWindow(); + [[window contentView] addSubview:webView.get()]; + [webView setFrame:[[window contentView] bounds]]; + [window makeKeyAndOrderFront:nil]; + + [webView synchronouslyLoadTestPageNamed:@"top-fixed-element"]; + [webView waitForNextPresentationUpdate]; + + EXPECT_NOT_NULL([webView _topScrollPocket]); + + __block bool didEnter = false; + __block bool didExit = false; + RetainPtr enterObserver = [NSNotificationCenter.defaultCenter addObserverForName:NSWindowDidEnterFullScreenNotification object:window.get() queue:nil usingBlock:^(NSNotification *) { + didEnter = true; + }]; + RetainPtr exitObserver = [NSNotificationCenter.defaultCenter addObserverForName:NSWindowDidExitFullScreenNotification object:window.get() queue:nil usingBlock:^(NSNotification *) { + didExit = true; + }]; + + [window toggleFullScreen:nil]; + EXPECT_TRUE(Util::runFor(&didEnter, 10_s)); + [webView waitForNextPresentationUpdate]; + + auto overlayHeight = [webView _fullScreenTitlebarOverlayHeightForTesting]; + EXPECT_GT(overlayHeight, topInset); + + auto screenTopSafeArea = [[[webView window] screen] safeAreaInsets].top; + EXPECT_NEAR(overlayHeight - screenTopSafeArea, NSHeight([[webView _topScrollPocket] frame]), 1); + + RetainPtr expectedColor = [webView _sampledTopFixedPositionContentColor]; + EXPECT_NOT_NULL(expectedColor.get()); + EXPECT_TRUE(Util::compareColors([[webView _topScrollPocket] captureColor], expectedColor.get())); + + __block bool reloadCommitted = false; + RetainPtr navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]); + [navigationDelegate setDidCommitNavigation:^(WKWebView *view, WKNavigation *) { + [view _doAfterNextPresentationUpdate:^{ + reloadCommitted = true; + }]; + }]; + [webView setNavigationDelegate:navigationDelegate.get()]; + + [webView reload]; + Util::run(&reloadCommitted); + [webView waitForNextPresentationUpdate]; + + EXPECT_NOT_NULL([webView _topScrollPocket]); + EXPECT_NEAR(overlayHeight - screenTopSafeArea, NSHeight([[webView _topScrollPocket] frame]), 1); + + RetainPtr expectedColorAfterReload = [webView _sampledTopFixedPositionContentColor]; + EXPECT_NOT_NULL(expectedColorAfterReload.get()); + EXPECT_TRUE(Util::compareColors([[webView _topScrollPocket] captureColor], expectedColorAfterReload.get())); + + [window toggleFullScreen:nil]; + EXPECT_TRUE(Util::runFor(&didExit, 10_s)); + [webView waitForNextPresentationUpdate]; + + [NSNotificationCenter.defaultCenter removeObserver:enterObserver.get()]; + [NSNotificationCenter.defaultCenter removeObserver:exitObserver.get()]; +} + +TEST(ObscuredContentInsets, ScrollPocketCoversFullScreenTitlebarAfterMovingIntoFullScreenWindow) +{ + constexpr CGFloat topInset = 20; + + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + [NSApp activateIgnoringOtherApps:YES]; + + RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600)]); + [webView _setAutomaticallyAdjustsContentInsets:NO]; + [webView setObscuredContentInsets:NSEdgeInsetsMake(topInset, 0, 0, 0)]; + [webView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + + RetainPtr originalWindow = createFullScreenCapableWindow(); + [[originalWindow contentView] addSubview:webView.get()]; + [webView setFrame:[[originalWindow contentView] bounds]]; + [originalWindow makeKeyAndOrderFront:nil]; + + [webView synchronouslyLoadTestPageNamed:@"top-fixed-element"]; + [webView waitForNextPresentationUpdate]; + + EXPECT_NOT_NULL([webView _topScrollPocket]); + + // Set up a second window and put it into fullscreen *before* the WKWebView is moved into it. + RetainPtr fullScreenWindow = createFullScreenCapableWindow(); + [fullScreenWindow makeKeyAndOrderFront:nil]; + + __block bool didEnter = false; + __block bool didExit = false; + RetainPtr enterObserver = [NSNotificationCenter.defaultCenter addObserverForName:NSWindowDidEnterFullScreenNotification object:fullScreenWindow.get() queue:nil usingBlock:^(NSNotification *) { + didEnter = true; + }]; + RetainPtr exitObserver = [NSNotificationCenter.defaultCenter addObserverForName:NSWindowDidExitFullScreenNotification object:fullScreenWindow.get() queue:nil usingBlock:^(NSNotification *) { + didExit = true; + }]; + + [fullScreenWindow toggleFullScreen:nil]; + EXPECT_TRUE(Util::runFor(&didEnter, 10_s)); + + EXPECT_TRUE([fullScreenWindow styleMask] & NSWindowStyleMaskFullScreen); + + // Move the WKWebView into the already-fullscreen window. + [webView removeFromSuperview]; + [[fullScreenWindow contentView] addSubview:webView.get()]; + [webView setFrame:[[fullScreenWindow contentView] bounds]]; + [webView waitForNextPresentationUpdate]; + + auto overlayHeight = [webView _fullScreenTitlebarOverlayHeightForTesting]; + EXPECT_GT(overlayHeight, topInset); + + auto screenTopSafeArea = [[[webView window] screen] safeAreaInsets].top; + EXPECT_NEAR(overlayHeight - screenTopSafeArea, NSHeight([[webView _topScrollPocket] frame]), 1); + + RetainPtr expectedColor = [webView _sampledTopFixedPositionContentColor]; + EXPECT_NOT_NULL(expectedColor.get()); + EXPECT_TRUE(Util::compareColors([[webView _topScrollPocket] captureColor], expectedColor.get())); + + [fullScreenWindow toggleFullScreen:nil]; + EXPECT_TRUE(Util::runFor(&didExit, 10_s)); + [webView waitForNextPresentationUpdate]; + + [NSNotificationCenter.defaultCenter removeObserver:enterObserver.get()]; + [NSNotificationCenter.defaultCenter removeObserver:exitObserver.get()]; +} + #endif // ENABLE(SCROLL_POCKET_IN_FULLSCREEN) #endif // ENABLE(CONTENT_INSET_BACKGROUND_FILL) diff --git a/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/SiteIsolation.mm b/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/SiteIsolation.mm index 3209dafe2820..e6cac4f5feda 100644 --- a/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/SiteIsolation.mm +++ b/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/SiteIsolation.mm @@ -4961,6 +4961,47 @@ HTTPServer server({ EXPECT_WK_STREQ([webView _test_waitForAlert], "c"); } +TEST(SiteIsolation, IntentionalAboutBlankIframeBackForwardNotSkipped) +{ + // Regression test for the URL-heuristic gap in isStaleInitialAboutBlankIframeTarget + // (https://bugs.webkit.org/show_bug.cgi?id=317458). A child frame that navigates + // *intentionally* to about:blank after its first real load produces a legitimate, + // traversable back/forward entry — distinct from the frame's initial empty document. + // A URL-string guard would wrongly skip the traversal into that entry; the + // authoritative isInitialAboutBlank flag lets it through. + HTTPServer server({ + { "/example"_s, { ""_s } }, + { "/a"_s, { ""_s } }, + { "/c"_s, { ""_s } } + }, HTTPServer::Protocol::HttpsProxy); + auto [webView, navigationDelegate] = siteIsolatedViewAndDelegate(server); + [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://example.com/example"]]]; + EXPECT_WK_STREQ([webView _test_waitForAlert], "a"); + + auto childURLIs = [webView = RetainPtr { webView }] (NSString *expected) { + for (int i = 0; i < 100; ++i) { + RetainPtr value = [webView objectByEvaluatingJavaScript:@"location.href" inFrame:[webView firstChildFrame]]; + if ([value isKindOfClass:[NSString class]] && [(NSString *)value.get() isEqualToString:expected]) + return true; + TestWebKitAPI::Util::runFor(0.05_s); + } + return false; + }; + + // Intentional about:blank navigation in the child frame, after its first real load. + [webView evaluateJavaScript:@"location.href = 'about:blank'" inFrame:[webView firstChildFrame] completionHandler:nil]; + EXPECT_TRUE(childURLIs(@"about:blank")); + + // Navigate the child to a real URL so the live frame is on a real document. + [webView evaluateJavaScript:@"location.href = 'https://webkit.org/c'" inFrame:[webView firstChildFrame] completionHandler:nil]; + EXPECT_WK_STREQ([webView _test_waitForAlert], "c"); + + // Going back must traverse the child back to the intentional about:blank entry, + // not leave it stranded on /c. With the old URL heuristic this was skipped. + [webView goBack]; + EXPECT_TRUE(childURLIs(@"about:blank")); +} + TEST(SiteIsolation, RedirectToCSP) { HTTPServer server({ diff --git a/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/UIDelegate.mm b/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/UIDelegate.mm index df990f03fbe9..01d71d8ec01e 100644 --- a/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/UIDelegate.mm +++ b/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/UIDelegate.mm @@ -1478,6 +1478,115 @@ - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigat TestWebKitAPI::Util::run(&done); } +@interface OpenPDFWithPreviewDelegate : NSObject +@property (nonatomic, readonly) RetainPtr capturedFileURL; +@property (nonatomic, readonly) BOOL receivedCallback; +@end + +@implementation OpenPDFWithPreviewDelegate + +- (void)_webView:(WKWebView *)webView shouldAllowPDFAtURL:(NSURL *)fileURL toOpenFromFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler +{ + _capturedFileURL = fileURL; + _receivedCallback = YES; + completionHandler(NO); +} + +@end + +#if ENABLE(IPC_TESTING_API) + +static void runOpenPDFWithPreviewTraversalTest(NSString *injectedFilename, NSString *traversalTargetPath) +{ + [[NSFileManager defaultManager] removeItemAtPath:traversalTargetPath error:nil]; + + RetainPtr pdfURL = [NSBundle.test_resourcesBundle URLForResource:@"test" withExtension:@"pdf"]; + + RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]); + for (_WKFeature *feature in [WKPreferences _features]) { + if ([feature.key isEqualToString:@"PDFPluginHUDEnabled"] || [feature.key isEqualToString:@"IPCTestingAPIEnabled"]) + [[configuration preferences] _setEnabled:YES forFeature:feature]; + } + + RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600) configuration:configuration.get()]); + [webView _setWindowOcclusionDetectionEnabled:NO]; + + RetainPtr delegate = adoptNS([OpenPDFWithPreviewDelegate new]); + [webView setUIDelegate:delegate.get()]; + + [webView loadRequest:[NSURLRequest requestWithURL:pdfURL.get()]]; + [webView _test_waitForDidFinishNavigation]; + + EXPECT_TRUE(TestWebKitAPI::Util::waitFor([webView] { + return !![webView _pdfHUDs].count; + })); + + RetainPtr listenerScript = [NSString stringWithFormat:@R"JS( + IPC.addIncomingMessageListener('UI', (message) => { + const replyMsgInfo = IPC.messages.WebPage_OpenPDFWithPreviewReply; + if (!replyMsgInfo) + return; + const requestMsgInfo = IPC.messages.WebPage_OpenPDFWithPreview; + if (!requestMsgInfo || message.name !== requestMsgInfo.name) + return; + if (typeof message.listenerID === 'undefined') + return; + + IPC.sendMessage('UI', message.listenerID, replyMsgInfo.name, [ + {type: 'String', value: '%@'}, + {type: 'bool', value: 1}, + {type: 'FrameInfoData', value: IPC}, + {type: 'uint64_t', value: 4}, + new Uint8Array([0x25, 0x50, 0x44, 0x46]) + ]); + }); + 'listener installed'; + )JS", injectedFilename]; + + bool listenerInstalled = false; + [webView evaluateJavaScript:listenerScript.get() completionHandler:[&listenerInstalled](id, NSError *error) { + EXPECT_NULL(error); + listenerInstalled = true; + }]; + TestWebKitAPI::Util::run(&listenerInstalled); + + [[webView _pdfHUDs].anyObject performSelector:NSSelectorFromString(@"_performActionForControl:") withObject:@"preview"]; + + EXPECT_TRUE(TestWebKitAPI::Util::waitFor([delegate] { + return [delegate receivedCallback]; + })); + + EXPECT_FALSE([[NSFileManager defaultManager] fileExistsAtPath:traversalTargetPath]); + + RetainPtr writtenPath = [[[delegate capturedFileURL] path] stringByStandardizingPath]; + RetainPtr temporaryDirectory = [NSTemporaryDirectory() stringByStandardizingPath]; + + EXPECT_TRUE([writtenPath hasPrefix:temporaryDirectory.get()]); + EXPECT_TRUE([writtenPath containsString:@"/WebKitPDFs-"]); + EXPECT_FALSE([writtenPath containsString:@"/../"]); + + [[NSFileManager defaultManager] removeItemAtURL:[delegate capturedFileURL].get() error:nil]; + [[NSFileManager defaultManager] removeItemAtPath:traversalTargetPath error:nil]; +} + +TEST(WebKit, OpenPDFWithPreviewIPCTraversalEncodedFilename) +{ + int pid = [[NSProcessInfo processInfo] processIdentifier]; + RetainPtr traversalTarget = [NSString stringWithFormat:@"/tmp/DEEP-TRAVERSAL-%d-encoded.pdf", pid]; + RetainPtr injectedFilename = [NSString stringWithFormat:@"..%%2F..%%2F..%%2F..%%2F..%%2F..%%2F..%%2Ftmp%%2FDEEP-TRAVERSAL-%d-encoded.pdf", pid]; + runOpenPDFWithPreviewTraversalTest(injectedFilename.get(), traversalTarget.get()); +} + +TEST(WebKit, OpenPDFWithPreviewIPCTraversalUnencodedFilename) +{ + int pid = [[NSProcessInfo processInfo] processIdentifier]; + RetainPtr traversalTarget = [NSString stringWithFormat:@"/tmp/DEEP-TRAVERSAL-%d-unencoded.pdf", pid]; + RetainPtr injectedFilename = [NSString stringWithFormat:@"../../../../../../../tmp/DEEP-TRAVERSAL-%d-unencoded.pdf", pid]; + runOpenPDFWithPreviewTraversalTest(injectedFilename.get(), traversalTarget.get()); +} + +#endif // ENABLE(IPC_TESTING_API) + #endif // ENABLE(PDF_HUD) #define RELIABLE_DID_NOT_HANDLE_WHEEL_EVENT 0 diff --git a/Websites/bugs.webkit.org/PrettyPatch/PrettyPatch.rb b/Websites/bugs.webkit.org/PrettyPatch/PrettyPatch.rb index eb4be3f5372f..f30ffa977945 100644 --- a/Websites/bugs.webkit.org/PrettyPatch/PrettyPatch.rb +++ b/Websites/bugs.webkit.org/PrettyPatch/PrettyPatch.rb @@ -740,7 +740,8 @@ def image_to_html image_checksum = IMAGE_CHECKSUM_ERROR end - return "

" + image_checksum + "

" + return "

" + image_checksum + "

" + end def to_html diff --git a/Websites/bugs.webkit.org/committers-autocomplete.js b/Websites/bugs.webkit.org/committers-autocomplete.js index 79c6809fe78e..c17f6fe6a24f 100644 --- a/Websites/bugs.webkit.org/committers-autocomplete.js +++ b/Websites/bugs.webkit.org/committers-autocomplete.js @@ -157,18 +157,31 @@ WebKitCommitters = (function() { return; } - var html = []; + function appendSpan(parent, className, text) { + var span = document.createElement('span'); + span.className = className; + span.textContent = text; + parent.append(span, ' '); + } + + var menu = getMenu(); + menu.textContent = ''; for (var i = 0; i < contacts.length; i++) { var contact = contacts[i]; - html.push('
' + contact.name + ' <' + contact.emails[0] + '> '); + + var suggestion = document.createElement('div'); + suggestion.className = 'committer-suggestion'; + suggestion.setAttribute('email', contact.emails[0]); + + appendSpan(suggestion, 'committer-name', contact.name); + appendSpan(suggestion, 'committer-email', '<' + contact.emails[0] + '>'); if (contact.nicks) - html.push(' @' + contact.nicks.join(', @') + ''); + appendSpan(suggestion, 'committer-nick', '@' + contact.nicks.join(', @')); if (contact.type) - html.push(' ' + contact.type + ''); - html.push('
'); + appendSpan(suggestion, 'committer-type', contact.type); + + menu.appendChild(suggestion); } - getMenu().innerHTML = html.join(''); selectItem(0); showMenu(true); } diff --git a/Websites/bugs.webkit.org/extensions/Commits/Extension.pm b/Websites/bugs.webkit.org/extensions/Commits/Extension.pm index 8573d2bd9c27..b143c8a9317d 100644 --- a/Websites/bugs.webkit.org/extensions/Commits/Extension.pm +++ b/Websites/bugs.webkit.org/extensions/Commits/Extension.pm @@ -26,6 +26,7 @@ use strict; use warnings; use parent qw(Bugzilla::Extension); +use Bugzilla::Util qw(html_quote); our $VERSION = "1.0.0"; @@ -36,14 +37,14 @@ sub bug_format_comment { # Should match "r12345" and "trac.webkit.org/r12345" but not "https://trac.webkit.org/r12345" push(@$regexes, { match => qr/(? \&_replace_reference }); push(@$regexes, { match => qr/(? \&_replace_reference }); - push(@$regexes, { match => qr/\b((? \&_replace_reference }); - push(@$regexes, { match => qr/\b((? \&_replace_reference }); + push(@$regexes, { match => qr/\b((? \&_replace_reference }); + push(@$regexes, { match => qr/\b((? \&_replace_reference }); } sub _replace_reference { my $args = shift; - my $text = $args->{matches}->[0]; - my $reference = $args->{matches}->[1]; + my $text = html_quote($args->{matches}->[0]); + my $reference = html_quote($args->{matches}->[1]); return qq{$text}; };