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