mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-08-17 09:20:28 +03:00
LibWeb: Cache text blocks used by find in page
The first step of the find in page algorithm is to walk the layout tree of each document on the page and construct a list of strings against which to search for matches. Previously, this was being done for each new query, even when the page content hadn't been updated. The output of this process is now cached in the viewport node of the associated document. This ensures that this process is no longer repeated unnceessarily.
This commit is contained in:
parent
31698281b6
commit
156c1083e9
Notes:
sideshowbarker
2024-07-17 10:16:43 +09:00
Author: https://github.com/tcl3 Commit: https://github.com/LadybirdBrowser/ladybird/commit/156c1083e9 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/311 Reviewed-by: https://github.com/kalenikaliaksandr
@ -5111,65 +5111,13 @@ void Document::set_needs_to_refresh_scroll_state(bool b)
|
|||||||
|
|
||||||
Vector<JS::Handle<DOM::Range>> Document::find_matching_text(String const& query, CaseSensitivity case_sensitivity)
|
Vector<JS::Handle<DOM::Range>> Document::find_matching_text(String const& query, CaseSensitivity case_sensitivity)
|
||||||
{
|
{
|
||||||
if (!document_element() || !document_element()->layout_node())
|
if (!layout_node())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
struct TextPosition {
|
|
||||||
DOM::Text& dom_node;
|
|
||||||
size_t start_offset { 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TextBlock {
|
|
||||||
String text;
|
|
||||||
Vector<TextPosition> positions;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto gather_text_blocks = [&]() -> Vector<TextBlock> {
|
|
||||||
StringBuilder builder;
|
|
||||||
size_t current_start_position = 0;
|
|
||||||
Vector<TextPosition> text_positions;
|
|
||||||
Vector<TextBlock> text_blocks;
|
|
||||||
document_element()->layout_node()->for_each_in_inclusive_subtree([&](auto const& layout_node) {
|
|
||||||
if (layout_node.display().is_none() || !layout_node.paintable() || !layout_node.paintable()->is_visible())
|
|
||||||
return TraversalDecision::Continue;
|
|
||||||
|
|
||||||
if (layout_node.is_block_container()) {
|
|
||||||
if (!builder.is_empty()) {
|
|
||||||
text_blocks.append({ builder.to_string_without_validation(), text_positions });
|
|
||||||
current_start_position = 0;
|
|
||||||
text_positions.clear_with_capacity();
|
|
||||||
builder.clear();
|
|
||||||
}
|
|
||||||
return TraversalDecision::Continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layout_node.is_text_node()) {
|
|
||||||
auto const& text_node = verify_cast<Layout::TextNode>(layout_node);
|
|
||||||
auto& dom_node = const_cast<DOM::Text&>(text_node.dom_node());
|
|
||||||
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();
|
|
||||||
current_start_position += current_node_text.bytes_as_string_view().length();
|
|
||||||
builder.append(move(current_node_text));
|
|
||||||
}
|
|
||||||
|
|
||||||
return TraversalDecision::Continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!builder.is_empty())
|
|
||||||
text_blocks.append({ builder.to_string_without_validation(), text_positions });
|
|
||||||
|
|
||||||
return text_blocks;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ensure the layout tree exists before searching for text matches.
|
// Ensure the layout tree exists before searching for text matches.
|
||||||
update_layout();
|
update_layout();
|
||||||
|
|
||||||
auto text_blocks = gather_text_blocks();
|
auto const& text_blocks = layout_node()->text_blocks();
|
||||||
if (text_blocks.is_empty())
|
if (text_blocks.is_empty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
|
@ -27,4 +27,67 @@ JS::GCPtr<Painting::Paintable> Viewport::create_paintable() const
|
|||||||
return Painting::ViewportPaintable::create(*this);
|
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.paintable() || !layout_node.paintable()->is_visible())
|
||||||
|
return TraversalDecision::Continue;
|
||||||
|
|
||||||
|
if (layout_node.is_box()) {
|
||||||
|
if (!builder.is_empty()) {
|
||||||
|
text_blocks.append({ builder.to_string_without_validation(), text_positions });
|
||||||
|
current_start_position = 0;
|
||||||
|
text_positions.clear_with_capacity();
|
||||||
|
builder.clear();
|
||||||
|
}
|
||||||
|
return TraversalDecision::Continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layout_node.is_text_node()) {
|
||||||
|
auto const& text_node = verify_cast<Layout::TextNode>(layout_node);
|
||||||
|
auto& dom_node = const_cast<DOM::Text&>(text_node.dom_node());
|
||||||
|
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();
|
||||||
|
current_start_position += current_node_text.bytes_as_string_view().length();
|
||||||
|
builder.append(move(current_node_text));
|
||||||
|
}
|
||||||
|
|
||||||
|
return TraversalDecision::Continue;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!builder.is_empty())
|
||||||
|
text_blocks.append({ builder.to_string_without_validation(), text_positions });
|
||||||
|
|
||||||
|
m_text_blocks = move(text_blocks);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,28 @@ public:
|
|||||||
explicit Viewport(DOM::Document&, NonnullRefPtr<CSS::StyleProperties>);
|
explicit Viewport(DOM::Document&, NonnullRefPtr<CSS::StyleProperties>);
|
||||||
virtual ~Viewport() override;
|
virtual ~Viewport() override;
|
||||||
|
|
||||||
|
struct TextPosition {
|
||||||
|
JS::NonnullGCPtr<DOM::Text> dom_node;
|
||||||
|
size_t start_offset { 0 };
|
||||||
|
};
|
||||||
|
struct TextBlock {
|
||||||
|
String text;
|
||||||
|
Vector<TextPosition> positions;
|
||||||
|
};
|
||||||
|
Vector<TextBlock> const& text_blocks();
|
||||||
|
|
||||||
const DOM::Document& dom_node() const { return static_cast<const DOM::Document&>(*Node::dom_node()); }
|
const DOM::Document& dom_node() const { return static_cast<const DOM::Document&>(*Node::dom_node()); }
|
||||||
|
|
||||||
|
virtual void visit_edges(Visitor&) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual JS::GCPtr<Painting::Paintable> create_paintable() const override;
|
virtual JS::GCPtr<Painting::Paintable> create_paintable() const override;
|
||||||
|
|
||||||
|
void update_text_blocks();
|
||||||
|
|
||||||
virtual bool is_viewport() const override { return true; }
|
virtual bool is_viewport() const override { return true; }
|
||||||
|
|
||||||
|
Optional<Vector<TextBlock>> m_text_blocks;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
|
Loading…
Reference in New Issue
Block a user