From 4ae31dc77784021877bbc2adf5dbce8fab1d6877 Mon Sep 17 00:00:00 2001 From: InvalidUsernameException Date: Mon, 1 Jun 2026 13:32:37 +0200 Subject: [PATCH] LibWeb: Invalidate layout tree of parent when replacing `CharacterData` When replacing the data in `CharacterData`, we were only invalidating text-dependent cache and layout of the layout node(s) corresponding to that `CharacterData`. This is not sufficient: With `::first-letter` styling, `TextSliceNode`s are being created for the first letter and the remainder of the text. So, contrary to regular `TextNode`s, these store offsets into the text data determined while building the layout tree. Subsequently these become stale when only invalidating layout, but not the layout tree. Using such stale offsets with updated text data leads to crashes in `TextNode::compute_text_for_rendering()` if the text got shorter and incorrect behavior if it got longer. To fix this, invalidate the layout tree of the parent of the affected `TextSliceNode`s, causing a rebuild that creates new slice nodes with the updated data. This fixes a crash when typing in the search box on https://search.brave.com/ where a first-letter style is used to capitalize the first letter of the search suggestions descriptions. This crash was a regression from b67d73a661fb995d567ecf9d2c090a995cb2deda. --- Libraries/LibWeb/DOM/CharacterData.cpp | 16 +++++++++------ Libraries/LibWeb/DOM/Node.h | 1 + ...Value-of-first-letter-styled-text-ref.html | 2 ++ ...nodeValue-of-first-letter-styled-text.html | 20 +++++++++++++++++++ 4 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 Tests/LibWeb/Ref/expected/modify-nodeValue-of-first-letter-styled-text-ref.html create mode 100644 Tests/LibWeb/Ref/input/modify-nodeValue-of-first-letter-styled-text.html diff --git a/Libraries/LibWeb/DOM/CharacterData.cpp b/Libraries/LibWeb/DOM/CharacterData.cpp index fda2fb10e4..9dd79bd0ce 100644 --- a/Libraries/LibWeb/DOM/CharacterData.cpp +++ b/Libraries/LibWeb/DOM/CharacterData.cpp @@ -140,17 +140,21 @@ WebIDL::ExceptionOr CharacterData::replace_data(size_t offset, size_t coun return {}; // NB: Called during DOM text mutation, layout is stale. - if (auto* text = as_if(*this)) { - Layout::TextOffsetMapping mapping { *text }; - mapping.for_each_fragment([](Layout::TextNode& slice) { + if (is(*this)) { + if (is(unsafe_layout_node())) { + // NB: Slice nodes cache data that is calculated at layout tree construction time. + // So for them, we need to invalidate the layout tree, not just layout. + if (parent()) + parent()->set_needs_layout_tree_update(true, SetNeedsLayoutTreeUpdateReason::CharacterDataReplaceData); + } else if (auto* text_layout_node = as_if(unsafe_layout_node())) { // NB: Since the text node's data has changed, we need to invalidate the text for rendering. // This ensures that the new text is reflected in layout, even if we don't end up doing a full layout // tree rebuild. - slice.invalidate_text_for_rendering(); + text_layout_node->invalidate_text_for_rendering(); // We also need to relayout. - slice.set_needs_layout_update(SetNeedsLayoutReason::CharacterDataReplaceData); - }); + text_layout_node->set_needs_layout_update(SetNeedsLayoutReason::CharacterDataReplaceData); + } } document().bump_character_data_version(); diff --git a/Libraries/LibWeb/DOM/Node.h b/Libraries/LibWeb/DOM/Node.h index 9c12c81811..fda4135ee4 100644 --- a/Libraries/LibWeb/DOM/Node.h +++ b/Libraries/LibWeb/DOM/Node.h @@ -82,6 +82,7 @@ enum class SetNeedsLayoutReason { [[nodiscard]] StringView to_string(SetNeedsLayoutReason); #define ENUMERATE_SET_NEEDS_LAYOUT_TREE_UPDATE_REASONS(X) \ + X(CharacterDataReplaceData) \ X(ElementSetInnerHTML) \ X(ElementSetShadowRoot) \ X(DetailsElementOpenedOrClosed) \ diff --git a/Tests/LibWeb/Ref/expected/modify-nodeValue-of-first-letter-styled-text-ref.html b/Tests/LibWeb/Ref/expected/modify-nodeValue-of-first-letter-styled-text-ref.html new file mode 100644 index 0000000000..cf01330a54 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/modify-nodeValue-of-first-letter-styled-text-ref.html @@ -0,0 +1,2 @@ + +

Short text

diff --git a/Tests/LibWeb/Ref/input/modify-nodeValue-of-first-letter-styled-text.html b/Tests/LibWeb/Ref/input/modify-nodeValue-of-first-letter-styled-text.html new file mode 100644 index 0000000000..51bd9250f5 --- /dev/null +++ b/Tests/LibWeb/Ref/input/modify-nodeValue-of-first-letter-styled-text.html @@ -0,0 +1,20 @@ + + + + +

this is some very long text. Well, actually not all that long, but long enough for our purposes.

+ +