diff --git a/Libraries/LibWeb/CSS/Invalidation/AncestorTraversal.h b/Libraries/LibWeb/CSS/Invalidation/AncestorTraversal.h new file mode 100644 index 0000000000..544e944395 --- /dev/null +++ b/Libraries/LibWeb/CSS/Invalidation/AncestorTraversal.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::CSS::Invalidation { + +enum class AncestorTraversal { + ShadowIncluding, + FlatTree, +}; + +template +void for_each_inclusive_ancestor_element(DOM::Node& start, AncestorTraversal traversal, Callback callback) +{ + if (auto* element = as_if(start)) { + if (callback(*element) == TraversalDecision::Break) + return; + } + + auto* ancestor = traversal == AncestorTraversal::FlatTree + ? start.flat_tree_parent_element() + : start.parent_or_shadow_host_element(); + for (; ancestor; ancestor = traversal == AncestorTraversal::FlatTree ? ancestor->flat_tree_parent_element() : ancestor->parent_or_shadow_host_element()) { + if (callback(*ancestor) == TraversalDecision::Break) + return; + } +} + +} diff --git a/Libraries/LibWeb/CSS/Invalidation/PseudoClassInvalidator.cpp b/Libraries/LibWeb/CSS/Invalidation/PseudoClassInvalidator.cpp index 2a4b2c665c..7f87f8a573 100644 --- a/Libraries/LibWeb/CSS/Invalidation/PseudoClassInvalidator.cpp +++ b/Libraries/LibWeb/CSS/Invalidation/PseudoClassInvalidator.cpp @@ -4,253 +4,100 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include +#include #include -#include #include +#include #include -#include -#include +#include #include #include #include -#include +#include namespace Web::CSS::Invalidation { -enum class RequireAffectedByPseudoClassMetadata : u8 { - Yes, - No, -}; - -enum class RuleOriginFilter : u8 { - All, - User, -}; - -static bool pseudo_class_state_can_be_observed_across_style_scopes(CSS::PseudoClass pseudo_class) +static bool pseudo_class_propagates_to_ancestors(CSS::PseudoClass pseudo_class) { - return first_is_one_of(pseudo_class, CSS::PseudoClass::Focus, CSS::PseudoClass::FocusWithin, CSS::PseudoClass::FocusVisible); + return first_is_one_of(pseudo_class, CSS::PseudoClass::Hover, CSS::PseudoClass::FocusWithin); } -static void schedule_document_user_has_invalidation_for_shadow_pseudo_class_state_change(CSS::PseudoClass pseudo_class, DOM::Node& node) +static AncestorTraversal ancestor_traversal_for_pseudo_class(CSS::PseudoClass pseudo_class) { - if (!is(node.root())) - return; - - auto& document_style_scope = node.document().style_scope(); - if (&node.style_scope() == &document_style_scope) - return; - - if (!document_style_scope.may_have_user_has_selectors()) - return; - - Vector properties { - { .type = CSS::InvalidationSet::Property::Type::PseudoClass, .value = pseudo_class }, - }; - - document_style_scope.record_pending_has_invalidation_mutation_features(node, properties); - document_style_scope.schedule_ancestors_style_invalidation_due_to_presence_of_has(node); -} - -static CSS::StyleScope& style_scope_for_invalidation_root(DOM::Document& document, DOM::Node& invalidation_root) -{ - auto& root = invalidation_root.root(); - if (auto* shadow_root = as_if(root)) - return shadow_root->style_scope(); - return document.style_scope(); -} - -static GC::Ptr shadow_host_for_rule_matching(DOM::Element const& element, GC::Ptr rule_shadow_root) -{ - auto const& root_node = element.root(); - auto shadow_root = as_if(root_node); - auto element_shadow_root = element.shadow_root(); - - GC::Ptr shadow_host; - if (element_shadow_root) - shadow_host = element; - else if (shadow_root) - shadow_host = shadow_root->host(); - - if (element.is_shadow_host() && rule_shadow_root != element.shadow_root()) - shadow_host = rule_shadow_root ? rule_shadow_root->host() : nullptr; - - return shadow_host; -} - -template -static void invalidate_style_after_pseudo_class_state_change_in_style_scope(CSS::PseudoClass pseudo_class, DOM::Document& document, StateSlot& state_slot, DOM::Node& invalidation_root, NewState new_state, CSS::StyleScope& style_scope, RequireAffectedByPseudoClassMetadata require_metadata, RuleOriginFilter rule_origin_filter = RuleOriginFilter::All) -{ - auto rule_shadow_root = as_if(style_scope.node()); - - auto const& rules = style_scope.get_pseudo_class_rule_cache(pseudo_class); - - auto& style_computer = document.style_computer(); - auto does_rule_match_on_element = [&](DOM::Element const& element, CSS::MatchingRule const& rule) { - auto const& selector = rule.selector; - if (selector.can_use_ancestor_filter() && style_computer.should_reject_with_ancestor_filter(selector)) - return false; - - SelectorEngine::MatchContext context { - .style_sheet_for_rule = *rule.sheet, - .subject = element, - .rule_shadow_root = rule_shadow_root, - }; - auto target_pseudo = selector.target_pseudo_element(); - return SelectorEngine::matches(selector, { element, target_pseudo }, shadow_host_for_rule_matching(element, rule_shadow_root), context); - }; - - auto matches_different_set_of_rules_after_state_change = [&](DOM::Element& element) { - bool result = false; - auto check_matching_rules = [&](auto const& matching_rules) { - for (auto& rule : matching_rules) { - if (rule_origin_filter == RuleOriginFilter::User && rule.cascade_origin != CascadeOrigin::User) - continue; - - bool before = does_rule_match_on_element(element, rule); - TemporaryChange change { state_slot, new_state }; - bool after = does_rule_match_on_element(element, rule); - if (before != after) { - result = true; - return IterationDecision::Break; - } - } - return IterationDecision::Continue; - }; - - auto check_abstract_element = [&](DOM::AbstractElement abstract_element) { - rules.for_each_matching_rules(abstract_element, [&](auto const& matching_rules) { - return check_matching_rules(matching_rules); - }); - }; - - check_abstract_element({ element }); - - for (u8 i = 0; !result && i < to_underlying(CSS::PseudoElement::KnownPseudoElementCount); ++i) { - auto pseudo_element = static_cast(i); - check_abstract_element({ element, pseudo_element }); - } - - if (!result) - (void)check_matching_rules(rules.slotted_rules); - if (!result) - (void)check_matching_rules(rules.part_rules); - - return result; - }; - - auto should_check_element = [&](DOM::Element const& element) { - if (require_metadata == RequireAffectedByPseudoClassMetadata::No) - return true; - return element.affected_by_pseudo_class(pseudo_class); - }; - - Function invalidate_affected_elements_recursively = [&](DOM::Node& node) -> void { - if (node.is_element()) { - auto& element = static_cast(node); - style_computer.push_ancestor(element); - if (should_check_element(element) && matches_different_set_of_rules_after_state_change(element)) - element.set_needs_style_update(true); - } - - node.for_each_child([&](auto& child) { - invalidate_affected_elements_recursively(child); - return IterationDecision::Continue; - }); - - if (node.is_element()) - style_computer.pop_ancestor(static_cast(node)); - }; - - // Seed the ancestor filter with ancestors above the starting node, - // so that ancestor-dependent selectors can still be correctly rejected. - for (auto* ancestor = invalidation_root.parent_or_shadow_host_element(); ancestor; ancestor = ancestor->parent_or_shadow_host_element()) - style_computer.push_ancestor(*ancestor); - - invalidate_affected_elements_recursively(invalidation_root); - - for (auto* ancestor = invalidation_root.parent_or_shadow_host_element(); ancestor; ancestor = ancestor->parent_or_shadow_host_element()) - style_computer.pop_ancestor(*ancestor); -} - -template -static void invalidate_document_user_style_after_shadow_pseudo_class_state_change(CSS::PseudoClass pseudo_class, DOM::Document& document, StateSlot& state_slot, DOM::Node& invalidation_root, NewState new_state) -{ - if (!is(invalidation_root.root())) - return; - - auto& document_style_scope = document.style_scope(); - if (!document_style_scope.may_have_user_pseudo_class_selectors(pseudo_class)) - return; - - invalidate_style_after_pseudo_class_state_change_in_style_scope(pseudo_class, document, state_slot, invalidation_root, new_state, document_style_scope, RequireAffectedByPseudoClassMetadata::Yes, RuleOriginFilter::User); -} - -template -static void invalidate_style_after_pseudo_class_state_change_impl(CSS::PseudoClass pseudo_class, DOM::Document& document, StateSlot& state_slot, DOM::Node& invalidation_root, NewState new_state) -{ - if (state_slot) - schedule_document_user_has_invalidation_for_shadow_pseudo_class_state_change(pseudo_class, *state_slot); - if (new_state) - schedule_document_user_has_invalidation_for_shadow_pseudo_class_state_change(pseudo_class, *new_state); - - auto& root_style_scope = style_scope_for_invalidation_root(document, invalidation_root); - invalidate_style_after_pseudo_class_state_change_in_style_scope(pseudo_class, document, state_slot, invalidation_root, new_state, root_style_scope, RequireAffectedByPseudoClassMetadata::Yes); - invalidate_document_user_style_after_shadow_pseudo_class_state_change(pseudo_class, document, state_slot, invalidation_root, new_state); - - if (!pseudo_class_state_can_be_observed_across_style_scopes(pseudo_class)) - return; - - Vector observer_style_scopes; - auto append_observer_style_scopes = [&](auto node) { - if (!node) - return; - node->for_each_style_scope_which_may_observe_the_node([&](CSS::StyleScope& style_scope) { - if (!observer_style_scopes.contains_slow(&style_scope)) - observer_style_scopes.append(&style_scope); - }); - }; - append_observer_style_scopes(state_slot); - append_observer_style_scopes(new_state); - - Vector part_invalidation_roots; - auto append_part_invalidation_roots = [&](auto node) { - if (!node) - return; - for (auto shadow_root = node->containing_shadow_root(); shadow_root; shadow_root = shadow_root->containing_shadow_root()) { - if (!part_invalidation_roots.contains_slow(shadow_root.ptr())) - part_invalidation_roots.append(shadow_root.ptr()); - } - }; - append_part_invalidation_roots(state_slot); - append_part_invalidation_roots(new_state); - - for (auto* observer_style_scope : observer_style_scopes) { - if (auto* shadow_root = as_if(observer_style_scope->node())) { - if (observer_style_scope != &root_style_scope || &invalidation_root != shadow_root) - invalidate_style_after_pseudo_class_state_change_in_style_scope(pseudo_class, document, state_slot, *shadow_root, new_state, *observer_style_scope, RequireAffectedByPseudoClassMetadata::No); - if (auto* host = shadow_root->host()) - invalidate_style_after_pseudo_class_state_change_in_style_scope(pseudo_class, document, state_slot, *host, new_state, *observer_style_scope, RequireAffectedByPseudoClassMetadata::No); - } else if (observer_style_scope != &root_style_scope) { - invalidate_style_after_pseudo_class_state_change_in_style_scope(pseudo_class, document, state_slot, observer_style_scope->node(), new_state, *observer_style_scope, RequireAffectedByPseudoClassMetadata::No); - } - - if (observer_style_scope->get_pseudo_class_rule_cache(pseudo_class).part_rules.is_empty()) - continue; - for (auto* part_invalidation_root : part_invalidation_roots) - invalidate_style_after_pseudo_class_state_change_in_style_scope(pseudo_class, document, state_slot, *part_invalidation_root, new_state, *observer_style_scope, RequireAffectedByPseudoClassMetadata::No); + switch (pseudo_class) { + case CSS::PseudoClass::FocusWithin: + return AncestorTraversal::FlatTree; + default: + return AncestorTraversal::ShadowIncluding; } } -void invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass pseudo_class, DOM::Document& document, GC::Ptr& state_slot, DOM::Node& invalidation_root, GC::Ptr new_state) +void invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass pseudo_class, GC::Ptr old_state, GC::Ptr new_state) { - invalidate_style_after_pseudo_class_state_change_impl(pseudo_class, document, state_slot, invalidation_root, new_state); -} + if (!old_state && !new_state) + return; -void invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass pseudo_class, DOM::Document& document, GC::Ptr& state_slot, DOM::Node& invalidation_root, GC::Ptr new_state) -{ - invalidate_style_after_pseudo_class_state_change_impl(pseudo_class, document, state_slot, invalidation_root, new_state); + bool const propagates = pseudo_class_propagates_to_ancestors(pseudo_class); + auto traversal = ancestor_traversal_for_pseudo_class(pseudo_class); + + Vector properties { { CSS::InvalidationSet::Property::Type::PseudoClass, pseudo_class } }; + DOM::StyleInvalidationOptions options { .invalidate_self = true }; + auto reason = DOM::StyleInvalidationReason::PseudoClassStateChange; + + auto invalidate = [&](DOM::Element& element) { + element.invalidate_style(reason, properties, options); + + // The interaction-state pseudo classes (Hover/Focus/etc.) aren't tracked in + // pseudo_classes_used_in_has_selectors, so invalidate_node_style_for_properties + // doesn't schedule :has() ancestor invalidation for them. Schedule it directly so + // rules like .a:has(:focus) ... re-evaluate when the state flips. + element.for_each_style_scope_which_may_observe_the_node([&](CSS::StyleScope& scope) { + if (!scope.may_have_has_selectors()) + return; + scope.record_pending_has_invalidation_mutation_features(element, properties); + scope.schedule_ancestors_style_invalidation_due_to_presence_of_has(element); + }); + }; + + auto build_chain = [&](GC::Ptr start) { + HashTable chain; + if (!start) + return chain; + if (propagates) { + for_each_inclusive_ancestor_element(*start, traversal, [&](DOM::Element& element) { + chain.set(&element); + return TraversalDecision::Continue; + }); + } else if (auto* element = as_if(*start)) { + chain.set(element); + } + return chain; + }; + + auto old_chain = build_chain(old_state); + auto new_chain = build_chain(new_state); + + // Walk start's ancestor chain (inclusive) and invalidate each element whose pseudo-class + // state changes. Elements in both chains have unchanged state and are skipped; once we + // reach one, all further ancestors are also in both chains so we stop. + auto walk_and_invalidate = [&](GC::Ptr start, HashTable const& other_chain) { + if (!start) + return; + if (propagates) { + for_each_inclusive_ancestor_element(*start, traversal, [&](DOM::Element& element) { + if (other_chain.contains(&element)) + return TraversalDecision::Break; + invalidate(element); + return TraversalDecision::Continue; + }); + } else if (auto* element = as_if(*start)) { + if (!other_chain.contains(element)) + invalidate(*element); + } + }; + + walk_and_invalidate(old_state, new_chain); + walk_and_invalidate(new_state, old_chain); } } diff --git a/Libraries/LibWeb/CSS/Invalidation/PseudoClassInvalidator.h b/Libraries/LibWeb/CSS/Invalidation/PseudoClassInvalidator.h index 026c2be2d3..5ef0ec98e2 100644 --- a/Libraries/LibWeb/CSS/Invalidation/PseudoClassInvalidator.h +++ b/Libraries/LibWeb/CSS/Invalidation/PseudoClassInvalidator.h @@ -7,19 +7,11 @@ #pragma once #include -#include - -namespace Web::DOM { - -class Document; -class Element; -class Node; - -} +#include +#include namespace Web::CSS::Invalidation { -void invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass, DOM::Document&, GC::Ptr& state_slot, DOM::Node& invalidation_root, GC::Ptr new_state); -void invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass, DOM::Document&, GC::Ptr& state_slot, DOM::Node& invalidation_root, GC::Ptr new_state); +void invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass, GC::Ptr old_state, GC::Ptr new_state); } diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index b79892e4ab..2a37c5b4ab 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -2656,20 +2656,7 @@ void Document::set_hovered_node(GC::Ptr node, Optional hov entered_ancestors.append(make_hover_event_target(*target)); } - GC::Ptr old_hovered_node_root = nullptr; - GC::Ptr new_hovered_node_root = nullptr; - if (old_hovered_node) - old_hovered_node_root = old_hovered_node->root(); - if (node) - new_hovered_node_root = node->root(); - if (old_hovered_node_root != new_hovered_node_root) { - if (old_hovered_node_root) - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::Hover, *this, m_hovered_node, *old_hovered_node_root, node); - if (new_hovered_node_root) - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::Hover, *this, m_hovered_node, *new_hovered_node_root, node); - } else { - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::Hover, *this, m_hovered_node, *common_ancestor, node); - } + CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::Hover, old_hovered_node, node); m_hovered_node = node; @@ -3359,30 +3346,9 @@ void Document::set_focused_area(GC::Ptr node) if (auto* old_focused_element = as_if(old_focused_area.ptr())) old_focused_element->did_lose_focus(); - auto* common_ancestor = find_common_ancestor(old_focused_area, node); - - GC::Ptr old_focused_node_root = nullptr; - GC::Ptr new_focused_node_root = nullptr; - if (old_focused_area) - old_focused_node_root = old_focused_area->root(); - if (node) - new_focused_node_root = node->root(); - if (old_focused_node_root != new_focused_node_root) { - if (old_focused_node_root) { - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::Focus, *this, m_focused_area, *old_focused_node_root, node); - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::FocusWithin, *this, m_focused_area, *old_focused_node_root, node); - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::FocusVisible, *this, m_focused_area, *old_focused_node_root, node); - } - if (new_focused_node_root) { - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::Focus, *this, m_focused_area, *new_focused_node_root, node); - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::FocusWithin, *this, m_focused_area, *new_focused_node_root, node); - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::FocusVisible, *this, m_focused_area, *new_focused_node_root, node); - } - } else { - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::Focus, *this, m_focused_area, *common_ancestor, node); - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::FocusWithin, *this, m_focused_area, *common_ancestor, node); - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::FocusVisible, *this, m_focused_area, *common_ancestor, node); - } + CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::Focus, old_focused_area, node); + CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::FocusWithin, old_focused_area, node); + CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::FocusVisible, old_focused_area, node); m_focused_area = node; @@ -3431,24 +3397,7 @@ void Document::set_target_element(GC::Ptr element) GC::Ptr old_target_element = move(m_target_element); - auto* common_ancestor = find_common_ancestor(old_target_element, element); - - GC::Ptr old_target_node_root = nullptr; - GC::Ptr new_target_node_root = nullptr; - if (old_target_element) - old_target_node_root = old_target_element->root(); - if (element) - new_target_node_root = element->root(); - if (old_target_node_root != new_target_node_root) { - if (old_target_node_root) { - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::Target, *this, m_target_element, *old_target_node_root, element); - } - if (new_target_node_root) { - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::Target, *this, m_target_element, *new_target_node_root, element); - } - } else { - CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::Target, *this, m_target_element, *common_ancestor, element); - } + CSS::Invalidation::invalidate_style_after_pseudo_class_state_change(CSS::PseudoClass::Target, old_target_element, element); m_target_element = element; diff --git a/Libraries/LibWeb/DOM/StyleInvalidationReason.h b/Libraries/LibWeb/DOM/StyleInvalidationReason.h index 9a88209ff7..ad63161dcd 100644 --- a/Libraries/LibWeb/DOM/StyleInvalidationReason.h +++ b/Libraries/LibWeb/DOM/StyleInvalidationReason.h @@ -45,6 +45,7 @@ namespace Web::DOM { X(NodeRemove) \ X(NodeSetTextContent) \ X(Other) \ + X(PseudoClassStateChange) \ X(SetSelectorText) \ X(SettingsChange) \ X(StyleSheetDisabledStateChange) \ diff --git a/Tests/LibWeb/Text/expected/css/child-state-pseudo-invalidation.txt b/Tests/LibWeb/Text/expected/css/child-state-pseudo-invalidation.txt index 2a4f16b9e1..7747132829 100644 --- a/Tests/LibWeb/Text/expected/css/child-state-pseudo-invalidation.txt +++ b/Tests/LibWeb/Text/expected/css/child-state-pseudo-invalidation.txt @@ -11,18 +11,18 @@ focus fragment old before append: rgb(0, 128, 0) focus fragment new before append: rgb(0, 0, 0) focus fragment old after append: rgb(0, 0, 0) focus fragment new after append: rgb(0, 128, 0) -focus pseudo old before append: none +focus pseudo old before append: focus focus pseudo new before append: none focus pseudo old after append: none focus pseudo new after append: focus focus pseudo new after remove: none -focus pseudo move old before moveBefore: none +focus pseudo move old before moveBefore: focus focus pseudo move new before moveBefore: none focus pseudo move old after moveBefore: none focus pseudo move new after moveBefore: focus focus pseudo move old after moveBefore back: focus focus pseudo move new after moveBefore back: none -focus pseudo fragment old before append: none +focus pseudo fragment old before append: focus focus pseudo fragment new before append: none focus pseudo fragment old after append: none focus pseudo fragment new after append: focus @@ -75,18 +75,18 @@ hover pseudo fragment old after append: none hover pseudo fragment new after append: hover hover pseudo fragment old after moveBefore back: hover hover pseudo fragment new after moveBefore back: none -shadow hover pseudo old before append: none +shadow hover pseudo old before append: shadow hover shadow hover pseudo new before append: none shadow hover pseudo old after append: none shadow hover pseudo new after append: shadow hover shadow hover pseudo new after remove: none -shadow hover pseudo move old before moveBefore: none +shadow hover pseudo move old before moveBefore: shadow hover shadow hover pseudo move new before moveBefore: none shadow hover pseudo move old after moveBefore: none shadow hover pseudo move new after moveBefore: shadow hover shadow hover pseudo move old after moveBefore back: shadow hover shadow hover pseudo move new after moveBefore back: none -shadow hover pseudo fragment old before append: none +shadow hover pseudo fragment old before append: shadow hover shadow hover pseudo fragment new before append: none shadow hover pseudo fragment old after append: none shadow hover pseudo fragment new after append: shadow hover diff --git a/Tests/LibWeb/Text/expected/css/focus-within-shadow-flat-tree.txt b/Tests/LibWeb/Text/expected/css/focus-within-shadow-flat-tree.txt index 2bca0bf645..f3dc095b19 100644 --- a/Tests/LibWeb/Text/expected/css/focus-within-shadow-flat-tree.txt +++ b/Tests/LibWeb/Text/expected/css/focus-within-shadow-flat-tree.txt @@ -1,11 +1,17 @@ host before light input focus: none shadow target before light input focus: none +slot before light input focus: none +slotted focus target before light input focus: none slotted input before light input focus: none host after light input focus: host shadow target after light input focus: shadow-target +slot after light input focus: slot +slotted focus target after light input focus: slotted-focus-target slotted input after light input focus: slotted host after light input blur: none shadow target after light input blur: none +slot after light input blur: none +slotted focus target after light input blur: none slotted input after light input blur: none host after shadow input focus: host shadow target after shadow input focus: shadow-target diff --git a/Tests/LibWeb/Text/expected/css/style-invalidation/has-structural-feature-filter.txt b/Tests/LibWeb/Text/expected/css/style-invalidation/has-structural-feature-filter.txt index cf93dafc9f..cc51325540 100644 --- a/Tests/LibWeb/Text/expected/css/style-invalidation/has-structural-feature-filter.txt +++ b/Tests/LibWeb/Text/expected/css/style-invalidation/has-structural-feature-filter.txt @@ -265,10 +265,10 @@ PASS: featureless-sensitive structural mutation keeps :has walk: child :only-chi PASS: featureless-sensitive structural mutation keeps :has walk: child :nth-child(2) starts matching | styleInvalidations=2, fullStyleInvalidations=0, elementStyleRecomputations=4, elementStyleNoopRecomputations=2, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=2, hasResultCacheHits=0, hasResultCacheMisses=2 PASS: featureless-sensitive structural mutation keeps :has walk: child :nth-last-child(2) starts matching | styleInvalidations=2, fullStyleInvalidations=0, elementStyleRecomputations=4, elementStyleNoopRecomputations=2, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=2, hasResultCacheHits=0, hasResultCacheMisses=2 PASS: featureless-sensitive structural mutation keeps :has walk: child :only-of-type stops matching | styleInvalidations=2, fullStyleInvalidations=0, elementStyleRecomputations=4, elementStyleNoopRecomputations=2, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=2, hasResultCacheHits=0, hasResultCacheMisses=2 -PASS: unprobeable pseudo structural mutation keeps :has walk: child :focus insertion with unrelated metadata | styleInvalidations=8, fullStyleInvalidations=0, elementStyleRecomputations=7, elementStyleNoopRecomputations=5, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 -PASS: unprobeable pseudo structural mutation keeps :has walk: descendant :focus insertion through wrapper with unrelated metadata | styleInvalidations=9, fullStyleInvalidations=0, elementStyleRecomputations=8, elementStyleNoopRecomputations=6, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 -PASS: unprobeable pseudo structural mutation keeps :has walk: child :focus-within insertion with unrelated metadata | styleInvalidations=9, fullStyleInvalidations=0, elementStyleRecomputations=8, elementStyleNoopRecomputations=6, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 -PASS: unprobeable pseudo structural mutation keeps :has walk: child :focus-visible insertion with unrelated metadata | styleInvalidations=8, fullStyleInvalidations=0, elementStyleRecomputations=7, elementStyleNoopRecomputations=5, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 +PASS: unprobeable pseudo structural mutation keeps :has walk: child :focus insertion with unrelated metadata | styleInvalidations=9, fullStyleInvalidations=0, elementStyleRecomputations=7, elementStyleNoopRecomputations=6, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 +PASS: unprobeable pseudo structural mutation keeps :has walk: descendant :focus insertion through wrapper with unrelated metadata | styleInvalidations=10, fullStyleInvalidations=0, elementStyleRecomputations=8, elementStyleNoopRecomputations=7, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 +PASS: unprobeable pseudo structural mutation keeps :has walk: child :focus-within insertion with unrelated metadata | styleInvalidations=10, fullStyleInvalidations=0, elementStyleRecomputations=8, elementStyleNoopRecomputations=7, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 +PASS: unprobeable pseudo structural mutation keeps :has walk: child :focus-visible insertion with unrelated metadata | styleInvalidations=9, fullStyleInvalidations=0, elementStyleRecomputations=7, elementStyleNoopRecomputations=6, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 PASS: unprobeable pseudo structural mutation keeps :has walk: child :default checkbox insertion with unrelated metadata | styleInvalidations=1, fullStyleInvalidations=0, elementStyleRecomputations=3, elementStyleNoopRecomputations=1, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 PASS: unprobeable pseudo structural mutation keeps :has walk: descendant :default selected option insertion with unrelated metadata | styleInvalidations=7, fullStyleInvalidations=0, elementStyleRecomputations=9, elementStyleNoopRecomputations=1, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 PASS: unprobeable pseudo structural mutation keeps :has walk: child :valid insertion with unrelated metadata | styleInvalidations=7, fullStyleInvalidations=0, elementStyleRecomputations=6, elementStyleNoopRecomputations=1, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 @@ -276,7 +276,7 @@ PASS: unprobeable pseudo structural mutation keeps :has walk: child :invalid ins PASS: unprobeable pseudo structural mutation keeps :has walk: child :open insertion with unrelated metadata | styleInvalidations=6, fullStyleInvalidations=0, elementStyleRecomputations=6, elementStyleNoopRecomputations=1, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 PASS: unprobeable pseudo structural mutation keeps :has walk: child :read-only insertion with unrelated metadata | styleInvalidations=7, fullStyleInvalidations=0, elementStyleRecomputations=6, elementStyleNoopRecomputations=1, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 PASS: unprobeable pseudo structural mutation keeps :has walk: child :read-write insertion with unrelated metadata | styleInvalidations=7, fullStyleInvalidations=0, elementStyleRecomputations=6, elementStyleNoopRecomputations=1, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 -PASS: unprobeable pseudo structural mutation keeps :has walk: child :is(:focus) insertion with unrelated metadata | styleInvalidations=8, fullStyleInvalidations=0, elementStyleRecomputations=7, elementStyleNoopRecomputations=5, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 +PASS: unprobeable pseudo structural mutation keeps :has walk: child :is(:focus) insertion with unrelated metadata | styleInvalidations=9, fullStyleInvalidations=0, elementStyleRecomputations=7, elementStyleNoopRecomputations=6, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 PASS: unprobeable pseudo structural mutation keeps :has walk: child :where(:invalid) insertion with unrelated metadata | styleInvalidations=7, fullStyleInvalidations=0, elementStyleRecomputations=6, elementStyleNoopRecomputations=1, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=4, hasResultCacheHits=0, hasResultCacheMisses=4 PASS: case-sensitive name structural mutation keeps :has walk: svg camel-case tag insertion | styleInvalidations=2, fullStyleInvalidations=0, elementStyleRecomputations=3, elementStyleNoopRecomputations=1, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=2, hasResultCacheHits=0, hasResultCacheMisses=2 PASS: case-sensitive name structural mutation keeps :has walk: svg camel-case tag nested insertion | styleInvalidations=3, fullStyleInvalidations=0, elementStyleRecomputations=4, elementStyleNoopRecomputations=1, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=1, hasInvalidationMetadataCandidates=0, hasMatchInvocations=2, hasResultCacheHits=0, hasResultCacheMisses=2 diff --git a/Tests/LibWeb/Text/input/css/focus-within-shadow-flat-tree.html b/Tests/LibWeb/Text/input/css/focus-within-shadow-flat-tree.html index 78f1a4ec84..bf5a1899fb 100644 --- a/Tests/LibWeb/Text/input/css/focus-within-shadow-flat-tree.html +++ b/Tests/LibWeb/Text/input/css/focus-within-shadow-flat-tree.html @@ -16,28 +16,39 @@ + `; const shadowTarget = shadowRoot.getElementById("shadow-target"); + const slot = shadowRoot.querySelector("slot"); + const slottedFocusTarget = shadowRoot.getElementById("slotted-focus-target"); println(`host before light input focus: ${state(host, "::before")}`); println(`shadow target before light input focus: ${state(shadowTarget)}`); + println(`slot before light input focus: ${state(slot)}`); + println(`slotted focus target before light input focus: ${state(slottedFocusTarget)}`); println(`slotted input before light input focus: ${state(lightInput)}`); lightInput.focus(); println(`host after light input focus: ${state(host, "::before")}`); println(`shadow target after light input focus: ${state(shadowTarget)}`); + println(`slot after light input focus: ${state(slot)}`); + println(`slotted focus target after light input focus: ${state(slottedFocusTarget)}`); println(`slotted input after light input focus: ${state(lightInput)}`); lightInput.blur(); println(`host after light input blur: ${state(host, "::before")}`); println(`shadow target after light input blur: ${state(shadowTarget)}`); + println(`slot after light input blur: ${state(slot)}`); + println(`slotted focus target after light input blur: ${state(slottedFocusTarget)}`); println(`slotted input after light input blur: ${state(lightInput)}`); const shadowInput = document.createElement("input");