LibWeb: Use IterationDecision in single level Node iteration methods

`Node::for_each_child()` and `Node::for_each_child_of_type()` callbacks
now return an `IterationDecision`, which allows us to break early if
required.
This commit is contained in:
Tim Ledbetter 2024-05-04 14:59:52 +01:00 committed by Andrew Kaster
commit c57d395a48
Notes: sideshowbarker 2024-07-17 02:35:27 +09:00
25 changed files with 81 additions and 27 deletions

View file

@ -300,6 +300,7 @@ String Node::child_text_content() const
if (maybe_content.has_value())
builder.append(maybe_content.value());
}
return IterationDecision::Continue;
});
return MUST(builder.to_string());
}
@ -903,6 +904,7 @@ JS::NonnullGCPtr<Node> Node::clone_node(Document* document, bool clone_children)
if (clone_children) {
for_each_child([&](auto& child) {
MUST(copy->append_child(child.clone_node(document, true)));
return IterationDecision::Continue;
});
}
@ -1032,6 +1034,7 @@ Vector<JS::Handle<Node>> Node::children_as_vector() const
for_each_child([&](auto& child) {
nodes.append(JS::make_handle(child));
return IterationDecision::Continue;
});
return nodes;
@ -1223,10 +1226,11 @@ void Node::serialize_tree_as_json(JsonObjectSerializer<StringBuilder>& object) c
auto children = MUST(object.add_array("children"sv));
auto add_child = [&children](DOM::Node const& child) {
if (child.is_uninteresting_whitespace_node())
return;
return IterationDecision::Continue;
JsonObjectSerializer<StringBuilder> child_object = MUST(children.add_object());
child.serialize_tree_as_json(child_object);
MUST(child_object.finish());
return IterationDecision::Continue;
};
for_each_child(add_child);
@ -1741,6 +1745,7 @@ void Node::build_accessibility_tree(AccessibilityTreeNode& parent)
if (document_element->has_child_nodes())
document_element->for_each_child([&parent](DOM::Node& child) {
child.build_accessibility_tree(parent);
return IterationDecision::Continue;
});
}
} else if (is_element()) {
@ -1755,11 +1760,13 @@ void Node::build_accessibility_tree(AccessibilityTreeNode& parent)
if (has_child_nodes()) {
for_each_child([&current_node](DOM::Node& child) {
child.build_accessibility_tree(*current_node);
return IterationDecision::Continue;
});
}
} else if (has_child_nodes()) {
for_each_child([&parent](DOM::Node& child) {
child.build_accessibility_tree(parent);
return IterationDecision::Continue;
});
}
} else if (is_text()) {
@ -1767,6 +1774,7 @@ void Node::build_accessibility_tree(AccessibilityTreeNode& parent)
if (has_child_nodes()) {
for_each_child([&parent](DOM::Node& child) {
child.build_accessibility_tree(parent);
return IterationDecision::Continue;
});
}
}
@ -1865,7 +1873,7 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
element->for_each_child([&total_accumulated_text, current_node, target, &document, &visited_nodes](
DOM::Node const& child_node) mutable {
if (visited_nodes.contains(child_node.unique_id()))
return;
return IterationDecision::Continue;
// a. Set the current node to the child node.
current_node = &child_node;
@ -1875,6 +1883,8 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
// c. Append the result to the accumulated text.
total_accumulated_text.append(result);
return IterationDecision::Continue;
});
// iv. Return the accumulated text.
return total_accumulated_text.to_string();

View file

@ -560,16 +560,20 @@ public:
template<typename Callback>
void for_each_child(Callback callback)
{
for (auto* node = first_child(); node; node = node->next_sibling())
callback(*node);
for (auto* node = first_child(); node; node = node->next_sibling()) {
if (callback(*node) == IterationDecision::Break)
return;
}
}
template<typename U, typename Callback>
void for_each_child_of_type(Callback callback)
{
for (auto* node = first_child(); node; node = node->next_sibling()) {
if (is<U>(node))
callback(verify_cast<U>(*node));
if (is<U>(node)) {
if (callback(verify_cast<U>(*node)) == IterationDecision::Break)
return;
}
}
}

View file

@ -70,15 +70,19 @@ inline U* Node::shadow_including_first_ancestor_of_type()
template<typename Callback>
inline void ParentNode::for_each_child(Callback callback) const
{
for (auto* node = first_child(); node; node = node->next_sibling())
callback(*node);
for (auto* node = first_child(); node; node = node->next_sibling()) {
if (callback(*node) == IterationDecision::Break)
return;
}
}
template<typename Callback>
inline void ParentNode::for_each_child(Callback callback)
{
for (auto* node = first_child(); node; node = node->next_sibling())
callback(*node);
for (auto* node = first_child(); node; node = node->next_sibling()) {
if (callback(*node) == IterationDecision::Break)
return;
}
}
}

View file

@ -134,7 +134,7 @@ Vector<Slottable> find_slottables(JS::NonnullGCPtr<HTML::HTMLSlotElement> slot)
else {
host->for_each_child([&](auto& node) {
if (!node.is_slottable())
return;
return IterationDecision::Continue;
auto slottable = node.as_slottable();
@ -144,6 +144,8 @@ Vector<Slottable> find_slottables(JS::NonnullGCPtr<HTML::HTMLSlotElement> slot)
// 2. If foundSlot is slot, then append slottable to result.
if (found_slot == slot)
result.append(move(slottable));
return IterationDecision::Continue;
});
}

View file

@ -118,6 +118,7 @@ void dump_tree(StringBuilder& builder, DOM::Node const& node)
if (!is<HTML::HTMLTemplateElement>(node)) {
static_cast<DOM::ParentNode const&>(node).for_each_child([&](auto& child) {
dump_tree(builder, child);
return IterationDecision::Continue;
});
} else {
auto& template_element = verify_cast<HTML::HTMLTemplateElement>(node);
@ -403,6 +404,7 @@ void dump_tree(StringBuilder& builder, Layout::Node const& layout_node, bool sho
++indent;
layout_node.for_each_child([&](auto& child) {
dump_tree(builder, child, show_box_model, show_specified_style, interactive);
return IterationDecision::Continue;
});
--indent;
}

View file

@ -864,6 +864,7 @@ static void update_the_source_set(DOM::Element& element)
elements.clear();
element.parent()->for_each_child_of_type<DOM::Element>([&](auto& child) {
elements.append(&child);
return IterationDecision::Continue;
});
}

View file

@ -135,6 +135,7 @@ void HTMLInputElement::set_checked(bool checked, ChangeSource change_source)
if (parent()) {
parent()->for_each_child([&](auto& child) {
child.invalidate_style();
return IterationDecision::Continue;
});
}
}

View file

@ -85,6 +85,7 @@ static void concatenate_descendants_text_content(DOM::Node const* node, StringBu
builder.append(verify_cast<DOM::Text>(node)->data());
node->for_each_child([&](auto const& node) {
concatenate_descendants_text_content(&node, builder);
return IterationDecision::Continue;
});
}
@ -98,6 +99,7 @@ String HTMLOptionElement::text() const
// script or SVG script elements.
for_each_child([&](auto const& node) {
concatenate_descendants_text_content(&node, builder);
return IterationDecision::Continue;
});
// Return the result of stripping and collapsing ASCII whitespace from the above concatenation.

View file

@ -174,12 +174,15 @@ Vector<JS::Handle<HTMLOptionElement>> HTMLSelectElement::list_of_options() const
for_each_child_of_type<HTMLOptionElement>([&](HTMLOptionElement& option_element) {
list.append(JS::make_handle(option_element));
return IterationDecision::Continue;
});
for_each_child_of_type<HTMLOptGroupElement>([&](HTMLOptGroupElement const& optgroup_element) {
optgroup_element.for_each_child_of_type<HTMLOptionElement>([&](HTMLOptionElement& option_element) {
list.append(JS::make_handle(option_element));
return IterationDecision::Continue;
});
return IterationDecision::Continue;
});
return list;

View file

@ -58,6 +58,8 @@ void HTMLTemplateElement::cloned(Node& copy, bool clone_children)
// FIXME: Should this use TreeNode::append_child instead?
MUST(template_clone.content()->append_child(cloned_child));
return IterationDecision::Continue;
});
}

View file

@ -4418,7 +4418,8 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS
if (is<DOM::Element>(current_node)) {
// -> If current node is an Element
auto& element = verify_cast<DOM::Element>(current_node);
return serialize_element(element);
serialize_element(element);
return IterationDecision::Continue;
}
if (is<DOM::Text>(current_node)) {
@ -4440,7 +4441,6 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS
// 2. Otherwise, append the value of current node's data IDL attribute, escaped as described below.
builder.append(escape_string(text_node.data(), AttributeMode::No));
return IterationDecision::Continue;
}
if (is<DOM::Comment>(current_node)) {

View file

@ -1215,6 +1215,7 @@ CSSPixels BlockFormattingContext::greatest_child_width(Box const& box) const
box.for_each_child_of_type<Box>([&](Box const& child) {
if (!child.is_absolutely_positioned())
max_width = max(max_width, m_state.get(child).margin_box_width());
return IterationDecision::Continue;
});
}
return max_width;

View file

@ -199,6 +199,7 @@ void FlexFormattingContext::parent_context_did_dimension_child_root_box()
auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom);
layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height));
}
return IterationDecision::Continue;
});
}

View file

@ -243,6 +243,7 @@ CSSPixels FormattingContext::greatest_child_width(Box const& box) const
box.for_each_child_of_type<Box>([&](Box const& child) {
if (!child.is_absolutely_positioned())
max_width = max(max_width, m_state.get(child).margin_box_width());
return IterationDecision::Continue;
});
}
return max_width;

View file

@ -1940,6 +1940,7 @@ void GridFormattingContext::parent_context_did_dimension_child_root_box()
auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom);
layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height));
}
return IterationDecision::Continue;
});
}

View file

@ -127,6 +127,8 @@ static CSSPixelRect measure_scrollable_overflow(Box const& box)
for (auto const& fragment : static_cast<Painting::InlinePaintable const&>(*child.paintable()).fragments())
scrollable_overflow_rect = scrollable_overflow_rect.united(fragment.absolute_rect());
}
return IterationDecision::Continue;
});
}

View file

@ -882,6 +882,7 @@ void NodeWithStyle::propagate_style_to_anonymous_wrappers()
auto& child_computed_values = static_cast<CSS::MutableComputedValues&>(static_cast<CSS::ComputedValues&>(const_cast<CSS::ImmutableComputedValues&>(child.computed_values())));
child_computed_values.inherit_from(computed_values());
}
return IterationDecision::Continue;
});
}

View file

@ -250,6 +250,7 @@ void SVGFormattingContext::run(Box const& box, LayoutMode, AvailableSpace const&
box.for_each_child_of_type<Box>([&](Box const& child) {
layout_svg_element(child);
return IterationDecision::Continue;
});
}
@ -264,6 +265,7 @@ void SVGFormattingContext::layout_svg_element(Box const& child)
child_state.set_content_offset(child_state.offset.translated(m_svg_offset));
child.for_each_child_of_type<SVGMaskBox>([&](SVGMaskBox const& child) {
layout_svg_element(child);
return IterationDecision::Continue;
});
} else if (is<SVGGraphicsBox>(child)) {
layout_graphics_element(static_cast<SVGGraphicsBox const&>(child));
@ -369,6 +371,7 @@ void SVGFormattingContext::layout_path_like_element(SVGGraphicsBox const& graphi
text_box.for_each_child_of_type<SVGGraphicsBox>([&](auto& child) {
if (is<SVGTextBox>(child) || is<SVGTextPathBox>(child))
layout_graphics_element(child);
return IterationDecision::Continue;
});
} else if (is<SVGTextPathBox>(graphics_box)) {
// FIXME: Support <tspan> in <textPath>.
@ -446,11 +449,12 @@ void SVGFormattingContext::layout_container_element(SVGBox const& container)
container.for_each_child_of_type<Box>([&](Box const& child) {
// Masks/clips do not change the bounding box of their parents.
if (is<SVGMaskBox>(child) || is<SVGClipBox>(child))
return;
return IterationDecision::Continue;
layout_svg_element(child);
auto& child_state = m_state.get(child);
bounding_box.add_point(child_state.offset);
bounding_box.add_point(child_state.offset.translated(child_state.content_width(), child_state.content_height()));
return IterationDecision::Continue;
});
box_state.set_content_x(bounding_box.x());
box_state.set_content_y(bounding_box.y());

View file

@ -76,6 +76,7 @@ public:
parent.for_each_child_of_type<Box>([&](Box const& child_box) {
if (matcher(child_box))
callback(child_box);
return IterationDecision::Continue;
});
}

View file

@ -553,6 +553,7 @@ void TreeBuilder::remove_irrelevant_boxes(NodeWithStyle& root)
for_each_in_tree_with_internal_display<CSS::DisplayInternal::TableColumn>(root, [&](Box& table_column) {
table_column.for_each_child([&](auto& child) {
to_remove.append(child);
return IterationDecision::Continue;
});
});
@ -561,6 +562,7 @@ void TreeBuilder::remove_irrelevant_boxes(NodeWithStyle& root)
table_column_group.for_each_child([&](auto& child) {
if (!child.display().is_table_column())
to_remove.append(child);
return IterationDecision::Continue;
});
});
@ -770,6 +772,7 @@ static void for_each_child_box_matching(Box& parent, Matcher matcher, Callback c
parent.for_each_child_of_type<Box>([&](Box& child_box) {
if (matcher(child_box))
callback(child_box);
return IterationDecision::Continue;
});
}

View file

@ -211,12 +211,13 @@ TraversalDecision InlinePaintable::hit_test(CSSPixelPoint position, HitTestType
bool should_exit = false;
for_each_child([&](Paintable const& child) {
if (should_exit)
return;
if (child.stacking_context())
return;
if (child.hit_test(position, type, callback) == TraversalDecision::Break)
return IterationDecision::Continue;
if (child.hit_test(position, type, callback) == TraversalDecision::Break) {
should_exit = true;
return IterationDecision::Break;
}
return IterationDecision::Continue;
});
if (should_exit)

View file

@ -112,13 +112,13 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const&
// FIXME: This may not be fully correct with respect to the paint phases.
if (phase == StackingContextPaintPhase::Foreground)
paint_node_as_stacking_context(child, context);
return;
return IterationDecision::Continue;
}
if (stacking_context && z_index.value_or(0) != 0)
return;
return IterationDecision::Continue;
if (child.is_positioned() && z_index.value_or(0) == 0)
return;
return IterationDecision::Continue;
if (stacking_context) {
// FIXME: This may not be fully correct with respect to the paint phases.
@ -126,7 +126,7 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const&
paint_child(context, *stacking_context);
}
// Note: Don't further recurse into descendants as paint_child() will do that.
return;
return IterationDecision::Continue;
}
bool child_is_inline_or_replaced = child.is_inline() || is<Layout::ReplacedBox>(child.layout_node());
@ -171,6 +171,8 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const&
paint_descendants(context, child, phase);
break;
}
return IterationDecision::Continue;
});
paintable.after_children_paint(context, to_paint_phase(phase));

View file

@ -38,7 +38,7 @@ static void collect_cell_boxes(Vector<PaintableBox const&>& cell_boxes, Paintabl
} else {
collect_cell_boxes(cell_boxes, child);
}
return TraversalDecision::Continue;
return IterationDecision::Continue;
});
}

View file

@ -76,6 +76,7 @@ private:
for_each_child_of_type<SVG::SVGStopElement>([&](auto& stop) {
color_stops_found = true;
callback(stop);
return IterationDecision::Continue;
});
if (!color_stops_found) {
if (auto gradient = linked_gradient(seen_gradients))

View file

@ -224,16 +224,20 @@ public:
template<typename Callback>
void for_each_child(Callback callback)
{
for (auto* node = first_child(); node; node = node->next_sibling())
callback(*node);
for (auto* node = first_child(); node; node = node->next_sibling()) {
if (callback(*node) == IterationDecision::Break)
return;
}
}
template<typename U, typename Callback>
void for_each_child_of_type(Callback callback)
{
for (auto* node = first_child(); node; node = node->next_sibling()) {
if (is<U>(node))
callback(verify_cast<U>(*node));
if (is<U>(node)) {
if (callback(verify_cast<U>(*node)) == IterationDecision::Break)
return;
}
}
}