mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 09:27:35 +00:00
LibWeb: Make inline paintables own their fragments
The paintable tree structure more closely matches the painting order when fragments are owned by corresponding inline paintables. This change does not affect the layout tree, as it is more convenient for layout purposes to have all fragments owned by a block container in one place. Additionally, this improves performance significantly on pages with many fragments, as we no longer have to walk the ancestor chain up to the closest block container to determine if a fragment belongs to an inline paintable.
This commit is contained in:
parent
5ed936289a
commit
2960bf4ec8
34 changed files with 310 additions and 258 deletions
|
@ -31,10 +31,12 @@
|
|||
#include <LibWeb/Layout/BlockContainer.h>
|
||||
#include <LibWeb/Layout/FormattingContext.h>
|
||||
#include <LibWeb/Layout/FrameBox.h>
|
||||
#include <LibWeb/Layout/InlineNode.h>
|
||||
#include <LibWeb/Layout/Node.h>
|
||||
#include <LibWeb/Layout/SVGBox.h>
|
||||
#include <LibWeb/Layout/TextNode.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
#include <LibWeb/Painting/InlinePaintable.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
#include <LibWeb/Painting/TextPaintable.h>
|
||||
#include <LibWeb/SVG/SVGDecodedImageData.h>
|
||||
|
@ -340,29 +342,43 @@ void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool sho
|
|||
}
|
||||
}
|
||||
|
||||
auto dump_fragment = [&](auto& fragment, size_t fragment_index) {
|
||||
for (size_t i = 0; i < indent; ++i)
|
||||
builder.append(" "sv);
|
||||
builder.appendff(" {}frag {}{} from {} ",
|
||||
fragment_color_on,
|
||||
fragment_index,
|
||||
color_off,
|
||||
fragment.layout_node().class_name());
|
||||
builder.appendff("start: {}, length: {}, rect: {} baseline: {}\n",
|
||||
fragment.start(),
|
||||
fragment.length(),
|
||||
fragment.absolute_rect(),
|
||||
fragment.baseline());
|
||||
if (is<Layout::TextNode>(fragment.layout_node())) {
|
||||
for (size_t i = 0; i < indent; ++i)
|
||||
builder.append(" "sv);
|
||||
auto const& layout_text = static_cast<Layout::TextNode const&>(fragment.layout_node());
|
||||
auto fragment_text = MUST(layout_text.text_for_rendering().substring_from_byte_offset(fragment.start(), fragment.length()));
|
||||
builder.appendff(" \"{}\"\n", fragment_text);
|
||||
}
|
||||
};
|
||||
|
||||
if (is<Layout::BlockContainer>(layout_node) && static_cast<Layout::BlockContainer const&>(layout_node).children_are_inline()) {
|
||||
auto& block = static_cast<Layout::BlockContainer const&>(layout_node);
|
||||
for (size_t fragment_index = 0; block.paintable_with_lines() && fragment_index < block.paintable_with_lines()->fragments().size(); ++fragment_index) {
|
||||
auto const& fragment = block.paintable_with_lines()->fragments()[fragment_index];
|
||||
for (size_t i = 0; i < indent; ++i)
|
||||
builder.append(" "sv);
|
||||
builder.appendff(" {}frag {}{} from {} ",
|
||||
fragment_color_on,
|
||||
fragment_index,
|
||||
color_off,
|
||||
fragment.layout_node().class_name());
|
||||
builder.appendff("start: {}, length: {}, rect: {} baseline: {}\n",
|
||||
fragment.start(),
|
||||
fragment.length(),
|
||||
fragment.absolute_rect(),
|
||||
fragment.baseline());
|
||||
if (is<Layout::TextNode>(fragment.layout_node())) {
|
||||
for (size_t i = 0; i < indent; ++i)
|
||||
builder.append(" "sv);
|
||||
auto const& layout_text = static_cast<Layout::TextNode const&>(fragment.layout_node());
|
||||
auto fragment_text = MUST(layout_text.text_for_rendering().substring_from_byte_offset(fragment.start(), fragment.length()));
|
||||
builder.appendff(" \"{}\"\n", fragment_text);
|
||||
}
|
||||
dump_fragment(fragment, fragment_index);
|
||||
}
|
||||
}
|
||||
|
||||
if (is<Layout::InlineNode>(layout_node) && layout_node.paintable()) {
|
||||
auto const& inline_node = static_cast<Layout::InlineNode const&>(layout_node);
|
||||
auto const& inline_paintable = static_cast<Painting::InlinePaintable const&>(*inline_node.paintable());
|
||||
auto const& fragments = inline_paintable.fragments();
|
||||
for (size_t fragment_index = 0; fragment_index < fragments.size(); ++fragment_index) {
|
||||
auto const& fragment = fragments[fragment_index];
|
||||
dump_fragment(fragment, fragment_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <AK/Debug.h>
|
||||
#include <LibWeb/Layout/AvailableSpace.h>
|
||||
#include <LibWeb/Layout/BlockContainer.h>
|
||||
#include <LibWeb/Layout/InlineNode.h>
|
||||
#include <LibWeb/Layout/LayoutState.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
#include <LibWeb/Painting/InlinePaintable.h>
|
||||
|
@ -116,6 +117,13 @@ static CSSPixelRect measure_scrollable_overflow(Box const& box)
|
|||
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
} else {
|
||||
box.for_each_child([&scrollable_overflow_rect](Node const& child) {
|
||||
if (child.paintable() && child.paintable()->is_inline_paintable()) {
|
||||
for (auto const& fragment : static_cast<Painting::InlinePaintable const&>(*child.paintable()).fragments())
|
||||
scrollable_overflow_rect = scrollable_overflow_rect.united(fragment.absolute_rect());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// FIXME: - The margin areas of grid item and flex item boxes for which the box establishes a containing block.
|
||||
|
@ -293,12 +301,7 @@ void LayoutState::resolve_border_radii()
|
|||
}
|
||||
|
||||
for (auto& inline_paintable : inline_paintables) {
|
||||
Vector<Painting::PaintableFragment&> fragments;
|
||||
verify_cast<Painting::PaintableWithLines>(*inline_paintable.containing_block()->paintable_box()).for_each_fragment([&](auto& fragment) {
|
||||
if (inline_paintable.layout_node().is_inclusive_ancestor_of(fragment.layout_node()))
|
||||
fragments.append(const_cast<Painting::PaintableFragment&>(fragment));
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
auto& fragments = inline_paintable.fragments();
|
||||
|
||||
auto const& top_left_border_radius = inline_paintable.computed_values().border_top_left_radius();
|
||||
auto const& top_right_border_radius = inline_paintable.computed_values().border_top_right_radius();
|
||||
|
@ -457,13 +460,29 @@ void LayoutState::commit(Box& root)
|
|||
resolve_relative_positions(paintables_with_lines);
|
||||
|
||||
// Make a pass over all the line boxes to:
|
||||
// - Measure absolute rect of each line box.
|
||||
// - Collect all text nodes, so we can create paintables for them later.
|
||||
// - Relocate fragments into matching inline paintables
|
||||
for (auto& paintable_with_lines : paintables_with_lines) {
|
||||
Vector<Painting::PaintableFragment> fragments_with_inline_paintables_removed;
|
||||
for (auto& fragment : paintable_with_lines.fragments()) {
|
||||
if (fragment.layout_node().is_text_node())
|
||||
text_nodes.set(static_cast<Layout::TextNode*>(const_cast<Layout::Node*>(&fragment.layout_node())));
|
||||
|
||||
auto find_closest_inline_paintable = [&](auto& fragment) -> Painting::InlinePaintable const* {
|
||||
for (auto const* parent = fragment.layout_node().parent(); parent; parent = parent->parent()) {
|
||||
if (is<InlineNode>(*parent))
|
||||
return static_cast<Painting::InlinePaintable const*>(parent->paintable());
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (auto const* inline_paintable = find_closest_inline_paintable(fragment)) {
|
||||
const_cast<Painting::InlinePaintable*>(inline_paintable)->fragments().append(fragment);
|
||||
} else {
|
||||
fragments_with_inline_paintables_removed.append(fragment);
|
||||
}
|
||||
}
|
||||
paintable_with_lines.set_fragments(move(fragments_with_inline_paintables_removed));
|
||||
}
|
||||
|
||||
for (auto* text_node : text_nodes)
|
||||
|
@ -483,18 +502,6 @@ void LayoutState::commit(Box& root)
|
|||
resolve_border_radii();
|
||||
resolve_box_shadow_data();
|
||||
resolve_text_shadows(paintables_with_lines);
|
||||
|
||||
for (auto& it : used_values_per_layout_node) {
|
||||
auto& used_values = *it.value;
|
||||
auto& node = const_cast<NodeWithStyle&>(used_values.node());
|
||||
auto* paintable = node.paintable();
|
||||
if (paintable && is<Painting::InlinePaintable>(*paintable)) {
|
||||
auto& inline_paintable = static_cast<Painting::InlinePaintable&>(*paintable);
|
||||
// FIXME: Marking fragments contained by inline node is a hack required to skip them while painting
|
||||
// PaintableWithLines content.
|
||||
inline_paintable.mark_contained_fragments();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LayoutState::UsedValues::set_node(NodeWithStyle& node, UsedValues const* containing_block_used_values)
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include <LibWeb/Layout/TextNode.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/Painting/InlinePaintable.h>
|
||||
#include <LibWeb/Platform/FontPlugin.h>
|
||||
|
||||
namespace Web::Layout {
|
||||
|
@ -232,13 +233,20 @@ void Node::set_needs_display()
|
|||
return;
|
||||
if (!containing_block->paintable_box())
|
||||
return;
|
||||
auto navigable = this->navigable();
|
||||
if (!navigable)
|
||||
return;
|
||||
|
||||
if (this->paintable() && is<Painting::InlinePaintable>(this->paintable())) {
|
||||
auto const& fragments = static_cast<Painting::InlinePaintable*>(this->paintable())->fragments();
|
||||
for (auto const& fragment : fragments)
|
||||
navigable->set_needs_display(fragment.absolute_rect());
|
||||
}
|
||||
|
||||
if (!is<Painting::PaintableWithLines>(*containing_block->paintable_box()))
|
||||
return;
|
||||
static_cast<Painting::PaintableWithLines const&>(*containing_block->paintable_box()).for_each_fragment([&](auto& fragment) {
|
||||
if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) {
|
||||
if (navigable())
|
||||
navigable()->set_needs_display(fragment.absolute_rect());
|
||||
}
|
||||
navigable->set_needs_display(fragment.absolute_rect());
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
@ -248,14 +256,19 @@ CSSPixelPoint Node::box_type_agnostic_position() const
|
|||
if (is<Box>(*this))
|
||||
return verify_cast<Box>(*this).paintable_box()->absolute_position();
|
||||
VERIFY(is_inline());
|
||||
|
||||
if (paintable() && paintable()->is_inline_paintable()) {
|
||||
auto const& inline_paintable = static_cast<Painting::InlinePaintable const&>(*paintable());
|
||||
if (!inline_paintable.fragments().is_empty())
|
||||
return inline_paintable.fragments().first().absolute_rect().location();
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
CSSPixelPoint position;
|
||||
if (auto* block = containing_block(); block && block->paintable() && is<Painting::PaintableWithLines>(*block->paintable())) {
|
||||
if (auto const* block = containing_block(); block && block->paintable() && is<Painting::PaintableWithLines>(*block->paintable())) {
|
||||
static_cast<Painting::PaintableWithLines const&>(*block->paintable_box()).for_each_fragment([&](auto& fragment) {
|
||||
if (&fragment.layout_node() == this || is_ancestor_of(fragment.layout_node())) {
|
||||
position = fragment.absolute_rect().location();
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
position = fragment.absolute_rect().location();
|
||||
return IterationDecision::Break;
|
||||
});
|
||||
}
|
||||
return position;
|
||||
|
|
|
@ -154,26 +154,26 @@ void InlinePaintable::paint(PaintContext& context, PaintPhase phase) const
|
|||
template<typename Callback>
|
||||
void InlinePaintable::for_each_fragment(Callback callback) const
|
||||
{
|
||||
// FIXME: This will be slow if the containing block has a lot of fragments!
|
||||
Vector<PaintableFragment const&> fragments;
|
||||
verify_cast<PaintableWithLines>(*containing_block()->paintable_box()).for_each_fragment([&](auto& fragment) {
|
||||
if (layout_node().is_inclusive_ancestor_of(fragment.layout_node()))
|
||||
fragments.append(fragment);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
for (size_t i = 0; i < fragments.size(); ++i) {
|
||||
auto const& fragment = fragments[i];
|
||||
callback(fragment, i == 0, i == fragments.size() - 1);
|
||||
for (size_t i = 0; i < m_fragments.size(); ++i) {
|
||||
auto const& fragment = m_fragments[i];
|
||||
callback(fragment, i == 0, i == m_fragments.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void InlinePaintable::mark_contained_fragments()
|
||||
Optional<HitTestResult> InlinePaintable::hit_test(CSSPixelPoint position, HitTestType type) const
|
||||
{
|
||||
verify_cast<PaintableWithLines>(*containing_block()->paintable_box()).for_each_fragment([&](auto& fragment) {
|
||||
if (layout_node().is_inclusive_ancestor_of(fragment.layout_node()))
|
||||
const_cast<PaintableFragment&>(fragment).set_contained_by_inline_node();
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
for (auto& fragment : m_fragments) {
|
||||
if (is<Layout::Box>(fragment.layout_node()) && static_cast<Layout::Box const&>(fragment.layout_node()).paintable_box()->stacking_context())
|
||||
continue;
|
||||
auto fragment_absolute_rect = fragment.absolute_rect();
|
||||
if (fragment_absolute_rect.contains(position)) {
|
||||
if (is<Layout::BlockContainer>(fragment.layout_node()) && fragment.layout_node().paintable())
|
||||
return fragment.layout_node().paintable()->hit_test(position, type);
|
||||
return HitTestResult { const_cast<Paintable&>(const_cast<Paintable&>(*fragment.layout_node().paintable())),
|
||||
fragment.text_index_at(position.x()) };
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
CSSPixelRect InlinePaintable::bounding_rect() const
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <LibWeb/Layout/InlineNode.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
#include <LibWeb/Painting/PaintableFragment.h>
|
||||
|
||||
namespace Web::Painting {
|
||||
|
||||
|
@ -23,10 +24,12 @@ public:
|
|||
auto const& box_model() const { return layout_node().box_model(); }
|
||||
|
||||
CSSPixelRect bounding_rect() const;
|
||||
Vector<PaintableFragment> const& fragments() const { return m_fragments; }
|
||||
Vector<PaintableFragment>& fragments() { return m_fragments; }
|
||||
|
||||
virtual bool is_inline_paintable() const override { return true; }
|
||||
|
||||
void mark_contained_fragments();
|
||||
virtual Optional<HitTestResult> hit_test(CSSPixelPoint, HitTestType) const override;
|
||||
|
||||
void set_box_shadow_data(Vector<ShadowData>&& box_shadow_data) { m_box_shadow_data = move(box_shadow_data); }
|
||||
Vector<ShadowData> const& box_shadow_data() const { return m_box_shadow_data; }
|
||||
|
@ -38,6 +41,7 @@ private:
|
|||
void for_each_fragment(Callback) const;
|
||||
|
||||
Vector<ShadowData> m_box_shadow_data;
|
||||
Vector<PaintableFragment> m_fragments;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -741,6 +741,15 @@ Optional<HitTestResult> PaintableWithLines::hit_test(CSSPixelPoint position, Hit
|
|||
if (!layout_box().children_are_inline() || m_fragments.is_empty())
|
||||
return PaintableBox::hit_test(position, type);
|
||||
|
||||
for (auto* child = first_child(); child; child = child->next_sibling()) {
|
||||
auto result = child->hit_test(position, type);
|
||||
if (!result.has_value())
|
||||
continue;
|
||||
if (!result->paintable->visible_for_hit_testing())
|
||||
continue;
|
||||
return result;
|
||||
}
|
||||
|
||||
Optional<HitTestResult> last_good_candidate;
|
||||
for (auto const& fragment : fragments()) {
|
||||
if (is<Layout::Box>(fragment.layout_node()) && static_cast<Layout::Box const&>(fragment.layout_node()).paintable_box()->stacking_context())
|
||||
|
|
|
@ -230,12 +230,15 @@ public:
|
|||
Layout::BlockContainer& layout_box();
|
||||
|
||||
Vector<PaintableFragment> const& fragments() const { return m_fragments; }
|
||||
Vector<PaintableFragment>& fragments() { return m_fragments; }
|
||||
|
||||
void add_fragment(Layout::LineBoxFragment const& fragment)
|
||||
{
|
||||
m_fragments.append(PaintableFragment { fragment });
|
||||
}
|
||||
|
||||
void set_fragments(Vector<PaintableFragment>&& fragments) { m_fragments = move(fragments); }
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_fragment(Callback callback) const
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue