diff --git a/JSTests/stress/warm-up-marked-blocks-state-machine.js b/JSTests/stress/warm-up-marked-blocks-state-machine.js new file mode 100644 index 0000000000000..47f1f09756b06 --- /dev/null +++ b/JSTests/stress/warm-up-marked-blocks-state-machine.js @@ -0,0 +1,52 @@ +//@ runDefault("--useDollarVM=1", "--warmUpMarkedBlockCount=8", "--warmUpMarkedBlockIdleTimeout=0.2") + +// Drives the warm-up supply through its whole state machine: fill, release on idle, restart, +// stand-down on allocation failure, and recovery once allocation works again. Every assertion is +// "reaches this state eventually", since a helper thread drives the transitions. + +const pollSeconds = 0.05; +const timeoutSeconds = 20; + +const retained = []; + +function allocateBlocks() { + // Retaining these is the point: a heap that can recycle stops asking the allocator for blocks, + // and sustained block demand is what the stand-down and restart paths are driven by. The count + // is kept small so that a run which ends up timing out still reports rather than exhausting + // memory first. + for (let i = 0; i < 2000; ++i) + retained.push({ a: i, b: i, c: i }); +} + +function waitFor(description, predicate, betweenAttempts = () => { }) { + for (let attempt = 0; attempt < timeoutSeconds / pollSeconds; ++attempt) { + if (predicate($vm.warmUpMarkedBlockState())) + return; + betweenAttempts(); + sleepSeconds(pollSeconds); + } + const state = $vm.warmUpMarkedBlockState(); + throw new Error(`Timed out waiting for ${description}; blocks=${state.blocks} phase=${state.phase}`); +} + +const isFilled = state => state.phase === "armed" && state.blocks > 0; + +// Demand starts the helper, which arms to the configured depth and fills. +allocateBlocks(); +waitFor("the supply to fill", isFilled, allocateBlocks); + +// With no demand at all, the helper hands everything back and shuts down. +waitFor("the supply to be released when idle", state => state.phase === "stopped"); + +// Fresh demand brings it back. +allocateBlocks(); +waitFor("the helper to restart", isFilled, allocateBlocks); + +// An allocation failure makes it stand down rather than spin against an exhausted heap. +$vm.setWarmUpMarkedBlockAllocationShouldFail(true); +waitFor("the helper to stand down", state => state.phase === "standingDown", allocateBlocks); + +// Standing down must not be permanent: the idle timeout still fires while demand continues, and +// lifts it once allocation works again. +$vm.setWarmUpMarkedBlockAllocationShouldFail(false); +waitFor("the stand-down to lift", isFilled, allocateBlocks); diff --git a/JSTests/stress/warm-up-marked-blocks.js b/JSTests/stress/warm-up-marked-blocks.js new file mode 100644 index 0000000000000..00010e37ad467 --- /dev/null +++ b/JSTests/stress/warm-up-marked-blocks.js @@ -0,0 +1,29 @@ +//@ runDefault("--useWarmUpMarkedBlocks=1", "--warmUpMarkedBlockCount=64") +//@ runDefault("--useWarmUpMarkedBlocks=1", "--warmUpMarkedBlockCount=1") +//@ runDefault("--useWarmUpMarkedBlocks=1", "--warmUpMarkedBlockCount=0") +//@ runDefault("--useWarmUpMarkedBlocks=0") +//@ runDefault("--useWarmUpMarkedBlocks=1", "--warmUpMarkedBlockCount=8", "--scribbleFreeCells=1") +//@ runDefault("--useWarmUpMarkedBlocks=1", "--forceMiniVMMode=1") + +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`Expected ${expected} but got ${actual}`); +} + +let chain = null; +let expectedLength = 0; +for (let i = 0; i < 300000; ++i) { + const garbage = { index: i, payload: [i, i + 1, i + 2] }; + shouldBe(garbage.payload[2], i + 2); + if (!(i % 1000)) { + chain = { index: i, next: chain }; + ++expectedLength; + } +} + +let length = 0; +for (let node = chain; node; node = node.next) { + ++length; + shouldBe(node.index, (expectedLength - length) * 1000); +} +shouldBe(length, expectedLength); diff --git a/LayoutTests/http/tests/site-isolation/now-playing-elected-across-frames.html b/LayoutTests/http/tests/site-isolation/now-playing-elected-across-frames.html index 10f0cc0dc4f5d..79078a399949c 100644 --- a/LayoutTests/http/tests/site-isolation/now-playing-elected-across-frames.html +++ b/LayoutTests/http/tests/site-isolation/now-playing-elected-across-frames.html @@ -4,6 +4,7 @@ + @@ -20,48 +21,6 @@ let mainFrameActiveAfterSubframePlays = true; let subframeActiveAfterSubframePlays = false; -function waitForSubframeMessage(replyType) -{ - return new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => { - window.removeEventListener("message", handler); - reject(new Error(`timed out waiting for "${replyType}" from the subframe`)); - }, 10000); - function handler(event) { - if (!event.data || (event.data.type !== replyType && event.data.type !== "error")) - return; - window.removeEventListener("message", handler); - clearTimeout(timeoutId); - if (event.data.type === "error") - reject(new Error(event.data.message)); - else - resolve(event.data.value); - } - window.addEventListener("message", handler); - }); -} - -function askFrame(query, replyType) -{ - const reply = waitForSubframeMessage(replyType); - iframe.contentWindow.postMessage(query, "*"); - return reply; -} - -function subframeIsActiveNowPlaying() -{ - return askFrame("isActiveNowPlaying?", "isActiveNowPlaying"); -} - -async function waitFor(predicate) -{ - for (let tries = 0; tries < 200; ++tries) { - if (await predicate()) - return; - await new Promise(resolve => setTimeout(resolve, 10)); - } -} - onload = async () => { if (!window.internals) { testFailed("This test requires the Internals API"); @@ -86,11 +45,11 @@ internals.withUserGesture(() => video.play()); await new Promise(resolve => video.addEventListener("playing", resolve, { once: true })); - await waitFor(async () => { + await waitUntil(async () => { mainFrameActiveInitially = await internals.elementIsActiveNowPlayingSessionInGPUProcess(video); - subframeActiveInitially = await subframeIsActiveNowPlaying(); + subframeActiveInitially = await subframeIsActiveNowPlaying(iframe); return mainFrameActiveInitially && !subframeActiveInitially; - }); + }, { tries: 200, intervalMs: 10 }); shouldBeTrue("mainFrameActiveInitially"); shouldBeFalse("subframeActiveInitially"); @@ -98,12 +57,12 @@ iframe.contentWindow.postMessage("play", "*"); await framePlaying; - await waitFor(async () => { + await waitUntil(async () => { mainFramePausedAfterSubframePlays = video.paused; mainFrameActiveAfterSubframePlays = await internals.elementIsActiveNowPlayingSessionInGPUProcess(video); - subframeActiveAfterSubframePlays = await subframeIsActiveNowPlaying(); + subframeActiveAfterSubframePlays = await subframeIsActiveNowPlaying(iframe); return mainFramePausedAfterSubframePlays && !mainFrameActiveAfterSubframePlays && subframeActiveAfterSubframePlays; - }); + }, { tries: 200, intervalMs: 10 }); shouldBeTrue("mainFramePausedAfterSubframePlays"); shouldBeFalse("mainFrameActiveAfterSubframePlays"); shouldBeTrue("subframeActiveAfterSubframePlays"); diff --git a/LayoutTests/http/tests/site-isolation/now-playing-reelects-on-resize.html b/LayoutTests/http/tests/site-isolation/now-playing-reelects-on-resize.html index 8d2f0998a9400..9f52a74d9d48a 100644 --- a/LayoutTests/http/tests/site-isolation/now-playing-reelects-on-resize.html +++ b/LayoutTests/http/tests/site-isolation/now-playing-reelects-on-resize.html @@ -4,6 +4,7 @@ + @@ -19,48 +20,6 @@ let mainFrameElectedAfterResize = true; let subframeElectedAfterResize = false; -function waitForSubframeMessage(replyType) -{ - return new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => { - window.removeEventListener("message", handler); - reject(new Error(`timed out waiting for "${replyType}" from the subframe`)); - }, 10000); - function handler(event) { - if (!event.data || (event.data.type !== replyType && event.data.type !== "error")) - return; - window.removeEventListener("message", handler); - clearTimeout(timeoutId); - if (event.data.type === "error") - reject(new Error(event.data.message)); - else - resolve(event.data.value); - } - window.addEventListener("message", handler); - }); -} - -function askFrame(query, replyType) -{ - const reply = waitForSubframeMessage(replyType); - iframe.contentWindow.postMessage(query, "*"); - return reply; -} - -function subframeIsActiveNowPlaying() -{ - return askFrame("isActiveNowPlaying?", "isActiveNowPlaying"); -} - -async function waitFor(predicate, tries, delay) -{ - for (let i = 0; i < tries; ++i) { - if (await predicate()) - return; - await new Promise(resolve => setTimeout(resolve, delay)); - } -} - onload = async () => { if (!window.internals) { testFailed("This test requires the Internals API"); @@ -91,21 +50,21 @@ await new Promise(resolve => video.addEventListener("playing", resolve, { once: true })); // Both videos are main-content-sized, so the more recently interacted-with main frame is elected. - await waitFor(async () => { + await waitUntil(async () => { mainFrameElectedInitially = await internals.elementIsActiveNowPlayingSessionInGPUProcess(video); - subframeElectedInitially = await subframeIsActiveNowPlaying(); + subframeElectedInitially = await subframeIsActiveNowPlaying(iframe); return mainFrameElectedInitially && !subframeElectedInitially; - }, 200, 10); + }, { tries: 200, intervalMs: 10 }); shouldBeTrue("mainFrameElectedInitially"); shouldBeFalse("subframeElectedInitially"); // Pausing the main-frame video leaves it eligible and still elected. video.pause(); await new Promise(resolve => video.addEventListener("pause", resolve, { once: true })); - await waitFor(async () => { + await waitUntil(async () => { mainFrameElectedAfterPause = await internals.elementIsActiveNowPlayingSessionInGPUProcess(video); return mainFrameElectedAfterPause; - }, 200, 10); + }, { tries: 200, intervalMs: 10 }); shouldBeTrue("mainFrameElectedAfterPause"); // Shrink the paused video below the main-content threshold. Its NowPlayingInfo does not change, so the @@ -115,11 +74,11 @@ video.style.height = "100px"; video.offsetWidth; - await waitFor(async () => { + await waitUntil(async () => { mainFrameElectedAfterResize = await internals.elementIsActiveNowPlayingSessionInGPUProcess(video); - subframeElectedAfterResize = await subframeIsActiveNowPlaying(); + subframeElectedAfterResize = await subframeIsActiveNowPlaying(iframe); return !mainFrameElectedAfterResize && subframeElectedAfterResize; - }, 600, 25); + }, { tries: 600, intervalMs: 25 }); shouldBeFalse("mainFrameElectedAfterResize"); shouldBeTrue("subframeElectedAfterResize"); } catch (e) { diff --git a/LayoutTests/http/tests/site-isolation/now-playing-remote-command-no-owner-expected.txt b/LayoutTests/http/tests/site-isolation/now-playing-remote-command-no-owner-expected.txt new file mode 100644 index 0000000000000..18b2c5b4b55ab --- /dev/null +++ b/LayoutTests/http/tests/site-isolation/now-playing-remote-command-no-owner-expected.txt @@ -0,0 +1,12 @@ +Under site isolation, a remote-control command received when no media session is NowPlaying-eligible is still delivered to the current session, matching the non-site-isolated behavior. The video is loaded but never played, so it is not NowPlaying-eligible and the GPU process has no elected owner; a 'play' command injected at the GPU process must reach the video and start playback. + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS videoIsCommandTarget is true +PASS videoIsElectedOwner is false +PASS videoStartedPlaying is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/http/tests/site-isolation/now-playing-remote-command-no-owner.html b/LayoutTests/http/tests/site-isolation/now-playing-remote-command-no-owner.html new file mode 100644 index 0000000000000..c6f7977abdfdf --- /dev/null +++ b/LayoutTests/http/tests/site-isolation/now-playing-remote-command-no-owner.html @@ -0,0 +1,60 @@ + + + + + + + + + + + + diff --git a/LayoutTests/http/tests/site-isolation/now-playing-remote-command-routing-expected.txt b/LayoutTests/http/tests/site-isolation/now-playing-remote-command-routing-expected.txt new file mode 100644 index 0000000000000..f97b60ce325fe --- /dev/null +++ b/LayoutTests/http/tests/site-isolation/now-playing-remote-command-routing-expected.txt @@ -0,0 +1,11 @@ +Under site isolation, a system remote-control command received by the GPU process is delivered to the GPU-elected NowPlaying session, even when that session lives in a different web process than the one that injects the command. The main-frame video plays first, then the cross-site subframe video, which becomes the elected session; a pause command injected from the main frame's process must reach and pause the subframe's session. (The main frame's own playback state is not asserted: platforms that forbid concurrent playback, e.g. iOS, pause it when the subframe starts.) + +On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". + + +PASS subframeElectedInitially is true +PASS subframePausedAfterCommand is true +PASS successfullyParsed is true + +TEST COMPLETE + diff --git a/LayoutTests/http/tests/site-isolation/now-playing-remote-command-routing.html b/LayoutTests/http/tests/site-isolation/now-playing-remote-command-routing.html new file mode 100644 index 0000000000000..51cc033f53ff8 --- /dev/null +++ b/LayoutTests/http/tests/site-isolation/now-playing-remote-command-routing.html @@ -0,0 +1,75 @@ + + + + + + + + + + + + + diff --git a/LayoutTests/http/tests/site-isolation/resources/now-playing-frame.html b/LayoutTests/http/tests/site-isolation/resources/now-playing-frame.html index ccf7ab4a775c1..262b79a5ba1bf 100644 --- a/LayoutTests/http/tests/site-isolation/resources/now-playing-frame.html +++ b/LayoutTests/http/tests/site-isolation/resources/now-playing-frame.html @@ -15,6 +15,10 @@ window.parent.postMessage({ type: "isActiveNowPlaying", value }, "*"); return; } + if (event.data === "isPaused?") { + window.parent.postMessage({ type: "isPaused", value: video.paused }, "*"); + return; + } if (event.data === "play") { try { video.volume = 0.001; diff --git a/LayoutTests/http/tests/site-isolation/resources/now-playing-test-helpers.js b/LayoutTests/http/tests/site-isolation/resources/now-playing-test-helpers.js new file mode 100644 index 0000000000000..b8c4cbab396dc --- /dev/null +++ b/LayoutTests/http/tests/site-isolation/resources/now-playing-test-helpers.js @@ -0,0 +1,36 @@ +// Helpers for talking to a subframe that follows the now-playing-frame.html message protocol: it replies with +// { type, value } (or { type: "error", message }) to queries posted to its window. + +function waitForSubframeMessage(replyType) { + return new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + window.removeEventListener("message", handler); + reject(new Error(`timed out waiting for "${replyType}" from the subframe`)); + }, 10000); + function handler(event) { + if (!event.data || (event.data.type !== replyType && event.data.type !== "error")) + return; + window.removeEventListener("message", handler); + clearTimeout(timeoutId); + if (event.data.type === "error") + reject(new Error(event.data.message)); + else + resolve(event.data.value); + } + window.addEventListener("message", handler); + }); +} + +function askFrame(frame, query, replyType) { + const reply = waitForSubframeMessage(replyType); + frame.contentWindow.postMessage(query, "*"); + return reply; +} + +function subframeIsActiveNowPlaying(frame) { + return askFrame(frame, "isActiveNowPlaying?", "isActiveNowPlaying"); +} + +function subframeIsPaused(frame) { + return askFrame(frame, "isPaused?", "isPaused"); +} diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/alignment/grid-align-baseline-single-item-001-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/alignment/grid-align-baseline-single-item-001-expected.txt new file mode 100644 index 0000000000000..fb266960d8f27 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/alignment/grid-align-baseline-single-item-001-expected.txt @@ -0,0 +1,6 @@ + +PASS .grid 1 +PASS .grid 2 +PASS .grid 3 +PASS .grid 4 + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/alignment/grid-align-baseline-single-item-001.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/alignment/grid-align-baseline-single-item-001.html new file mode 100644 index 0000000000000..0b975987c4565 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/alignment/grid-align-baseline-single-item-001.html @@ -0,0 +1,44 @@ + + +CSS Grid Layout Test: Baseline alignment of a grid item that is alone in its baseline-sharing group + + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/grid-items/aspect-ratio-grid-item-dynamic-container-shrink-001-expected.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/grid-items/aspect-ratio-grid-item-dynamic-container-shrink-001-expected.html new file mode 100644 index 0000000000000..ffaa0f4736664 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/grid-items/aspect-ratio-grid-item-dynamic-container-shrink-001-expected.html @@ -0,0 +1,19 @@ + + +CSS Reference + +

Test passes if the green and the black rectangle have the same width.

+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/grid-items/aspect-ratio-grid-item-dynamic-container-shrink-001-ref.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/grid-items/aspect-ratio-grid-item-dynamic-container-shrink-001-ref.html new file mode 100644 index 0000000000000..ffaa0f4736664 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/grid-items/aspect-ratio-grid-item-dynamic-container-shrink-001-ref.html @@ -0,0 +1,19 @@ + + +CSS Reference + +

Test passes if the green and the black rectangle have the same width.

+
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/grid-items/aspect-ratio-grid-item-dynamic-container-shrink-001.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/grid-items/aspect-ratio-grid-item-dynamic-container-shrink-001.html new file mode 100644 index 0000000000000..172b0fef91cd0 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-grid/grid-items/aspect-ratio-grid-item-dynamic-container-shrink-001.html @@ -0,0 +1,29 @@ + + +CSS Test: flexible tracks stay equal when the grid container shrinks around an aspect-ratio item + + + + +

Test passes if the green and the black rectangle have the same width.

+
+
+
+
+ diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/cssom/getComputedStyle-in-unrendered-subframe-with-query-container-crash.html b/LayoutTests/imported/w3c/web-platform-tests/css/cssom/getComputedStyle-in-unrendered-subframe-with-query-container-crash.html new file mode 100644 index 0000000000000..51a05d7f63793 --- /dev/null +++ b/LayoutTests/imported/w3c/web-platform-tests/css/cssom/getComputedStyle-in-unrendered-subframe-with-query-container-crash.html @@ -0,0 +1,47 @@ + + +Reading a resolved value under a query container inside a frame whose owner element generates no box + + + + + +
+ + + diff --git a/LayoutTests/media/utilities.js b/LayoutTests/media/utilities.js index a199cb7acf3eb..d31ead572dcd4 100644 --- a/LayoutTests/media/utilities.js +++ b/LayoutTests/media/utilities.js @@ -196,3 +196,13 @@ async function waitForAudioSessionCategory(category, description) { } throw new Error(`audio session category is "${observed}", expected "${category}"${description ? ` (${description})` : ""}`); } + +// Polls a predicate until it returns true, or gives up after the attempts run out (callers then assert the +// specific conditions they expected, so the failure names which one). +async function waitUntil(predicate, { tries = 200, intervalMs = 10 } = {}) { + for (let i = 0; i < tries; ++i) { + if (await predicate()) + return; + await delay(intervalMs); + } +} diff --git a/Source/JavaScriptCore/heap/FastMallocAlignedMemoryAllocator.cpp b/Source/JavaScriptCore/heap/FastMallocAlignedMemoryAllocator.cpp index 4e5fed02c393c..833f607583c2c 100644 --- a/Source/JavaScriptCore/heap/FastMallocAlignedMemoryAllocator.cpp +++ b/Source/JavaScriptCore/heap/FastMallocAlignedMemoryAllocator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Apple Inc. All rights reserved. + * Copyright (C) 2017-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 @@ -20,16 +20,231 @@ * 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. + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "FastMallocAlignedMemoryAllocator.h" +#include "MarkedBlock.h" +#include "Options.h" +#include "VM.h" +#include +#include #include +#include +#include +#include +#include +#include +#include namespace JSC { +#if !ENABLE(MALLOC_HEAP_BREAKDOWN) + +namespace { + +// Lets a test drive the exhaustion path without running the machine out of memory. +std::atomic s_allocationFailsForTesting { false }; + +void* tryAllocateBlock() +{ + if (s_allocationFailsForTesting.load(std::memory_order_relaxed)) [[unlikely]] + return nullptr; + return tryFastCompactAlignedMalloc(MarkedBlock::blockSize, MarkedBlock::blockSize); +} + +// The first store into a freshly allocated MarkedBlock takes a write fault, and a heap that is +// ramping up pays that fault thousands of times on the mutator thread. WarmUpBlockProvider keeps a +// supply of blocks whose pages a helper thread has already made resident, so the fault lands on the +// helper instead. +// +// The supply has to be deep from the very first request, because ramp demand runs at tens of blocks +// per millisecond and a depth that grows in proportion to observed demand arrives too late to help. +// Warming a page is not free even when it is handed back promptly, so the supply is given up once an +// interval passes with no demand at all. +class WarmUpBlockProvider { +public: + using Phase = WarmUpMarkedBlockPhase; + + WarmUpBlockProvider() + : m_lock(Box::create()) + , m_condition(AutomaticThreadCondition::create()) + , m_thread(adoptRef(*new WarmUpThread(Locker { *m_lock }, *this))) + { + } + + static bool isEnabled() + { + // Mini mode trades throughput for footprint, which is the opposite of the bargain here. + return Options::useWarmUpMarkedBlocks() && Options::warmUpMarkedBlockCount() && !VM::isInMiniMode(); + } + + static WarmUpBlockProvider& singleton() + { + static LazyNeverDestroyed provider; + static std::once_flag flag; + std::call_once(flag, [] { + provider.construct(); + }); + return provider; + } + + void* tryTake() + { + Locker locker { *m_lock }; + void* result = m_blocks.isEmpty() ? nullptr : m_blocks.takeLast(); + m_demandSinceRefill = true; + m_demandSinceIdleCheck = true; + // A miss means the mutator is about to take the very fault this exists to avoid, and it is + // also the only thing that brings the helper back once it has shut itself down. While it is + // standing down, a notify would only postpone the timeout that lifts the stand-down. + if ((!result || isRunningLow()) && m_phase != Phase::StandingDown) + m_condition->notifyOne(locker); + return result; + } + + WarmUpMarkedBlockState stateForTesting() + { + Locker locker { *m_lock }; + return { m_blocks.size(), m_phase }; + } + +private: + // AutomaticThread has no voluntary temporary stop: PollResult::Stop is permanent, and start() + // release-asserts that the thread is still running. So giving up after an allocation failure has + // to be a wait that suppresses notifies, which is what leaves the idle timeout free to lift it. + class WarmUpThread final : public AutomaticThread { + WTF_MAKE_TZONE_ALLOCATED_INLINE(WarmUpThread); + WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(WarmUpThread); + public: + WarmUpThread(const AbstractLocker& locker, WarmUpBlockProvider& provider) + : AutomaticThread(locker, provider.m_lock, provider.m_condition.copyRef(), Seconds(Options::warmUpMarkedBlockIdleTimeout())) + , m_provider(provider) + { + } + + ASCIILiteral name() const final { return "JSCWarmUp"_s; } + + protected: + void threadDidStart() final + { + Locker locker { *m_provider.m_lock }; + m_provider.m_phase = Phase::Armed; + } + + PollResult poll(const AbstractLocker&) final + { + assertIsHeld(*m_provider.m_lock); + if (m_provider.m_phase != Phase::Armed) + return PollResult::Wait; + // Topping the supply back up while demand is still arriving keeps it near its full depth + // on a ramp, rather than letting it drain to the watermark first. + if (m_provider.m_demandSinceRefill || m_provider.isRunningLow()) + return PollResult::Work; + return PollResult::Wait; + } + + WorkResult work() final + { + m_provider.refill(); + return WorkResult::Continue; + } + + bool shouldSleep(const AbstractLocker&) final + { + assertIsHeld(*m_provider.m_lock); + if (!std::exchange(m_provider.m_demandSinceIdleCheck, false)) + return true; + m_provider.m_phase = Phase::Armed; + return false; + } + + void threadIsStopping(const AbstractLocker&) final + { + assertIsHeld(*m_provider.m_lock); + // fastFree does not reach back into this lock, so it is safe to call with it held. + m_provider.m_phase = Phase::Stopped; + for (void* block : std::exchange(m_provider.m_blocks, { })) + fastFree(block); + } + + private: + WarmUpBlockProvider& m_provider; + }; + + size_t targetDepth() WTF_REQUIRES_LOCK(*m_lock) { return m_phase == Phase::Armed ? Options::warmUpMarkedBlockCount() : 0; } + + bool isRunningLow() WTF_REQUIRES_LOCK(*m_lock) { return m_blocks.size() * 4 < targetDepth(); } + + static void makeResident(void* block) + { + // One store per page is what takes the fault. Striding by the real page size rather than by + // the smallest a supported system could have avoids repeating the store within a page. + size_t pageSize = WTF::pageSize(); + ASSERT(!(MarkedBlock::blockSize % pageSize)); + auto bytes = unsafeMakeSpan(static_cast(block), MarkedBlock::blockSize); + for (size_t offset = 0; offset < bytes.size(); offset += pageSize) + bytes[offset] = 0; + } + + void refill() + { + size_t want; + { + Locker locker { *m_lock }; + m_demandSinceRefill = false; + size_t target = targetDepth(); + want = target > m_blocks.size() ? target - m_blocks.size() : 0; + } + + Vector staging; + bool exhausted = false; + for (size_t i = 0; i < want; ++i) { + void* block = tryAllocateBlock(); + if (!block) { + exhausted = true; + break; + } + makeResident(block); + staging.append(block); + } + + Locker locker { *m_lock }; + m_blocks.appendVector(WTF::move(staging)); + if (exhausted) + m_phase = Phase::StandingDown; + } + + const Box m_lock; + const Ref m_condition; + const Ref m_thread; + Vector m_blocks WTF_GUARDED_BY_LOCK(*m_lock); + Phase m_phase WTF_GUARDED_BY_LOCK(*m_lock) { Phase::Stopped }; + bool m_demandSinceRefill WTF_GUARDED_BY_LOCK(*m_lock) { false }; + bool m_demandSinceIdleCheck WTF_GUARDED_BY_LOCK(*m_lock) { false }; +}; + +} // anonymous namespace + +WarmUpMarkedBlockState warmUpMarkedBlockStateForTesting() +{ + return WarmUpBlockProvider::singleton().stateForTesting(); +} + +void setWarmUpMarkedBlockAllocationShouldFailForTesting(bool shouldFail) +{ + s_allocationFailsForTesting.store(shouldFail, std::memory_order_relaxed); +} + +#else // ENABLE(MALLOC_HEAP_BREAKDOWN) + +WarmUpMarkedBlockState warmUpMarkedBlockStateForTesting() { return { }; } +void setWarmUpMarkedBlockAllocationShouldFailForTesting(bool) { } + +#endif + FastMallocAlignedMemoryAllocator::FastMallocAlignedMemoryAllocator() #if ENABLE(MALLOC_HEAP_BREAKDOWN) : m_heap("WebKit FastMallocAlignedMemoryAllocator") @@ -44,6 +259,12 @@ void* FastMallocAlignedMemoryAllocator::tryAllocateAlignedMemory(size_t alignmen #if ENABLE(MALLOC_HEAP_BREAKDOWN) return m_heap.memalign(alignment, size, true); #else + // MarkedBlock::tryCreate is the only caller today and always asks for a block-shaped region. + // The guard keeps a future caller of some other size from being handed a block. + if (alignment == MarkedBlock::blockSize && size == MarkedBlock::blockSize && WarmUpBlockProvider::isEnabled()) { + if (void* block = WarmUpBlockProvider::singleton().tryTake()) + return block; + } return tryFastCompactAlignedMalloc(alignment, size); #endif diff --git a/Source/JavaScriptCore/heap/FastMallocAlignedMemoryAllocator.h b/Source/JavaScriptCore/heap/FastMallocAlignedMemoryAllocator.h index 0cd38f4f4a139..77ea338674694 100644 --- a/Source/JavaScriptCore/heap/FastMallocAlignedMemoryAllocator.h +++ b/Source/JavaScriptCore/heap/FastMallocAlignedMemoryAllocator.h @@ -53,5 +53,15 @@ class FastMallocAlignedMemoryAllocator final : public AlignedMemoryAllocator { #endif }; +// The supply of pre-warmed MarkedBlocks is a process-wide singleton with no other observer, so $vm +// reaches its state through these rather than through any VM. +enum class WarmUpMarkedBlockPhase : uint8_t { Stopped, Armed, StandingDown }; +struct WarmUpMarkedBlockState { + size_t blockCount { 0 }; + WarmUpMarkedBlockPhase phase { WarmUpMarkedBlockPhase::Stopped }; +}; +WarmUpMarkedBlockState warmUpMarkedBlockStateForTesting(); +void setWarmUpMarkedBlockAllocationShouldFailForTesting(bool); + } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/OptionsList.h b/Source/JavaScriptCore/runtime/OptionsList.h index 01a97e6a5e359..c151a7024ae3a 100644 --- a/Source/JavaScriptCore/runtime/OptionsList.h +++ b/Source/JavaScriptCore/runtime/OptionsList.h @@ -271,6 +271,9 @@ bool hasCapacityToUseLargeGigacage(); v(Double, gcIncrementBytes, 10000, Normal, nullptr) \ v(Double, gcIncrementMaxBytes, 100000, Normal, nullptr) \ v(Double, gcIncrementScale, 0, Normal, nullptr) \ + v(Bool, useWarmUpMarkedBlocks, true, Normal, "hand MarkedBlock allocation pages that a helper thread already made resident"_s) \ + v(Unsigned, warmUpMarkedBlockCount, 32, Normal, "how many MarkedBlocks the helper thread keeps ready with their pages already resident; 0 turns it off"_s) \ + v(Double, warmUpMarkedBlockIdleTimeout, 10, Normal, "seconds without a MarkedBlock request before the helper thread releases what it is holding and shuts down"_s) \ v(Bool, scribbleFreeCells, false, Normal, nullptr) \ v(Double, sizeClassProgression, 1.4, Normal, nullptr) \ v(Unsigned, preciseAllocationCutoff, 100000, Normal, nullptr) \ diff --git a/Source/JavaScriptCore/tools/JSDollarVM.cpp b/Source/JavaScriptCore/tools/JSDollarVM.cpp index f9982397be8ab..3eb5f9a6c8e1e 100644 --- a/Source/JavaScriptCore/tools/JSDollarVM.cpp +++ b/Source/JavaScriptCore/tools/JSDollarVM.cpp @@ -42,6 +42,7 @@ #include "DOMJITGetterSetter.h" #include "Debugger.h" #include "ExecutableBaseInlines.h" +#include "FastMallocAlignedMemoryAllocator.h" #include "FrameTracers.h" #include "FunctionCodeBlock.h" #include "GetterSetter.h" @@ -58,6 +59,7 @@ #include "JSString.h" #include "LinkBuffer.h" #include "NativeCallee.h" +#include "ObjectConstructor.h" #include "ObjectPropertyCondition.h" #include "OperationResult.h" #include "Options.h" @@ -2249,6 +2251,8 @@ static JSC_DECLARE_HOST_FUNCTION(functionInstallPropertyInlineCacheClearingWatch static JSC_DECLARE_HOST_FUNCTION(functionDeltaBetweenButterflies); static JSC_DECLARE_HOST_FUNCTION(functionCurrentCPUTime); static JSC_DECLARE_HOST_FUNCTION(functionTotalGCTime); +static JSC_DECLARE_HOST_FUNCTION(functionWarmUpMarkedBlockState); +static JSC_DECLARE_HOST_FUNCTION(functionSetWarmUpMarkedBlockAllocationShouldFail); static JSC_DECLARE_HOST_FUNCTION(functionParseCount); static JSC_DECLARE_HOST_FUNCTION(functionIsWasmSupported); static JSC_DECLARE_HOST_FUNCTION(functionWasmCanonicalTypeCount); @@ -4086,6 +4090,35 @@ JSC_DEFINE_HOST_FUNCTION(functionTotalGCTime, (JSGlobalObject* globalObject, Cal return JSValue::encode(jsNumber(vm.heap.totalGCTime().seconds())); } +JSC_DEFINE_HOST_FUNCTION(functionWarmUpMarkedBlockState, (JSGlobalObject* globalObject, CallFrame*)) +{ + DollarVMAssertScope assertScope; + VM& vm = globalObject->vm(); + auto state = warmUpMarkedBlockStateForTesting(); + ASCIILiteral phase = [&] { + switch (state.phase) { + case WarmUpMarkedBlockPhase::Stopped: + return "stopped"_s; + case WarmUpMarkedBlockPhase::Armed: + return "armed"_s; + case WarmUpMarkedBlockPhase::StandingDown: + return "standingDown"_s; + } + RELEASE_ASSERT_NOT_REACHED(); + }(); + JSObject* result = constructEmptyObject(globalObject); + result->putDirect(vm, Identifier::fromString(vm, "blocks"_s), jsNumber(static_cast(state.blockCount))); + result->putDirect(vm, Identifier::fromString(vm, "phase"_s), jsString(vm, String(phase))); + return JSValue::encode(result); +} + +JSC_DEFINE_HOST_FUNCTION(functionSetWarmUpMarkedBlockAllocationShouldFail, (JSGlobalObject* globalObject, CallFrame* callFrame)) +{ + DollarVMAssertScope assertScope; + setWarmUpMarkedBlockAllocationShouldFailForTesting(callFrame->argument(0).toBoolean(globalObject)); + return JSValue::encode(jsUndefined()); +} + JSC_DEFINE_HOST_FUNCTION(functionParseCount, (JSGlobalObject*, CallFrame*)) { DollarVMAssertScope assertScope; @@ -5593,6 +5626,8 @@ void JSDollarVM::finishCreation(VM& vm) addFunction(vm, alwaysAllow, "currentCPUTime"_s, functionCurrentCPUTime, 0); addFunction(vm, alwaysAllow, "totalGCTime"_s, functionTotalGCTime, 0); + addFunction(vm, alwaysAllow, "warmUpMarkedBlockState"_s, functionWarmUpMarkedBlockState, 0); + addFunction(vm, alwaysAllow, "setWarmUpMarkedBlockAllocationShouldFail"_s, functionSetWarmUpMarkedBlockAllocationShouldFail, 1); addFunction(vm, alwaysAllow, "parseCount"_s, functionParseCount, 0); diff --git a/Source/WebCore/css/CSSGroupingRule.cpp b/Source/WebCore/css/CSSGroupingRule.cpp index fa5d9e01ba05a..d8c114baaf8d6 100644 --- a/Source/WebCore/css/CSSGroupingRule.cpp +++ b/Source/WebCore/css/CSSGroupingRule.cpp @@ -82,7 +82,7 @@ ExceptionOr CSSGroupingRule::insertRule(const String& ruleString, unsi // CSSNestedDeclarations parsing is allowed if there is an ancestor style rule or an ancestor scope rule. if (!nestedContextWithCurrentRule) return Exception { ExceptionCode::SyntaxError }; - newRule = CSSParser::parseNestedDeclarations(parserContext(), ruleString); + newRule = CSSParser::parseNestedDeclarations(ruleString, parserContext()); if (!newRule) return Exception { ExceptionCode::SyntaxError }; } diff --git a/Source/WebCore/css/CSSStyleRule.cpp b/Source/WebCore/css/CSSStyleRule.cpp index ba8f28e18d3bc..6dc11d997b324 100644 --- a/Source/WebCore/css/CSSStyleRule.cpp +++ b/Source/WebCore/css/CSSStyleRule.cpp @@ -239,7 +239,7 @@ ExceptionOr CSSStyleRule::insertRule(const String& ruleString, unsigne RefPtr styleSheet = parentStyleSheet(); RefPtr newRule = CSSParser::parseRule(ruleString, parserContext(), styleSheet ? protect(styleSheet->contents()).ptr() : nullptr, CSSParser::AllowedRules::ImportRules, CSSParserEnum::NestedContextType::Style); if (!newRule) { - newRule = CSSParser::parseNestedDeclarations(parserContext(), ruleString); + newRule = CSSParser::parseNestedDeclarations(ruleString, parserContext()); if (!newRule) return Exception { ExceptionCode::SyntaxError }; } diff --git a/Source/WebCore/css/parser/CSSParser.cpp b/Source/WebCore/css/parser/CSSParser.cpp index 8a2ea9c3a7bbf..6060b2fb85d3b 100644 --- a/Source/WebCore/css/parser/CSSParser.cpp +++ b/Source/WebCore/css/parser/CSSParser.cpp @@ -101,7 +101,7 @@ CSSParser::CSSParser(const CSSParserContext& context, StyleSheetContents* styleS { } -CSSParser::CSSParser(const CSSParserContext& context, const String& string, StyleSheetContents* styleSheet, CSSParserObserverWrapper* wrapper, CSSParserEnum::NestedContext nestedContext) +CSSParser::CSSParser(const CSSParserContext& context, StringView string, StyleSheetContents* styleSheet, CSSParserObserverWrapper* wrapper, CSSParserEnum::NestedContext nestedContext) : m_context(context) , m_styleSheet(styleSheet) , m_tokenizer(wrapper ? CSSTokenizer::tryCreate(string, *wrapper) : CSSTokenizer::tryCreate(string)) @@ -112,7 +112,7 @@ CSSParser::CSSParser(const CSSParserContext& context, const String& string, Styl m_ancestorRuleTypeStack.append(*nestedContext); } -auto CSSParser::parseValue(MutableStyleProperties& declaration, CSSPropertyID propertyID, const String& string, IsImportant important, const CSSParserContext& context) -> ParseResult +auto CSSParser::parseValue(MutableStyleProperties& declaration, CSSPropertyID propertyID, StringView string, IsImportant important, const CSSParserContext& context) -> ParseResult { auto ruleType = context.enclosingRuleType.value_or(StyleRuleType::Style); @@ -132,7 +132,7 @@ auto CSSParser::parseValue(MutableStyleProperties& declaration, CSSPropertyID pr return declaration.addParsedProperties(parser.topContext().m_parsedProperties) ? ParseResult::Changed : ParseResult::Unchanged; } -auto CSSParser::parseCustomPropertyValue(MutableStyleProperties& declaration, const AtomString& propertyName, const String& string, IsImportant important, const CSSParserContext& context) -> ParseResult +auto CSSParser::parseCustomPropertyValue(MutableStyleProperties& declaration, const AtomString& propertyName, StringView string, IsImportant important, const CSSParserContext& context) -> ParseResult { CSSParser parser(context, string); @@ -187,7 +187,7 @@ static Ref createStyleProperties(ParsedPropertyVector& return result; } -Ref CSSParser::parseInlineStyleDeclaration(const String& string, const Element& element) +Ref CSSParser::parseInlineStyleDeclaration(StringView string, const Element& element) { CSSParserContext context(element.document()); context.mode = strictToCSSParserMode(element.isHTMLElement() && !element.document().inQuirksMode()); @@ -197,7 +197,7 @@ Ref CSSParser::parseInlineStyleDeclaration(const Strin return createStyleProperties(parser.topContext().m_parsedProperties, context.mode); } -bool CSSParser::parseDeclarationList(MutableStyleProperties& declaration, const String& string, const CSSParserContext& context) +bool CSSParser::parseDeclarationList(MutableStyleProperties& declaration, StringView string, const CSSParserContext& context) { CSSParser parser(context, string); auto ruleType = context.enclosingRuleType.value_or(StyleRuleType::Style); @@ -216,7 +216,7 @@ bool CSSParser::parseDeclarationList(MutableStyleProperties& declaration, const return declaration.addParsedProperties(results); } -RefPtr CSSParser::parseRule(const String& string, const CSSParserContext& context, StyleSheetContents* styleSheet, AllowedRules allowedRules, CSSParserEnum::NestedContext nestedContext) +RefPtr CSSParser::parseRule(StringView string, const CSSParserContext& context, StyleSheetContents* styleSheet, AllowedRules allowedRules, CSSParserEnum::NestedContext nestedContext) { CSSParser parser(context, string, styleSheet, nullptr, nestedContext); CSSParserTokenRange range = parser.tokenizer()->tokenRange(); @@ -236,13 +236,13 @@ RefPtr CSSParser::parseRule(const String& string, const CSSParser return rule; } -RefPtr CSSParser::parseKeyframeRule(const String& string, const CSSParserContext& context) +RefPtr CSSParser::parseKeyframeRule(StringView string, const CSSParserContext& context) { RefPtr keyframe = parseRule(string, context, nullptr, CSSParser::AllowedRules::KeyframeRules); return downcast(keyframe.get()); } -RefPtr CSSParser::parseNestedDeclarations(const CSSParserContext&context , const String& string) +RefPtr CSSParser::parseNestedDeclarations(StringView string, const CSSParserContext& context) { auto properties = MutableStyleProperties::createEmpty(); if (!parseDeclarationList(properties, string , context)) @@ -251,7 +251,7 @@ RefPtr CSSParser::parseNestedDeclarations(const CSS return StyleRuleNestedDeclarations::create(WTF::move(properties)); } -void CSSParser::parseStyleSheet(const String& string, const CSSParserContext& context, StyleSheetContents& styleSheet) +void CSSParser::parseStyleSheet(StringView string, const CSSParserContext& context, StyleSheetContents& styleSheet) { CSSParser parser(context, string, &styleSheet, nullptr); bool firstRuleValid = parser.consumeRuleList(parser.tokenizer()->tokenRange(), RuleList::TopLevel, [&](Ref rule) { @@ -318,7 +318,7 @@ bool CSSParser::supportsDeclaration(CSSParserTokenRange& range) return result; } -void CSSParser::parseDeclarationListForInspector(const String& declaration, const CSSParserContext& context, CSSParserObserver& observer) +void CSSParser::parseDeclarationListForInspector(StringView declaration, const CSSParserContext& context, CSSParserObserver& observer) { Ref wrapper = CSSParserObserverWrapper::create(observer); CSSParser parser(context, declaration, nullptr, wrapper.ptr()); @@ -327,7 +327,7 @@ void CSSParser::parseDeclarationListForInspector(const String& declaration, cons parser.consumeDeclarationList(parser.tokenizer()->tokenRange(), StyleRuleType::Style); } -void CSSParser::parseStyleSheetForInspector(const String& string, const CSSParserContext& context, StyleSheetContents& styleSheet, CSSParserObserver& observer) +void CSSParser::parseStyleSheetForInspector(StringView string, const CSSParserContext& context, StyleSheetContents& styleSheet, CSSParserObserver& observer) { Ref wrapper = CSSParserObserverWrapper::create(observer); CSSParser parser(context, string, &styleSheet, wrapper.ptr()); diff --git a/Source/WebCore/css/parser/CSSParser.h b/Source/WebCore/css/parser/CSSParser.h index 2bb6f95e4d531..9aa58559462a5 100644 --- a/Source/WebCore/css/parser/CSSParser.h +++ b/Source/WebCore/css/parser/CSSParser.h @@ -71,10 +71,6 @@ class ImmutableStyleProperties; class Element; class MutableStyleProperties; -namespace Style { -struct Color; -} - enum CSSAtRuleID : uint8_t; class CSSParser { @@ -86,9 +82,6 @@ class CSSParser { Error }; - CSSParser(const CSSParserContext&, const String&, StyleSheetContents* = nullptr, CSSParserObserverWrapper* = nullptr, CSSParserEnum::NestedContext = { }); - ~CSSParser(); - enum class AllowedRules : uint8_t { // As per css-syntax, css-cascade and css-namespaces, @charset rules // must come first, followed by @import then @namespace. @@ -106,28 +99,32 @@ class CSSParser { NoRules, // For parsing at-rules inside declaration lists (without nesting support) }; - static ParseResult parseValue(MutableStyleProperties&, CSSPropertyID, const String&, IsImportant, const CSSParserContext&); - static ParseResult parseCustomPropertyValue(MutableStyleProperties&, const AtomString& propertyName, const String&, IsImportant, const CSSParserContext&); - static Ref parseInlineStyleDeclaration(const String&, const Element&); - WEBCORE_EXPORT static bool parseDeclarationList(MutableStyleProperties&, const String&, const CSSParserContext&); - static RefPtr parseRule(const String&, const CSSParserContext&, StyleSheetContents*, AllowedRules, CSSParserEnum::NestedContext = { }); - static RefPtr parseKeyframeRule(const String&, const CSSParserContext&); - static void parseStyleSheet(const String&, const CSSParserContext&, StyleSheetContents&); + CSSParser(const CSSParserContext&, StringView string LIFETIME_BOUND, StyleSheetContents* = nullptr, CSSParserObserverWrapper* = nullptr, CSSParserEnum::NestedContext = { }); + ~CSSParser(); + + static ParseResult parseValue(MutableStyleProperties&, CSSPropertyID, StringView, IsImportant, const CSSParserContext&); + static ParseResult parseCustomPropertyValue(MutableStyleProperties&, const AtomString& propertyName, StringView, IsImportant, const CSSParserContext&); + WEBCORE_EXPORT static bool parseDeclarationList(MutableStyleProperties&, StringView, const CSSParserContext&); + + static Ref parseInlineStyleDeclaration(StringView, const Element&); + static RefPtr parseRule(StringView, const CSSParserContext&, StyleSheetContents*, AllowedRules, CSSParserEnum::NestedContext = { }); + static RefPtr parseKeyframeRule(StringView, const CSSParserContext&); + static RefPtr parseNestedDeclarations(StringView, const CSSParserContext&); + + static void parseStyleSheet(StringView, const CSSParserContext&, StyleSheetContents&); static CSSSelectorList parsePageSelector(CSSParserTokenRange, StyleSheetContents*); bool supportsDeclaration(CSSParserTokenRange&); - const CSSParserContext& context() const LIFETIME_BOUND { return m_context; } // This function updates the range it's given. RefPtr consumeAtRule(CSSParserTokenRange&, AllowedRules); - static void parseDeclarationListForInspector(const String&, const CSSParserContext&, CSSParserObserver&); - static void parseStyleSheetForInspector(const String&, const CSSParserContext&, StyleSheetContents&, CSSParserObserver&); + static void parseDeclarationListForInspector(StringView, const CSSParserContext&, CSSParserObserver&); + static void parseStyleSheetForInspector(StringView, const CSSParserContext&, StyleSheetContents&, CSSParserObserver&); static IsImportant consumeTrailingImportantAndWhitespace(CSSParserTokenRange&); - static RefPtr parseNestedDeclarations(const CSSParserContext&, const String&); - + const CSSParserContext& context() const LIFETIME_BOUND { return m_context; } const CSSTokenizer* tokenizer() const LIFETIME_BOUND { return m_tokenizer.get(); } private: diff --git a/Source/WebCore/css/parser/CSSPropertyParser.cpp b/Source/WebCore/css/parser/CSSPropertyParser.cpp index 0da21687b63f2..cd95d85276eee 100644 --- a/Source/WebCore/css/parser/CSSPropertyParser.cpp +++ b/Source/WebCore/css/parser/CSSPropertyParser.cpp @@ -270,7 +270,7 @@ bool CSSPropertyParser::parseValue(CSSPropertyID property, IsImportant important return parseSuccess; } -RefPtr CSSPropertyParser::parseStylePropertyLonghand(CSSPropertyID property, const String& string, const CSSParserContext& context) +RefPtr CSSPropertyParser::parseStylePropertyLonghand(CSSPropertyID property, StringView string, const CSSParserContext& context) { ASSERT(!WebCore::isShorthand(property)); @@ -324,7 +324,7 @@ RefPtr CSSPropertyParser::parseStylePropertyLonghand(CSSPropertyID pro return value; } -RefPtr CSSPropertyParser::parseCounterStyleDescriptor(CSSPropertyID property, const String& string, const CSSParserContext& context) +RefPtr CSSPropertyParser::parseCounterStyleDescriptor(CSSPropertyID property, StringView string, const CSSParserContext& context) { auto tokenizer = CSSTokenizer(string); auto range = tokenizer.tokenRange(); diff --git a/Source/WebCore/css/parser/CSSPropertyParser.h b/Source/WebCore/css/parser/CSSPropertyParser.h index 48e947ec2656d..47c6e92d4fa2d 100644 --- a/Source/WebCore/css/parser/CSSPropertyParser.h +++ b/Source/WebCore/css/parser/CSSPropertyParser.h @@ -57,11 +57,11 @@ class CSSPropertyParser { static bool parseValue(CSSPropertyID, IsImportant, CSSParserTokenRange, const CSSParserContext&, Vector& result, StyleRuleType, const CSSNamespacePrefixMap& = { }); // Parses a longhand style property. - static RefPtr parseStylePropertyLonghand(CSSPropertyID, const String&, const CSSParserContext&); + static RefPtr parseStylePropertyLonghand(CSSPropertyID, StringView, const CSSParserContext&); static RefPtr parseStylePropertyLonghand(CSSPropertyID, CSSParserTokenRange, const CSSParserContext&); // Parses a @counter-style descriptor. - static RefPtr parseCounterStyleDescriptor(CSSPropertyID, const String&, const CSSParserContext&); + static RefPtr parseCounterStyleDescriptor(CSSPropertyID, StringView, const CSSParserContext&); static RefPtr parseTypedCustomPropertyInitialValue(const AtomString&, const CSSCustomPropertySyntax&, CSSParserTokenRange, Style::BuilderState&, const CSSParserContext&); static std::optional, CSSWideKeyword>> parseTypedCustomPropertyValue(const AtomString& name, const CSSCustomPropertySyntax&, CSSParserTokenRange, Style::BuilderState&, const CSSParserContext&, Style::IsAttrTainted); diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Animations.cpp b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Animations.cpp index 2df6c85399142..94a2dfc3adc52 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Animations.cpp +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Animations.cpp @@ -87,7 +87,7 @@ Vector>> consumeKeyframeKeyList(CSSParse } } -Vector>> parseKeyframeKeyList(const String& string, const CSSParserContext& context) +Vector>> parseKeyframeKeyList(StringView string, const CSSParserContext& context) { auto tokenizer = CSSTokenizer(string); auto range = tokenizer.tokenRange(); diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Animations.h b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Animations.h index d68380d0cd51a..aa4a8426d719d 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Animations.h +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Animations.h @@ -47,7 +47,7 @@ Vector>> consumeKeyframeKeyList(CSSParse // MARK: parsing // https://drafts.csswg.org/css-animations-1/#typedef-keyframe-selector -Vector>> parseKeyframeKeyList(const String&, const CSSParserContext&); +Vector>> parseKeyframeKeyList(StringView, const CSSParserContext&); // MARK: consuming // https://drafts.csswg.org/css-animations/#typedef-keyframes-name diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.cpp b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.cpp index a8d3cc19fb984..6664ae35af1a8 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.cpp +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.cpp @@ -918,7 +918,7 @@ Color consumeColorRaw(CSSParserTokenRange& range, CSS::PropertyParserState& prop // MARK: - Raw parsing entry points -Color parseColorRawGeneral(const String& string, const CSSParserContext& context, ScriptExecutionContext& scriptExecutionContext, const CSSColorParsingOptions& options, CSS::PlatformColorResolutionState& eagerResolutionState) +Color parseColorRawGeneral(StringView string, const CSSParserContext& context, ScriptExecutionContext& scriptExecutionContext, const CSSColorParsingOptions& options, CSS::PlatformColorResolutionState& eagerResolutionState) { CSSTokenizer tokenizer(string); CSSParserTokenRange range(tokenizer.tokenRange()); @@ -938,7 +938,7 @@ Color parseColorRawGeneral(const String& string, const CSSParserContext& context return createColor(*result, eagerResolutionState); } -Color deprecatedParseColorRawWithoutContext(const String& string, const CSSColorParsingOptions& options) +Color deprecatedParseColorRawWithoutContext(StringView string, const CSSColorParsingOptions& options) { auto& context = strictCSSParserContext(); if (auto color = CSSParserFastPaths::parseSimpleColor(string, context)) diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.h b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.h index 7b554f23c8bb7..351545368208e 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.h +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.h @@ -69,19 +69,19 @@ WebCore::Color consumeColorRaw(CSSParserTokenRange&, CSS::PropertyParserState&, // Parse with default options. // NOTE: Callers must include CSSPropertyParserConsumer+ColorInlines.h to use this. -WebCore::Color parseColorRaw(const String&, const CSSParserContext&, ScriptExecutionContext&); +WebCore::Color parseColorRaw(StringView, const CSSParserContext&, ScriptExecutionContext&); // Fast variant to be used when ScriptExecutionContext is expensive to obtain or when need to pass parsing options. // If the result is invalid, callers should call parseColorRawGeneral(). // NOTE: Callers must include CSSPropertyParserConsumer+ColorInlines.h to use this. -WebCore::Color parseColorRawSimple(const String&, const CSSParserContext&); +WebCore::Color parseColorRawSimple(StringView, const CSSParserContext&); // Parse with specific options. -WEBCORE_EXPORT WebCore::Color parseColorRawGeneral(const String&, const CSSParserContext&, ScriptExecutionContext&, const CSSColorParsingOptions&, CSS::PlatformColorResolutionState&); +WEBCORE_EXPORT WebCore::Color parseColorRawGeneral(StringView, const CSSParserContext&, ScriptExecutionContext&, const CSSColorParsingOptions&, CSS::PlatformColorResolutionState&); // FIXME: All callers are not getting the right Settings, keyword resolution and calc resolution // when using this function and should switch to parseColorRaw(). -WEBCORE_EXPORT WebCore::Color deprecatedParseColorRawWithoutContext(const String&, const CSSColorParsingOptions& = { }); +WEBCORE_EXPORT WebCore::Color deprecatedParseColorRawWithoutContext(StringView, const CSSColorParsingOptions& = { }); // MARK: (unresolved) std::optional consumeUnresolvedDynamicRangeLimit(CSSParserTokenRange&, CSS::PropertyParserState&); diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+ColorAdjust.cpp b/Source/WebCore/css/parser/CSSPropertyParserConsumer+ColorAdjust.cpp index 2527fd22143c2..41ed205c0a714 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+ColorAdjust.cpp +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+ColorAdjust.cpp @@ -110,7 +110,7 @@ std::optional consumeUnresolvedColorScheme(CSSParserTokenRange return result; } -std::optional parseUnresolvedColorScheme(const String& string, const CSSParserContext& context) +std::optional parseUnresolvedColorScheme(StringView string, const CSSParserContext& context) { auto tokenizer = CSSTokenizer(string); auto range = tokenizer.tokenRange(); diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+ColorAdjust.h b/Source/WebCore/css/parser/CSSPropertyParserConsumer+ColorAdjust.h index defef2520fc3d..6d19b2366db6a 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+ColorAdjust.h +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+ColorAdjust.h @@ -49,7 +49,7 @@ namespace CSSPropertyParserHelpers { std::optional consumeUnresolvedColorScheme(CSSParserTokenRange&, CSS::PropertyParserState&); // MARK: <'color-scheme'> parsing (unresolved) -std::optional parseUnresolvedColorScheme(const String&, const CSSParserContext&); +std::optional parseUnresolvedColorScheme(StringView, const CSSParserContext&); // MARK: <'color-scheme'> consuming (CSSValue) RefPtr consumeColorScheme(CSSParserTokenRange&, CSS::PropertyParserState&); diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+ColorInlines.h b/Source/WebCore/css/parser/CSSPropertyParserConsumer+ColorInlines.h index 5979bfb369992..1e2f47bad7ad9 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+ColorInlines.h +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+ColorInlines.h @@ -33,12 +33,12 @@ namespace CSSPropertyParserHelpers { // MARK: parsing (raw) -inline WebCore::Color parseColorRawSimple(const String& string, const CSSParserContext& context) +inline WebCore::Color parseColorRawSimple(StringView string, const CSSParserContext& context) { return CSSParserFastPaths::parseSimpleColor(string, context); } -inline WebCore::Color parseColorRaw(const String& string, const CSSParserContext& context, ScriptExecutionContext& scriptExecutionContext) +inline WebCore::Color parseColorRaw(StringView string, const CSSParserContext& context, ScriptExecutionContext& scriptExecutionContext) { auto color = parseColorRawSimple(string, context); if (color.isValid()) diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Easing.cpp b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Easing.cpp index 70ca387d4c3e7..28110aa42188f 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Easing.cpp +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Easing.cpp @@ -367,7 +367,7 @@ RefPtr consumeEasingFunction(CSSParserTokenRange& range, CSS::Property return { }; } -RefPtr parseEasingFunctionDeprecated(const String& string, const CSSParserContext& context) +RefPtr parseEasingFunctionDeprecated(StringView string, const CSSParserContext& context) { auto tokenizer = CSSTokenizer(string); auto range = tokenizer.tokenRange(); diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Easing.h b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Easing.h index 3e7a9bb91ea55..31799f67f45b2 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Easing.h +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Easing.h @@ -52,7 +52,7 @@ std::optional consumeUnresolvedEasingFunction(CSSParserToke RefPtr consumeEasingFunction(CSSParserTokenRange&, CSS::PropertyParserState&); // MARK: parsing (raw) -RefPtr parseEasingFunctionDeprecated(const String&, const CSSParserContext&); +RefPtr parseEasingFunctionDeprecated(StringView, const CSSParserContext&); } // namespace CSSPropertyParserHelpers } // namespace WebCore diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Filter.cpp b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Filter.cpp index ae7e5f7beb89a..eb03a91f13bbc 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Filter.cpp +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Filter.cpp @@ -455,7 +455,7 @@ RefPtr consumeAppleColorFilter(CSSParserTokenRange& range, CSS::Proper return nullptr; } -std::optional parseFilterValueListOrNoneRaw(const String& string, const CSSParserContext& context, const Document& document, Style::ComputedStyle& style) +std::optional parseFilterValueListOrNoneRaw(StringView string, const CSSParserContext& context, const Document& document, Style::ComputedStyle& style) { auto tokenizer = CSSTokenizer(string); auto range = tokenizer.tokenRange(); diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Filter.h b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Filter.h index 322f647eb6315..438685289ee44 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Filter.h +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Filter.h @@ -64,7 +64,7 @@ std::optional consumeUnresolvedFilter(CSSParserTokenRange&, CSS::Pr std::optional consumeUnresolvedAppleColorFilter(CSSParserTokenRange&, CSS::PropertyParserState&); // MARK: <'filter'> parsing (raw) -std::optional parseFilterValueListOrNoneRaw(const String&, const CSSParserContext&, const Document&, Style::ComputedStyle&); +std::optional parseFilterValueListOrNoneRaw(StringView, const CSSParserContext&, const Document&, Style::ComputedStyle&); } // namespace CSSPropertyParserHelpers } // namespace WebCore diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Font.cpp b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Font.cpp index e3c7a8af09816..fdcd44bd9443f 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Font.cpp +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Font.cpp @@ -455,7 +455,7 @@ static std::optional consumeUnresolvedFont(CSSParserTokenRange& }; } -std::optional parseUnresolvedFont(const String& string, ScriptExecutionContext& context, std::optional parserModeOverride) +std::optional parseUnresolvedFont(StringView string, ScriptExecutionContext& context, std::optional parserModeOverride) { auto parserContext = CSSParserContext(parserModeOverride ? *parserModeOverride : parserMode(context)); auto tokenizer = CSSTokenizer(string); @@ -633,7 +633,7 @@ RefPtr consumeFontFaceSrc(CSSParserTokenRange& range, CSS::Propert return CSSValueList::createCommaSeparated(WTF::move(values)); } -RefPtr parseFontFaceSrc(const String& string, ScriptExecutionContext& context) +RefPtr parseFontFaceSrc(StringView string, ScriptExecutionContext& context) { RefPtr document = dynamicDowncast(context); CSSParserContext parserContext = document ? CSSParserContext(*document) : CSSParserContext(HTMLStandardMode); @@ -655,7 +655,7 @@ RefPtr parseFontFaceSrc(const String& string, ScriptExecutionConte // MARK: @font-face 'size-adjust' -RefPtr parseFontFaceSizeAdjust(const String& string, ScriptExecutionContext& context) +RefPtr parseFontFaceSizeAdjust(StringView string, ScriptExecutionContext& context) { // <'size-adjust'> = // https://www.w3.org/TR/css-fonts-5/#descdef-font-face-size-adjust @@ -677,7 +677,7 @@ RefPtr parseFontFaceSizeAdjust(const String& string, ScriptExecutionCo return parsedValue; } -static RefPtr parseFontFaceMetricOverride(const String& string, ScriptExecutionContext& context, +static RefPtr parseFontFaceMetricOverride(StringView string, ScriptExecutionContext& context, NOESCAPE const Function(CSSParserTokenRange&, CSS::PropertyParserState&)>& consumeMetricOverride) { // = normal | @@ -700,24 +700,24 @@ static RefPtr parseFontFaceMetricOverride(const String& string, Script return parsedValue; } -RefPtr parseFontFaceAscentOverride(const String& string, ScriptExecutionContext& context) +RefPtr parseFontFaceAscentOverride(StringView string, ScriptExecutionContext& context) { return parseFontFaceMetricOverride(string, context, CSSPropertyParsing::consumeFontFaceAscentOverride); } -RefPtr parseFontFaceDescentOverride(const String& string, ScriptExecutionContext& context) +RefPtr parseFontFaceDescentOverride(StringView string, ScriptExecutionContext& context) { return parseFontFaceMetricOverride(string, context, CSSPropertyParsing::consumeFontFaceDescentOverride); } -RefPtr parseFontFaceLineGapOverride(const String& string, ScriptExecutionContext& context) +RefPtr parseFontFaceLineGapOverride(StringView string, ScriptExecutionContext& context) { return parseFontFaceMetricOverride(string, context, CSSPropertyParsing::consumeFontFaceLineGapOverride); } // MARK: @font-face 'unicode-range' -RefPtr parseFontFaceUnicodeRange(const String& string, ScriptExecutionContext& context) +RefPtr parseFontFaceUnicodeRange(StringView string, ScriptExecutionContext& context) { // <'unicode-range'> = # // https://drafts.csswg.org/css-fonts/#descdef-font-face-unicode-range @@ -740,7 +740,7 @@ RefPtr parseFontFaceUnicodeRange(const String& string, ScriptExecu // MARK: @font-face 'font-display' -RefPtr parseFontFaceDisplay(const String& string, ScriptExecutionContext& context) +RefPtr parseFontFaceDisplay(StringView string, ScriptExecutionContext& context) { // <'font-display'> = auto | block | swap | fallback | optional // https://drafts.csswg.org/css-fonts/#descdef-font-face-font-display @@ -763,7 +763,7 @@ RefPtr parseFontFaceDisplay(const String& string, ScriptExecutionConte // MARK: @font-face 'font-style' -RefPtr parseFontFaceFontStyle(const String& string, ScriptExecutionContext& context) +RefPtr parseFontFaceFontStyle(StringView string, ScriptExecutionContext& context) { // <'font-style'> = auto | normal | italic | oblique [ {1,2} ]? // https://drafts.csswg.org/css-fonts/#descdef-font-face-font-style @@ -915,7 +915,7 @@ RefPtr consumeFeatureTagValue(CSSParserTokenRange& range, CSS::Propert return CSSFontFeatureValue::create(WTF::move(*tag), WTF::move(*tagValue)); } -RefPtr parseFontFaceFeatureSettings(const String& string, ScriptExecutionContext& context) +RefPtr parseFontFaceFeatureSettings(StringView string, ScriptExecutionContext& context) { // <'font-feature-settings'> = normal | # // https://drafts.csswg.org/css-fonts/#descdef-font-face-font-feature-settings @@ -961,7 +961,7 @@ RefPtr consumeVariationTagValue(CSSParserTokenRange& range, CSS::Prope // MARK: @font-face 'font-width' -RefPtr parseFontFaceFontWidth(const String& string, ScriptExecutionContext& context) +RefPtr parseFontFaceFontWidth(StringView string, ScriptExecutionContext& context) { // = auto | <'font-width'>{1,2} // https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-width @@ -985,7 +985,7 @@ RefPtr parseFontFaceFontWidth(const String& string, ScriptExecutionCon // MARK: @font-face 'font-weight' -RefPtr parseFontFaceFontWeight(const String& string, ScriptExecutionContext& context) +RefPtr parseFontFaceFontWeight(StringView string, ScriptExecutionContext& context) { // <'font-weight'> = auto | {1,2} // https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-weight diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Font.h b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Font.h index 2073f8c5880a1..ed877a7e3111f 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Font.h +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Font.h @@ -100,7 +100,7 @@ struct UnresolvedFont { // MARK: 'font' (shorthand) // https://drafts.csswg.org/css-fonts-4/#font-prop -std::optional parseUnresolvedFont(const String&, ScriptExecutionContext&, std::optional parserModeOverride = std::nullopt); +std::optional parseUnresolvedFont(StringView, ScriptExecutionContext&, std::optional parserModeOverride = std::nullopt); // MARK: 'font-style' // https://drafts.csswg.org/css-fonts-4/#font-style-prop @@ -125,7 +125,7 @@ RefPtr consumeFontSizeAdjust(CSSParserTokenRange&, CSS::PropertyParser // MARK: @font-face 'src' // https://drafts.csswg.org/css-fonts-4/#src-desc -RefPtr parseFontFaceSrc(const String&, ScriptExecutionContext&); +RefPtr parseFontFaceSrc(StringView, ScriptExecutionContext&); RefPtr consumeFontFaceSrc(CSSParserTokenRange&, CSS::PropertyParserState&); // Sub-production of 'src: // https://drafts.csswg.org/css-fonts-4/#font-tech-values @@ -136,31 +136,31 @@ String consumeFontFormat(CSSParserTokenRange&, CSS::PropertyParserState&, bool r // MARK: @font-face 'size-adjust' // https://drafts.csswg.org/css-fonts-5/#descdef-font-face-size-adjust -RefPtr parseFontFaceSizeAdjust(const String&, ScriptExecutionContext&); +RefPtr parseFontFaceSizeAdjust(StringView, ScriptExecutionContext&); // MARK: @font-face 'unicode-range' // https://drafts.csswg.org/css-fonts-4/#descdef-font-face-unicode-range -RefPtr parseFontFaceUnicodeRange(const String&, ScriptExecutionContext&); +RefPtr parseFontFaceUnicodeRange(StringView, ScriptExecutionContext&); // MARK: @font-face 'font-display' // https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-display -RefPtr parseFontFaceDisplay(const String&, ScriptExecutionContext&); +RefPtr parseFontFaceDisplay(StringView, ScriptExecutionContext&); // MARK: @font-face metric override descriptors // https://drafts.csswg.org/css-fonts-4/#font-metrics-override-desc -RefPtr parseFontFaceAscentOverride(const String&, ScriptExecutionContext&); -RefPtr parseFontFaceDescentOverride(const String&, ScriptExecutionContext&); -RefPtr parseFontFaceLineGapOverride(const String&, ScriptExecutionContext&); +RefPtr parseFontFaceAscentOverride(StringView, ScriptExecutionContext&); +RefPtr parseFontFaceDescentOverride(StringView, ScriptExecutionContext&); +RefPtr parseFontFaceLineGapOverride(StringView, ScriptExecutionContext&); // MARK: @font-face 'font-style' // https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-style -RefPtr parseFontFaceFontStyle(const String&, ScriptExecutionContext&); +RefPtr parseFontFaceFontStyle(StringView, ScriptExecutionContext&); RefPtr consumeFontFaceFontStyle(CSSParserTokenRange&, CSS::PropertyParserState&); std::optional consumeUnresolvedFontFaceFontStyle(CSSParserTokenRange&, CSS::PropertyParserState&); // MARK: @font-face 'font-feature-settings' // https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-feature-settings -RefPtr parseFontFaceFeatureSettings(const String&, ScriptExecutionContext&); +RefPtr parseFontFaceFeatureSettings(StringView, ScriptExecutionContext&); // Sub-production of 'font-feature-settings': // https://drafts.csswg.org/css-fonts-4/#feature-tag-value RefPtr consumeFeatureTagValue(CSSParserTokenRange&, CSS::PropertyParserState&); @@ -174,11 +174,11 @@ RefPtr consumeVariationTagValue(CSSParserTokenRange&, CSS::PropertyPar // MARK: @font-face 'font-width' // https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-width -RefPtr parseFontFaceFontWidth(const String&, ScriptExecutionContext&); +RefPtr parseFontFaceFontWidth(StringView, ScriptExecutionContext&); // MARK: @font-face 'font-weight' // https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-weight -RefPtr parseFontFaceFontWeight(const String&, ScriptExecutionContext&); +RefPtr parseFontFaceFontWeight(StringView, ScriptExecutionContext&); // MARK: - @font-feature-values descriptor consumers diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Timeline.cpp b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Timeline.cpp index 67d438f4e5767..4c20023e39dfc 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Timeline.cpp +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Timeline.cpp @@ -68,7 +68,7 @@ static RefPtr consumeTimelineRangeName(CSSParserTokenRange& range) return nullptr; } -std::optional parseTimelineRangeNameRaw(const String& rangeString) +std::optional parseTimelineRangeNameRaw(StringView rangeString) { if (rangeString == "cover"_s) return Style::SingleAnimationRangeName::Cover; @@ -87,7 +87,7 @@ std::optional parseTimelineRangeNameRaw(const S return std::nullopt; } -std::optional parseTimelineRangeNameOrNormalRaw(const String& rangeString) +std::optional parseTimelineRangeNameOrNormalRaw(StringView rangeString) { if (rangeString == "normal"_s) return Style::SingleAnimationRangeName::Normal; @@ -170,7 +170,7 @@ RefPtr consumeSingleViewTimelineInsetItem(CSSParserTokenRange& range, return startInset; } -std::optional parseAbsoluteSingleViewTimelineInsetItemRaw(const String& string, const CSSParserContext& context, const Document& document) +std::optional parseAbsoluteSingleViewTimelineInsetItemRaw(StringView string, const CSSParserContext& context, const Document& document) { auto tokenizer = CSSTokenizer(string); auto range = tokenizer.tokenRange(); @@ -242,7 +242,7 @@ RefPtr consumeSingleAnimationRangeEnd(CSSParserTokenRange& range, CSS: } template -static std::optional parseAbsoluteSingleAnimationRangeEdgeRaw(const String& string, const CSSParserContext& context, const Document& document) +static std::optional parseAbsoluteSingleAnimationRangeEdgeRaw(StringView string, const CSSParserContext& context, const Document& document) { auto tokenizer = CSSTokenizer(string); auto range = tokenizer.tokenRange(); @@ -269,12 +269,12 @@ static std::optional parseAbsoluteSingleAnimationRangeEdgeRaw(const String& s return Style::toStyleFromCSSValue(*CheckedPtr { dummyState.ptr() }, *parsedValue); } -std::optional parseAbsoluteSingleAnimationRangeStartRaw(const String& string, const CSSParserContext& context, const Document& document) +std::optional parseAbsoluteSingleAnimationRangeStartRaw(StringView string, const CSSParserContext& context, const Document& document) { return parseAbsoluteSingleAnimationRangeEdgeRaw(string, context, document); } -std::optional parseAbsoluteSingleAnimationRangeEndRaw(const String& string, const CSSParserContext& context, const Document& document) +std::optional parseAbsoluteSingleAnimationRangeEndRaw(StringView string, const CSSParserContext& context, const Document& document) { return parseAbsoluteSingleAnimationRangeEdgeRaw(string, context, document); } diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Timeline.h b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Timeline.h index 2dc3ed577c702..000c3445a9a34 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Timeline.h +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Timeline.h @@ -50,8 +50,8 @@ struct ViewTimelineInsetItem; namespace CSSPropertyParserHelpers { bool NODELETE isTimelineRangeName(CSSValueID); -std::optional parseTimelineRangeNameRaw(const String&); -std::optional parseTimelineRangeNameOrNormalRaw(const String&); +std::optional parseTimelineRangeNameRaw(StringView); +std::optional parseTimelineRangeNameOrNormalRaw(StringView); // MARK: - Consumer functions @@ -67,7 +67,7 @@ RefPtr consumeAnimationTimelineView(CSSParserTokenRange&, CSS::Propert // https://drafts.csswg.org/scroll-animations-1/#propdef-view-timeline-inset RefPtr consumeSingleViewTimelineInsetItem(CSSParserTokenRange&, CSS::PropertyParserState&); -std::optional parseAbsoluteSingleViewTimelineInsetItemRaw(const String&, const CSSParserContext&, const Document&); +std::optional parseAbsoluteSingleViewTimelineInsetItemRaw(StringView, const CSSParserContext&, const Document&); // = normal | | ? // https://drafts.csswg.org/scroll-animations-1/#propdef-animation-range-start @@ -75,8 +75,8 @@ RefPtr consumeSingleAnimationRange(CSSParserTokenRange&, CSS::Property RefPtr consumeSingleAnimationRangeStart(CSSParserTokenRange&, CSS::PropertyParserState&); RefPtr consumeSingleAnimationRangeEnd(CSSParserTokenRange&, CSS::PropertyParserState&); -std::optional parseAbsoluteSingleAnimationRangeStartRaw(const String&, const CSSParserContext&, const Document&); -std::optional parseAbsoluteSingleAnimationRangeEndRaw(const String&, const CSSParserContext&, const Document&); +std::optional parseAbsoluteSingleAnimationRangeStartRaw(StringView, const CSSParserContext&, const Document&); +std::optional parseAbsoluteSingleAnimationRangeEndRaw(StringView, const CSSParserContext&, const Document&); } // namespace CSSPropertyParserHelpers } // namespace WebCore diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Transform.cpp b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Transform.cpp index 0ed8aadb6d942..28a193e95c0a1 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Transform.cpp +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Transform.cpp @@ -410,7 +410,7 @@ RefPtr consumeScale(CSSParserTokenRange& range, CSS::PropertyParserSta return CSSValueList::createSpaceSeparated(x.releaseNonNull()); } -std::optional parseTransformRaw(const String& string, const CSSParserContext& context, const Document& document) +std::optional parseTransformRaw(StringView string, const CSSParserContext& context, const Document& document) { auto tokenizer = CSSTokenizer(string); auto range = tokenizer.tokenRange(); diff --git a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Transform.h b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Transform.h index 677d25b5d9f94..76165356ff295 100644 --- a/Source/WebCore/css/parser/CSSPropertyParserConsumer+Transform.h +++ b/Source/WebCore/css/parser/CSSPropertyParserConsumer+Transform.h @@ -61,7 +61,7 @@ RefPtr consumeScale(CSSParserTokenRange&, CSS::PropertyParserState&); RefPtr consumeRotate(CSSParserTokenRange&, CSS::PropertyParserState&); // MARK: <'transform'> parsing (raw) -std::optional parseTransformRaw(const String&, const CSSParserContext&, const Document&); +std::optional parseTransformRaw(StringView, const CSSParserContext&, const Document&); } // namespace CSSPropertyParserHelpers } // namespace WebCore diff --git a/Source/WebCore/css/parser/CSSSelectorParser.cpp b/Source/WebCore/css/parser/CSSSelectorParser.cpp index 5dd761d528469..ae29066bfefb5 100644 --- a/Source/WebCore/css/parser/CSSSelectorParser.cpp +++ b/Source/WebCore/css/parser/CSSSelectorParser.cpp @@ -1472,7 +1472,7 @@ static std::optional NODELETE pseudoElementIdent // FIXME: It's probably worth investigating if more logic can be shared with // CSSSelectorParser::consumePseudo(), though note that the requirements are subtly different. -std::optional CSSSelectorParser::parsePseudoElement(const String& input, const CSSSelectorParserContext& context) +std::optional CSSSelectorParser::parsePseudoElement(StringView input, const CSSSelectorParserContext& context) { auto tokenizer = CSSTokenizer { input }; auto range = tokenizer.tokenRange(); diff --git a/Source/WebCore/css/parser/CSSSelectorParser.h b/Source/WebCore/css/parser/CSSSelectorParser.h index cb91580386f0d..f2db14f7c4a65 100644 --- a/Source/WebCore/css/parser/CSSSelectorParser.h +++ b/Source/WebCore/css/parser/CSSSelectorParser.h @@ -61,7 +61,7 @@ class CSSSelectorParser { static CSSSelectorList resolveNestingParent(const CSSSelectorList& nestedSelectorList, const CSSSelectorList* parentResolvedSelectorList, bool parentRuleIsScope = false); static CSSSelectorList makeHasScopeSelector(const Vector& compoundSelectors); static CSSSelectorList makeHasArgumentWithScope(const CSSSelector& hasArgument, const CSSSelector& scopeSelector); - static std::optional parsePseudoElement(const String&, const CSSSelectorParserContext&); + static std::optional parsePseudoElement(StringView, const CSSSelectorParserContext&); private: template MutableCSSSelectorList consumeSelectorList(CSSParserTokenRange&, ConsumeSelector&&); diff --git a/Source/WebCore/css/parser/CSSSupportsParser.cpp b/Source/WebCore/css/parser/CSSSupportsParser.cpp index 02a9cf80f02d4..b30f25bda14f7 100644 --- a/Source/WebCore/css/parser/CSSSupportsParser.cpp +++ b/Source/WebCore/css/parser/CSSSupportsParser.cpp @@ -60,7 +60,7 @@ CSSSupportsParser::SupportsResult CSSSupportsParser::supportsCondition(CSSParser return supportsParser.consumeSupportsFeatureOrGeneralEnclosed(range); } -CSSSupportsParser::SupportsResult CSSSupportsParser::supportsCondition(const String& condition, const CSSParserContext& context, ParsingMode mode) +CSSSupportsParser::SupportsResult CSSSupportsParser::supportsCondition(StringView condition, const CSSParserContext& context, ParsingMode mode) { CSSParser parser(context, condition); if (!parser.tokenizer()) diff --git a/Source/WebCore/css/parser/CSSSupportsParser.h b/Source/WebCore/css/parser/CSSSupportsParser.h index 299219bf96b80..7983c3d11e74b 100644 --- a/Source/WebCore/css/parser/CSSSupportsParser.h +++ b/Source/WebCore/css/parser/CSSSupportsParser.h @@ -52,7 +52,7 @@ class CSSSupportsParser { }; static SupportsResult supportsCondition(CSSParserTokenRange, CSSParser&, ParsingMode); - static SupportsResult supportsCondition(const String&, const CSSParserContext&, ParsingMode); + static SupportsResult supportsCondition(StringView, const CSSParserContext&, ParsingMode); private: CSSSupportsParser(CSSParser& parser) diff --git a/Source/WebCore/css/parser/SizesAttributeParser.cpp b/Source/WebCore/css/parser/SizesAttributeParser.cpp index 640d2cfdb21f9..95c8e250f3253 100644 --- a/Source/WebCore/css/parser/SizesAttributeParser.cpp +++ b/Source/WebCore/css/parser/SizesAttributeParser.cpp @@ -54,7 +54,7 @@ namespace WebCore { -SizesAttributeParser::SizesAttributeParser(const String& attribute, const Document& document) +SizesAttributeParser::SizesAttributeParser(StringView attribute, const Document& document) : m_document(document) { if (!attribute.isEmpty()) diff --git a/Source/WebCore/css/parser/SizesAttributeParser.h b/Source/WebCore/css/parser/SizesAttributeParser.h index bff84c5cbd245..ab58fd5ba32a7 100644 --- a/Source/WebCore/css/parser/SizesAttributeParser.h +++ b/Source/WebCore/css/parser/SizesAttributeParser.h @@ -44,7 +44,7 @@ struct CSSParserContext; class SizesAttributeParser { public: - SizesAttributeParser(const String&, const Document&); + SizesAttributeParser(StringView attribute LIFETIME_BOUND, const Document&); std::optional effectiveSize(); bool isAuto() const { return m_isAuto; } diff --git a/Source/WebCore/dom/Document.cpp b/Source/WebCore/dom/Document.cpp index 4099202f72edc..149b73bd93bba 100644 --- a/Source/WebCore/dom/Document.cpp +++ b/Source/WebCore/dom/Document.cpp @@ -2978,6 +2978,9 @@ bool Document::needsStyleRecalc() const if (backForwardCacheState() != NotInBackForwardCache) return false; + if (renderTreeState() != RenderTreeState::Built) + return false; + if (m_needsFullStyleRebuild) return true; @@ -3017,6 +3020,13 @@ bool Document::updateStyleIfNeeded() #if ENABLE(CONTENT_CHANGE_OBSERVER) ContentChangeObserver::StyleRecalcScope observingScope(*this); #endif + + if (!renderView()) { + // needsStyleRecalc() is what keeps this true, and resolveStyle() resolves nothing without it. + ASSERT_NOT_REACHED(); + return false; + } + resolveStyle(); updateRenderTreesForDescendantFrames(); diff --git a/Source/WebCore/inspector/InspectorStyleSheet.cpp b/Source/WebCore/inspector/InspectorStyleSheet.cpp index 6e332b78878d1..0a667906ee29f 100644 --- a/Source/WebCore/inspector/InspectorStyleSheet.cpp +++ b/Source/WebCore/inspector/InspectorStyleSheet.cpp @@ -166,15 +166,17 @@ static ASCIILiteral atRuleIdentifierForType(StyleRuleType styleRuleType) static bool isValidRuleHeaderText(const String& headerText, StyleRuleType styleRuleType, Document* document, CSSParserEnum::NestedContext nestedContext = { }) { - auto isValidAtRuleHeaderText = [&] (const String& atRuleIdentifier) { + auto isValidAtRuleHeaderText = [&](const String& atRuleIdentifier) { if (headerText.isEmpty()) return false; + auto parseText = makeString(atRuleIdentifier, ' ', headerText, " {}"_s); + // Make sure the engine can parse the provided `@` rule, even if it only uses unsupported features. As long as // the rule text is entirely consumed and it creates a rule of the expected type, we consider it valid because // we will be able to continue to edit the rule in the future. CSSParserContext context(parserContextForDocument(document)); // CSSParser holds a reference to this. - CSSParser parser(context, makeString(atRuleIdentifier, ' ', headerText, " {}"_s)); + CSSParser parser(context, parseText); if (!parser.tokenizer()) return false; diff --git a/Source/WebCore/layout/formattingContexts/inline/InlineLayoutState.h b/Source/WebCore/layout/formattingContexts/inline/InlineLayoutState.h index 6957ad1d5ecee..76222ffad1b1b 100644 --- a/Source/WebCore/layout/formattingContexts/inline/InlineLayoutState.h +++ b/Source/WebCore/layout/formattingContexts/inline/InlineLayoutState.h @@ -84,6 +84,9 @@ class InlineLayoutState { void setShouldNotSynthesizeInlineBlockBaseline() { m_shouldNotSynthesizeInlineBlockBaseline = true; } bool shouldNotSynthesizeInlineBlockBaseline() const { return m_shouldNotSynthesizeInlineBlockBaseline; } + void setContentMayHaveInkOverflow(bool mayHaveInkOverflow) { m_contentMayHaveInkOverflow |= mayHaveInkOverflow; } + bool contentMayHaveInkOverflow() const { return m_contentMayHaveInkOverflow; } + private: BlockLayoutState& m_parentBlockLayoutState; InlineLayoutUnit m_clearGapBeforeFirstLine { 0.f }; @@ -98,6 +101,7 @@ class InlineLayoutState { HashMap, LayoutUnit> m_nestedListMarkerOffsets; Vector m_excludedMarkerLayoutBounds; AvailableLineWidthOverride m_availableLineWidthOverride; + bool m_contentMayHaveInkOverflow { false }; bool m_shouldNotSynthesizeInlineBlockBaseline { false }; bool m_inStandardsMode { false }; bool m_shouldShapeTextAcrossInlineBoxes { false }; diff --git a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayBox.h b/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayBox.h index dc5ec495ade0a..906dd6552ff19 100644 --- a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayBox.h +++ b/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayBox.h @@ -136,7 +136,9 @@ struct Box { void expandVertically(float delta); void expandHorizontally(float delta); - void adjustInkOverflow(const FloatRect& childBorderBox) { return m_inkOverflow.uniteEvenIfEmpty(childBorderBox); } + void setInkOverflow(const FloatRect& inkOverflow) { m_inkOverflow = inkOverflow; } + FloatBoxExtent glyphOverflow() const { return { static_cast(m_glyphOverflowTop), 0.f, static_cast(m_glyphOverflowBottom), 0.f }; } + void setGlyphOverflow(uint8_t top, uint8_t bottom) { m_glyphOverflowTop = top; m_glyphOverflowBottom = bottom; } void setLeft(float physicalLeft); void setRight(float physicalRight); void setTop(float physicalTop); @@ -194,6 +196,9 @@ struct Box { bool m_isFullyTruncated : 1 { false }; bool m_isInGlyphDisplayListCache : 1 { false }; bool m_isFirstFormattedLine : 1 { false }; + // FIXME: Move this to Box::Text when there's enough bit in there. + uint8_t m_glyphOverflowTop : 5 { 0 }; + uint8_t m_glyphOverflowBottom : 3 { 0 }; Text m_text; }; diff --git a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp b/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp index a616970433969..b61dce3999314 100644 --- a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp +++ b/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.cpp @@ -110,9 +110,7 @@ InlineDisplay::Boxes InlineDisplayContentBuilder::build(const LineLayoutResult& insertRubyAnnotationBoxes(processRubyContent(boxes.mutableSpan(), lineLayoutResult), boxes); - auto newDisplayBoxes = boxes.mutableSpan(); - collectInkOverflowForTextDecorations(newDisplayBoxes); - collectInkOverflowForInlineBoxes(newDisplayBoxes); + formattingContext().layoutState().setContentMayHaveInkOverflow(m_contentHasInkOverflow); return boxes; } @@ -139,35 +137,17 @@ InlineDisplay::Boxes InlineDisplayContentBuilder::buildTextOnlyContent(const Lin } ASSERT_NOT_REACHED(); } - collectInkOverflowForTextDecorations(boxes); + formattingContext().layoutState().setContentMayHaveInkOverflow(m_contentHasInkOverflow); return boxes; } -static inline bool computeInkOverflowForInlineLevelBox(const Style::ComputedStyle& style, FloatRect& inkOverflow) +static inline bool isNestedInlineBoxWithDifferentFontCascadeFromParent(const Box& layoutBox, bool isFirstFormattedLine) { - auto hasInkOverflow = false; - auto zoom = style.usedZoomForLength(); - - auto inflateWithOutline = [&] { - if (!style.hasOutlineInVisualOverflow()) - return; - inkOverflow.inflate(style.usedOutlineSize(zoom, style.deviceScaleFactor())); - hasInkOverflow = true; - }; - inflateWithOutline(); - - auto inflateWithBoxShadow = [&] { - // FIXME: Use `Style::shadowOutsetExtent` to get all 4 extents at once after static cast to `int` in `shadowVerticalExtent` is understood. - auto [topBoxShadow, bottomBoxShadow] = Style::shadowVerticalExtent(style.boxShadow(), zoom); - auto [leftBoxShadow, rightBoxShadow] = Style::shadowHorizontalExtent(style.boxShadow(), zoom); - if (!topBoxShadow && !bottomBoxShadow && !leftBoxShadow && !rightBoxShadow) - return; - inkOverflow.inflate(-leftBoxShadow.toFloat(), -topBoxShadow.toFloat(), rightBoxShadow.toFloat(), bottomBoxShadow.toFloat()); - hasInkOverflow = true; - }; - inflateWithBoxShadow(); - - return hasInkOverflow; + if (!layoutBox.parent().isInlineBox()) + return false; + auto& style = isFirstFormattedLine ? layoutBox.firstLineStyle() : layoutBox.style(); + CheckedRef parentStyle = isFirstFormattedLine ? layoutBox.parent().firstLineStyle() : layoutBox.parent().style(); + return style.fontCascade() != parentStyle->fontCascade(); } static inline bool hasInlineBoxInkOverflow(const InlineLevelBox& inlineBox, const Style::ComputedStyle& style) @@ -176,23 +156,6 @@ static inline bool hasInlineBoxInkOverflow(const InlineLevelBox& inlineBox, cons return style.hasOutlineInVisualOverflow() || !style.boxShadow().isNone() || inlineBox.hasTextEmphasis(); } -static inline void adjustInkOverflowForInlineBox(const Box& layoutBox, const ElementBox& rootBox, const Style::ComputedStyle& style, FloatRect& inkOverflow) -{ - if (style.hasOutlineInVisualOverflow()) - inkOverflow.inflate(style.usedOutlineSize(style.usedZoomForLength(), style.deviceScaleFactor())); - - if (!style.boxShadow().isNone()) { - auto [topBoxShadow, bottomBoxShadow] = Style::shadowVerticalExtent(style.boxShadow(), style.usedZoomForLength()); - auto [leftBoxShadow, rightBoxShadow] = Style::shadowHorizontalExtent(style.boxShadow(), style.usedZoomForLength()); - if (topBoxShadow || bottomBoxShadow || leftBoxShadow || rightBoxShadow) - inkOverflow.inflate(-leftBoxShadow.toFloat(), -topBoxShadow.toFloat(), rightBoxShadow.toFloat(), bottomBoxShadow.toFloat()); - } - - auto [textEmphasisAbove, textEmphasisBelow] = InlineFormattingUtils::textEmphasisForInlineBox(layoutBox, rootBox); - if (textEmphasisAbove || textEmphasisBelow) - inkOverflow.inflate(0.f, textEmphasisAbove, 0.f, textEmphasisBelow); -} - void InlineDisplayContentBuilder::appendTextDisplayBox(const Line::Run& lineRun, const InlineRect& textRunRect, InlineDisplay::Boxes& boxes) { ASSERT(lineRun.isText() && is(lineRun.layoutBox())); @@ -203,60 +166,27 @@ void InlineDisplayContentBuilder::appendTextDisplayBox(const Line::Run& lineRun, auto& text = lineRun.textContent(); auto isContentful = true; - m_hasSeenTextDecoration = m_hasSeenTextDecoration || (isFirstFormattedLine() ? inlineTextBox->parent().firstLineStyle().textDecorationLineInEffect() : inlineTextBox->parent().style().textDecorationLineInEffect()); - - auto inkOverflow = [&] { - auto inkOverflow = textRunRect; - - auto addLetterSpacingOverflow = [&] { - auto letterSpacing = style.fontCascade().letterSpacing(); - if (letterSpacing >= 0) - return; - // Large negative letter spacing can produce text boxes with negative width (when glyphs position order gets completely backwards (123 turns into 321 starting at an offset)) - // Such spacing should go to ink overflow. - auto textRunWidth = textRunRect.width(); - if (textRunWidth < 0) { - inkOverflow.setWidth({ }); - inkOverflow.shiftLeftTo(textRunWidth); - } - // Last letter's negative spacing shrinks logical rect. Push it to ink overflow. - inkOverflow.expand(-letterSpacing, { }); - }; - addLetterSpacingOverflow(); - - auto addStrokeOverflow = [&] { - inkOverflow.inflate(ceilf(style.usedStrokeWidth(m_initialContainingBlockSize))); - }; - addStrokeOverflow(); - - auto addTextShadow = [&] { - auto textShadow = Style::shadowOutsetExtent(style.textShadow(), style.usedZoomForLength()); - inkOverflow.inflate(-textShadow.top(), textShadow.right(), textShadow.bottom(), -textShadow.left()); - }; - addTextShadow(); - - auto addGlyphOverflow = [&] { - auto glyphOverflow = lineRun.glyphOverflow(); - if (glyphOverflow.isEmpty()) - return; - - // Maxed-out glyph overflow values indicate arithmetic overflow. Fallback to collecting overflow post-measure. - constexpr size_t maximumAscent = 31; - constexpr size_t maximumDescent = 7; - if (glyphOverflow.top == maximumAscent || glyphOverflow.bottom == maximumDescent) { - auto enclosingAscentAndDescent = TextUtil::enclosingGlyphBoundsForText(StringView(content).substring(text.start, text.length), style, inlineTextBox->shouldUseSimpleGlyphOverflowCodePath() ? TextUtil::ShouldUseSimpleGlyphOverflowCodePath::Yes : TextUtil::ShouldUseSimpleGlyphOverflowCodePath::No); - auto& fontMetrics = style.metricsOfPrimaryFont(); - glyphOverflow.top = std::max(0.f, -enclosingAscentAndDescent.ascent - fontMetrics.ascent(FontBaseline::Alphabetic)); - glyphOverflow.bottom = std::max(0.f, enclosingAscentAndDescent.descent - fontMetrics.descent(FontBaseline::Alphabetic)); - } - inkOverflow.inflate(glyphOverflow.top, { }, glyphOverflow.bottom, { }); - }; - addGlyphOverflow(); - - return inkOverflow; + auto& textStyle = isFirstFormattedLine() ? inlineTextBox->parent().firstLineStyle() : inlineTextBox->parent().style(); + m_contentHasInkOverflow = m_contentHasInkOverflow || textStyle.textDecorationLineInEffect() || !Style::shadowOutsetExtent(textStyle.textShadow(), textStyle.usedZoomForLength()).isZero() || textStyle.hasPositiveStrokeWidth() || style.fontCascade().letterSpacing() < 0 || !lineRun.glyphOverflow().isEmpty(); + + auto glyphOverflow = [&] { + auto glyphOverflow = lineRun.glyphOverflow(); + if (glyphOverflow.isEmpty()) + return glyphOverflow; + + // Maxed-out glyph overflow values indicate arithmetic overflow. Fallback to collecting overflow post-measure. + constexpr size_t maximumAscent = 31; + constexpr size_t maximumDescent = 7; + if (glyphOverflow.top == maximumAscent || glyphOverflow.bottom == maximumDescent) { + auto enclosingAscentAndDescent = TextUtil::enclosingGlyphBoundsForText(StringView(content).substring(text.start, text.length), style, inlineTextBox->shouldUseSimpleGlyphOverflowCodePath() ? TextUtil::ShouldUseSimpleGlyphOverflowCodePath::Yes : TextUtil::ShouldUseSimpleGlyphOverflowCodePath::No); + auto& fontMetrics = style.metricsOfPrimaryFont(); + glyphOverflow.top = std::max(0.f, -enclosingAscentAndDescent.ascent - fontMetrics.ascent(FontBaseline::Alphabetic)); + glyphOverflow.bottom = std::max(0.f, enclosingAscentAndDescent.descent - fontMetrics.descent(FontBaseline::Alphabetic)); + } + return glyphOverflow; }(); - m_contentHasInkOverflow = m_contentHasInkOverflow || (&inlineTextBox->parent() != &root() && textRunRect != inkOverflow); + auto inkOverflow = textRunRect; if (inlineTextBox->isCombined()) { static auto objectReplacementCharacterString = NeverDestroyed { span(objectReplacementCharacter) }; @@ -273,6 +203,7 @@ void InlineDisplayContentBuilder::appendTextDisplayBox(const Line::Run& lineRun, , isContentful , isLineFullyTruncatedInBlockDirection() }); + boxes.last().setGlyphOverflow(glyphOverflow.top, glyphOverflow.bottom); return; } @@ -300,6 +231,7 @@ void InlineDisplayContentBuilder::appendTextDisplayBox(const Line::Run& lineRun, , isContentful , isLineFullyTruncatedInBlockDirection() }); + boxes.last().setGlyphOverflow(glyphOverflow.top, glyphOverflow.bottom); } void InlineDisplayContentBuilder::appendSoftLineBreakDisplayBox(const Line::Run& lineRun, const InlineRect& softLineBreakRunRect, InlineDisplay::Boxes& boxes) const @@ -347,21 +279,15 @@ void InlineDisplayContentBuilder::appendAtomicInlineLevelDisplayBox(const Line:: CheckedRef layoutBox = lineRun.layoutBox(); auto isContentful = true; - auto inkOverflow = [&] { - auto inkOverflow = FloatRect { borderBoxRect }; - CheckedRef style = isFirstFormattedLine() ? layoutBox->firstLineStyle() : layoutBox->style(); - computeInkOverflowForInlineLevelBox(style, inkOverflow); - // Atomic inline box contribute to their inline box parents ink overflow at all times (e.g. ). - m_contentHasInkOverflow = m_contentHasInkOverflow || &layoutBox->parent() != &root(); - return inkOverflow; - }; + // Atomic inline box contribute to their inline box parents ink overflow at all times (e.g. ). + m_contentHasInkOverflow = m_contentHasInkOverflow || &layoutBox->parent() != &root(); boxes.append({ lineIndex() , InlineDisplay::Box::Type::AtomicInlineBox , layoutBox , lineRun.bidiLevel() , borderBoxRect - , inkOverflow() + , borderBoxRect , isFirstFormattedLine() , lineRun.expansion() , { } @@ -406,15 +332,6 @@ void InlineDisplayContentBuilder::appendRootInlineBoxDisplayBox(const InlineRect }); } -static inline bool isNestedInlineBoxWithDifferentFontCascadeFromParent(const Box& layoutBox, bool isFirstFormattedLine) -{ - if (!layoutBox.parent().isInlineBox()) - return false; - auto& style = isFirstFormattedLine ? layoutBox.firstLineStyle() : layoutBox.style(); - CheckedRef parentStyle = isFirstFormattedLine ? layoutBox.parent().firstLineStyle() : layoutBox.parent().style(); - return style.fontCascade() != parentStyle->fontCascade(); -} - void InlineDisplayContentBuilder::appendInlineBoxDisplayBox(const Line::Run& lineRun, const InlineLevelBox& inlineBox, const InlineRect& inlineBoxBorderBox, InlineDisplay::Boxes& boxes) { ASSERT(lineRun.layoutBox().isInlineBox()); @@ -423,21 +340,16 @@ void InlineDisplayContentBuilder::appendInlineBoxDisplayBox(const Line::Run& lin CheckedRef layoutBox = lineRun.layoutBox(); m_hasSeenRubyBase = m_hasSeenRubyBase || layoutBox->isRubyBase(); - m_hasSeenNestedInlineBoxesWithDifferentFontCascade = m_hasSeenNestedInlineBoxesWithDifferentFontCascade || isNestedInlineBoxWithDifferentFontCascadeFromParent(layoutBox, isFirstFormattedLine()); - auto inkOverflow = [&] { - CheckedRef style = isFirstFormattedLine() ? layoutBox->firstLineStyle() : layoutBox->style(); - auto inkOverflow = FloatRect { inlineBoxBorderBox }; - m_contentHasInkOverflow |= hasInlineBoxInkOverflow(inlineBox, style); - return inkOverflow; - }; + m_contentHasInkOverflow |= hasInlineBoxInkOverflow(inlineBox, isFirstFormattedLine() ? layoutBox->firstLineStyle() : layoutBox->style()) + || isNestedInlineBoxWithDifferentFontCascadeFromParent(layoutBox, isFirstFormattedLine()); boxes.append({ lineIndex() , InlineDisplay::Box::Type::NonRootInlineBox , layoutBox , lineRun.bidiLevel() , inlineBoxBorderBox - , inkOverflow() + , inlineBoxBorderBox , isFirstFormattedLine() , { } , { } @@ -809,12 +721,8 @@ void InlineDisplayContentBuilder::adjustVisualGeometryForDisplayBox(size_t displ auto* inlineBox = lineBox().inlineLevelBoxFor(layoutBox); ASSERT(inlineBox); - auto computeInkOverflow = [&] { - auto inkOverflow = FloatRect { displayBox.visualRectIgnoringBlockDirection() }; - m_contentHasInkOverflow |= hasInlineBoxInkOverflow(*inlineBox, isFirstFormattedLine() ? layoutBox->firstLineStyle() : layoutBox->style()); - displayBox.adjustInkOverflow(inkOverflow); - }; - computeInkOverflow(); + m_contentHasInkOverflow |= hasInlineBoxInkOverflow(*inlineBox, isFirstFormattedLine() ? layoutBox->firstLineStyle() : layoutBox->style()) + || isNestedInlineBoxWithDifferentFontCascadeFromParent(layoutBox, isFirstFormattedLine()); auto inlineBoxLeftInInlineDirectionVisualOrder = isHorizontalWritingMode ? displayBox.left() : displayBox.top(); auto logicalWidthForBiDiFragment = isHorizontalWritingMode ? displayBox.width() : displayBox.height(); @@ -1088,37 +996,6 @@ void InlineDisplayContentBuilder::processBidiContent(const LineLayoutResult& lin closeInlineBoxes(); } -void InlineDisplayContentBuilder::collectInkOverflowForInlineBoxes(std::span boxes) -{ - if (!m_contentHasInkOverflow && !m_hasSeenNestedInlineBoxesWithDifferentFontCascade) - return; - // Visit the inline boxes and propagate ink overflow to their parents -except to the root inline box. - // (e.g. Small font sizeLarger font size. This overflows the top most span.). - auto accumulatedInkOverflowRect = InlineRect { { }, { } }; - for (auto& displayBox : boxes | std::views::reverse) { - auto mayHaveInkOverflow = displayBox.isText() || displayBox.isAtomicInlineBox() || displayBox.isGenericInlineLevelBox() || displayBox.isNonRootInlineBox(); - if (!mayHaveInkOverflow) - continue; - if (displayBox.isNonRootInlineBox()) { - if (!accumulatedInkOverflowRect.isEmpty()) - displayBox.adjustInkOverflow(accumulatedInkOverflowRect); - auto& layoutBox = displayBox.layoutBox(); - auto inkOverflowRect = displayBox.inkOverflow(); - adjustInkOverflowForInlineBox(layoutBox, root(), layoutBox.style(), inkOverflowRect); - displayBox.adjustInkOverflow(inkOverflowRect); - } - - // We stop collecting ink overflow for at root inline box (i.e. don't inflate the root inline box with the inline content here). - auto parentBoxIsRoot = &displayBox.layoutBox().parent() == &root(); - if (parentBoxIsRoot) - accumulatedInkOverflowRect = InlineRect { { }, { } }; - else if (accumulatedInkOverflowRect.isEmpty()) - accumulatedInkOverflowRect = displayBox.inkOverflow(); - else - accumulatedInkOverflowRect.expandToContain(displayBox.inkOverflow()); - } -} - static inline size_t NODELETE runIndex(auto i, auto listSize, auto isBidiLTR) { if (isBidiLTR) @@ -1192,93 +1069,6 @@ void InlineDisplayContentBuilder::setGeometryForBlockLevelOutOfFlowBoxes(const V setGeometryForOutOfFlowBoxes(indexListOfOutOfFlowBoxes, firstOutOfFlowIndexWithPreviousInflowSibling, lineRuns, visualOrderList, formattingContext, lineBox(), constraints()); } -static float logicalBottomForTextDecorationContent(std::span boxes, bool isHorizontalWritingMode) -{ - auto logicalBottom = std::optional { }; - for (auto& displayBox : boxes) { - if (displayBox.isRootInlineBox()) - continue; - if (!displayBox.style().textDecorationLineInEffect().hasUnderline()) - continue; - if (displayBox.isText() || displayBox.style().textDecorationSkipInk() == TextDecorationSkipInk::None) { - auto contentLogicalBottom = isHorizontalWritingMode ? displayBox.bottom() : displayBox.right(); - logicalBottom = logicalBottom ? std::max(*logicalBottom, contentLogicalBottom) : contentLogicalBottom; - } - } - // This function is not called unless there's at least one run on the line with Style::TextDecorationLine::Flag::Underline. - ASSERT(logicalBottom); - return logicalBottom.value_or(0.f); -} - -void InlineDisplayContentBuilder::collectInkOverflowForTextDecorations(std::span boxes) -{ - if (!m_hasSeenTextDecoration) - return; - - auto logicalBottomForTextDecoration = std::optional { }; - auto writingMode = root().writingMode(); - auto isHorizontalWritingMode = writingMode.isHorizontal(); - - for (auto& displayBox : boxes) { - if (!displayBox.isText()) - continue; - - // Note that decoration properties are not inherited but propagated - auto decorationOverflow = [&] { - auto overflowForDecoratingBox = [&](auto& decoratingBoxStyle) { - if (!decoratingBoxStyle.textDecorationLineInEffect().hasUnderline()) - return inkOverflowForDecorations(decoratingBoxStyle); - - if (!logicalBottomForTextDecoration) - logicalBottomForTextDecoration = logicalBottomForTextDecorationContent(boxes, isHorizontalWritingMode); - auto textRunLogicalOffsetFromLineBottom = *logicalBottomForTextDecoration - (isHorizontalWritingMode ? displayBox.bottom() : displayBox.right()); - auto textRunLogicalHeight = isHorizontalWritingMode ? displayBox.height() : displayBox.width(); - return inkOverflowForDecorations(decoratingBoxStyle, { textRunLogicalHeight, textRunLogicalOffsetFromLineBottom }); - }; - - // Several ancestors may each decorate this text box, which then has to accommodate whichever of them overflows it the most on each side. - auto maximumOverflow = InkOverflowForDecorations { }; - for (CheckedPtr box = &displayBox.layoutBox().parent(); box; box = &box->parent()) { - CheckedRef style = isFirstFormattedLine() ? box->firstLineStyle() : box->style(); - if (style->textDecorationLine()) { - auto overflow = overflowForDecoratingBox(style.get()); - maximumOverflow.top() = std::max(maximumOverflow.top(), overflow.top()); - maximumOverflow.right() = std::max(maximumOverflow.right(), overflow.right()); - maximumOverflow.bottom() = std::max(maximumOverflow.bottom(), overflow.bottom()); - maximumOverflow.left() = std::max(maximumOverflow.left(), overflow.left()); - } - if (box == &root()) - break; - } - return maximumOverflow; - }(); - - if (!decorationOverflow.isZero()) { - m_contentHasInkOverflow = true; - auto inflatedInkOverflowRect = [&] { - auto inkOverflowRect = displayBox.inkOverflow(); - switch (writingMode.blockDirection()) { - case FlowDirection::TopToBottom: - case FlowDirection::BottomToTop: - inkOverflowRect.inflate(decorationOverflow.left(), decorationOverflow.top(), decorationOverflow.right(), decorationOverflow.bottom()); - break; - case FlowDirection::LeftToRight: - inkOverflowRect.inflate(decorationOverflow.bottom(), decorationOverflow.right(), decorationOverflow.top(), decorationOverflow.left()); - break; - case FlowDirection::RightToLeft: - inkOverflowRect.inflate(decorationOverflow.top(), decorationOverflow.right(), decorationOverflow.bottom(), decorationOverflow.left()); - break; - default: - ASSERT_NOT_REACHED(); - break; - } - return inkOverflowRect; - }; - displayBox.adjustInkOverflow(inflatedInkOverflowRect()); - } - } -} - size_t InlineDisplayContentBuilder::processRubyBase(size_t rubyBaseStart, std::span displayBoxes, Vector>& interlinearRubyColumnRangeList, Vector& rubyBaseStartIndexListWithAnnotation) { auto& formattingContext = this->formattingContext(); diff --git a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.h b/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.h index b1f4bbb9709f4..738247961b720 100644 --- a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.h +++ b/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayContentBuilder.h @@ -50,8 +50,7 @@ class InlineDisplayContentBuilder { void processNonBidiContent(const LineLayoutResult&, InlineDisplay::Boxes&); void processBidiContent(const LineLayoutResult&, InlineDisplay::Boxes&); bool processBidiLinesWithNoContent(const LineLayoutResult&, InlineDisplay::Boxes&); - void collectInkOverflowForInlineBoxes(std::span); - void collectInkOverflowForTextDecorations(std::span); + void truncateForEllipsisPolicy(LineEndingTruncationPolicy, const LineLayoutResult&, InlineDisplay::Boxes&); void appendTextDisplayBox(const Line::Run&, const InlineRect&, InlineDisplay::Boxes&); @@ -102,8 +101,6 @@ class InlineDisplayContentBuilder { bool m_lineIsFullyTruncatedInBlockDirection { false }; bool m_contentHasInkOverflow { false }; bool m_hasSeenRubyBase { false }; - bool m_hasSeenTextDecoration { false }; - bool m_hasSeenNestedInlineBoxesWithDifferentFontCascade { false }; }; inline InlineRect InlineDisplayContentBuilder::mapInlineRectLogicalToVisual(const InlineRect& logicalRect, const InlineRect& containerLogicalRect, WritingMode writingMode) diff --git a/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContent.h b/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContent.h index 2ab03f85b5658..8eedb884e5ac2 100644 --- a/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContent.h +++ b/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContent.h @@ -117,6 +117,8 @@ class InlineContent : public CanMakeWeakPtr { void setClearGapAfterLastLine(float clearGapAfterLastLine) { m_clearGapAfterLastLine = clearGapAfterLastLine; } void setFirstLinePaginationOffset(float firstLinePaginationOffset) { m_firstLinePaginationOffset = firstLinePaginationOffset; } void setHasBlockLevelBoxes() { m_hasBlockLevelBoxes = true; } + void setContentMayHaveInkOverflow(bool mayHaveInkOverflow) { m_contentMayHaveInkOverflow = mayHaveInkOverflow; } + bool contentMayHaveInkOverflow() const { return m_contentMayHaveInkOverflow; } void setHasPaintedInlineLevelBoxes() { m_hasPaintedInlineLevelBoxes = true; } const Vector& nonRootInlineBoxIndexesForLayoutBox(const Layout::Box&) const LIFETIME_BOUND; @@ -137,6 +139,7 @@ class InlineContent : public CanMakeWeakPtr { bool m_hasMultilinePaintOverlap { false }; bool m_hasBlockLevelBoxes { false }; + bool m_contentMayHaveInkOverflow { false }; bool m_hasPaintedInlineLevelBoxes { false }; Vector> m_svgTextFragmentsForBoxes; diff --git a/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.cpp b/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.cpp index 8b8501677f5a0..90ffa71037044 100644 --- a/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.cpp +++ b/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.cpp @@ -30,13 +30,17 @@ #include "FontInlines.h" #include "InlineDamage.h" #include "InlineDisplayBoxInlines.h" +#include "InlineFormattingUtils.h" +#include "InlineTextBoxStyle.h" #include "LayoutBoxGeometry.h" #include "LayoutIntegrationInlineContent.h" #include "LayoutState.h" #include "RenderBlockFlowInlines.h" #include "RenderBoxInlines.h" +#include "RenderView.h" #include "StringTruncator.h" #include "StyleComputedStyle+GettersInlines.h" +#include namespace WebCore { namespace LayoutIntegration { @@ -113,7 +117,7 @@ FloatRect InlineContentBuilder::build(std::unique_ptr boxes, bool isHorizontalWritingMode) +{ + auto logicalBottom = std::optional { }; + for (auto& displayBox : boxes) { + if (displayBox.isRootInlineBox()) + continue; + CheckedRef style = displayBox.style(); + if (!style->textDecorationLineInEffect().hasUnderline()) + continue; + if (displayBox.isText() || style->textDecorationSkipInk() == TextDecorationSkipInk::None) { + auto contentLogicalBottom = isHorizontalWritingMode ? displayBox.bottom() : displayBox.right(); + logicalBottom = logicalBottom ? std::max(*logicalBottom, contentLogicalBottom) : contentLogicalBottom; + } + } + // This function is not called unless there's at least one run on the line with Style::TextDecorationLine::Flag::Underline. + ASSERT(logicalBottom); + return logicalBottom.value_or(0.f); +} + +static FloatBoxExtent inkOverflowOutsetsForTextDecorations(const InlineDisplay::Box& displayBox, const Layout::ElementBox& root, std::span boxesOnLine, WritingMode writingMode, std::optional& logicalBottomForTextDecoration) +{ + ASSERT(displayBox.isText()); + + auto isHorizontalWritingMode = writingMode.isHorizontal(); + auto overflowForDecoratingBox = [&](auto& decoratingBoxStyle) { + if (!decoratingBoxStyle.textDecorationLineInEffect().hasUnderline()) + return inkOverflowForDecorations(decoratingBoxStyle); + + if (!logicalBottomForTextDecoration) + logicalBottomForTextDecoration = logicalBottomForTextDecorationContent(boxesOnLine, isHorizontalWritingMode); + auto textRunLogicalOffsetFromLineBottom = *logicalBottomForTextDecoration - (isHorizontalWritingMode ? displayBox.bottom() : displayBox.right()); + auto textRunLogicalHeight = isHorizontalWritingMode ? displayBox.height() : displayBox.width(); + return inkOverflowForDecorations(decoratingBoxStyle, { textRunLogicalHeight, textRunLogicalOffsetFromLineBottom }); + }; + + // Note that decoration properties are not inherited but propagated, and several ancestors may each decorate + // this text box, which then has to accommodate whichever of them overflows it the most on each side. + auto decorationOverflow = InkOverflowForDecorations { }; + for (CheckedPtr box = &displayBox.layoutBox().parent(); box; box = &box->parent()) { + CheckedRef style = displayBox.isFirstFormattedLine() ? box->firstLineStyle() : box->style(); + if (style->textDecorationLine()) { + auto overflow = overflowForDecoratingBox(style.get()); + decorationOverflow.top() = std::max(decorationOverflow.top(), overflow.top()); + decorationOverflow.right() = std::max(decorationOverflow.right(), overflow.right()); + decorationOverflow.bottom() = std::max(decorationOverflow.bottom(), overflow.bottom()); + decorationOverflow.left() = std::max(decorationOverflow.left(), overflow.left()); + } + if (box == &root) + break; + } + + if (decorationOverflow.isZero()) + return { }; + + switch (writingMode.blockDirection()) { + case FlowDirection::TopToBottom: + case FlowDirection::BottomToTop: + return { decorationOverflow.top(), decorationOverflow.right(), decorationOverflow.bottom(), decorationOverflow.left() }; + case FlowDirection::LeftToRight: + return { decorationOverflow.right(), decorationOverflow.top(), decorationOverflow.left(), decorationOverflow.bottom() }; + case FlowDirection::RightToLeft: + return { decorationOverflow.right(), decorationOverflow.bottom(), decorationOverflow.left(), decorationOverflow.top() }; + default: + ASSERT_NOT_REACHED(); + return { }; + } +} + +void InlineContentBuilder::updateOverflow(InlineContent& inlineContent, size_t startIndex) const +{ + updateInkOverflowForBoxes(inlineContent, startIndex); + computeOverflowFromBoxes(inlineContent, startIndex); +} + +void InlineContentBuilder::updateInkOverflowForBoxes(InlineContent& inlineContent, size_t startIndex) const +{ + if (!inlineContent.contentMayHaveInkOverflow()) + return; + + // Note that display lines don't know their box range yet (computeOverflowFromBoxes assigns it), so + // group the boxes by the line index they carry. + auto& displayContent = inlineContent.displayContent(); + auto boxes = displayContent.boxes.mutableSpan(); + CheckedRef root = downcast(*m_blockFlow.layoutBox()); + auto initialContainingBlockSize = ceiledIntSize(LayoutSize { m_blockFlow.view().contentBoxWidth(), m_blockFlow.view().contentBoxHeight() }); + + size_t firstBoxIndex = 0; + while (firstBoxIndex < boxes.size() && boxes[firstBoxIndex].lineIndex() < startIndex) + ++firstBoxIndex; + + while (firstBoxIndex < boxes.size()) { + auto lineIndex = boxes[firstBoxIndex].lineIndex(); + auto boxCount = size_t { 0 }; + while (firstBoxIndex + boxCount < boxes.size() && boxes[firstBoxIndex + boxCount].lineIndex() == lineIndex) + ++boxCount; + + auto boxesOnLine = boxes.subspan(firstBoxIndex, boxCount); + updateInkOverflowForText(boxesOnLine, root, initialContainingBlockSize); + updateInkOverflowForInlineBoxes(boxesOnLine, root); + + firstBoxIndex += boxCount; + } +} + +void InlineContentBuilder::updateInkOverflowForText(std::span boxes, const Layout::ElementBox& root, const IntSize& initialContainingBlockSize) +{ + auto logicalBottomForTextDecoration = std::optional { }; + auto writingMode = root.writingMode(); + + for (auto& displayBox : boxes) { + if (!displayBox.isText()) + continue; + + CheckedRef textStyle = displayBox.style(); + + auto textRunRect = displayBox.visualRectIgnoringBlockDirection(); + auto inkOverflow = textRunRect; + + auto letterSpacing = textStyle->fontCascade().letterSpacing(); + if (letterSpacing < 0) { + // Large negative letter spacing can produce text boxes with negative width (when glyphs position order gets completely backwards (123 turns into 321 starting at an offset)) + // Such spacing should go to ink overflow. + if (textRunRect.width() < 0) { + inkOverflow.setWidth({ }); + inkOverflow.shiftXEdgeTo(textRunRect.width()); + } + // Last letter's negative spacing shrinks logical rect. Push it to ink overflow. + inkOverflow.expand(-letterSpacing, { }); + } + + auto glyphOverflow = displayBox.glyphOverflow(); + inkOverflow.inflate(0.f, glyphOverflow.top(), 0.f, glyphOverflow.bottom()); + + auto outsets = strokeAndTextShadowInkOverflowOutsets(textStyle, initialContainingBlockSize); + inkOverflow.inflate(outsets.left(), outsets.top(), outsets.right(), outsets.bottom()); + + auto decorations = inkOverflowOutsetsForTextDecorations(displayBox, root, boxes, writingMode, logicalBottomForTextDecoration); + inkOverflow.inflate(decorations.left(), decorations.top(), decorations.right(), decorations.bottom()); + + displayBox.setInkOverflow(inkOverflow); + } +} + +void InlineContentBuilder::updateInkOverflowForInlineBoxes(std::span boxes, const Layout::ElementBox& root) +{ + auto accumulatedInkOverflowRect = Layout::InlineRect { { }, { } }; + for (auto& displayBox : boxes | std::views::reverse) { + auto mayHaveInkOverflow = displayBox.isText() || displayBox.isAtomicInlineBox() || displayBox.isGenericInlineLevelBox() || displayBox.isNonRootInlineBox(); + if (!mayHaveInkOverflow) + continue; + // Note that an atomic inline level box brings its own ink overflow with it (computed while building + // the display content, from its renderer), so it only contributes to its parents here. + if (displayBox.isAtomicInlineBox() || displayBox.isGenericInlineLevelBox()) { + CheckedRef style = displayBox.style(); + auto inkOverflowRect = FloatRect { displayBox.visualRectIgnoringBlockDirection() }; + + if (style->hasOutlineInVisualOverflow()) + inkOverflowRect.inflate(style->usedOutlineSize(style->usedZoomForLength(), style->deviceScaleFactor())); + + if (!style->boxShadow().isNone()) { + auto [topBoxShadow, bottomBoxShadow] = Style::shadowVerticalExtent(style->boxShadow(), style->usedZoomForLength()); + auto [leftBoxShadow, rightBoxShadow] = Style::shadowHorizontalExtent(style->boxShadow(), style->usedZoomForLength()); + if (topBoxShadow || bottomBoxShadow || leftBoxShadow || rightBoxShadow) + inkOverflowRect.inflate(-leftBoxShadow.toFloat(), -topBoxShadow.toFloat(), rightBoxShadow.toFloat(), bottomBoxShadow.toFloat()); + } + + displayBox.setInkOverflow(inkOverflowRect); + } + + if (displayBox.isNonRootInlineBox()) { + CheckedRef layoutBox = displayBox.layoutBox(); + CheckedRef style = layoutBox->style(); + auto inkOverflowRect = FloatRect { displayBox.visualRectIgnoringBlockDirection() }; + // Fold the children in before the box's own decorations: an outline or a shadow goes around the + // whole thing, not just around the box's own rect. + if (!accumulatedInkOverflowRect.isEmpty()) + inkOverflowRect.uniteEvenIfEmpty(accumulatedInkOverflowRect); + + if (style->hasOutlineInVisualOverflow()) + inkOverflowRect.inflate(style->usedOutlineSize(style->usedZoomForLength(), style->deviceScaleFactor())); + + if (!style->boxShadow().isNone()) { + auto [topBoxShadow, bottomBoxShadow] = Style::shadowVerticalExtent(style->boxShadow(), style->usedZoomForLength()); + auto [leftBoxShadow, rightBoxShadow] = Style::shadowHorizontalExtent(style->boxShadow(), style->usedZoomForLength()); + if (topBoxShadow || bottomBoxShadow || leftBoxShadow || rightBoxShadow) + inkOverflowRect.inflate(-leftBoxShadow.toFloat(), -topBoxShadow.toFloat(), rightBoxShadow.toFloat(), bottomBoxShadow.toFloat()); + } + + auto [textEmphasisAbove, textEmphasisBelow] = Layout::InlineFormattingUtils::textEmphasisForInlineBox(layoutBox, root); + if (textEmphasisAbove || textEmphasisBelow) + inkOverflowRect.inflate(0.f, textEmphasisAbove, 0.f, textEmphasisBelow); + + displayBox.setInkOverflow(inkOverflowRect); + } + + auto parentBoxIsRoot = &displayBox.layoutBox().parent() == &root; + if (parentBoxIsRoot) + accumulatedInkOverflowRect = Layout::InlineRect { { }, { } }; + else if (accumulatedInkOverflowRect.isEmpty()) + accumulatedInkOverflowRect = displayBox.inkOverflow(); + else + accumulatedInkOverflowRect.expandToContain(displayBox.inkOverflow()); + } +} + void InlineContentBuilder::updateLineOverflow(InlineContent& inlineContent) const { - adjustDisplayLines(inlineContent, 0); + computeOverflowFromBoxes(inlineContent, 0); } -void InlineContentBuilder::adjustDisplayLines(InlineContent& inlineContent, size_t startIndex) const +void InlineContentBuilder::computeOverflowFromBoxes(InlineContent& inlineContent, size_t startIndex) const { auto& lines = inlineContent.displayContent().lines; auto& boxes = inlineContent.displayContent().boxes; @@ -492,7 +708,7 @@ FloatRect InlineContentBuilder::handlePartialDisplayContentUpdate(Layout::Inline ASSERT_NOT_REACHED(); } - adjustDisplayLines(inlineContent, *firstDamagedLineIndex); + updateOverflow(inlineContent, *firstDamagedLineIndex); // Repaint the new content boundary. adjustDamagedRectWithLineRange(*firstDamagedLineIndex, numberOfNewLines); diff --git a/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.h b/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.h index 6ea7e031e53ee..1a494cc6229dd 100644 --- a/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.h +++ b/Source/WebCore/layout/integration/inline/LayoutIntegrationInlineContentBuilder.h @@ -45,7 +45,14 @@ class InlineContentBuilder { void updateLineOverflow(InlineContent&) const; private: - void adjustDisplayLines(InlineContent&, size_t startIndex) const; + // Recomputes the style driven part of ink overflow on the display boxes from startIndex on, then + // recomputes line and block level overflow from them. + void updateOverflow(InlineContent&, size_t startIndex) const; + void updateInkOverflowForBoxes(InlineContent&, size_t startIndex) const; + static void updateInkOverflowForText(std::span, const Layout::ElementBox& root, const IntSize& initialContainingBlockSize); + static void updateInkOverflowForInlineBoxes(std::span, const Layout::ElementBox& root); + + void computeOverflowFromBoxes(InlineContent&, size_t startIndex) const; using DecoratingBoxes = HashSet>; void adjustInkOverflowForPercentageTextDecorationInsets(InlineContent&, size_t startIndex, const DecoratingBoxes&) const; void computeIsFirstIsLastBoxAndBidiReorderingForInlineContent(InlineDisplay::Boxes&) const; diff --git a/Source/WebCore/layout/integration/inline/LayoutIntegrationLineLayout.cpp b/Source/WebCore/layout/integration/inline/LayoutIntegrationLineLayout.cpp index df93de04ee8ea..364e643cee7ee 100644 --- a/Source/WebCore/layout/integration/inline/LayoutIntegrationLineLayout.cpp +++ b/Source/WebCore/layout/integration/inline/LayoutIntegrationLineLayout.cpp @@ -619,7 +619,8 @@ std::optional LineLayout::layout(RenderBlockFlow::MarginInfo& margin FloatRect LineLayout::constructContent(const Layout::InlineLayoutState& inlineLayoutState, std::unique_ptr&& layoutResult) { - auto damagedRect = InlineContentBuilder { flow() }.build(WTF::move(layoutResult), ensureInlineContent(), m_lineDamage.get()); + ensureInlineContent().setContentMayHaveInkOverflow(inlineLayoutState.contentMayHaveInkOverflow()); + auto damagedRect = InlineContentBuilder { flow() }.build(WTF::move(layoutResult), *m_inlineContent, m_lineDamage.get()); m_inlineContent->setClearGapBeforeFirstLine(inlineLayoutState.clearGapBeforeFirstLine()); m_inlineContent->setClearGapAfterLastLine(inlineLayoutState.clearGapAfterLastLine()); diff --git a/Source/WebCore/platform/MediaSessionIdentifier.h b/Source/WebCore/platform/MediaSessionIdentifier.h index c5ba24a42d2ee..caecb75c23266 100644 --- a/Source/WebCore/platform/MediaSessionIdentifier.h +++ b/Source/WebCore/platform/MediaSessionIdentifier.h @@ -25,11 +25,13 @@ #pragma once +#include #include namespace WebCore { struct MediaSessionIdentifierType; using MediaSessionIdentifier = ObjectIdentifier; +using QualifiedMediaSessionIdentifier = ProcessQualified; } diff --git a/Source/WebCore/platform/MediaStrategy.cpp b/Source/WebCore/platform/MediaStrategy.cpp index 59400a5a8a539..7811a8dc4f61a 100644 --- a/Source/WebCore/platform/MediaStrategy.cpp +++ b/Source/WebCore/platform/MediaStrategy.cpp @@ -56,6 +56,11 @@ void MediaStrategy::isActiveNowPlayingSessionInGPUProcessForTesting(MediaSession completion(false); } +void MediaStrategy::isRemoteCommandTargetSessionInGPUProcessForTesting(MediaSessionIdentifier, CompletionHandler&& completion) +{ + completion(false); +} + void MediaStrategy::resetMediaEngines() { #if ENABLE(VIDEO) diff --git a/Source/WebCore/platform/MediaStrategy.h b/Source/WebCore/platform/MediaStrategy.h index ca57f97253c53..3077dda7ea006 100644 --- a/Source/WebCore/platform/MediaStrategy.h +++ b/Source/WebCore/platform/MediaStrategy.h @@ -65,6 +65,8 @@ class WEBCORE_EXPORT MediaStrategy : public CanMakeThreadSafeCheckedPtr createNowPlayingManager() const; virtual void isActiveNowPlayingSessionInGPUProcessForTesting(MediaSessionIdentifier, CompletionHandler&&); + virtual void isRemoteCommandTargetSessionInGPUProcessForTesting(MediaSessionIdentifier, CompletionHandler&&); + virtual bool postNowPlayingRemoteControlCommandToGPUProcessForTesting(PlatformMediaSession::RemoteControlCommandType, const PlatformMediaSession::RemoteCommandArgument&); void resetMediaEngines(); virtual bool hasThreadSafeMediaSourceSupport() const; #if ENABLE(MEDIA_SOURCE) @@ -105,4 +107,10 @@ inline void MediaStrategy::nativeImageFromVideoFrame(const VideoFrame&, Completi } #endif +inline bool MediaStrategy::postNowPlayingRemoteControlCommandToGPUProcessForTesting(PlatformMediaSession::RemoteControlCommandType, const PlatformMediaSession::RemoteCommandArgument&) +{ + return false; +} + + } // namespace WebCore diff --git a/Source/WebCore/platform/TrackInfo.h b/Source/WebCore/platform/TrackInfo.h index e37da573883e2..833600dc87a8c 100644 --- a/Source/WebCore/platform/TrackInfo.h +++ b/Source/WebCore/platform/TrackInfo.h @@ -60,6 +60,12 @@ enum class EncryptionBoxType : uint8_t { TransportStreamEncryptionInitData }; +// Number of fields per frame. +enum class PlatformVideoFieldCount : uint8_t { + Progressive = 1, + Interlaced = 2 +}; + // Ordering of the two fields of an interlaced frame, both temporally and within // the frame buffer. enum class PlatformVideoFieldDetail : uint8_t { @@ -156,8 +162,7 @@ struct VideoSpecificInfoData { FloatSize displaySize { }; uint8_t bitDepth { 8 }; PlatformVideoColorSpace colorSpace { }; - // Number of fields per frame: 1 for progressive content, 2 for interlaced. - std::optional fieldCount { }; + std::optional fieldCount { }; std::optional fieldDetail { }; Vector extensionAtoms { }; @@ -180,7 +185,7 @@ class VideoInfo : public TrackInfo { const FloatSize& displaySize() const LIFETIME_BOUND { return m_data.displaySize; } uint8_t bitDepth() const { return m_data.bitDepth; } const PlatformVideoColorSpace& colorSpace() const LIFETIME_BOUND { return m_data.colorSpace; } - std::optional fieldCount() const { return m_data.fieldCount; } + std::optional fieldCount() const { return m_data.fieldCount; } std::optional fieldDetail() const { return m_data.fieldDetail; } const Vector& extensionAtoms() const LIFETIME_BOUND { return m_data.extensionAtoms; } diff --git a/Source/WebCore/platform/audio/MediaSessionManagerInterface.cpp b/Source/WebCore/platform/audio/MediaSessionManagerInterface.cpp index 1b98b9ad18b16..a475771d1b73f 100644 --- a/Source/WebCore/platform/audio/MediaSessionManagerInterface.cpp +++ b/Source/WebCore/platform/audio/MediaSessionManagerInterface.cpp @@ -813,28 +813,40 @@ int MediaSessionManagerInterface::countActiveAudioCaptureSources() return count; } -void MediaSessionManagerInterface::processDidReceiveRemoteControlCommand(PlatformMediaSession::RemoteControlCommandType command, const PlatformMediaSession::RemoteCommandArgument& argument) +bool MediaSessionManagerInterface::processDidReceiveRemoteControlCommand(PlatformMediaSession::RemoteControlCommandType command, const PlatformMediaSession::RemoteCommandArgument& argument, std::optional targetSession) { -#if ENABLE(VIDEO) || ENABLE(audio) +#if ENABLE(VIDEO) || ENABLE(WEB_AUDIO) RefPtr activeSession; - for (auto& weakSession : copySessionsToVector()) { - RefPtr session = weakSession.get(); - if (!session || !session->canReceiveRemoteControlCommands()) - continue; - - if (session->isNowPlayingEligible()) { - activeSession = WTF::move(session); - break; + if (targetSession) { + // A NowPlaying owner elected across processes; deliver only to that session (this manager may not own it). + activeSession = firstSessionMatching([&](auto& session) { + return session.mediaSessionIdentifier() == *targetSession && session.canReceiveRemoteControlCommands(); + }).get(); + } else { + for (auto& weakSession : copySessionsToVector()) { + RefPtr session = weakSession.get(); + if (!session || !session->canReceiveRemoteControlCommands()) + continue; + + if (session->isNowPlayingEligible()) { + activeSession = WTF::move(session); + break; + } + if (!activeSession) + activeSession = WTF::move(session); } - if (!activeSession) - activeSession = WTF::move(session); } - if (activeSession) - activeSession->didReceiveRemoteControlCommand(command, argument); + if (!activeSession) + return false; + + activeSession->didReceiveRemoteControlCommand(command, argument); + return true; #else UNUSED_PARAM(command); UNUSED_PARAM(argument); + UNUSED_PARAM(targetSession); + return false; #endif } diff --git a/Source/WebCore/platform/audio/MediaSessionManagerInterface.h b/Source/WebCore/platform/audio/MediaSessionManagerInterface.h index 2532b10e52166..5972b75e7ce89 100644 --- a/Source/WebCore/platform/audio/MediaSessionManagerInterface.h +++ b/Source/WebCore/platform/audio/MediaSessionManagerInterface.h @@ -165,7 +165,7 @@ class WEBCORE_EXPORT MediaSessionManagerInterface virtual Ref audioCaptureSourceStateChanged(IsCaptureStarting); virtual size_t audioCaptureSourceCount() const { return m_audioCaptureSources.computeSize(); } - virtual void processDidReceiveRemoteControlCommand(PlatformMediaSessionRemoteControlCommandType, const PlatformMediaSessionRemoteCommandArgument&); + bool processDidReceiveRemoteControlCommand(PlatformMediaSessionRemoteControlCommandType, const PlatformMediaSessionRemoteCommandArgument&, std::optional targetSession = std::nullopt); virtual bool processIsSuspended() const { return m_processIsSuspended; }; virtual void processSystemWillSleep(); virtual void processSystemDidWake(); diff --git a/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.cpp b/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.cpp index 5c7c324107bf8..7fc40a588982c 100644 --- a/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.cpp +++ b/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.cpp @@ -191,7 +191,7 @@ std::optional colorSpaceFromFormatDescription(CMFormatD return colorSpace; } -std::optional fieldCountFromFormatDescription(CMFormatDescriptionRef formatDescription) +std::optional fieldCountFromFormatDescription(CMFormatDescriptionRef formatDescription) { if (!formatDescription) return { }; @@ -201,10 +201,17 @@ std::optional fieldCountFromFormatDescription(CMFormatDescriptionRef fo return { }; int value = 0; - if (!CFNumberGetValue(fieldCount.get(), kCFNumberIntType, &value) || value < 1 || value > 2) + if (!CFNumberGetValue(fieldCount.get(), kCFNumberIntType, &value)) return { }; - return static_cast(value); + switch (value) { + case 1: + return PlatformVideoFieldCount::Progressive; + case 2: + return PlatformVideoFieldCount::Interlaced; + } + + return { }; } std::optional fieldDetailFromFormatDescription(CMFormatDescriptionRef formatDescription) diff --git a/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.h b/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.h index 81420a6db492c..e76e2998cd553 100644 --- a/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.h +++ b/Source/WebCore/platform/graphics/avfoundation/FormatDescriptionUtilities.h @@ -40,7 +40,7 @@ struct PlatformVideoColorSpace; TrackInfoTrackType typeFromFormatDescription(CMFormatDescriptionRef); FloatSize presentationSizeFromFormatDescription(CMFormatDescriptionRef); WEBCORE_EXPORT std::optional colorSpaceFromFormatDescription(CMFormatDescriptionRef); -WEBCORE_EXPORT std::optional fieldCountFromFormatDescription(CMFormatDescriptionRef); +WEBCORE_EXPORT std::optional fieldCountFromFormatDescription(CMFormatDescriptionRef); WEBCORE_EXPORT std::optional fieldDetailFromFormatDescription(CMFormatDescriptionRef); String codecFromFormatDescription(CMFormatDescriptionRef); bool formatDescriptionIsProtected(CMFormatDescriptionRef); diff --git a/Source/WebCore/platform/graphics/cocoa/CMUtilities.mm b/Source/WebCore/platform/graphics/cocoa/CMUtilities.mm index 651baf43476d4..b9d3c05821cca 100644 --- a/Source/WebCore/platform/graphics/cocoa/CMUtilities.mm +++ b/Source/WebCore/platform/graphics/cocoa/CMUtilities.mm @@ -492,7 +492,7 @@ static CFStringRef convertToCMFieldDetail(PlatformVideoFieldDetail fieldDetail) } if (videoInfo.fieldCount()) - CFDictionaryAddValue(extensions.get(), kCVImageBufferFieldCountKey, (__bridge CFTypeRef)@(*videoInfo.fieldCount())); + CFDictionaryAddValue(extensions.get(), kCVImageBufferFieldCountKey, (__bridge CFTypeRef)@(std::to_underlying(*videoInfo.fieldCount()))); if (videoInfo.fieldDetail()) { if (RetainPtr cmFieldDetail = convertToCMFieldDetail(*videoInfo.fieldDetail())) diff --git a/Source/WebCore/rendering/GridBaselineAlignment.cpp b/Source/WebCore/rendering/GridBaselineAlignment.cpp index 761b566e8abf9..5304b89173ccf 100644 --- a/Source/WebCore/rendering/GridBaselineAlignment.cpp +++ b/Source/WebCore/rendering/GridBaselineAlignment.cpp @@ -146,9 +146,11 @@ void GridBaselineAlignment::updateBaselineAlignmentContext(ItemPosition preferen return AlignmentContext { makeUnique(alignmentAxis, m_writingMode), { } }; }).iterator->value; auto groupIndex = context.sharedGroups->sharedGroupIndex(gridItem.writingMode(), preference); - if (groupIndex == context.maxAscents.size()) - context.maxAscents.append(LayoutUnit()); - context.maxAscents[groupIndex] = std::max(context.maxAscents[groupIndex], ascent); + if (groupIndex >= context.alignments.size()) + context.alignments.grow(groupIndex + 1); + auto& alignment = context.alignments[groupIndex]; + ++alignment.alignmentSubjectCount; + alignment.maxAscent = std::max(alignment.maxAscent, ascent); } LayoutUnit GridBaselineAlignment::baselineOffsetForGridItem(ItemPosition preference, unsigned sharedContext, const RenderBox& gridItem, Style::GridTrackSizingDirection alignmentContextType) const @@ -159,10 +161,12 @@ LayoutUnit GridBaselineAlignment::baselineOffsetForGridItem(ItemPosition prefere ASSERT(it != contextMap.end()); auto& context = it->value; auto groupIndex = context.sharedGroups->sharedGroupIndex(gridItem.writingMode(), preference); - // No recorded ascent means a lone participant (its own ascent is the max), so there is no baseline shim. - if (groupIndex >= context.maxAscents.size()) + if (groupIndex >= context.alignments.size()) return { }; - return context.maxAscents[groupIndex] - logicalAscentForGridItem(gridItem, alignmentContextType, preference); + auto& alignment = context.alignments[groupIndex]; + if (alignment.alignmentSubjectCount < 2) + return { }; + return alignment.maxAscent - logicalAscentForGridItem(gridItem, alignmentContextType, preference); } void GridBaselineAlignment::clear(Style::GridTrackSizingDirection alignmentContextType) diff --git a/Source/WebCore/rendering/GridBaselineAlignment.h b/Source/WebCore/rendering/GridBaselineAlignment.h index bb22cda32c9bf..a35d4f393b4dd 100644 --- a/Source/WebCore/rendering/GridBaselineAlignment.h +++ b/Source/WebCore/rendering/GridBaselineAlignment.h @@ -75,9 +75,13 @@ class GridBaselineAlignment { // Per baseline alignment-context: the baseline-sharing groups plus this context's max ascent per group, // indexed by the group index returned from BaselineAlignmentState::sharedGroupIndex. + struct SharedGroupAlignment { + LayoutUnit maxAscent; + size_t alignmentSubjectCount { 0 }; + }; struct AlignmentContext { std::unique_ptr sharedGroups; - Vector maxAscents; + Vector alignments; }; using AlignmentContextMap = HashMap, WTF::UnsignedWithZeroKeyHashTraits>; diff --git a/Source/WebCore/rendering/RenderBox.cpp b/Source/WebCore/rendering/RenderBox.cpp index 8345150e0e5b8..506120e8f394f 100644 --- a/Source/WebCore/rendering/RenderBox.cpp +++ b/Source/WebCore/rendering/RenderBox.cpp @@ -1424,9 +1424,11 @@ bool RenderBox::hasAlwaysPresentScrollbar(ScrollbarOrientation orientation) cons bool RenderBox::shouldInvalidateContentWidths() const { + // A stretched flex or grid item transfers the size it is stretched to through its aspect ratio, + // so its cached contribution is only valid for the cross/block size of the layout that measured it. return style().paddingStart().isPercentOrCalculated() || style().paddingEnd().isPercentOrCalculated() - || (style().aspectRatio().hasRatio() && (hasRelativeLogicalHeight() || (isFlexItem() && hasStretchedLogicalHeight()))); + || (style().aspectRatio().hasRatio() && (hasRelativeLogicalHeight() || ((isFlexItem() || isGridItem()) && hasStretchedLogicalHeight()))); } ScrollPosition RenderBox::scrollPosition() const diff --git a/Source/WebCore/svg/SVGAngleValue.cpp b/Source/WebCore/svg/SVGAngleValue.cpp index 35e147f97b90c..47515aa06aca0 100644 --- a/Source/WebCore/svg/SVGAngleValue.cpp +++ b/Source/WebCore/svg/SVGAngleValue.cpp @@ -108,7 +108,7 @@ static inline SVGAngleValue::Type NODELETE cssAngleUnitToSVGAngleType(CSS::Angle return SVGAngleValue::SVG_ANGLETYPE_UNKNOWN; } -ExceptionOr SVGAngleValue::setValueAsString(const String& value) +ExceptionOr SVGAngleValue::setValueAsString(StringView value) { if (value.isEmpty()) { m_unitType = SVG_ANGLETYPE_UNSPECIFIED; diff --git a/Source/WebCore/svg/SVGAngleValue.h b/Source/WebCore/svg/SVGAngleValue.h index 68699877e2918..de76f02721f78 100644 --- a/Source/WebCore/svg/SVGAngleValue.h +++ b/Source/WebCore/svg/SVGAngleValue.h @@ -49,7 +49,7 @@ class SVGAngleValue { void setValueInSpecifiedUnits(float valueInSpecifiedUnits) { m_valueInSpecifiedUnits = valueInSpecifiedUnits; } float valueInSpecifiedUnits() const { return m_valueInSpecifiedUnits; } - ExceptionOr setValueAsString(const String&); + ExceptionOr setValueAsString(StringView); String valueAsString() const; ExceptionOr newValueSpecifiedUnits(unsigned short unitType, float valueInSpecifiedUnits); diff --git a/Source/WebCore/testing/Internals.cpp b/Source/WebCore/testing/Internals.cpp index ae8515e27ec72..9df54d693b85b 100644 --- a/Source/WebCore/testing/Internals.cpp +++ b/Source/WebCore/testing/Internals.cpp @@ -5617,41 +5617,61 @@ void Internals::setMediaElementRestrictions(HTMLMediaElement& element, StringVie element.mediaSession().addBehaviorRestriction(restrictions); } +static std::optional remoteControlCommandForString(const String& commandString) +{ + if (equalLettersIgnoringASCIICase(commandString, "play"_s)) + return PlatformMediaSession::RemoteControlCommandType::PlayCommand; + if (equalLettersIgnoringASCIICase(commandString, "pause"_s)) + return PlatformMediaSession::RemoteControlCommandType::PauseCommand; + if (equalLettersIgnoringASCIICase(commandString, "stop"_s)) + return PlatformMediaSession::RemoteControlCommandType::StopCommand; + if (equalLettersIgnoringASCIICase(commandString, "toggleplaypause"_s)) + return PlatformMediaSession::RemoteControlCommandType::TogglePlayPauseCommand; + if (equalLettersIgnoringASCIICase(commandString, "beginseekingbackward"_s)) + return PlatformMediaSession::RemoteControlCommandType::BeginSeekingBackwardCommand; + if (equalLettersIgnoringASCIICase(commandString, "endseekingbackward"_s)) + return PlatformMediaSession::RemoteControlCommandType::EndSeekingBackwardCommand; + if (equalLettersIgnoringASCIICase(commandString, "beginseekingforward"_s)) + return PlatformMediaSession::RemoteControlCommandType::BeginSeekingForwardCommand; + if (equalLettersIgnoringASCIICase(commandString, "endseekingforward"_s)) + return PlatformMediaSession::RemoteControlCommandType::EndSeekingForwardCommand; + if (equalLettersIgnoringASCIICase(commandString, "seektoplaybackposition"_s)) + return PlatformMediaSession::RemoteControlCommandType::SeekToPlaybackPositionCommand; + if (equalLettersIgnoringASCIICase(commandString, "beginscrubbing"_s)) + return PlatformMediaSession::RemoteControlCommandType::BeginScrubbingCommand; + if (equalLettersIgnoringASCIICase(commandString, "endscrubbing"_s)) + return PlatformMediaSession::RemoteControlCommandType::EndScrubbingCommand; + return std::nullopt; +} + ExceptionOr Internals::postRemoteControlCommand(const String& commandString, float argument) { RefPtr manager = sessionManager(); if (!manager) return Exception { ExceptionCode::InvalidAccessError }; - PlatformMediaSession::RemoteControlCommandType command; - PlatformMediaSession::RemoteCommandArgument parameter { argument, { } }; + auto command = remoteControlCommandForString(commandString); + if (!command) + return Exception { ExceptionCode::InvalidAccessError }; - if (equalLettersIgnoringASCIICase(commandString, "play"_s)) - command = PlatformMediaSession::RemoteControlCommandType::PlayCommand; - else if (equalLettersIgnoringASCIICase(commandString, "pause"_s)) - command = PlatformMediaSession::RemoteControlCommandType::PauseCommand; - else if (equalLettersIgnoringASCIICase(commandString, "stop"_s)) - command = PlatformMediaSession::RemoteControlCommandType::StopCommand; - else if (equalLettersIgnoringASCIICase(commandString, "toggleplaypause"_s)) - command = PlatformMediaSession::RemoteControlCommandType::TogglePlayPauseCommand; - else if (equalLettersIgnoringASCIICase(commandString, "beginseekingbackward"_s)) - command = PlatformMediaSession::RemoteControlCommandType::BeginSeekingBackwardCommand; - else if (equalLettersIgnoringASCIICase(commandString, "endseekingbackward"_s)) - command = PlatformMediaSession::RemoteControlCommandType::EndSeekingBackwardCommand; - else if (equalLettersIgnoringASCIICase(commandString, "beginseekingforward"_s)) - command = PlatformMediaSession::RemoteControlCommandType::BeginSeekingForwardCommand; - else if (equalLettersIgnoringASCIICase(commandString, "endseekingforward"_s)) - command = PlatformMediaSession::RemoteControlCommandType::EndSeekingForwardCommand; - else if (equalLettersIgnoringASCIICase(commandString, "seektoplaybackposition"_s)) - command = PlatformMediaSession::RemoteControlCommandType::SeekToPlaybackPositionCommand; - else if (equalLettersIgnoringASCIICase(commandString, "beginscrubbing"_s)) - command = PlatformMediaSession::RemoteControlCommandType::BeginScrubbingCommand; - else if (equalLettersIgnoringASCIICase(commandString, "endscrubbing"_s)) - command = PlatformMediaSession::RemoteControlCommandType::EndScrubbingCommand; - else + manager->processDidReceiveRemoteControlCommand(*command, { argument, { } }); + return { }; +} + +ExceptionOr Internals::postSystemRemoteControlCommand(const String& commandString, float argument) +{ + RefPtr manager = sessionManager(); + if (!manager) + return Exception { ExceptionCode::InvalidAccessError }; + + auto command = remoteControlCommandForString(commandString); + if (!command) return Exception { ExceptionCode::InvalidAccessError }; - manager->processDidReceiveRemoteControlCommand(command, parameter); + // The GPU process delivers the command the way a real system command arrives. Without one (WebKitLegacy, or + // WebKit not using a GPU process) fall back to injecting into the local manager. + if (!platformStrategies()->mediaStrategy()->postNowPlayingRemoteControlCommandToGPUProcessForTesting(*command, { argument, { } })) + manager->processDidReceiveRemoteControlCommand(*command, { argument, { } }); return { }; } @@ -5986,6 +6006,13 @@ void Internals::elementIsActiveNowPlayingSessionInGPUProcess(HTMLMediaElement& e }); } +void Internals::elementIsRemoteCommandTargetInGPUProcess(HTMLMediaElement& element, DOMPromiseDeferred&& promise) +{ + platformStrategies()->mediaStrategy()->isRemoteCommandTargetSessionInGPUProcessForTesting(element.mediaSession().mediaSessionIdentifier(), [promise = WTF::move(promise)](bool result) mutable { + promise.resolve(result); + }); +} + #endif // ENABLE(VIDEO) #if ENABLE(WIRELESS_PLAYBACK_TARGET) diff --git a/Source/WebCore/testing/Internals.h b/Source/WebCore/testing/Internals.h index 084b79dd2b0cf..50b4fbd6be5e8 100644 --- a/Source/WebCore/testing/Internals.h +++ b/Source/WebCore/testing/Internals.h @@ -947,6 +947,7 @@ class Internals final ExceptionOr mediaSessionRestrictions(const String& mediaType) const; void setMediaElementRestrictions(HTMLMediaElement&, StringView restrictionsString); ExceptionOr postRemoteControlCommand(const String&, float argument); + ExceptionOr postSystemRemoteControlCommand(const String&, float argument); void activeAudioRouteDidChange(bool shouldPause); bool NODELETE elementIsBlockingDisplaySleep(const HTMLMediaElement&) const; bool NODELETE isPlayerVisibleInViewport(const HTMLMediaElement&) const; @@ -1352,6 +1353,7 @@ class Internals final bool elementIsActiveNowPlayingSession(HTMLMediaElement&) const; void elementIsActiveNowPlayingSessionInGPUProcess(HTMLMediaElement&, DOMPromiseDeferred&&); + void elementIsRemoteCommandTargetInGPUProcess(HTMLMediaElement&, DOMPromiseDeferred&&); #endif // ENABLE(VIDEO) diff --git a/Source/WebCore/testing/Internals.idl b/Source/WebCore/testing/Internals.idl index 4f44323c73f03..f4fbd9ecc1402 100644 --- a/Source/WebCore/testing/Internals.idl +++ b/Source/WebCore/testing/Internals.idl @@ -1123,6 +1123,9 @@ enum ContentsFormat { [Conditional=VIDEO] undefined setMediaElementRestrictions(HTMLMediaElement element, DOMString restrictions); [Conditional=WEB_AUDIO] undefined setAudioContextRestrictions(AudioContext context, DOMString restrictions); [Conditional=VIDEO] undefined postRemoteControlCommand(DOMString command, optional unrestricted float argument = 0); + // Injects at the GPU process's NowPlayingManager (as a real system command arrives); requires the + // allowTestOnlyIPC test option, otherwise the GPU message is rejected and the WebContent process is killed. + [Conditional=VIDEO] undefined postSystemRemoteControlCommand(DOMString command, optional unrestricted float argument = 0); [Conditional=VIDEO] undefined activeAudioRouteDidChange(boolean shouldPause); [Conditional=VIDEO] undefined beginAudioSessionInterruption(); [Conditional=VIDEO] undefined endAudioSessionInterruption(); @@ -1375,6 +1378,7 @@ enum ContentsFormat { [Conditional=VIDEO] readonly attribute NowPlayingState nowPlayingState; [Conditional=VIDEO] boolean elementIsActiveNowPlayingSession(HTMLMediaElement element); [Conditional=VIDEO] Promise elementIsActiveNowPlayingSessionInGPUProcess(HTMLMediaElement element); + [Conditional=VIDEO] Promise elementIsRemoteCommandTargetInGPUProcess(HTMLMediaElement element); [Conditional=VIDEO] attribute double nowPlayingUpdateInterval; [Conditional=VIDEO] HTMLMediaElement? bestMediaElementForRemoteControls(PlaybackControlsPurpose purpose); diff --git a/Source/WebGPU/WebGPU/Queue.mm b/Source/WebGPU/WebGPU/Queue.mm index 4b69897f2bdc3..613e040f71141 100644 --- a/Source/WebGPU/WebGPU/Queue.mm +++ b/Source/WebGPU/WebGPU/Queue.mm @@ -50,7 +50,7 @@ namespace WebGPU { -constexpr static auto largeBufferSize = 32 * 1024 * 1024; +constexpr static auto largeBufferSize = WGPU_LARGE_BUFFER_SIZE; constexpr bool skipMemoryAttribution = true; WTF_MAKE_TZONE_ALLOCATED_IMPL(Queue); @@ -1402,8 +1402,16 @@ static void invalidateCommandBuffers(Vector>&& comman return; } - if (noCopy) + if (noCopy) { + if (!newData.isEmpty()) { + // The MTLBuffer above was created with newBufferWithBytesNoCopy and aliases newData's storage; keep that storage alive until the GPU has consumed it. + __block Vector retainedNewData = WTF::move(newData); + [m_commandBuffer addCompletedHandler:^(id) { + retainedNewData = { }; + }]; + } finalizeBlitCommandEncoder(); + } } void Queue::setLabel(String&& label) diff --git a/Source/WebGPU/WebGPU/Queue.swift b/Source/WebGPU/WebGPU/Queue.swift index 9201109a8604a..f4aaf90d98d86 100644 --- a/Source/WebGPU/WebGPU/Queue.swift +++ b/Source/WebGPU/WebGPU/Queue.swift @@ -23,8 +23,9 @@ import Metal import WebGPU_Internal.Queue +import WebGPU_Private.WebGPUExt -private let largeBufferSize = 32 * 1024 * 1024 +private let largeBufferSize = Int(WGPU_LARGE_BUFFER_SIZE) @_expose(Cxx) func queueWriteBuffer(_ queue: WebGPU.Queue, buffer: any MTLBuffer, bufferOffset: UInt64, data: WebGPU.SpanUInt8) { diff --git a/Source/WebGPU/WebGPU/RenderPipeline.mm b/Source/WebGPU/WebGPU/RenderPipeline.mm index debac7bcd4f36..c67a2a74e923b 100644 --- a/Source/WebGPU/WebGPU/RenderPipeline.mm +++ b/Source/WebGPU/WebGPU/RenderPipeline.mm @@ -988,6 +988,13 @@ static auto makeBindingLayout(WGPUBindGroupLayoutEntry& newEntry, auto& bindingM for (auto& bindGroupLayout : pipelineLayout.bindGroupLayouts) { auto& entries = pipelineEntries[bindGroupLayout.group]; HashMap entryMap; + // Wrapping the bump would let the array-length entry alias a user binding and defeat the bounds check. + auto bumpForArrayLength = [&](uint32_t webBinding) -> std::optional { + auto checked = checkedSum(webBinding, limits().maxBindingsPerBindGroup); + if (checked.hasOverflowed()) + return std::nullopt; + return checked.value(); + }; for (auto& entry : bindGroupLayout.entries) { auto visibility = convertVisibility(entry.visibility); auto stage = visibility / 2; @@ -997,7 +1004,10 @@ static auto makeBindingLayout(WGPUBindGroupLayoutEntry& newEntry, auto& bindingM uint32_t webBinding = entry.webBinding; if (auto& entryName = entry.name; entryName.length()) { if (entryName.endsWith("_ArrayLength"_s)) { - webBinding += limits().maxBindingsPerBindGroup; + auto bumped = bumpForArrayLength(webBinding); + if (!bumped) + return @"Binding index overflow in auto-generated layouts"; + webBinding = *bumped; isArrayLength = true; } } @@ -1016,7 +1026,10 @@ static auto makeBindingLayout(WGPUBindGroupLayoutEntry& newEntry, auto& bindingM WGPUBufferBindingType bufferTypeOverride = WGPUBufferBindingType_Undefined; if (auto& entryName = entry.name; entryName.length()) { if (isArrayLength) { - webBinding += limits().maxBindingsPerBindGroup; + auto bumped = bumpForArrayLength(webBinding); + if (!bumped) + return @"Binding index overflow in auto-generated layouts"; + webBinding = *bumped; bufferTypeOverride = static_cast(WGPUBufferBindingType_ArrayLength); auto shortName = entryName.substring(2, entryName.length() - (sizeof("_ArrayLength") + 1)); if (auto it = entryMap.find(shortName); it != entryMap.end()) diff --git a/Source/WebGPU/WebGPU/WebGPUExt.h b/Source/WebGPU/WebGPU/WebGPUExt.h index 8fa30a1f1c748..4214d5cfe71d8 100644 --- a/Source/WebGPU/WebGPU/WebGPUExt.h +++ b/Source/WebGPU/WebGPU/WebGPUExt.h @@ -42,6 +42,13 @@ #define WGPU_FUZZER_ASSERT_NOT_REACHED(...) WTFLogAlways(__VA_ARGS__) #endif +// Threshold above which the Metal backend uses newBufferWithBytesNoCopy in writeBuffer / writeTexture +// and aliases the caller's storage rather than copying. Callers passing transfers >= this size MUST +// keep the source bytes alive until the GPU has consumed them (e.g. via addCompletedHandler). +// Value is 32 * 1024 * 1024; written as a single integer literal so Swift's clang macro importer +// picks it up as `WGPU_LARGE_BUFFER_SIZE` rather than skipping it. +#define WGPU_LARGE_BUFFER_SIZE 33554432 + #include #include #include diff --git a/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.cpp b/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.cpp index 51a719140600e..ffb77aafec548 100644 --- a/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.cpp +++ b/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.cpp @@ -418,8 +418,8 @@ void GPUConnectionToWebProcess::didClose(IPC::Connection& connection) { assertIsMainThread(); - if (m_isActiveNowPlayingProcess) - clearNowPlayingInfoForPage(std::nullopt); + if (m_isNowPlayingManagerClient) + nowPlayingClientDidClose(); #if ENABLE(ROUTING_ARBITRATION) && HAVE(AVAUDIO_ROUTING_ARBITER) if (m_routingArbitrator) @@ -829,7 +829,7 @@ void GPUConnectionToWebProcess::clearNowPlayingInfoForPage(std::optionalnowPlayingManager().removeClient(*this); } @@ -871,7 +871,7 @@ void GPUConnectionToWebProcess::setNowPlayingInfoForPage(NowPlayingInfo&& nowPla return; } - m_isActiveNowPlayingProcess = true; + m_isNowPlayingManagerClient = true; gpuProcess->nowPlayingManager().addClient(*this); gpuProcess->nowPlayingManager().setNowPlayingInfo(WTF::move(nowPlayingInfo)); updateSupportedRemoteCommands(); @@ -883,7 +883,7 @@ void GPUConnectionToWebProcess::becomeNowPlayingOwner(WebCore::PageIdentifier pa if (it == m_nowPlayingCandidates.end()) return; - m_isActiveNowPlayingProcess = true; + m_isNowPlayingManagerClient = true; Ref gpuProcess = this->gpuProcess(); gpuProcess->nowPlayingManager().addClient(*this); if (it->value->info) @@ -891,20 +891,54 @@ void GPUConnectionToWebProcess::becomeNowPlayingOwner(WebCore::PageIdentifier pa updateSupportedRemoteCommands(); } -void GPUConnectionToWebProcess::resignNowPlayingOwner() +void GPUConnectionToWebProcess::becomeRemoteCommandFallbackTarget() { - m_isActiveNowPlayingProcess = false; + m_isNowPlayingManagerClient = true; + gpuProcess().nowPlayingManager().addClient(*this); + updateSupportedRemoteCommands(); +} + +void GPUConnectionToWebProcess::resignNowPlayingManagerClient() +{ + m_isNowPlayingManagerClient = false; gpuProcess().nowPlayingManager().removeClient(*this); } +void GPUConnectionToWebProcess::nowPlayingClientDidClose() +{ + ASSERT(m_isNowPlayingManagerClient); + + m_nowPlayingCandidates.clear(); + + // Resign here rather than leaving it to GPUProcess::recomputeNowPlayingOwner. This connection is still in the + // GPU process's connection map right now, but it is gone by the time the recompute from + // removeGPUConnectionToWebProcess runs, so a resign that looks the connection up by process identifier would + // silently do nothing and leave a dead NowPlayingManager client holding the system command listener. + resignNowPlayingManagerClient(); + + Ref gpuProcess = this->gpuProcess(); + if (gpuProcess->isNowPlayingArbiterActive()) + gpuProcess->nowPlayingClientDidClose(webProcessIdentifier()); +} + void GPUConnectionToWebProcess::isActiveNowPlayingSessionForTesting(WebCore::MediaSessionIdentifier identifier, CompletionHandler&& completion) { completion(gpuProcess().isActiveNowPlayingSession(webProcessIdentifier(), identifier)); } +void GPUConnectionToWebProcess::isRemoteCommandTargetSessionForTesting(WebCore::MediaSessionIdentifier identifier, CompletionHandler&& completion) +{ + completion(gpuProcess().isRemoteCommandTargetSession(webProcessIdentifier(), identifier)); +} + +void GPUConnectionToWebProcess::postNowPlayingRemoteControlCommandForTesting(WebCore::PlatformMediaSessionRemoteControlCommandType type, const WebCore::PlatformMediaSessionRemoteCommandArgument& argument) +{ + gpuProcess().nowPlayingManager().didReceiveRemoteControlCommand(type, argument); +} + void GPUConnectionToWebProcess::updateSupportedRemoteCommands() { - if (!m_isActiveNowPlayingProcess || !m_remoteRemoteCommandListener) + if (!m_isNowPlayingManagerClient || !m_remoteRemoteCommandListener) return; Ref gpuProcess = this->gpuProcess(); @@ -914,7 +948,7 @@ void GPUConnectionToWebProcess::updateSupportedRemoteCommands() void GPUConnectionToWebProcess::didReceiveRemoteControlCommand(PlatformMediaSession::RemoteControlCommandType type, const PlatformMediaSession::RemoteCommandArgument& argument) { - m_connection->send(Messages::GPUProcessConnection::DidReceiveRemoteCommand(type, argument), 0); + m_connection->send(Messages::GPUProcessConnection::DidReceiveRemoteCommand(type, argument, gpuProcess().remoteCommandTargetSessionInProcess(webProcessIdentifier())), 0); } #if USE(AUDIO_SESSION) diff --git a/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.h b/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.h index 5cd2f43c2807d..2b3178c214509 100644 --- a/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.h +++ b/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.h @@ -174,7 +174,8 @@ class GPUConnectionToWebProcess }; const HashMap>& nowPlayingCandidates() const LIFETIME_BOUND { return m_nowPlayingCandidates; } void becomeNowPlayingOwner(WebCore::PageIdentifier); - void resignNowPlayingOwner(); + void becomeRemoteCommandFallbackTarget(); + void resignNowPlayingManagerClient(); Ref sharedResourceCache(); #if ENABLE(VIDEO) @@ -320,7 +321,10 @@ class GPUConnectionToWebProcess void clearNowPlayingInfoForPage(std::optional); void setNowPlayingInfoForPage(WebCore::NowPlayingInfo&&, std::optional); void setNowPlayingCandidateState(WebCore::NowPlayingCandidateState&&); + void nowPlayingClientDidClose(); void isActiveNowPlayingSessionForTesting(WebCore::MediaSessionIdentifier, CompletionHandler&&); + void isRemoteCommandTargetSessionForTesting(WebCore::MediaSessionIdentifier, CompletionHandler&&); + void postNowPlayingRemoteControlCommandForTesting(WebCore::PlatformMediaSessionRemoteControlCommandType, const WebCore::PlatformMediaSessionRemoteCommandArgument&); #if PLATFORM(COCOA) && ENABLE(MEDIA_STREAM) void updateSampleBufferDisplayLayerBoundsAndPosition(WebKit::SampleBufferDisplayLayerIdentifier, WebCore::FloatRect, std::optional&&); @@ -457,7 +461,7 @@ class GPUConnectionToWebProcess RefPtr m_remoteRemoteCommandListener; HashMap> m_nowPlayingCandidates; - bool m_isActiveNowPlayingProcess { false }; + bool m_isNowPlayingManagerClient { false }; const bool m_isLockdownModeEnabled { false }; #if ENABLE(EXTENSION_CAPABILITIES) diff --git a/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.messages.in b/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.messages.in index b81edd95e2cdc..ee17fa6a13553 100644 --- a/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.messages.in +++ b/Source/WebKit/GPUProcess/GPUConnectionToWebProcess.messages.in @@ -40,6 +40,8 @@ messages -> GPUConnectionToWebProcess WantsDispatchMessage { void SetNowPlayingInfoForPage(struct WebCore::NowPlayingInfo nowPlayingInfo, std::optional pageIdentifier) void SetNowPlayingCandidateState(struct WebCore::NowPlayingCandidateState candidate) [EnabledBy=AllowTestOnlyIPC] IsActiveNowPlayingSessionForTesting(WebCore::MediaSessionIdentifier identifier) -> (bool isActive) + [EnabledBy=AllowTestOnlyIPC] IsRemoteCommandTargetSessionForTesting(WebCore::MediaSessionIdentifier identifier) -> (bool isTarget) + [EnabledBy=AllowTestOnlyIPC] PostNowPlayingRemoteControlCommandForTesting(enum:uint8_t WebCore::PlatformMediaSessionRemoteControlCommandType type, struct WebCore::PlatformMediaSessionRemoteCommandArgument argument) #if USE(AUDIO_SESSION) [EnabledBy=UseGPUProcessForMediaEnabled && MediaPlaybackEnabled] EnsureAudioSession() -> (struct WebKit::RemoteAudioSessionConfiguration configuration) Synchronous #endif diff --git a/Source/WebKit/GPUProcess/GPUProcess.cpp b/Source/WebKit/GPUProcess/GPUProcess.cpp index df2c1933622cc..60d0ed559f3c6 100644 --- a/Source/WebKit/GPUProcess/GPUProcess.cpp +++ b/Source/WebKit/GPUProcess/GPUProcess.cpp @@ -299,6 +299,10 @@ CoreAudioCaptureUnit::defaultSingleton().setStatusBarWasTappedCallback([weakProc if (!parameters.overrideLanguages.isEmpty()) overrideUserPreferredLanguages(parameters.overrideLanguages); +#if ENABLE(VIDEO) || ENABLE(WEB_AUDIO) + m_nowPlayingFallbackSession = parameters.nowPlayingFallbackSession; +#endif + #if USE(OS_STATE) registerWithStateDumper("GPUProcess state"_s); #endif @@ -393,6 +397,19 @@ static bool isPreferredNowPlayingCandidate(const WebCore::NowPlayingCandidateSta return false; } +namespace { + +enum class NowPlayingSeatRole : bool { EligibleOwner, CommandOnly }; + +struct NowPlayingSeat { + WebCore::ProcessIdentifier process; + NowPlayingSeatRole role; + + friend bool operator==(const NowPlayingSeat&, const NowPlayingSeat&) = default; +}; + +} + void GPUProcess::recomputeNowPlayingOwner() { if (!m_isNowPlayingArbiterActive) @@ -427,22 +444,97 @@ void GPUProcess::recomputeNowPlayingOwner() } } - if (!winnerState) { - if (RefPtr previous = m_activeNowPlayingOwner ? webProcessConnection(m_activeNowPlayingOwner->process) : nullptr) - previous->resignNowPlayingOwner(); - m_activeNowPlayingOwner = std::nullopt; - return; + RefPtr seatedConnection; + std::optional eligibleOwner; + std::optional commandTarget; + + if (winnerState) { + seatedConnection = winningConnection; + eligibleOwner = NowPlayingOwner { winningConnection->webProcessIdentifier(), *winnerPage, winnerState->sessionIdentifier }; + commandTarget = QualifiedMediaSessionIdentifier { winnerState->sessionIdentifier, winningConnection->webProcessIdentifier() }; + } else if (m_nowPlayingFallbackSession) { + if (RefPtr connection = webProcessConnection(m_nowPlayingFallbackSession->processIdentifier())) { + seatedConnection = connection; + commandTarget = m_nowPlayingFallbackSession; + } + } + + // A single connection is the NowPlayingManager client at a time: the elected owner (which also drives the + // NowPlaying panel and audio session) or, when no session is eligible, a command-only fallback that receives + // remote commands but shows no panel. Track the seated process and role so an unrelated recompute does not + // churn a fallback client's remote-command listener. + std::optional previousSeat; + if (m_remoteCommandTarget) { + auto previousRole = m_activeNowPlayingOwner ? NowPlayingSeatRole::EligibleOwner : NowPlayingSeatRole::CommandOnly; + previousSeat = NowPlayingSeat { m_remoteCommandTarget->processIdentifier(), previousRole }; + } + std::optional newSeat; + if (seatedConnection) { + auto newRole = eligibleOwner ? NowPlayingSeatRole::EligibleOwner : NowPlayingSeatRole::CommandOnly; + newSeat = NowPlayingSeat { seatedConnection->webProcessIdentifier(), newRole }; + } + + if (eligibleOwner) { + // Add the new owner as the client before resigning the previous one so the panel never blanks between + // owners (NowPlayingManager::removeClient no-ops once m_client has been replaced). This also refreshes the + // owner's NowPlayingInfo, so it runs even when the seat is unchanged. + seatedConnection->becomeNowPlayingOwner(eligibleOwner->page); + if (previousSeat && previousSeat->process != seatedConnection->webProcessIdentifier()) { + if (RefPtr previous = webProcessConnection(previousSeat->process)) + previous->resignNowPlayingManagerClient(); + } + } else if (newSeat != previousSeat) { + // No eligible session. Resign the previous client first so an owner->fallback transition drops the stale + // panel, then seat the UI process's current session (if any) as a command-only client. Skipped entirely + // when it is already the command-only client, so its remote-command listener is not destroyed and recreated. + if (previousSeat) { + if (RefPtr previous = webProcessConnection(previousSeat->process)) + previous->resignNowPlayingManagerClient(); + } + if (seatedConnection) + seatedConnection->becomeRemoteCommandFallbackTarget(); } - auto winnerProcess = winningConnection->webProcessIdentifier(); + m_activeNowPlayingOwner = eligibleOwner; + m_remoteCommandTarget = commandTarget; +} + +void GPUProcess::setNowPlayingFallbackSession(std::optional session) +{ + m_nowPlayingFallbackSession = session; + + // The fallback is only consulted when the election has no eligible owner, and every change to the candidates + // recomputes on its own, so an owner means this cannot change the outcome. + if (m_activeNowPlayingOwner) + return; + + recomputeNowPlayingOwner(); +} + +void GPUProcess::nowPlayingClientDidClose(WebCore::ProcessIdentifier process) +{ + if (m_nowPlayingFallbackSession && m_nowPlayingFallbackSession->processIdentifier() == process) + m_nowPlayingFallbackSession = std::nullopt; + if (m_remoteCommandTarget && m_remoteCommandTarget->processIdentifier() == process) + m_remoteCommandTarget = std::nullopt; + + recomputeNowPlayingOwner(); +} + +std::optional GPUProcess::remoteCommandTargetSessionInProcess(ProcessIdentifier process) const +{ + if (!m_remoteCommandTarget) + return std::nullopt; - winningConnection->becomeNowPlayingOwner(*winnerPage); - if (m_activeNowPlayingOwner && m_activeNowPlayingOwner->process != winnerProcess) { - if (RefPtr previous = webProcessConnection(m_activeNowPlayingOwner->process)) - previous->resignNowPlayingOwner(); + // The caller is the NowPlayingManager client, which is always the target's own process; the bare + // MediaSessionIdentifier is only meaningful there. + ASSERT(m_remoteCommandTarget->processIdentifier() == process); + if (m_remoteCommandTarget->processIdentifier() != process) { + RELEASE_LOG_ERROR(Media, "GPUProcess::remoteCommandTargetSessionInProcess: command target belongs to another process; delivering nothing rather than a cross-process identifier"); + return std::nullopt; } - m_activeNowPlayingOwner = { winnerProcess, *winnerPage, winnerState->sessionIdentifier }; + return m_remoteCommandTarget->object(); } void GPUProcess::updateSandboxAccess(const Vector& extensions) diff --git a/Source/WebKit/GPUProcess/GPUProcess.h b/Source/WebKit/GPUProcess/GPUProcess.h index f301c8e08cf36..91822cfe2ab46 100644 --- a/Source/WebKit/GPUProcess/GPUProcess.h +++ b/Source/WebKit/GPUProcess/GPUProcess.h @@ -132,9 +132,15 @@ class GPUProcess final : public AuxiliaryProcess, public ThreadSafeRefCounted); + void nowPlayingClientDidClose(WebCore::ProcessIdentifier); bool isNowPlayingArbiterActive() const { return m_isNowPlayingArbiterActive; } bool isActiveNowPlayingPage(WebCore::ProcessIdentifier process, WebCore::PageIdentifier page) const { return m_activeNowPlayingOwner && m_activeNowPlayingOwner->process == process && m_activeNowPlayingOwner->page == page; } bool isActiveNowPlayingSession(WebCore::ProcessIdentifier process, WebCore::MediaSessionIdentifier session) const { return m_activeNowPlayingOwner && m_activeNowPlayingOwner->process == process && m_activeNowPlayingOwner->session == session; } + // Unlike remoteCommandTargetSessionInProcess(), safe to ask from any process: it compares rather than assuming + // the caller owns the target. + bool isRemoteCommandTargetSession(WebCore::ProcessIdentifier process, WebCore::MediaSessionIdentifier session) const { return m_remoteCommandTarget && *m_remoteCommandTarget == WebCore::QualifiedMediaSessionIdentifier { session, process }; } + std::optional remoteCommandTargetSessionInProcess(WebCore::ProcessIdentifier) const; #if ENABLE(MEDIA_STREAM) && PLATFORM(COCOA) WorkQueue& videoMediaStreamTrackRendererQueue(); @@ -322,6 +328,8 @@ class GPUProcess final : public AuxiliaryProcess, public ThreadSafeRefCounted m_activeNowPlayingOwner; + std::optional m_nowPlayingFallbackSession; + std::optional m_remoteCommandTarget; String m_applicationVisibleName; #if PLATFORM(MAC) String m_uiProcessName; diff --git a/Source/WebKit/GPUProcess/GPUProcess.messages.in b/Source/WebKit/GPUProcess/GPUProcess.messages.in index 161291771b3f3..bbf51c7cf947e 100644 --- a/Source/WebKit/GPUProcess/GPUProcess.messages.in +++ b/Source/WebKit/GPUProcess/GPUProcess.messages.in @@ -114,6 +114,8 @@ messages -> GPUProcess : AuxiliaryProcess { PostWillTakeSnapshotNotification() -> () RegisterFonts(Vector sandboxExtensions) #endif + + SetNowPlayingFallbackSession(std::optional session) } #endif // ENABLE(GPU_PROCESS) diff --git a/Source/WebKit/GPUProcess/GPUProcessCreationParameters.h b/Source/WebKit/GPUProcess/GPUProcessCreationParameters.h index fda44aa43ea0e..d53e2ab9fbb57 100644 --- a/Source/WebKit/GPUProcess/GPUProcessCreationParameters.h +++ b/Source/WebKit/GPUProcess/GPUProcessCreationParameters.h @@ -32,6 +32,10 @@ #include "SecurityFlags.h" #include +#if ENABLE(VIDEO) || ENABLE(WEB_AUDIO) +#include +#endif + #if USE(GBM) #include #endif @@ -71,6 +75,11 @@ struct GPUProcessCreationParameters { #endif Vector overrideLanguages; + +#if ENABLE(VIDEO) || ENABLE(WEB_AUDIO) + std::optional nowPlayingFallbackSession; +#endif + #if PLATFORM(COCOA) bool enableMetalDebugDeviceForTesting { false }; bool enableMetalShaderValidationForTesting { false }; diff --git a/Source/WebKit/GPUProcess/GPUProcessCreationParameters.serialization.in b/Source/WebKit/GPUProcess/GPUProcessCreationParameters.serialization.in index 9b7c5d92191ff..2c4c75204778d 100644 --- a/Source/WebKit/GPUProcess/GPUProcessCreationParameters.serialization.in +++ b/Source/WebKit/GPUProcess/GPUProcessCreationParameters.serialization.in @@ -53,6 +53,11 @@ #endif Vector overrideLanguages; + +#if ENABLE(VIDEO) || ENABLE(WEB_AUDIO) + std::optional nowPlayingFallbackSession; +#endif + #if PLATFORM(COCOA) bool enableMetalDebugDeviceForTesting; bool enableMetalShaderValidationForTesting; diff --git a/Source/WebKit/GPUProcess/graphics/WebGPU/RemoteQueue.cpp b/Source/WebKit/GPUProcess/graphics/WebGPU/RemoteQueue.cpp index d9b6cc8bcd55c..a1988b3eadd19 100644 --- a/Source/WebKit/GPUProcess/graphics/WebGPU/RemoteQueue.cpp +++ b/Source/WebKit/GPUProcess/graphics/WebGPU/RemoteQueue.cpp @@ -37,10 +37,31 @@ #include #include +#if HAVE(WEBGPU_IMPLEMENTATION) +#include +#include +#endif + namespace WebKit { WTF_MAKE_TZONE_ALLOCATED_IMPL(RemoteQueue); +// For transfers at or above WGPU_LARGE_BUFFER_SIZE the backend uses newBufferWithBytesNoCopy and aliases `data`'s mapping; keep it alive until the GPU has consumed the bytes. Smaller transfers are copied into a Metal buffer synchronously, so `data` can be released as soon as we return. +static void keepAliveUntilSubmittedWorkDone(WebCore::WebGPU::Queue& backing, RefPtr&& data) +{ +#if HAVE(WEBGPU_IMPLEMENTATION) + if (!data || data->size() < WGPU_LARGE_BUFFER_SIZE) + return; + backing.onSubmittedWorkDone([data = WTF::move(data)]() mutable { + data = nullptr; + }); +#else + // Only the Metal backend aliases the caller's storage, and it is the only WebGPU implementation. + UNUSED_PARAM(backing); + UNUSED_PARAM(data); +#endif +} + RemoteQueue::RemoteQueue(WebCore::WebGPU::Queue& queue, WebGPU::ObjectHeap& objectHeap, Ref&& streamConnection, RemoteGPU& gpu, WebGPUIdentifier identifier) : m_backing(queue) , m_objectHeap(objectHeap) @@ -98,7 +119,9 @@ void RemoteQueue::writeBuffer( return; } - protect(m_backing)->writeBufferNoCopy(protect(*convertedBuffer), bufferOffset, data ? data->mutableSpan() : std::span { }, 0, std::nullopt); + Ref backing = protect(m_backing); + backing->writeBufferNoCopy(protect(*convertedBuffer), bufferOffset, data->mutableSpan(), 0, std::nullopt); + keepAliveUntilSubmittedWorkDone(backing, WTF::move(data)); completionHandler(true); } @@ -128,15 +151,17 @@ void RemoteQueue::writeTexture( auto convertedDestination = objectHeap->convertFromBacking(destination); ASSERT(convertedDestination); auto convertedDataLayout = objectHeap->convertFromBacking(dataLayout); - ASSERT(convertedDestination); + ASSERT(convertedDataLayout); auto convertedSize = objectHeap->convertFromBacking(size); ASSERT(convertedSize); - if (!convertedDestination || !convertedDestination || !convertedSize || !data || data->size() <= WebGPU::maxCrossProcessResourceCopySize) { + if (!convertedDestination || !convertedDataLayout || !convertedSize || !data || data->size() <= WebGPU::maxCrossProcessResourceCopySize) { completionHandler(false); return; } - protect(m_backing)->writeTexture(*convertedDestination, data ? data->mutableSpan() : std::span { }, *convertedDataLayout, *convertedSize); + Ref backing = protect(m_backing); + backing->writeTexture(*convertedDestination, data->mutableSpan(), *convertedDataLayout, *convertedSize); + keepAliveUntilSubmittedWorkDone(backing, WTF::move(data)); completionHandler(true); } diff --git a/Source/WebKit/Scripts/webkit/messages.py b/Source/WebKit/Scripts/webkit/messages.py index 524a5e71f800d..2bbde2592eb77 100644 --- a/Source/WebKit/Scripts/webkit/messages.py +++ b/Source/WebKit/Scripts/webkit/messages.py @@ -651,6 +651,7 @@ def types_that_cannot_be_forward_declared(): 'WebCore::PlatformMediaError', 'WebCore::PlaybackTargetClientContextIdentifier', 'WebCore::PointerID', + 'WebCore::QualifiedMediaSessionIdentifier', 'WebCore::RTCDataChannelIdentifier', 'WebCore::ReferrerPolicy', 'WebCore::RenderingMode', @@ -1340,6 +1341,7 @@ def headers_for_type(type, for_implementation_file=False): 'WebCore::PlatformMediaSessionRemoteCommandArgument': [''], 'WebCore::PlayingToAutomotiveHeadUnit': [''], 'WebCore::PlaybackSessionModelExternalPlaybackTargetType': [''], + 'WebCore::QualifiedMediaSessionIdentifier': ['', '', ''], 'WebCore::LockBackForwardList': [''], 'WebCore::MediaPlaybackTargetMockState': [''], 'WebCore::MediaPlayerBufferingPolicy': [''], diff --git a/Source/WebKit/Shared/ProcessQualified.serialization.in b/Source/WebKit/Shared/ProcessQualified.serialization.in index 07fb76d678eab..27e9ac25c6c69 100644 --- a/Source/WebKit/Shared/ProcessQualified.serialization.in +++ b/Source/WebKit/Shared/ProcessQualified.serialization.in @@ -24,6 +24,7 @@ additional_forward_declaration: namespace WebCore { using BackForwardFrameItemIdentifierID = ObjectIdentifier; } additional_forward_declaration: namespace WebCore { using BackForwardItemIdentifierID = ObjectIdentifier; } additional_forward_declaration: namespace WebCore { using DOMCacheIdentifierID = AtomicObjectIdentifier; } +additional_forward_declaration: namespace WebCore { using MediaSessionIdentifier = ObjectIdentifier; } additional_forward_declaration: namespace WebCore { using OpaqueOriginIdentifier = AtomicObjectIdentifier; } additional_forward_declaration: namespace WebCore { using PlatformLayerIdentifierID = ObjectIdentifier; } additional_forward_declaration: namespace WebCore { using PlaybackTargetClientContextID = ObjectIdentifier; } @@ -90,6 +91,11 @@ header: WebCore::ProcessIdentifier processIdentifier(); }; +[Alias=class ProcessQualified, CustomHeader] alias WebCore::QualifiedMediaSessionIdentifier { + WebCore::MediaSessionIdentifier object(); + WebCore::ProcessIdentifier processIdentifier(); +}; + [Alias=class ProcessQualified, CustomHeader] alias WebCore::ScriptExecutionContextIdentifier { WTF::UUID object(); WebCore::ProcessIdentifier processIdentifier(); diff --git a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in index 56d13d99cb614..cba068dc5745e 100644 --- a/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in +++ b/Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in @@ -6945,6 +6945,12 @@ enum class WebCore::EncryptionBoxType : uint8_t { TransportStreamEncryptionInitData }; +header: +enum class WebCore::PlatformVideoFieldCount : uint8_t { + Progressive, + Interlaced +}; + header: enum class WebCore::PlatformVideoFieldDetail : uint8_t { TemporalTopFirst, @@ -6988,7 +6994,7 @@ header: WebCore::FloatSize displaySize; uint8_t bitDepth; WebCore::PlatformVideoColorSpace colorSpace; - std::optional fieldCount; + std::optional fieldCount; std::optional fieldDetail; Vector>> extensionAtoms; diff --git a/Source/WebKit/UIProcess/GPU/GPUProcessProxy.cpp b/Source/WebKit/UIProcess/GPU/GPUProcessProxy.cpp index 802b47eb419f7..80ae890b19ed2 100644 --- a/Source/WebKit/UIProcess/GPU/GPUProcessProxy.cpp +++ b/Source/WebKit/UIProcess/GPU/GPUProcessProxy.cpp @@ -210,6 +210,11 @@ GPUProcessProxy::GPUProcessProxy() parameters.drmDevice = drmMainDevice(); #endif +#if ENABLE(VIDEO) || ENABLE(WEB_AUDIO) + if (RefPtr mediaSessionManagerProxy = RemoteMediaSessionManagerProxy::singletonIfCreated()) + parameters.nowPlayingFallbackSession = mediaSessionManagerProxy->computeNowPlayingFallbackSession(); +#endif + #if PLATFORM(COCOA) m_isMetalDebugDeviceEnabledForTesting = s_enableMetalDebugDeviceInNewGPUProcessesForTesting; m_isMetalShaderValidationEnabledForTesting = s_enableMetalShaderValidationInNewGPUProcessesForTesting; diff --git a/Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.cpp b/Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.cpp index d4d3ac19df434..1a9b344c78f1b 100644 --- a/Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.cpp +++ b/Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.cpp @@ -28,6 +28,10 @@ #if ENABLE(VIDEO) || ENABLE(WEB_AUDIO) +#if ENABLE(GPU_PROCESS) +#include "GPUProcessMessages.h" +#include "GPUProcessProxy.h" +#endif #include "MessageSenderInlines.h" #include "RemoteMediaSessionManagerMessages.h" #include "RemoteMediaSessionManagerProxyMessages.h" @@ -138,6 +142,7 @@ void RemoteMediaSessionManagerProxy::addMediaSession(IPC::Connection& connection session->updateState(state); REMOTE_MEDIA_SESSION_MANAGER_BASE_CLASS::addSession(session); + updateNowPlayingFallbackSession(); } void RemoteMediaSessionManagerProxy::removeMediaSession(IPC::Connection& connection, RemoteMediaSessionState&& state) @@ -146,11 +151,12 @@ void RemoteMediaSessionManagerProxy::removeMediaSession(IPC::Connection& connect if (RefPtr session = findAndUpdateSession(connection, state)) removeSession(*session); m_sessionProxies.remove({ state.sessionIdentifier, processIdentifier }); + updateNowPlayingFallbackSession(); } void RemoteMediaSessionManagerProxy::webProcessWillShutDown(WebCore::ProcessIdentifier processIdentifier) { - Vector> staleKeys; + Vector staleKeys; for (auto& key : m_sessionProxies.keys()) { if (key.processIdentifier() == processIdentifier) staleKeys.append(key); @@ -161,6 +167,7 @@ void RemoteMediaSessionManagerProxy::webProcessWillShutDown(WebCore::ProcessIden removeSession(*session); m_sessionProxies.remove(key); } + updateNowPlayingFallbackSession(); // Audio-capture-source counts (getUserMedia) are tracked per page outside m_sessionProxies, so drop // this process's entries too; otherwise countActiveAudioCaptureSources() stays inflated and the audio @@ -196,6 +203,8 @@ void RemoteMediaSessionManagerProxy::updateMediaSessionStates(IPC::Connection& c m_audioCaptureSourceCountsByPage.remove(key); else m_audioCaptureSourceCountsByPage.set(key, audioCaptureSourceCount); + + updateNowPlayingFallbackSession(); } int RemoteMediaSessionManagerProxy::countActiveAudioCaptureSources() @@ -209,6 +218,7 @@ int RemoteMediaSessionManagerProxy::countActiveAudioCaptureSources() void RemoteMediaSessionManagerProxy::mediaSessionStateChanged(IPC::Connection& connection, WebKit::RemoteMediaSessionState&& state) { findAndUpdateSession(connection, state); + updateNowPlayingFallbackSession(); } void RemoteMediaSessionManagerProxy::setCurrentSession(WebCore::PlatformMediaSessionInterface& session) @@ -223,6 +233,36 @@ void RemoteMediaSessionManagerProxy::setCurrentSession(WebCore::PlatformMediaSes } REMOTE_MEDIA_SESSION_MANAGER_BASE_CLASS::setCurrentSession(session); + updateNowPlayingFallbackSession(); +} + +#if ENABLE(GPU_PROCESS) +std::optional RemoteMediaSessionManagerProxy::computeNowPlayingFallbackSession() const +{ + for (auto& weakSession : copySessionsToVector()) { + RefPtr proxy = dynamicDowncast(weakSession.get()); + if (!proxy || !proxy->canReceiveRemoteControlCommands()) + continue; + + if (auto identifier = proxy->qualifiedSessionIdentifier()) + return identifier; + } + + return std::nullopt; +} +#endif + +void RemoteMediaSessionManagerProxy::updateNowPlayingFallbackSession() +{ +#if ENABLE(GPU_PROCESS) + auto fallback = computeNowPlayingFallbackSession(); + if (fallback == m_nowPlayingFallbackSession) + return; + m_nowPlayingFallbackSession = fallback; + + if (RefPtr gpuProcess = GPUProcessProxy::singletonIfCreated()) + gpuProcess->send(Messages::GPUProcess::SetNowPlayingFallbackSession(fallback), 0); +#endif } void RemoteMediaSessionManagerProxy::mediaSessionWillBeginPlayback(IPC::Connection& connection, RemoteMediaSessionState&& state) diff --git a/Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.h b/Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.h index f24604f1e05cb..47c7d4d3c5ea1 100644 --- a/Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.h +++ b/Source/WebKit/UIProcess/Media/RemoteMediaSessionManagerProxy.h @@ -79,6 +79,10 @@ class RemoteMediaSessionManagerProxy static Ref singleton(); static RefPtr singletonIfCreated(); +#if ENABLE(GPU_PROCESS) + std::optional computeNowPlayingFallbackSession() const; +#endif + virtual ~RemoteMediaSessionManagerProxy(); void webProcessWillShutDown(WebCore::ProcessIdentifier); @@ -112,6 +116,8 @@ class RemoteMediaSessionManagerProxy void setCurrentSession(WebCore::PlatformMediaSessionInterface&) final; + void updateNowPlayingFallbackSession(); + void addMediaSessionRestriction(WebCore::PlatformMediaSessionMediaType, WebCore::MediaSessionRestrictions); void removeMediaSessionRestriction(WebCore::PlatformMediaSessionMediaType, WebCore::MediaSessionRestrictions); void resetMediaSessionRestrictions(); @@ -153,8 +159,11 @@ class RemoteMediaSessionManagerProxy ASCIILiteral logClassName() const final; #endif - HashMap, Ref> m_sessionProxies; + HashMap> m_sessionProxies; HashMap, uint64_t> m_audioCaptureSourceCountsByPage; +#if ENABLE(GPU_PROCESS) + std::optional m_nowPlayingFallbackSession; +#endif #if PLATFORM(COCOA) RefPtr m_audioHardwareListenerProxy; diff --git a/Source/WebKit/UIProcess/Media/RemoteMediaSessionProxy.cpp b/Source/WebKit/UIProcess/Media/RemoteMediaSessionProxy.cpp index 39600e4a536e4..6f227f5fef3cc 100644 --- a/Source/WebKit/UIProcess/Media/RemoteMediaSessionProxy.cpp +++ b/Source/WebKit/UIProcess/Media/RemoteMediaSessionProxy.cpp @@ -70,6 +70,15 @@ void RemoteMediaSessionProxy::updateState(const RemoteMediaSessionState& remoteS downcast(protect(client())).updateState(remoteState); } +std::optional RemoteMediaSessionProxy::qualifiedSessionIdentifier() const +{ + RefPtr process = m_process.get(); + if (!process) + return std::nullopt; + + return WebCore::QualifiedMediaSessionIdentifier { m_sessionState.sessionIdentifier, process->coreProcessIdentifier() }; +} + void RemoteMediaSessionProxy::setState(WebCore::PlatformMediaSessionState state) { PlatformMediaSession::setState(state); diff --git a/Source/WebKit/UIProcess/Media/RemoteMediaSessionProxy.h b/Source/WebKit/UIProcess/Media/RemoteMediaSessionProxy.h index 42a9a60572a5e..d3dc84bf9de78 100644 --- a/Source/WebKit/UIProcess/Media/RemoteMediaSessionProxy.h +++ b/Source/WebKit/UIProcess/Media/RemoteMediaSessionProxy.h @@ -30,6 +30,7 @@ #include "MessageSender.h" #include "RemoteMediaSessionState.h" #include +#include namespace WebKit { @@ -48,6 +49,7 @@ class RemoteMediaSessionProxy final WebCore::MediaSessionIdentifier sessionIdentifier() const { return m_sessionState.sessionIdentifier; } WebCore::PageIdentifier pageIdentifier() const { return m_sessionState.pageIdentifier; } + std::optional qualifiedSessionIdentifier() const; private: RemoteMediaSessionProxy(Ref&&, const RemoteMediaSessionState&, WebProcessProxy&); diff --git a/Source/WebKit/WebProcess/GPU/GPUProcessConnection.cpp b/Source/WebKit/WebProcess/GPU/GPUProcessConnection.cpp index eacbf7faccb67..b7c6f0bb565f0 100644 --- a/Source/WebKit/WebProcess/GPU/GPUProcessConnection.cpp +++ b/Source/WebKit/WebProcess/GPU/GPUProcessConnection.cpp @@ -335,10 +335,10 @@ bool GPUProcessConnection::waitForDidInitialize() return m_connection->isValid(); } -void GPUProcessConnection::didReceiveRemoteCommand(PlatformMediaSession::RemoteControlCommandType type, const PlatformMediaSession::RemoteCommandArgument& argument) +void GPUProcessConnection::didReceiveRemoteCommand(PlatformMediaSession::RemoteControlCommandType type, const PlatformMediaSession::RemoteCommandArgument& argument, std::optional targetSession) { #if ENABLE(VIDEO) || ENABLE(WEB_AUDIO) - WebProcess::singleton().didReceiveRemoteCommand(type, argument); + WebProcess::singleton().didReceiveRemoteCommand(type, argument, targetSession); #endif } diff --git a/Source/WebKit/WebProcess/GPU/GPUProcessConnection.h b/Source/WebKit/WebProcess/GPU/GPUProcessConnection.h index a90f17f031b71..59b2c8e1f5581 100644 --- a/Source/WebKit/WebProcess/GPU/GPUProcessConnection.h +++ b/Source/WebKit/WebProcess/GPU/GPUProcessConnection.h @@ -37,6 +37,7 @@ #include "StreamServerConnection.h" #include "WebGPUIdentifier.h" #include +#include #include #include #include @@ -150,7 +151,7 @@ class GPUProcessConnection : public ThreadSafeRefCountedAndCanMakeThreadSafeWeak bool dispatchSyncMessage(IPC::Connection&, IPC::Decoder&, UniqueRef&); // Messages. - void didReceiveRemoteCommand(WebCore::PlatformMediaSession::RemoteControlCommandType, const WebCore::PlatformMediaSession::RemoteCommandArgument&); + void didReceiveRemoteCommand(WebCore::PlatformMediaSession::RemoteControlCommandType, const WebCore::PlatformMediaSession::RemoteCommandArgument&, std::optional targetSession); void didInitialize(std::optional&&); #if ENABLE(ROUTING_ARBITRATION) diff --git a/Source/WebKit/WebProcess/GPU/GPUProcessConnection.messages.in b/Source/WebKit/WebProcess/GPU/GPUProcessConnection.messages.in index 078f3688aa3fd..b8964417b3dd4 100644 --- a/Source/WebKit/WebProcess/GPU/GPUProcessConnection.messages.in +++ b/Source/WebKit/WebProcess/GPU/GPUProcessConnection.messages.in @@ -28,7 +28,7 @@ ] messages -> GPUProcessConnection WantsDispatchMessage { DidInitialize(struct std::optional info) CanDispatchOutOfOrder - DidReceiveRemoteCommand(enum:uint8_t WebCore::PlatformMediaSessionRemoteControlCommandType type, struct WebCore::PlatformMediaSessionRemoteCommandArgument argument) + DidReceiveRemoteCommand(enum:uint8_t WebCore::PlatformMediaSessionRemoteControlCommandType type, struct WebCore::PlatformMediaSessionRemoteCommandArgument argument, std::optional targetSession) #if ENABLE(ROUTING_ARBITRATION) BeginRoutingArbitrationWithCategory(enum:uint8_t WebCore::AudioSessionCategory category) -> (enum:uint8_t WebCore::AudioSessionRoutingArbitrationError error, enum:bool WebCore::AudioSessionRoutingArbitrationClient::DefaultRouteChanged defaultRouteChanged) diff --git a/Source/WebKit/WebProcess/GPU/media/WebMediaStrategy.cpp b/Source/WebKit/WebProcess/GPU/media/WebMediaStrategy.cpp index 8d3f10ed48f41..5607b3c552fc3 100644 --- a/Source/WebKit/WebProcess/GPU/media/WebMediaStrategy.cpp +++ b/Source/WebKit/WebProcess/GPU/media/WebMediaStrategy.cpp @@ -162,6 +162,40 @@ void WebMediaStrategy::isActiveNowPlayingSessionInGPUProcessForTesting(WebCore:: completion(false); } +void WebMediaStrategy::isRemoteCommandTargetSessionInGPUProcessForTesting(WebCore::MediaSessionIdentifier identifier, CompletionHandler&& completion) +{ +#if ENABLE(GPU_PROCESS) + if (m_useGPUProcess) { + RefPtr gpuProcessConnection = WebProcess::singleton().existingGPUProcessConnection(); + if (!gpuProcessConnection) { + completion(false); + return; + } + gpuProcessConnection->connection().sendWithAsyncReply(Messages::GPUConnectionToWebProcess::IsRemoteCommandTargetSessionForTesting(identifier), WTF::move(completion), 0); + return; + } +#endif + + completion(false); +} + +bool WebMediaStrategy::postNowPlayingRemoteControlCommandToGPUProcessForTesting(WebCore::PlatformMediaSession::RemoteControlCommandType type, const WebCore::PlatformMediaSession::RemoteCommandArgument& argument) +{ +#if ENABLE(GPU_PROCESS) + if (m_useGPUProcess) { + if (RefPtr gpuProcessConnection = WebProcess::singleton().existingGPUProcessConnection()) { + gpuProcessConnection->connection().send(Messages::GPUConnectionToWebProcess::PostNowPlayingRemoteControlCommandForTesting(type, argument), 0); + return true; + } + } +#else + UNUSED_PARAM(type); + UNUSED_PARAM(argument); +#endif + + return false; +} + bool WebMediaStrategy::hasThreadSafeMediaSourceSupport() const { #if USE(AVFOUNDATION) diff --git a/Source/WebKit/WebProcess/GPU/media/WebMediaStrategy.h b/Source/WebKit/WebProcess/GPU/media/WebMediaStrategy.h index 69c9cf9c986c7..76da8ff106abf 100644 --- a/Source/WebKit/WebProcess/GPU/media/WebMediaStrategy.h +++ b/Source/WebKit/WebProcess/GPU/media/WebMediaStrategy.h @@ -53,6 +53,8 @@ class WebMediaStrategy final : public WebCore::MediaStrategy { #endif std::unique_ptr createNowPlayingManager() const final; void isActiveNowPlayingSessionInGPUProcessForTesting(WebCore::MediaSessionIdentifier, CompletionHandler&&) final; + void isRemoteCommandTargetSessionInGPUProcessForTesting(WebCore::MediaSessionIdentifier, CompletionHandler&&) final; + bool postNowPlayingRemoteControlCommandToGPUProcessForTesting(WebCore::PlatformMediaSession::RemoteControlCommandType, const WebCore::PlatformMediaSession::RemoteCommandArgument&) final; bool hasThreadSafeMediaSourceSupport() const final; #if ENABLE(MEDIA_SOURCE) void enableMockMediaSource() final; diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp index b907a0785d3a6..f564033adad34 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp +++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp @@ -7513,10 +7513,10 @@ void WebPage::processDidResume() manager->processDidResume(); } -void WebPage::didReceiveRemoteCommand(PlatformMediaSession::RemoteControlCommandType type, const PlatformMediaSession::RemoteCommandArgument& argument) +bool WebPage::didReceiveRemoteCommand(PlatformMediaSession::RemoteControlCommandType type, const PlatformMediaSession::RemoteCommandArgument& argument, std::optional targetSession) { - if (RefPtr manager = mediaSessionManagerIfExists()) - manager->processDidReceiveRemoteControlCommand(type, argument); + RefPtr manager = mediaSessionManagerIfExists(); + return manager && manager->processDidReceiveRemoteControlCommand(type, argument, targetSession); } void WebPage::setMayStartMediaWhenInWindow(bool mayStartMedia) diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h index 18638585acb3f..ea810a6c68703 100644 --- a/Source/WebKit/WebProcess/WebPage/WebPage.h +++ b/Source/WebKit/WebProcess/WebPage/WebPage.h @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -123,10 +124,6 @@ #include #endif -#if ENABLE(MEDIA_USAGE) -#include -#endif - #if PLATFORM(COCOA) #include "GestureTypes.h" #include @@ -1601,7 +1598,7 @@ class WebPage final : public API::ObjectImpl, pub void processWillSuspend(); void processDidResume(); - void didReceiveRemoteCommand(WebCore::PlatformMediaSessionRemoteControlCommandType, const WebCore::PlatformMediaSessionRemoteCommandArgument&); + bool didReceiveRemoteCommand(WebCore::PlatformMediaSessionRemoteControlCommandType, const WebCore::PlatformMediaSessionRemoteCommandArgument&, std::optional targetSession); #if PLATFORM(COCOA) void processSystemWillSleep() const; diff --git a/Source/WebKit/WebProcess/WebProcess.cpp b/Source/WebKit/WebProcess/WebProcess.cpp index db1e74839a1a2..3286c8a22abb9 100644 --- a/Source/WebKit/WebProcess/WebProcess.cpp +++ b/Source/WebKit/WebProcess/WebProcess.cpp @@ -2699,10 +2699,29 @@ void WebProcess::setResourceMonitorContentRuleListAsync(WebCompiledContentRuleLi } #endif -void WebProcess::didReceiveRemoteCommand(PlatformMediaSession::RemoteControlCommandType type, const PlatformMediaSession::RemoteCommandArgument& argument) +void WebProcess::didReceiveRemoteCommand(PlatformMediaSession::RemoteControlCommandType type, const PlatformMediaSession::RemoteCommandArgument& argument, std::optional targetSession) { - for (auto& page : m_pageMap.values()) - page->didReceiveRemoteCommand(type, argument); + if (!targetSession) { + // Non-site-isolated NowPlaying: every page's manager re-selects locally, as it always has. + for (auto& page : m_pageMap.values()) + page->didReceiveRemoteCommand(type, argument, std::nullopt); + return; + } + + // The GPU process elected one session, and at most one page's manager owns it. + for (auto& page : m_pageMap.values()) { + if (page->didReceiveRemoteCommand(type, argument, targetSession)) + return; + } + + // The elected session went away or stopped accepting commands between the election and now. Fall back to local + // re-selection so the command is not dropped, as it would be without site isolation. Best effort only: m_pageMap + // has no stable order and, under site isolation, each page has its own manager, so there is no cross-page + // current-session order to follow here. + for (auto& page : m_pageMap.values()) { + if (page->didReceiveRemoteCommand(type, argument, std::nullopt)) + return; + } } void WebProcess::contentWorldDestroyed(ContentWorldIdentifier identifier) diff --git a/Source/WebKit/WebProcess/WebProcess.h b/Source/WebKit/WebProcess/WebProcess.h index 882993c06a471..4b0061a350ffb 100644 --- a/Source/WebKit/WebProcess/WebProcess.h +++ b/Source/WebKit/WebProcess/WebProcess.h @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -530,7 +531,7 @@ class WebProcess final : public AuxiliaryProcess { void registerFontMap(HashMap&&, HashMap>&&, Vector&& sandboxExtensions); #endif - void didReceiveRemoteCommand(WebCore::PlatformMediaSessionRemoteControlCommandType, const WebCore::PlatformMediaSessionRemoteCommandArgument&); + void didReceiveRemoteCommand(WebCore::PlatformMediaSessionRemoteControlCommandType, const WebCore::PlatformMediaSessionRemoteCommandArgument&, std::optional targetSession); #if ENABLE(INITIALIZE_ACCESSIBILITY_ON_DEMAND) void initializeAccessibility(Vector&&); diff --git a/Tools/TestWebKitAPI/Tests/WebCore/cocoa/CoreMediaUtilities.mm b/Tools/TestWebKitAPI/Tests/WebCore/cocoa/CoreMediaUtilities.mm index 68a13d083d8df..d03af66c50dd1 100644 --- a/Tools/TestWebKitAPI/Tests/WebCore/cocoa/CoreMediaUtilities.mm +++ b/Tools/TestWebKitAPI/Tests/WebCore/cocoa/CoreMediaUtilities.mm @@ -765,9 +765,10 @@ TEST(CMUtilities, FieldCountAndDetailRoundTrip) { + using C = WebCore::PlatformVideoFieldCount; using F = WebCore::PlatformVideoFieldDetail; - auto testFieldDetail = [&](uint8_t fieldCount, F input) { + auto testFieldDetail = [&](C fieldCount, F input) { auto videoInfo = WebCore::VideoInfo::create({ { .codecName = WebCore::FourCC('avc1') }, { .size = { 640, 480 }, @@ -782,11 +783,11 @@ EXPECT_EQ(WebCore::fieldDetailFromFormatDescription(desc.get()), input); }; - testFieldDetail(2, F::TemporalTopFirst); - testFieldDetail(2, F::TemporalBottomFirst); - testFieldDetail(2, F::SpatialFirstLineEarly); - testFieldDetail(2, F::SpatialFirstLineLate); - testFieldDetail(1, F::TemporalTopFirst); + testFieldDetail(C::Interlaced, F::TemporalTopFirst); + testFieldDetail(C::Interlaced, F::TemporalBottomFirst); + testFieldDetail(C::Interlaced, F::SpatialFirstLineEarly); + testFieldDetail(C::Interlaced, F::SpatialFirstLineLate); + testFieldDetail(C::Progressive, F::TemporalTopFirst); } TEST(CMUtilities, AbsentFieldInfoStaysAbsent)