LibWeb: Use Utf16StringBuilder for UTF-16 text assembly

Replace the remaining UTF-16-mode StringBuilder instances in LibWeb
with Utf16StringBuilder when they assemble Utf16String values or UTF-16
text views. This keeps those paths in UTF-16 throughout and uses
explicit ASCII append helpers for ASCII-only pieces.

Keep StringBuilder in place for JSON, markup, CSS serialization, and
other byte-oriented string construction paths.
This commit is contained in:
Andreas Kling 2026-06-22 13:30:00 +02:00 committed by Andreas Kling
parent 950c460efd
commit a1bc2a6223
19 changed files with 82 additions and 67 deletions

View file

@ -5,6 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Utf16StringBuilder.h>
#include <LibJS/Runtime/ExternalMemory.h>
#include <LibUnicode/Segmenter.h>
#include <LibWeb/Bindings/CharacterData.h>
@ -89,13 +90,13 @@ WebIDL::ExceptionOr<void> CharacterData::replace_data(size_t offset, size_t coun
auto before_data = m_data.substring_view(0, offset);
auto after_data = m_data.substring_view(offset + count);
StringBuilder full_data(StringBuilder::Mode::UTF16, before_data.length_in_code_units() + data.length_in_code_units() + after_data.length_in_code_units());
Utf16StringBuilder full_data(before_data.length_in_code_units() + data.length_in_code_units() + after_data.length_in_code_units());
full_data.append(before_data);
full_data.append(data);
full_data.append(after_data);
auto old_data = m_data;
m_data = full_data.to_utf16_string();
m_data = full_data.to_string();
// 4. Queue a mutation record of "characterData" for node with null, null, nodes data, « », « », null, and null.
// NOTE: We do this later so that the mutation observer may notify UI clients of this node's new value.

View file

@ -12,6 +12,7 @@
#include <AK/JsonObjectSerializer.h>
#include <AK/NeverDestroyed.h>
#include <AK/StringBuilder.h>
#include <AK/Utf16StringBuilder.h>
#include <LibGC/DeferGC.h>
#include <LibGC/WeakHashMap.h>
#include <LibJS/Runtime/ExternalMemory.h>
@ -191,14 +192,14 @@ Optional<String> Node::alternative_text() const
// https://dom.spec.whatwg.org/#concept-descendant-text-content
Utf16String Node::descendant_text_content() const
{
StringBuilder builder(StringBuilder::Mode::UTF16);
Utf16StringBuilder builder;
for_each_in_subtree_of_type<Text>([&](auto& text_node) {
builder.append(text_node.data());
return TraversalDecision::Continue;
});
return builder.to_utf16_string();
return builder.to_string();
}
// https://dom.spec.whatwg.org/#dom-node-textcontent
@ -310,12 +311,12 @@ WebIDL::ExceptionOr<void> Node::normalize()
}
// 3. Let data be the concatenation of the data of nodes contiguous exclusive Text nodes (excluding itself), in tree order.
StringBuilder data(StringBuilder::Mode::UTF16);
Utf16StringBuilder data;
for (auto const& text_node : contiguous_exclusive_text_nodes_excluding_self(node))
data.append(text_node->data());
// 4. Replace data with node node, offset length, count 0, and data data.
TRY(character_data.replace_data(length, 0, data.to_utf16_string()));
TRY(character_data.replace_data(length, 0, data.to_string()));
// 5. Let currentNode be nodes next sibling.
auto* current_node = node.next_sibling();
@ -471,7 +472,7 @@ Utf16String Node::child_text_content() const
if (!parent_node)
return {};
StringBuilder builder(StringBuilder::Mode::UTF16);
Utf16StringBuilder builder;
parent_node->for_each_child_of_type<Text>([&](auto const& child) {
if (auto content = child.text_content(); content.has_value())
@ -479,7 +480,7 @@ Utf16String Node::child_text_content() const
return IterationDecision::Continue;
});
return builder.to_utf16_string();
return builder.to_string();
}
// https://dom.spec.whatwg.org/#concept-shadow-including-root
@ -3362,13 +3363,13 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
// aria-labelledby or aria-describedby and/or un-hidden. See the comment for substep A above.
if (is_text() && (!parent_element() || (parent_element()->is_referenced() || !parent_element()->is_hidden() || !parent_element()->has_hidden_ancestor() || parent_element()->has_referenced_and_hidden_ancestor()))) {
if (layout_node()) {
StringBuilder builder { StringBuilder::Mode::UTF16 };
Utf16StringBuilder builder;
Layout::TextOffsetMapping mapping { static_cast<DOM::Text const&>(*this) };
mapping.for_each_fragment([&](Layout::TextNode const& slice) {
builder.append(slice.text_for_rendering());
});
if (!builder.is_empty())
return builder.to_utf16_string().to_utf8_but_should_be_ported_to_utf16();
return builder.to_string().to_utf8_but_should_be_ported_to_utf16();
}
return text_content()->to_utf8_but_should_be_ported_to_utf16();
}

View file

@ -9,6 +9,7 @@
*/
#include <AK/NeverDestroyed.h>
#include <AK/Utf16StringBuilder.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/Range.h>
#include <LibWeb/DOM/Comment.h>
@ -569,7 +570,7 @@ WebIDL::ExceptionOr<WebIDL::Short> Range::compare_point(GC::Ref<Node> node, WebI
Utf16String Range::to_string() const
{
// 1. Let s be the empty string.
StringBuilder builder(StringBuilder::Mode::UTF16);
Utf16StringBuilder builder;
// 2. If thiss start node is thiss end node and it is a Text node,
// then return the substring of that Text nodes data beginning at thiss start offset and ending at thiss end offset.
@ -593,7 +594,7 @@ Utf16String Range::to_string() const
builder.append(MUST(end_text->substring_data(0, end_offset())));
// 6. Return s.
return builder.to_utf16_string();
return builder.to_string();
}
// https://dom.spec.whatwg.org/#dom-range-extractcontents

View file

@ -5,6 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Utf16StringBuilder.h>
#include <LibUnicode/CharacterTypes.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/Text.h>
@ -141,11 +142,11 @@ Utf16String Text::whole_text()
current_node = current_node->next_sibling();
}
StringBuilder builder(StringBuilder::Mode::UTF16);
Utf16StringBuilder builder;
for (auto const& text_node : nodes)
builder.append(text_node->data());
return builder.to_utf16_string();
return builder.to_string();
}
// https://html.spec.whatwg.org/multipage/dom.html#text-node-directionality

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Utf16StringBuilder.h>
#include <LibGfx/Color.h>
#include <LibWeb/Bindings/Document.h>
#include <LibWeb/CSS/CascadedProperties.h>
@ -286,16 +287,18 @@ Utf16String canonical_space_sequence(size_t length, bool non_breaking_start, boo
return "\u00A0"_utf16;
// 4. Let buffer be the empty string.
StringBuilder buffer { StringBuilder::Mode::UTF16 };
Utf16StringBuilder buffer;
// 5. If non-breaking start is true, let repeated pair be U+00A0 U+0020. Otherwise, let it be
// U+0020 U+00A0.
auto repeated_pair = non_breaking_start ? "\u00A0 "sv : " \u00A0"sv;
auto first_repeated_code_unit = non_breaking_start ? u'\u00A0' : u' ';
auto second_repeated_code_unit = non_breaking_start ? u' ' : u'\u00A0';
// 6. While n is greater than three, append repeated pair to buffer and subtract two from n.
// AD-HOC: Other browsers seem to fit in as many repeated pairs until the remaining length is <= 2.
while (n > 2) {
buffer.append(repeated_pair);
buffer.append_code_unit(first_repeated_code_unit);
buffer.append_code_unit(second_repeated_code_unit);
n -= 2;
}
@ -330,16 +333,16 @@ Utf16String canonical_space_sequence(size_t length, bool non_breaking_start, boo
// AD-HOC: Other browsers seem to ignore the above and deal differently with padding the remainder; the first
// remaining position is filled with the first character from repeated pair.
if (n > 0) {
buffer.append(repeated_pair.substring_view(0, 1) == " "sv ? " "sv : "\u00A0"sv);
buffer.append_code_unit(first_repeated_code_unit);
--n;
}
// AD-HOC: Then, the final position is set depending on the value of non-breaking end.
if (n > 0)
buffer.append(non_breaking_end ? "\u00A0"sv : " "sv);
buffer.append_code_unit(non_breaking_end ? u'\u00A0' : u' ');
// 9. Return buffer.
return buffer.to_utf16_string();
return buffer.to_string();
}
// https://w3c.github.io/editing/docs/execCommand/#canonicalize-whitespace

View file

@ -7,7 +7,7 @@
*/
#include <AK/NeverDestroyed.h>
#include <AK/StringBuilder.h>
#include <AK/Utf16StringBuilder.h>
#include <LibURL/Origin.h>
#include <LibURL/URL.h>
#include <LibWeb/Crypto/Crypto.h>
@ -29,10 +29,10 @@ BlobURLStore& blob_url_store()
ErrorOr<Utf16String> generate_new_blob_url()
{
// 1. Let result be the empty string.
StringBuilder result { StringBuilder::Mode::UTF16 };
Utf16StringBuilder result;
// 2. Append the string "blob:" to result.
TRY(result.try_append("blob:"sv));
result.append_ascii("blob:"sv);
// 3. Let settings be the current settings object
auto& settings = HTML::current_settings_object();
@ -48,17 +48,17 @@ ErrorOr<Utf16String> generate_new_blob_url()
serialized = "ladybird"_string;
// 7. Append serialized to result.
TRY(result.try_append(serialized));
result.append_ascii(serialized.bytes_as_string_view());
// 8. Append U+0024 SOLIDUS (/) to result.
TRY(result.try_append('/'));
result.append_ascii('/');
// 9. Generate a UUID [RFC4122] as a string and append it to result.
auto uuid = Crypto::generate_random_uuid();
TRY(result.try_append(uuid));
result.append_ascii(uuid.bytes_as_string_view());
// 10. Return result.
return result.to_utf16_string();
return result.to_string();
}
// https://w3c.github.io/FileAPI/#add-an-entry

View file

@ -8,6 +8,7 @@
#include <AK/Base64.h>
#include <AK/ByteBuffer.h>
#include <AK/Time.h>
#include <AK/Utf16StringBuilder.h>
#include <LibGC/Heap.h>
#include <LibJS/Runtime/Promise.h>
#include <LibJS/Runtime/Realm.h>
@ -139,10 +140,10 @@ WebIDL::ExceptionOr<FileReader::Result> FileReader::blob_package_data(JS::Realm&
return JS::ArrayBuffer::create(realm, move(bytes));
case Type::BinaryString:
// Return bytes as a binary string, in which every byte is represented by a code unit of equal value [0..255].
StringBuilder builder(StringBuilder::Mode::UTF16, bytes.size());
Utf16StringBuilder builder(bytes.size());
for (auto byte : bytes.bytes())
builder.append_code_unit(byte);
return MUST(builder.utf16_string_view().to_utf8());
return MUST(builder.view().to_utf8());
}
VERIFY_NOT_REACHED();
}

View file

@ -12,6 +12,7 @@
#include <AK/Checked.h>
#include <AK/NumericLimits.h>
#include <AK/OwnPtr.h>
#include <AK/Utf16StringBuilder.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/CanvasCommandList.h>
#include <LibGfx/CompositingAndBlendingOperator.h>
@ -981,11 +982,11 @@ CanvasRenderingContext2D::PreparedText CanvasRenderingContext2D::prepare_text(Ut
}
// 2. Replace all ASCII whitespace in text with U+0020 SPACE characters.
StringBuilder builder { StringBuilder::Mode::UTF16, text.length_in_code_units() };
Utf16StringBuilder builder { text.length_in_code_units() };
for (auto c : text) {
builder.append_code_point(Infra::is_ascii_whitespace(c) ? ' ' : c);
}
auto replaced_text = builder.to_utf16_string();
auto replaced_text = builder.to_string();
// 3. Let font be the current font of target, as given by that object's font attribute.
auto glyph_runs = Gfx::shape_text({ 0, 0 }, replaced_text.utf16_view(), *font_cascade_list(), resolved_letter_spacing());

View file

@ -6,6 +6,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Utf16StringBuilder.h>
#include <LibUnicode/CharacterTypes.h>
#include <LibUnicode/Segmenter.h>
#include <LibWeb/CSS/Invalidation/FormControlInvalidator.h>
@ -887,20 +888,20 @@ WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_range_text(Utf16
// 9. If start is less than end, delete the sequence of code units within the element's relevant value starting with
// the code unit at the startth position and ending with the code unit at the (end-1)th position.
if (start < end) {
StringBuilder builder(StringBuilder::Mode::UTF16, the_relevant_value.length_in_code_units() - (end - start));
Utf16StringBuilder builder(the_relevant_value.length_in_code_units() - (end - start));
builder.append(the_relevant_value.substring_view(0, start));
builder.append(the_relevant_value.substring_view(end));
the_relevant_value = builder.to_utf16_string();
the_relevant_value = builder.to_string();
}
// 10. Insert the value of the first argument into the text of the relevant value of the text control, immediately before the startth code unit.
StringBuilder builder(StringBuilder::Mode::UTF16, the_relevant_value.length_in_code_units() + replacement.length_in_code_units());
Utf16StringBuilder builder(the_relevant_value.length_in_code_units() + replacement.length_in_code_units());
builder.append(the_relevant_value.substring_view(0, start));
builder.append(replacement);
builder.append(the_relevant_value.substring_view(start));
the_relevant_value = builder.to_utf16_string();
the_relevant_value = builder.to_string();
TRY(set_relevant_value(the_relevant_value));
// 11. Let new length be the length of the value of the first argument.

View file

@ -5,7 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/StringBuilder.h>
#include <AK/Utf16StringBuilder.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibWeb/ARIA/Roles.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
@ -418,7 +418,7 @@ Utf16String HTMLElement::get_the_text_steps()
// 6. Replace each remaining run of consecutive required line break count items with a string consisting of as many
// U+000A LF code points as the maximum of the values in the required line break count items.
StringBuilder builder(StringBuilder::Mode::UTF16);
Utf16StringBuilder builder;
for (size_t i = 0; i < results.size(); ++i) {
results[i].visit(
[&](Utf16String const& string) {
@ -435,12 +435,12 @@ Utf16String HTMLElement::get_the_text_steps()
// Skip over the run of required line break counts.
i = j - 1;
builder.append_repeated('\n', max_line_breaks);
builder.append_repeated_ascii('\n', max_line_breaks);
});
}
// 7. Return the concatenation of the string items in results.
return builder.to_utf16_string();
return builder.to_string();
}
// https://html.spec.whatwg.org/multipage/dom.html#dom-innertext

View file

@ -1843,7 +1843,7 @@ Utf16String HTMLInputElement::value_sanitization_algorithm(Utf16String const& va
if (!value.contains('\r') && !value.contains('\n'))
return value;
StringBuilder builder(StringBuilder::Mode::UTF16);
Utf16StringBuilder builder;
for (size_t i = 0; i < value.length_in_code_units(); ++i) {
auto code_unit = value.code_unit_at(i);
@ -1851,7 +1851,7 @@ Utf16String HTMLInputElement::value_sanitization_algorithm(Utf16String const& va
builder.append_code_unit(code_unit);
}
return builder.to_utf16_string();
return builder.to_string();
};
auto strip_newlines_and_trim = [&]() {

View file

@ -5,7 +5,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/StringBuilder.h>
#include <AK/Utf16StringBuilder.h>
#include <LibWeb/ARIA/Roles.h>
#include <LibWeb/Bindings/HTMLOptionElement.h>
#include <LibWeb/Bindings/Intrinsics.h>
@ -119,7 +119,7 @@ void HTMLOptionElement::set_value(Utf16String const& value)
set_attribute_value(HTML::AttributeNames::value, value.to_utf8_but_should_be_ported_to_utf16());
}
static void concatenate_descendants_text_content(DOM::Node const* node, StringBuilder& builder)
static void concatenate_descendants_text_content(DOM::Node const* node, Utf16StringBuilder& builder)
{
if (is<HTMLScriptElement>(node) || is<SVG::SVGScriptElement>(node))
return;
@ -151,7 +151,7 @@ void HTMLOptionElement::set_label(String const& label)
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-text
Utf16String HTMLOptionElement::text() const
{
StringBuilder builder(StringBuilder::Mode::UTF16);
Utf16StringBuilder builder;
// Concatenation of data of all the Text node descendants of the option element, in tree order,
// excluding any that are descendants of descendants of the option element that are themselves
@ -162,7 +162,7 @@ Utf16String HTMLOptionElement::text() const
});
// Return the result of stripping and collapsing ASCII whitespace from the above concatenation.
return Infra::strip_and_collapse_whitespace(builder.to_utf16_string());
return Infra::strip_and_collapse_whitespace(builder.to_string());
}
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-option-text

View file

@ -13,6 +13,7 @@
#include <AK/GenericLexer.h>
#include <AK/String.h>
#include <AK/Utf16String.h>
#include <AK/Utf16StringBuilder.h>
#include <AK/Utf16View.h>
#include <AK/Utf8View.h>
#include <LibWeb/Infra/CharacterTypes.h>
@ -52,19 +53,19 @@ Utf16String normalize_newlines(Utf16String const& string)
return string;
// FIXME: Implement a UTF-16 GenericLexer.
StringBuilder builder(StringBuilder::Mode::UTF16, string.length_in_code_units());
Utf16StringBuilder builder(string.length_in_code_units());
for (size_t i = 0; i < string.length_in_code_units(); ++i) {
if (auto code_unit = string.code_unit_at(i); code_unit == '\r') {
if (i + 1 < string.length_in_code_units() && string.code_unit_at(i + 1) == '\n')
++i;
builder.append('\n');
builder.append_ascii('\n');
} else {
builder.append_code_unit(code_unit);
}
}
return builder.to_utf16_string();
return builder.to_string();
}
// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
@ -92,12 +93,12 @@ Utf16String strip_and_collapse_whitespace(Utf16String const& string)
if (!string.contains_any_of(Infra::ASCII_WHITESPACE_CODE_POINTS))
return string;
StringBuilder builder(StringBuilder::Mode::UTF16);
Utf16StringBuilder builder;
for (auto code_point : string) {
if (Infra::is_ascii_whitespace(code_point)) {
if (!builder.utf16_string_view().ends_with(' '))
builder.append(' ');
if (!builder.view().ends_with(' '))
builder.append_ascii(' ');
continue;
}
@ -105,7 +106,7 @@ Utf16String strip_and_collapse_whitespace(Utf16String const& string)
}
// ...and then remove any leading and trailing ASCII whitespace from that string.
return builder.to_utf16_string().trim(Infra::ASCII_WHITESPACE);
return builder.to_string().trim(Infra::ASCII_WHITESPACE);
}
// https://infra.spec.whatwg.org/#code-unit-prefix

View file

@ -9,6 +9,7 @@
*/
#include <AK/Debug.h>
#include <AK/Utf16StringBuilder.h>
#include <LibGfx/BoundingBox.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Path.h>
@ -439,7 +440,7 @@ Gfx::Path SVGFormattingContext::compute_path_for_text(SVGTextBox const& text_box
static Utf16String rendered_text_contents(SVG::SVGTextContentElement const& element)
{
StringBuilder builder(StringBuilder::Mode::UTF16);
Utf16StringBuilder builder;
element.for_each_in_subtree_of_type<DOM::Text>([&](auto const& text_node) {
if (text_node.parent() && text_node.parent()->unsafe_layout_node()) {
if (auto content = text_node.text_content(); content.has_value())
@ -447,7 +448,7 @@ static Utf16String rendered_text_contents(SVG::SVGTextContentElement const& elem
}
return TraversalDecision::Continue;
});
return builder.to_utf16_string().trim_ascii_whitespace();
return builder.to_string().trim_ascii_whitespace();
}
Gfx::Path SVGFormattingContext::compute_path_for_text_path(SVGTextPathBox const& text_path_box) const

View file

@ -8,8 +8,8 @@
*/
#include <AK/CharacterTypes.h>
#include <AK/StringBuilder.h>
#include <AK/UnicodeUtils.h>
#include <AK/Utf16StringBuilder.h>
#include <LibUnicode/CharacterTypes.h>
#include <LibUnicode/Locale.h>
#include <LibWeb/DOM/Document.h>
@ -307,12 +307,12 @@ static Utf16String apply_math_auto_text_transform(Utf16String const& string)
}
};
StringBuilder builder { StringBuilder::Mode::UTF16, string.length_in_code_units() };
Utf16StringBuilder builder { string.length_in_code_units() };
for (auto code_point : string)
builder.append_code_point(map_code_point_to_italic(code_point));
return builder.to_utf16_string();
return builder.to_string();
}
static Utf16String apply_text_transform(Utf16String const& string, CSS::TextTransform text_transform, Optional<StringView> const& locale)
@ -457,13 +457,13 @@ Utf16String TextNode::compute_text_for_rendering(TextForRenderingCacheKey const&
// AD-HOC: It's important to not change the amount of code units in the resulting transformed text, so ChunkIterator
// can pass views to this string with associated code unit offsets that still match the original text.
if (convert_newlines || convert_tabs) {
StringBuilder text_builder { StringBuilder::Mode::UTF16, text.length_in_code_units() };
Utf16StringBuilder text_builder { text.length_in_code_units() };
for (auto code_point : text) {
if ((convert_newlines && code_point == '\n') || (convert_tabs && code_point == '\t'))
code_point = ' ';
text_builder.append_code_point(code_point);
}
text = text_builder.to_utf16_string();
text = text_builder.to_string();
}
return text;

View file

@ -5,6 +5,7 @@
*/
#include <AK/CharacterTypes.h>
#include <AK/Utf16StringBuilder.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Range.h>
#include <LibWeb/Dump.h>
@ -43,7 +44,7 @@ Vector<Viewport::TextBlock> const& Viewport::text_blocks()
void Viewport::update_text_blocks()
{
StringBuilder builder(StringBuilder::Mode::UTF16);
Utf16StringBuilder builder;
Vector<TextPosition> text_positions;
Vector<TextBlock> text_blocks;
@ -55,7 +56,7 @@ void Viewport::update_text_blocks()
auto flush_block = [&] {
if (!builder.is_empty())
text_blocks.append({ builder.to_utf16_string(), text_positions });
text_blocks.append({ builder.to_string(), text_positions });
text_positions.clear_with_capacity();
builder.clear();
builder_length_in_code_units = 0;

View file

@ -10,6 +10,7 @@
#include <AK/Array.h>
#include <AK/GenericShorthands.h>
#include <AK/StdLibExtras.h>
#include <AK/Utf16StringBuilder.h>
#include <LibGfx/Font/Font.h>
#include <LibWeb/CSS/ComputedProperties.h>
#include <LibWeb/CSS/ComputedValues.h>
@ -1371,10 +1372,10 @@ void PaintableBox::paint_inspector_overlay_internal(DisplayListRecordingContext&
auto font = Platform::FontPlugin::the().default_font(12);
StringBuilder builder(StringBuilder::Mode::UTF16);
builder.append(debug_description());
Utf16StringBuilder builder;
builder.appendff("{}", debug_description());
builder.appendff(" {}x{} @ {},{}", border_rect.width(), border_rect.height(), border_rect.x(), border_rect.y());
auto size_text = builder.to_utf16_string();
auto size_text = builder.to_string();
auto size_text_rect = border_rect;
size_text_rect.set_y(border_rect.y() + border_rect.height());
size_text_rect.set_top(size_text_rect.top());

View file

@ -254,8 +254,8 @@ void XMLDocumentBuilder::text(StringView data)
if (auto* last = m_current_node->last_child(); last && last->is_text()) {
auto& text_node = static_cast<DOM::Text&>(*last);
m_text_builder.append(text_node.data());
m_text_builder.append(data);
text_node.set_data(m_text_builder.to_utf16_string());
m_text_builder.append(Utf16String::from_utf8(data));
text_node.set_data(m_text_builder.to_string());
m_text_builder.clear();
} else if (!data.is_empty()) {
auto node = m_document->create_text_node(Utf16String::from_utf8(data));

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/Utf16StringBuilder.h>
#include <LibWeb/DOM/Comment.h>
#include <LibWeb/DOM/ElementFactory.h>
#include <LibWeb/DOM/Node.h>
@ -51,7 +52,7 @@ private:
GC::Ptr<DOM::Node> m_current_node;
XMLScriptingSupport m_scripting_support { XMLScriptingSupport::Enabled };
bool m_has_error { false };
StringBuilder m_text_builder { StringBuilder::Mode::UTF16 };
Utf16StringBuilder m_text_builder;
struct NamespaceStackEntry {
Vector<NamespaceAndPrefix, 2> namespaces;