LibJS+LibWeb: Add missing GC marking visits

This adds visit_edges(Cell::Visitor&) methods to various helper structs
that contain GC pointers, and makes sure they are called from owning
GC-heap-allocated objects as needed.

These were found by our Clang plugin after expanding its capabilities.
The added rules will be enforced by CI going forward.
This commit is contained in:
Andreas Kling 2026-01-06 00:36:34 +01:00 committed by Andreas Kling
parent 2677338f43
commit a9cc425cde
41 changed files with 322 additions and 38 deletions

View file

@ -95,6 +95,7 @@ void Executable::visit_edges(Visitor& visitor)
visitor.visit(constants);
for (auto& cache : template_object_caches)
visitor.visit(cache.cached_template_object);
property_key_table->visit_edges(visitor);
}
Optional<Executable::ExceptionHandlers const&> Executable::exception_handlers_for_offset(size_t offset) const

View file

@ -1396,6 +1396,10 @@ private:
{
Base::visit_edges(visitor);
visitor.visit(m_object);
for (auto& key : m_properties)
key.visit_edges(visitor);
if (!m_iterator.is_end())
m_iterator->visit_edges(visitor);
}
GC::Ref<Object> m_object;

View file

@ -32,6 +32,12 @@ public:
ReadonlySpan<PropertyKey const> property_keys() const { return m_property_keys; }
void visit_edges(GC::Cell::Visitor& visitor)
{
for (auto& key : m_property_keys)
key.visit_edges(visitor);
}
private:
Vector<PropertyKey> m_property_keys;
};

View file

@ -65,6 +65,7 @@ set(SOURCES
Runtime/BooleanObject.cpp
Runtime/BooleanPrototype.cpp
Runtime/BoundFunction.cpp
Runtime/ClassFieldDefinition.cpp
Runtime/Completion.cpp
Runtime/CompletionCell.cpp
Runtime/ConsoleObjectPrototype.cpp

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2026, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/ClassFieldDefinition.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
namespace JS {
void ClassFieldDefinition::visit_edges(Cell::Visitor& visitor)
{
name.visit(
[&](PropertyKey const& key) { key.visit_edges(visitor); },
[&](PrivateName const&) {});
initializer.visit(
[&](GC::Ref<ECMAScriptFunctionObject>& function) { visitor.visit(function); },
[&](Value& value) { visitor.visit(value); },
[&](Empty) {});
}
}

View file

@ -19,6 +19,8 @@ using ClassElementName = Variant<PropertyKey, PrivateName>;
struct ClassFieldDefinition {
ClassElementName name; // [[Name]]
Variant<GC::Ref<ECMAScriptFunctionObject>, Value, Empty> initializer; // [[Initializer]]
void visit_edges(Cell::Visitor& visitor);
};
}

View file

@ -108,6 +108,11 @@ public:
return { m_type, release_value() };
}
void visit_edges(Cell::Visitor& visitor)
{
visitor.visit(m_value);
}
private:
class EmptyTag {
};
@ -223,6 +228,11 @@ namespace JS {
struct ErrorValue {
Value error;
void visit_edges(Cell::Visitor& visitor)
{
visitor.visit(error);
}
};
template<typename ValueType>
@ -295,6 +305,21 @@ public:
return Completion { Completion::Type::Throw, error.error };
}
void visit_edges(Cell::Visitor& visitor)
{
m_value_or_error.visit(
[&](ValueType& value) {
if constexpr (IsSame<ValueType, Value>) {
visitor.visit(value);
} else if constexpr (requires { value.visit_edges(visitor); }) {
value.visit_edges(visitor);
}
},
[&](ErrorValue& error) {
error.visit_edges(visitor);
});
}
private:
Variant<ValueType, ErrorValue> m_value_or_error;
};
@ -333,6 +358,11 @@ public:
Completion error() const { return Completion { Completion::Type::Throw, m_value }; }
Completion release_error() { return error(); }
void visit_edges(Cell::Visitor& visitor)
{
visitor.visit(m_value);
}
private:
Value m_value;
};

View file

@ -9,6 +9,7 @@
#include <LibGC/Heap.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Runtime/DeclarativeEnvironment.h>
#include <LibJS/Runtime/ExecutionContext.h>
#include <LibJS/Runtime/FunctionObject.h>
@ -139,6 +140,9 @@ void ExecutionContext::visit_edges(Cell::Visitor& visitor)
visitor.visit(this_value);
visitor.visit(executable);
visitor.visit(registers_and_constants_and_locals_and_arguments_span());
visitor.visit(global_object);
visitor.visit(global_declarative_environment);
visitor.visit(arguments);
script_or_module.visit(
[](Empty) {},
[&](auto& script_or_module) {

View file

@ -18,6 +18,11 @@ struct ValueAndAttributes {
PropertyAttributes attributes { default_attributes };
Optional<u32> property_offset {};
void visit_edges(Cell::Visitor& visitor)
{
visitor.visit(value);
}
};
class IndexedProperties;
@ -45,6 +50,8 @@ public:
size_t array_like_size() const { return m_array_size; }
virtual bool set_array_like_size(size_t new_size) = 0;
virtual void visit_edges(Cell::Visitor&) = 0;
bool is_simple_storage() const { return m_is_simple_storage; }
protected:
@ -79,6 +86,12 @@ public:
virtual size_t size() const override { return m_packed_elements.size(); }
virtual bool set_array_like_size(size_t new_size) override;
virtual void visit_edges(Cell::Visitor& visitor) override
{
for (auto& value : m_packed_elements)
visitor.visit(value);
}
Vector<Value> const& elements() const { return m_packed_elements; }
[[nodiscard]] bool inline_has_index(u32 index) const
@ -123,6 +136,12 @@ public:
virtual size_t size() const override { return m_sparse_elements.size(); }
virtual bool set_array_like_size(size_t new_size) override;
virtual void visit_edges(Cell::Visitor& visitor) override
{
for (auto& element : m_sparse_elements)
element.value.visit_edges(visitor);
}
HashMap<u32, ValueAndAttributes> const& sparse_elements() const { return m_sparse_elements; }
private:
@ -196,6 +215,12 @@ public:
}
}
void visit_edges(Cell::Visitor& visitor)
{
if (m_storage)
m_storage->visit_edges(visitor);
}
private:
void switch_to_generic_storage();
void ensure_storage();

View file

@ -66,6 +66,11 @@ public:
bool operator==(IteratorImpl const& other) const { return m_index == other.m_index && &m_map == &other.m_map; }
bool operator==(EndIterator const&) const { return is_end(); }
void visit_edges(Cell::Visitor& visitor)
{
visitor.visit(m_map);
}
private:
friend class Map;
IteratorImpl(Map const& map)

View file

@ -30,6 +30,7 @@ void MapIterator::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_map);
m_iterator.visit_edges(visitor);
}
BuiltinIterator* MapIterator::as_builtin_iterator_if_next_is_not_redefined(Value next_method)

View file

@ -1532,13 +1532,11 @@ void Object::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_shape);
visitor.visit(m_storage);
m_indexed_properties.for_each_value([&visitor](auto& value) {
visitor.visit(value);
});
m_indexed_properties.visit_edges(visitor);
if (m_private_elements) {
for (auto& private_element : *m_private_elements)
visitor.visit(private_element.value);
private_element.visit_edges(visitor);
}
}

View file

@ -37,6 +37,11 @@ struct PrivateElement {
PrivateName key;
Kind kind { Kind::Field };
Value value;
void visit_edges(Cell::Visitor& visitor)
{
visitor.visit(value);
}
};
// Non-standard: This is information optionally returned by object property access functions.

View file

@ -123,6 +123,20 @@ public:
Optional<EnvironmentCoordinate> environment_coordinate() const { return m_environment_coordinate; }
void visit_edges(Cell::Visitor& visitor)
{
if (m_base_type == BaseType::Value) {
visitor.visit(m_base_value);
} else if (m_base_type == BaseType::Environment) {
visitor.visit(m_base_environment);
}
m_name.visit(
[&](PropertyKey const& key) { key.visit_edges(visitor); },
[&](PrivateName const&) { /* no GC pointers */ });
if (m_this_value.has_value())
visitor.visit(*m_this_value);
}
private:
Completion throw_reference_error(VM&) const;

View file

@ -29,6 +29,7 @@ void SetIterator::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_set);
m_iterator.visit_edges(visitor);
}
BuiltinIterator* SetIterator::as_builtin_iterator_if_next_is_not_redefined(Value next_method)

View file

@ -210,6 +210,11 @@ void Shape::visit_edges(Cell::Visitor& visitor)
}
visitor.visit(m_prototype_chain_validity);
if (m_property_table) {
for (auto& it : *m_property_table)
it.key.visit_edges(visitor);
}
}
Optional<PropertyMetadata> Shape::lookup(PropertyKey const& property_key) const

View file

@ -34,6 +34,11 @@ struct TransitionKey {
{
return property_key == other.property_key && attributes == other.attributes;
}
void visit_edges(Cell::Visitor& visitor)
{
property_key.visit_edges(visitor);
}
};
class PrototypeChainValidity final : public Cell {
@ -100,11 +105,6 @@ public:
OrderedHashMap<PropertyKey, PropertyMetadata> const& property_table() const;
u32 property_count() const { return m_property_count; }
struct Property {
PropertyKey key;
PropertyMetadata value;
};
void set_prototype_without_transition(Object* new_prototype);
private:

View file

@ -292,6 +292,7 @@ void SharedFunctionInstanceData::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_executable);
m_class_field_initializer_name.visit([&](PropertyKey const& key) { key.visit_edges(visitor); }, [](auto&) {});
}
SharedFunctionInstanceData::~SharedFunctionInstanceData() = default;

View file

@ -226,17 +226,21 @@ void Animatable::clear_registered_transitions(Optional<CSS::PseudoElement> pseud
void Animatable::visit_edges(JS::Cell::Visitor& visitor)
{
auto& impl = ensure_impl();
visitor.visit(impl.associated_animations);
for (auto const& css_animation : impl.css_defined_animations) {
if (m_impl)
m_impl->visit_edges(visitor);
}
void Animatable::Impl::visit_edges(JS::Cell::Visitor& visitor)
{
visitor.visit(associated_animations);
for (auto const& css_animation : css_defined_animations) {
if (css_animation)
visitor.visit(*css_animation);
}
for (auto const& transition : impl.transitions) {
if (transition) {
for (auto const& transition : transitions) {
if (transition)
visitor.visit(transition->associated_transitions);
}
}
}

View file

@ -87,6 +87,8 @@ private:
mutable Array<OwnPtr<Transition>, to_underlying(CSS::PseudoElement::KnownPseudoElementCount) + 1> transitions;
~Impl();
void visit_edges(JS::Cell::Visitor&);
};
Impl& ensure_impl() const;
Transition* ensure_transition(Optional<CSS::PseudoElement>) const;

View file

@ -442,6 +442,15 @@ struct ContentData {
Vector<Variant<String, NonnullRefPtr<ImageStyleValue>>> data;
Optional<String> alt_text {};
void visit_edges(GC::Cell::Visitor& visitor) const
{
for (auto const& item : data) {
if (auto* ptr = item.get_pointer<NonnullRefPtr<ImageStyleValue>>()) {
(*ptr)->visit_edges(visitor);
}
}
}
};
struct CounterData {
@ -492,6 +501,11 @@ public:
ComputedValues() = default;
~ComputedValues() = default;
void visit_edges(GC::Cell::Visitor& visitor)
{
m_noninherited.visit_edges(visitor);
}
AspectRatio aspect_ratio() const { return m_noninherited.aspect_ratio; }
Float float_() const { return m_noninherited.float_; }
Length border_spacing_horizontal() const { return m_inherited.border_spacing_horizontal; }
@ -504,7 +518,7 @@ public:
PreferredColorScheme color_scheme() const { return m_inherited.color_scheme; }
ContentVisibility content_visibility() const { return m_inherited.content_visibility; }
Vector<CursorData> const& cursor() const { return m_inherited.cursor; }
ContentData content() const { return m_noninherited.content; }
ContentData const& content() const { return m_noninherited.content; }
PointerEvents pointer_events() const { return m_inherited.pointer_events; }
Display display() const { return m_noninherited.display; }
Display display_before_box_type_transformation() const { return m_noninherited.display_before_box_type_transformation; }
@ -692,7 +706,7 @@ public:
}
protected:
struct {
struct InheritedValues {
Color caret_color { InitialValues::caret_color() };
CSSPixels font_size { InitialValues::font_size() };
RefPtr<Gfx::FontCascadeList const> font_list {};
@ -753,9 +767,11 @@ protected:
int math_depth { InitialValues::math_depth() };
ScrollbarColorData scrollbar_color { InitialValues::scrollbar_color() };
float stroke_opacity { InitialValues::stroke_opacity() };
} m_inherited;
};
struct {
InheritedValues m_inherited;
struct NonInheritedValues {
AspectRatio aspect_ratio { InitialValues::aspect_ratio() };
Float float_ { InitialValues::float_() };
Clear clear { InitialValues::clear() };
@ -877,7 +893,26 @@ protected:
Vector<CounterData, 0> counter_reset;
Vector<CounterData, 0> counter_set;
WillChange will_change { InitialValues::will_change() };
} m_noninherited;
void visit_edges(GC::Cell::Visitor& visitor)
{
for (auto& layer : background_layers)
layer.background_image->visit_edges(visitor);
if (mask_image)
mask_image->visit_edges(visitor);
for (auto const& transform : transformations)
transform->visit_edges(visitor);
if (rotate)
rotate->visit_edges(visitor);
if (translate)
translate->visit_edges(visitor);
if (scale)
scale->visit_edges(visitor);
content.visit_edges(visitor);
}
};
NonInheritedValues m_noninherited;
};
class ImmutableComputedValues final : public ComputedValues {

View file

@ -26,6 +26,11 @@ struct HasResultCacheKey {
CSS::Selector const* selector;
GC::Ptr<DOM::Element const> element;
void visit_edges(GC::Cell::Visitor& visitor)
{
visitor.visit(element);
}
bool operator==(HasResultCacheKey const&) const = default;
};

View file

@ -133,6 +133,8 @@ void StyleComputer::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_document);
if (m_has_result_cache)
visitor.visit(*m_has_result_cache);
}
Optional<String> StyleComputer::user_agent_style_sheet_source(StringView name)

View file

@ -22,9 +22,60 @@
namespace Web::CSS {
void RuleCaches::visit_edges(GC::Cell::Visitor& visitor)
{
main.visit_edges(visitor);
for (auto& it : by_layer) {
it.value->visit_edges(visitor);
}
}
void StyleScope::visit_edges(GC::Cell::Visitor& visitor)
{
visitor.visit(m_node);
visitor.visit(m_user_style_sheet);
for (auto& cache : m_pseudo_class_rule_cache) {
if (cache)
cache->visit_edges(visitor);
}
if (m_author_rule_cache)
m_author_rule_cache->visit_edges(visitor);
if (m_user_rule_cache)
m_user_rule_cache->visit_edges(visitor);
if (m_user_agent_rule_cache)
m_user_agent_rule_cache->visit_edges(visitor);
}
void MatchingRule::visit_edges(GC::Cell::Visitor& visitor)
{
visitor.visit(shadow_root);
visitor.visit(rule);
visitor.visit(sheet);
}
void RuleCache::visit_edges(GC::Cell::Visitor& visitor)
{
auto visit_vector = [&](auto& vector) {
for (auto& rule : vector)
rule.visit_edges(visitor);
};
auto visit_map = [&](auto& map) {
for (auto& [_, rules] : map) {
visit_vector(rules);
}
};
visit_map(rules_by_id);
visit_map(rules_by_class);
visit_map(rules_by_tag_name);
visit_map(rules_by_attribute_name);
for (auto& rules : rules_by_pseudo_element) {
visit_vector(rules);
}
visit_vector(root_rules);
visit_vector(slotted_rules);
visit_vector(part_rules);
visit_vector(other_rules);
}
StyleScope::StyleScope(GC::Ref<DOM::Node> node)

View file

@ -38,6 +38,8 @@ struct MatchingRule {
CSSStyleProperties const& declaration() const;
SelectorList const& absolutized_selectors() const;
FlyString const& qualified_layer_name() const;
void visit_edges(GC::Cell::Visitor&);
};
struct RuleCache {
@ -55,11 +57,15 @@ struct RuleCache {
void add_rule(MatchingRule const&, Optional<PseudoElement>, bool contains_root_pseudo_class);
void for_each_matching_rules(DOM::AbstractElement, Function<IterationDecision(Vector<MatchingRule> const&)> callback) const;
void visit_edges(GC::Cell::Visitor&);
};
struct RuleCaches {
RuleCache main;
HashMap<FlyString, NonnullOwnPtr<RuleCache>> by_layer;
void visit_edges(GC::Cell::Visitor&);
};
struct SelectorInsights {

View file

@ -3065,7 +3065,7 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
// following the “iii. For each child node of the current node” code.
if (auto before = element->get_pseudo_element_node(CSS::PseudoElement::Before)) {
if (before->computed_values().content().alt_text.has_value()) {
total_accumulated_text.append(before->computed_values().content().alt_text.release_value());
total_accumulated_text.append(before->computed_values().content().alt_text.value());
} else {
for (auto& item : before->computed_values().content().data) {
if (auto const* string = item.get_pointer<String>())
@ -3125,7 +3125,7 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
// NOTE: See step ii.b above.
if (auto after = element->get_pseudo_element_node(CSS::PseudoElement::After)) {
if (after->computed_values().content().alt_text.has_value()) {
total_accumulated_text.append(after->computed_values().content().alt_text.release_value());
total_accumulated_text.append(after->computed_values().content().alt_text.value());
} else {
for (auto& item : after->computed_values().content().data) {
if (auto const* string = item.get_pointer<String>())

View file

@ -111,6 +111,12 @@ public:
Bindings::CanvasTextAlign text_align { Bindings::CanvasTextAlign::Start };
Bindings::CanvasTextBaseline text_baseline { Bindings::CanvasTextBaseline::Alphabetic };
Bindings::CanvasDirection direction { Bindings::CanvasDirection::Inherit };
void visit_edges(GC::Cell::Visitor& visitor)
{
fill_style.visit_edges(visitor);
stroke_style.visit_edges(visitor);
}
};
DrawingState& drawing_state() { return m_drawing_state; }
DrawingState const& drawing_state() const { return m_drawing_state; }
@ -122,9 +128,9 @@ public:
void visit_edges(GC::Cell::Visitor& visitor)
{
m_drawing_state.visit_edges(visitor);
for (auto& state : m_drawing_state_stack) {
state.fill_style.visit_edges(visitor);
state.stroke_style.visit_edges(visitor);
state.visit_edges(visitor);
}
}

View file

@ -67,6 +67,7 @@ void HTMLFormElement::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_associated_elements);
visitor.visit(m_planned_navigation);
visitor.visit(m_rel_list);
visitor.visit(m_past_names_map);
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#implicit-submission

View file

@ -142,6 +142,11 @@ private:
struct PastNameEntry {
GC::Ptr<DOM::Node const> node;
MonotonicTime insertion_time;
void visit_edges(GC::Cell::Visitor& visitor)
{
visitor.visit(node);
}
};
HashMap<FlyString, PastNameEntry> mutable m_past_names_map;

View file

@ -199,6 +199,7 @@ void HTMLParser::visit_edges(Cell::Visitor& visitor)
m_stack_of_open_elements.visit_edges(visitor);
m_list_of_active_formatting_elements.visit_edges(visitor);
m_tokenizer.visit_edges(visitor);
}
void HTMLParser::initialize(JS::Realm& realm)

View file

@ -3005,4 +3005,9 @@ String HTMLTokenizer::consume_current_builder()
return string;
}
void HTMLTokenizer::visit_edges(GC::Cell::Visitor& visitor)
{
visitor.visit(m_parser);
}
}

View file

@ -11,6 +11,7 @@
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <AK/Types.h>
#include <LibGC/Cell.h>
#include <LibGC/Ptr.h>
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
@ -147,6 +148,8 @@ public:
void parser_did_run(Badge<HTMLParser>);
void visit_edges(GC::Cell::Visitor&);
private:
void skip(size_t count);
Optional<u32> next_code_point(StopAtInsertionPoint);

View file

@ -477,6 +477,8 @@ void NodeWithStyle::visit_edges(Visitor& visitor)
if (m_list_style_image && m_list_style_image->is_image())
m_list_style_image->as_image().visit_edges(visitor);
m_computed_values->visit_edges(visitor);
}
void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style)

View file

@ -250,6 +250,7 @@ private:
{
Base::visit_edges(visitor);
visitor.visit(m_layout_node);
m_image->visit_edges(visitor);
}
virtual void image_provider_visit_edges(Visitor& visitor) const override

View file

@ -1607,6 +1607,8 @@ void EventHandler::visit_edges(JS::Cell::Visitor& visitor) const
if (m_mouse_selection_target)
visitor.visit(m_mouse_selection_target->as_cell());
visitor.visit(m_navigable);
}
Unicode::Segmenter& EventHandler::word_segmenter()

View file

@ -81,6 +81,12 @@ PaintableBox::~PaintableBox()
{
}
void PaintableBox::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_stacking_context);
}
PaintableWithLines::PaintableWithLines(Layout::BlockContainer const& layout_box)
: PaintableBox(layout_box)
{
@ -567,7 +573,7 @@ void PaintableBox::paint_inspector_overlay_internal(DisplayListRecordingContext&
context.display_list_recorder().draw_text(size_text_device_rect, size_text, font->with_size(font->point_size() * context.device_pixels_per_css_pixel()), Gfx::TextAlignment::Center, context.palette().color(Gfx::ColorRole::TooltipText));
}
void PaintableBox::set_stacking_context(NonnullOwnPtr<StackingContext> stacking_context)
void PaintableBox::set_stacking_context(GC::Ref<StackingContext> stacking_context)
{
m_stacking_context = move(stacking_context);
}

View file

@ -39,7 +39,7 @@ public:
StackingContext* stacking_context() { return m_stacking_context; }
StackingContext const* stacking_context() const { return m_stacking_context; }
void set_stacking_context(NonnullOwnPtr<StackingContext>);
void set_stacking_context(GC::Ref<StackingContext>);
void invalidate_stacking_context();
virtual Optional<CSSPixelRect> get_masking_area() const;
@ -269,6 +269,8 @@ protected:
explicit PaintableBox(Layout::Box const&);
explicit PaintableBox(Layout::InlineNode const&);
virtual void visit_edges(Visitor&) override;
virtual void paint_border(DisplayListRecordingContext&) const;
virtual void paint_backdrop_filter(DisplayListRecordingContext&) const;
virtual void paint_background(DisplayListRecordingContext&) const;
@ -309,7 +311,7 @@ private:
bool scrollbar_contains_mouse_position(ScrollDirection, CSSPixelPoint);
void scroll_to_mouse_position(CSSPixelPoint);
OwnPtr<StackingContext> m_stacking_context;
GC::Ptr<StackingContext> m_stacking_context;
Optional<OverflowData> m_overflow_data;

View file

@ -24,6 +24,8 @@
namespace Web::Painting {
GC_DEFINE_ALLOCATOR(StackingContext);
static void paint_node(Paintable const& paintable, DisplayListRecordingContext& context, PaintPhase phase)
{
TemporaryChange save_nesting_level(context.display_list_recorder().m_save_nesting_level, 0);
@ -42,7 +44,7 @@ StackingContext::StackingContext(PaintableBox& paintable, StackingContext* paren
{
VERIFY(m_parent != this);
if (m_parent)
m_parent->m_children.append(this);
m_parent->m_children.append(*this);
}
void StackingContext::sort()
@ -55,10 +57,20 @@ void StackingContext::sort()
return a_z_index < b_z_index;
});
for (auto* child : m_children)
for (auto child : m_children)
child->sort();
}
void StackingContext::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_paintable);
visitor.visit(m_non_positioned_floating_descendants);
visitor.visit(m_positioned_descendants_and_stacking_contexts_with_stack_level_0);
visitor.visit(m_parent);
visitor.visit(m_children);
}
void StackingContext::set_last_paint_generation_id(u64 generation_id)
{
if (m_last_paint_generation_id.has_value() && m_last_paint_generation_id.value() >= generation_id) {
@ -238,7 +250,7 @@ void StackingContext::paint_internal(DisplayListRecordingContext& context) const
// Here, we treat non-positioned stacking contexts as if they were positioned, because CSS 2.0 spec does not
// account for new properties like `transform` and `opacity` that can create stacking contexts.
// https://github.com/w3c/csswg-drafts/issues/2717
for (auto* child : m_children) {
for (auto child : m_children) {
if (child->paintable_box().computed_values().z_index().has_value() && child->paintable_box().computed_values().z_index().value() < 0)
paint_child(context, *child);
}
@ -271,7 +283,7 @@ void StackingContext::paint_internal(DisplayListRecordingContext& context) const
// Here, we treat non-positioned stacking contexts as if they were positioned, because CSS 2.0 spec does not
// account for new properties like `transform` and `opacity` that can create stacking contexts.
// https://github.com/w3c/csswg-drafts/issues/2717
for (auto* child : m_children) {
for (auto child : m_children) {
if (child->paintable_box().computed_values().z_index().has_value() && child->paintable_box().computed_values().z_index().value() >= 1)
paint_child(context, *child);
}
@ -414,7 +426,7 @@ TraversalDecision StackingContext::hit_test(CSSPixelPoint position, HitTestType
// 7. the child stacking contexts with positive stack levels (least positive first).
// NOTE: Hit testing follows reverse painting order, that's why the conditions here are reversed.
for (auto const* child : m_children.in_reverse()) {
for (auto const child : m_children.in_reverse()) {
if (child->paintable_box().computed_values().z_index().value_or(0) <= 0)
break;
if (child->hit_test(transformed_position, type, callback) == TraversalDecision::Break)
@ -464,7 +476,7 @@ TraversalDecision StackingContext::hit_test(CSSPixelPoint position, HitTestType
// 2. the child stacking contexts with negative stack levels (most negative first).
// NOTE: Hit testing follows reverse painting order, that's why the conditions here are reversed.
for (auto const* child : m_children.in_reverse()) {
for (auto const child : m_children.in_reverse()) {
if (child->paintable_box().computed_values().z_index().value_or(0) >= 0)
break;
if (child->hit_test(transformed_position, type, callback) == TraversalDecision::Break)

View file

@ -7,12 +7,15 @@
#pragma once
#include <AK/Vector.h>
#include <LibGC/CellAllocator.h>
#include <LibWeb/Export.h>
#include <LibWeb/Painting/Paintable.h>
namespace Web::Painting {
class WEB_API StackingContext {
class WEB_API StackingContext final : public GC::Cell {
GC_CELL(StackingContext, GC::Cell);
GC_DECLARE_ALLOCATOR(StackingContext);
friend class ViewportPaintable;
public:
@ -46,10 +49,12 @@ public:
void set_last_paint_generation_id(u64 generation_id);
virtual void visit_edges(Visitor&) override;
private:
GC::Ref<PaintableBox> m_paintable;
StackingContext* const m_parent { nullptr };
Vector<StackingContext*> m_children;
GC::Ptr<StackingContext> m_parent;
Vector<GC::Ref<StackingContext>> m_children;
size_t m_index_in_tree_order { 0 };
Optional<u64> m_last_paint_generation_id;

View file

@ -38,7 +38,7 @@ void ViewportPaintable::build_stacking_context_tree_if_needed()
void ViewportPaintable::build_stacking_context_tree()
{
set_stacking_context(make<StackingContext>(*this, nullptr, 0));
set_stacking_context(heap().allocate<StackingContext>(*this, nullptr, 0));
size_t index_in_tree_order = 1;
for_each_in_subtree_of_type<PaintableBox>([&](auto& paintable_box) {
@ -54,7 +54,7 @@ void ViewportPaintable::build_stacking_context_tree()
return TraversalDecision::Continue;
}
VERIFY(parent_context);
paintable_box.set_stacking_context(make<StackingContext>(paintable_box, parent_context, index_in_tree_order++));
paintable_box.set_stacking_context(heap().allocate<StackingContext>(paintable_box, parent_context, index_in_tree_order++));
return TraversalDecision::Continue;
});

View file

@ -32,6 +32,8 @@ void XPathResult::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_node_set);
if (!m_node_set_iter.is_end())
visitor.visit(*m_node_set_iter);
}
XPathResult::~XPathResult() = default;