ladybird/Libraries/LibWeb/Layout/Viewport.cpp
Jelle Raaijmakers b42c2c5e8f LibWeb: Use code unit offsets in Document::find_matching_text()
We were passing in byte offsets instead of UTF-16 code unit offsets,
which could lead to crashes if the offsets found exceeded the number of
code units in text fragments on the page.

Fixes #4908.

Co-authored-by: Tim Ledbetter <tim.ledbetter@ladybird.org>
2025-06-13 15:08:26 +02:00

98 lines
3.1 KiB
C++

/*
* Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/DOM/Range.h>
#include <LibWeb/Dump.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Painting/StackingContext.h>
#include <LibWeb/Painting/ViewportPaintable.h>
namespace Web::Layout {
GC_DEFINE_ALLOCATOR(Viewport);
Viewport::Viewport(DOM::Document& document, GC::Ref<CSS::ComputedProperties> style)
: BlockContainer(document, &document, move(style))
{
}
Viewport::~Viewport() = default;
GC::Ptr<Painting::Paintable> Viewport::create_paintable() const
{
return Painting::ViewportPaintable::create(*this);
}
void Viewport::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
if (!m_text_blocks.has_value())
return;
for (auto& text_block : *m_text_blocks) {
for (auto& text_position : text_block.positions)
visitor.visit(text_position.dom_node);
}
}
Vector<Viewport::TextBlock> const& Viewport::text_blocks()
{
if (!m_text_blocks.has_value())
update_text_blocks();
return *m_text_blocks;
}
void Viewport::update_text_blocks()
{
StringBuilder builder;
size_t current_start_position = 0;
Vector<TextPosition> text_positions;
Vector<TextBlock> text_blocks;
for_each_in_inclusive_subtree([&](auto const& layout_node) {
if (layout_node.display().is_none() || !layout_node.first_paintable() || !layout_node.first_paintable()->is_visible())
return TraversalDecision::Continue;
if (layout_node.is_box() || layout_node.is_generated()) {
if (!builder.is_empty()) {
text_blocks.append({ MUST(AK::utf8_to_utf16(builder.string_view())), text_positions });
current_start_position = 0;
text_positions.clear_with_capacity();
builder.clear();
}
return TraversalDecision::Continue;
}
if (auto* text_node = as_if<Layout::TextNode>(layout_node)) {
// https://html.spec.whatwg.org/multipage/interaction.html#inert-subtrees
// When a node is inert:
// - The user agent should ignore the node for the purposes of find-in-page.
if (auto& dom_node = const_cast<DOM::Text&>(text_node->dom_node()); !dom_node.is_inert()) {
if (text_positions.is_empty()) {
text_positions.empend(dom_node);
} else {
text_positions.empend(dom_node, current_start_position);
}
auto const& current_node_text = text_node->text_for_rendering();
auto const current_node_text_utf16 = MUST(AK::utf8_to_utf16(current_node_text));
current_start_position += current_node_text_utf16.data.size();
builder.append(move(current_node_text));
}
}
return TraversalDecision::Continue;
});
if (!builder.is_empty())
text_blocks.append({ MUST(AK::utf8_to_utf16(builder.string_view())), text_positions });
m_text_blocks = move(text_blocks);
}
}