LibWeb: Split shadow stylesheet host-side invalidation reach
Broad shadow-root stylesheet changes already restyle the whole shadow tree, but host-side fallout does not always need a document-wide invalidation. Split the host-side reach classification so selectors contained to the host subtree, such as `:host *` and `:host > *`, invalidate the host, while sibling-escaping selectors such as `:host + :has(*)` still invalidate the host's root. Recognize sibling escapes through positive selector-list pseudos such as `:is()` and `:where()` as well, including selectors like `:is(:host) + :has(*)` and `:is(:host + .item)`. This avoids turning host-contained shadow stylesheet changes into full document style invalidations. On https://pomax.github.io/bezierinfo/, this reduces the time to produce a layout tree from about 8.7s to 3.6s on my machine.
This commit is contained in:
parent
1cc288730c
commit
2f39b2dd63
7 changed files with 261 additions and 26 deletions
|
|
@ -38,6 +38,83 @@ bool selector_may_match_light_dom_under_shadow_host(Selector const& selector)
|
|||
return selector.compound_selectors().size() > 1;
|
||||
}
|
||||
|
||||
static bool selector_may_match_shadow_host(Selector const&);
|
||||
static bool selector_may_match_light_dom_outside_shadow_host_via_positive_selector_list(Selector const&);
|
||||
|
||||
static bool pseudo_class_has_positive_selector_list_arguments(PseudoClass pseudo_class)
|
||||
{
|
||||
return first_is_one_of(pseudo_class, PseudoClass::Is, PseudoClass::Where);
|
||||
}
|
||||
|
||||
static bool simple_selector_may_match_shadow_host(Selector::SimpleSelector const& simple_selector)
|
||||
{
|
||||
if (simple_selector.type != Selector::SimpleSelector::Type::PseudoClass)
|
||||
return false;
|
||||
|
||||
auto const& pseudo_class = simple_selector.pseudo_class();
|
||||
if (pseudo_class.type == PseudoClass::Host)
|
||||
return true;
|
||||
|
||||
if (!pseudo_class_has_positive_selector_list_arguments(pseudo_class.type))
|
||||
return false;
|
||||
|
||||
return any_of(pseudo_class.argument_selector_list, selector_may_match_shadow_host);
|
||||
}
|
||||
|
||||
static bool selector_may_match_shadow_host(Selector const& selector)
|
||||
{
|
||||
return any_of(selector.compound_selectors(), [](auto const& compound_selector) {
|
||||
return any_of(compound_selector.simple_selectors, [](auto const& simple_selector) {
|
||||
return simple_selector_may_match_shadow_host(simple_selector);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static bool compound_selector_may_match_shadow_host(Selector::CompoundSelector const& compound_selector)
|
||||
{
|
||||
return any_of(compound_selector.simple_selectors, simple_selector_may_match_shadow_host);
|
||||
}
|
||||
|
||||
static bool simple_selector_may_match_light_dom_outside_shadow_host(Selector::SimpleSelector const& simple_selector)
|
||||
{
|
||||
if (simple_selector.type != Selector::SimpleSelector::Type::PseudoClass)
|
||||
return false;
|
||||
|
||||
auto const& pseudo_class = simple_selector.pseudo_class();
|
||||
if (!pseudo_class_has_positive_selector_list_arguments(pseudo_class.type))
|
||||
return false;
|
||||
|
||||
return any_of(pseudo_class.argument_selector_list, selector_may_match_light_dom_outside_shadow_host_via_positive_selector_list);
|
||||
}
|
||||
|
||||
static bool compound_selector_may_match_light_dom_outside_shadow_host(Selector::CompoundSelector const& compound_selector)
|
||||
{
|
||||
return any_of(compound_selector.simple_selectors, simple_selector_may_match_light_dom_outside_shadow_host);
|
||||
}
|
||||
|
||||
static bool selector_may_match_light_dom_outside_shadow_host_via_positive_selector_list(Selector const& selector)
|
||||
{
|
||||
auto const& compound_selectors = selector.compound_selectors();
|
||||
for (size_t i = 0; i < compound_selectors.size(); ++i) {
|
||||
if (compound_selector_may_match_light_dom_outside_shadow_host(compound_selectors[i]))
|
||||
return true;
|
||||
|
||||
if (!compound_selector_may_match_shadow_host(compound_selectors[i]))
|
||||
continue;
|
||||
|
||||
for (size_t j = i + 1; j < compound_selectors.size(); ++j) {
|
||||
if (first_is_one_of(compound_selectors[j].combinator, Selector::Combinator::NextSibling, Selector::Combinator::SubsequentSibling))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool selector_may_match_light_dom_outside_shadow_host(Selector const& selector)
|
||||
{
|
||||
return selector_may_match_light_dom_outside_shadow_host_via_positive_selector_list(selector);
|
||||
}
|
||||
|
||||
bool selector_may_match_light_dom_under_shadow_host(StringView selector_text)
|
||||
{
|
||||
CSS::Parser::ParsingParams parsing_params;
|
||||
|
|
@ -47,6 +124,15 @@ bool selector_may_match_light_dom_under_shadow_host(StringView selector_text)
|
|||
return selector_may_match_light_dom_under_shadow_host(selectors->first());
|
||||
}
|
||||
|
||||
bool selector_may_match_light_dom_outside_shadow_host(StringView selector_text)
|
||||
{
|
||||
CSS::Parser::ParsingParams parsing_params;
|
||||
auto selectors = parse_selector(parsing_params, selector_text);
|
||||
if (!selectors.has_value() || selectors->size() != 1)
|
||||
return false;
|
||||
return selector_may_match_light_dom_outside_shadow_host(selectors->first());
|
||||
}
|
||||
|
||||
static bool is_universal_only_compound(Selector::CompoundSelector const& compound_selector)
|
||||
{
|
||||
if (compound_selector.simple_selectors.is_empty())
|
||||
|
|
@ -127,6 +213,7 @@ void extend_style_sheet_invalidation_set_with_style_rule(StyleSheetInvalidationS
|
|||
|
||||
for (auto const& selector : style_rule.absolutized_selectors()) {
|
||||
result.may_match_light_dom_under_shadow_host |= selector_may_match_light_dom_under_shadow_host(*selector);
|
||||
result.may_match_light_dom_outside_shadow_host |= selector_may_match_light_dom_outside_shadow_host(*selector);
|
||||
result.may_match_shadow_host |= selector->contains_pseudo_class(PseudoClass::Host);
|
||||
|
||||
auto const& compound_selectors = selector->compound_selectors();
|
||||
|
|
@ -316,13 +403,14 @@ void invalidate_root_for_style_sheet_change(DOM::Node& root, StyleSheetInvalidat
|
|||
|
||||
if (auto* host = shadow_root->host()) {
|
||||
// Broad shadow-root mutations are only allowed to escape the shadow tree when the stylesheet can
|
||||
// actually reach host-side nodes. A layer-order-only change, for example, still needs a full restyle
|
||||
// inside the shadow tree, but it should not turn into a document-wide invalidation for unrelated
|
||||
// light-DOM.
|
||||
if (result.may_match_light_dom_under_shadow_host && !result.may_match_shadow_host) {
|
||||
host->invalidate_style(reason);
|
||||
} else if (result.may_match_light_dom_under_shadow_host) {
|
||||
// actually reach host-side nodes. Keep host-contained escapes bounded to the host subtree; a
|
||||
// layer-order-only change, for example, still needs a full restyle inside the shadow tree, but it
|
||||
// should not turn into a document-wide invalidation for unrelated light-DOM. Selectors with
|
||||
// host-side sibling reach still need a root-side invalidation.
|
||||
if (result.may_match_light_dom_outside_shadow_host) {
|
||||
host->root().invalidate_style(reason);
|
||||
} else if (result.may_match_light_dom_under_shadow_host) {
|
||||
host->invalidate_style(reason);
|
||||
} else if (result.may_match_shadow_host) {
|
||||
host->invalidate_style(reason);
|
||||
shadow_root->set_needs_style_update(true);
|
||||
|
|
@ -340,16 +428,13 @@ void invalidate_root_for_style_sheet_change(DOM::Node& root, StyleSheetInvalidat
|
|||
invalidate_assigned_elements_for_dirty_slots(*shadow_root);
|
||||
|
||||
if (auto* host = shadow_root->host()) {
|
||||
// Slotted selectors never match the host itself, so a targeted ::slotted(...) invalidation only needs to
|
||||
// walk the current host's light-DOM subtree. We can identify that case because it reaches host-side nodes
|
||||
// without ever setting may_match_shadow_host.
|
||||
//
|
||||
// :host combinators are different: they can escape to siblings or other nodes rooted alongside the host,
|
||||
// Slotted and host-contained :host selectors only need to walk the current host's light-DOM subtree.
|
||||
// :host selectors with sibling combinators can escape to siblings or other nodes rooted alongside the host,
|
||||
// so they still need the broader host-root walk below.
|
||||
if (result.may_match_light_dom_under_shadow_host && !result.may_match_shadow_host) {
|
||||
invalidate_elements_matching_invalidation_set_and_anchor_rules(*host, result, host, shadow_root);
|
||||
} else if (result.may_match_light_dom_under_shadow_host) {
|
||||
if (result.may_match_light_dom_outside_shadow_host) {
|
||||
invalidate_elements_matching_invalidation_set_and_anchor_rules(host->root(), result, host, shadow_root);
|
||||
} else if (result.may_match_light_dom_under_shadow_host) {
|
||||
invalidate_elements_matching_invalidation_set_and_anchor_rules(*host, result, host, shadow_root);
|
||||
} else if (result.may_match_shadow_host) {
|
||||
bool host_or_shadow_tree_needs_style_update = false;
|
||||
if (Invalidation::element_matches_any_invalidation_set_property(*host, invalidation_set))
|
||||
|
|
@ -510,7 +595,7 @@ static ShadowRootStylesheetEffects determine_shadow_root_stylesheet_effects_for_
|
|||
};
|
||||
|
||||
style_sheet.for_each_effective_style_producing_rule([&](CSSRule const& rule) {
|
||||
if (effects.may_match_shadow_host && effects.may_match_light_dom_under_shadow_host && effects.may_affect_assigned_nodes_via_slots)
|
||||
if (effects.all_set())
|
||||
return;
|
||||
|
||||
if (!is<CSSStyleRule>(rule))
|
||||
|
|
@ -520,11 +605,12 @@ static ShadowRootStylesheetEffects determine_shadow_root_stylesheet_effects_for_
|
|||
for (auto const& selector : style_rule.absolutized_selectors()) {
|
||||
effects.may_match_shadow_host |= selector->contains_pseudo_class(PseudoClass::Host);
|
||||
effects.may_match_light_dom_under_shadow_host |= selector_may_match_light_dom_under_shadow_host(*selector);
|
||||
effects.may_match_light_dom_outside_shadow_host |= selector_may_match_light_dom_outside_shadow_host(*selector);
|
||||
|
||||
if (!effects.may_affect_assigned_nodes_via_slots && !slots.is_empty())
|
||||
effects.may_affect_assigned_nodes_via_slots = selector_may_affect_assigned_nodes_via_slot_inheritance(*selector);
|
||||
|
||||
if (effects.may_match_shadow_host && effects.may_match_light_dom_under_shadow_host && effects.may_affect_assigned_nodes_via_slots)
|
||||
if (effects.all_set())
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
|
@ -540,6 +626,7 @@ ShadowRootStylesheetEffects determine_shadow_root_stylesheet_effects(DOM::Shadow
|
|||
auto sheet_effects = determine_shadow_root_stylesheet_effects_for_sheet(style_sheet, shadow_root);
|
||||
effects.may_match_shadow_host |= sheet_effects.may_match_shadow_host;
|
||||
effects.may_match_light_dom_under_shadow_host |= sheet_effects.may_match_light_dom_under_shadow_host;
|
||||
effects.may_match_light_dom_outside_shadow_host |= sheet_effects.may_match_light_dom_outside_shadow_host;
|
||||
effects.may_affect_assigned_nodes_via_slots |= sheet_effects.may_affect_assigned_nodes_via_slots;
|
||||
});
|
||||
|
||||
|
|
@ -558,9 +645,10 @@ ShadowRootStylesheetEffects determine_shadow_root_stylesheet_effects(CSSStyleShe
|
|||
auto sheet_effects = determine_shadow_root_stylesheet_effects_for_sheet(style_sheet, *shadow_root);
|
||||
effects.may_match_shadow_host |= sheet_effects.may_match_shadow_host;
|
||||
effects.may_match_light_dom_under_shadow_host |= sheet_effects.may_match_light_dom_under_shadow_host;
|
||||
effects.may_match_light_dom_outside_shadow_host |= sheet_effects.may_match_light_dom_outside_shadow_host;
|
||||
effects.may_affect_assigned_nodes_via_slots |= sheet_effects.may_affect_assigned_nodes_via_slots;
|
||||
|
||||
if (effects.may_match_shadow_host && effects.may_match_light_dom_under_shadow_host && effects.may_affect_assigned_nodes_via_slots)
|
||||
if (effects.all_set())
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -573,10 +661,10 @@ static bool invalidate_shadow_host_side_for_style_sheet_change(DOM::ShadowRoot&
|
|||
if (!host)
|
||||
return false;
|
||||
|
||||
if (effects.may_match_light_dom_under_shadow_host && !effects.may_match_shadow_host) {
|
||||
host->invalidate_style(reason);
|
||||
} else if (effects.may_match_light_dom_under_shadow_host) {
|
||||
if (effects.may_match_light_dom_outside_shadow_host) {
|
||||
host->root().invalidate_style(reason);
|
||||
} else if (effects.may_match_light_dom_under_shadow_host) {
|
||||
host->invalidate_style(reason);
|
||||
} else if (effects.may_affect_assigned_nodes_via_slots) {
|
||||
host->invalidate_style(reason);
|
||||
} else if (effects.may_match_shadow_host) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ struct StyleSheetInvalidationSet {
|
|||
InvalidationSet invalidation_set;
|
||||
bool may_match_shadow_host { false };
|
||||
bool may_match_light_dom_under_shadow_host { false };
|
||||
bool may_match_light_dom_outside_shadow_host { false };
|
||||
|
||||
struct PseudoElementInvalidationRule {
|
||||
InvalidationSet anchor_set;
|
||||
|
|
@ -42,7 +43,16 @@ struct StyleSheetInvalidationSet {
|
|||
struct ShadowRootStylesheetEffects {
|
||||
bool may_match_shadow_host { false };
|
||||
bool may_match_light_dom_under_shadow_host { false };
|
||||
bool may_match_light_dom_outside_shadow_host { false };
|
||||
bool may_affect_assigned_nodes_via_slots { false };
|
||||
|
||||
bool all_set() const
|
||||
{
|
||||
return may_match_shadow_host
|
||||
&& may_match_light_dom_under_shadow_host
|
||||
&& may_match_light_dom_outside_shadow_host
|
||||
&& may_affect_assigned_nodes_via_slots;
|
||||
}
|
||||
};
|
||||
|
||||
enum class ShouldInvalidateRuleCache {
|
||||
|
|
@ -60,6 +70,11 @@ void extend_style_sheet_invalidation_set_with_style_rule(StyleSheetInvalidationS
|
|||
bool selector_may_match_light_dom_under_shadow_host(Selector const&);
|
||||
WEB_API bool selector_may_match_light_dom_under_shadow_host(StringView selector_text);
|
||||
|
||||
// Shadow-root :host rules with sibling combinators can target host siblings or other nodes outside the host subtree.
|
||||
// These need broader host-root invalidation than host-subtree selectors like :host > * or :host *.
|
||||
bool selector_may_match_light_dom_outside_shadow_host(Selector const&);
|
||||
WEB_API bool selector_may_match_light_dom_outside_shadow_host(StringView selector_text);
|
||||
|
||||
// Apply a built invalidation set to `root` (a Document or a ShadowRoot). When `force_broad_invalidation` is true,
|
||||
// schedule a tree-wide restyle regardless of the targeted set; this is used when the sheet contains rule kinds (such
|
||||
// as @property or @keyframes) whose effects are not captured by selector invalidation alone.
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ void StyleSheetList::add_sheet(CSSStyleSheet& sheet)
|
|||
auto effects = determine_shadow_root_stylesheet_effects(*shadow_root);
|
||||
invalidation_set_result.may_match_shadow_host |= effects.may_match_shadow_host;
|
||||
invalidation_set_result.may_match_light_dom_under_shadow_host |= effects.may_match_light_dom_under_shadow_host;
|
||||
invalidation_set_result.may_match_light_dom_outside_shadow_host |= effects.may_match_light_dom_outside_shadow_host;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -224,6 +225,7 @@ void StyleSheetList::remove_sheet(CSSStyleSheet& sheet)
|
|||
auto effects = determine_shadow_root_stylesheet_effects(*shadow_root);
|
||||
invalidation_set_result.may_match_shadow_host |= effects.may_match_shadow_host;
|
||||
invalidation_set_result.may_match_light_dom_under_shadow_host |= effects.may_match_light_dom_under_shadow_host;
|
||||
invalidation_set_result.may_match_light_dom_outside_shadow_host |= effects.may_match_light_dom_outside_shadow_host;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,49 @@ TEST_CASE(selector_escape_to_light_dom_under_shadow_host)
|
|||
{
|
||||
EXPECT(!CSS::selector_may_match_light_dom_under_shadow_host(":host"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_under_shadow_host(":host(.active)"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_under_shadow_host(":is(:host)"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_under_shadow_host(":where(:host)"sv));
|
||||
|
||||
EXPECT(CSS::selector_may_match_light_dom_under_shadow_host(":host > *"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_under_shadow_host(":host + .item"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_under_shadow_host(":host ~ .item"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_under_shadow_host(":is(:host) > *"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_under_shadow_host(":where(:host) .item"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_under_shadow_host(":is(.foo, :host) + .item"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_under_shadow_host("::slotted(.item)"sv));
|
||||
}
|
||||
|
||||
TEST_CASE(selector_escape_to_light_dom_outside_shadow_host)
|
||||
{
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":host"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":host(.active)"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":host > *"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":host *"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host("::slotted(.item)"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":is(:host)"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":where(:host)"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":is(:host) > *"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":where(:host) *"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":is(:host > .item)"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":where(:host .item)"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":host:has(+ .item)"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":has(:host) + .item"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":not(:host) + .item"sv));
|
||||
EXPECT(!CSS::selector_may_match_light_dom_outside_shadow_host(":nth-child(1 of :host) + .item"sv));
|
||||
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":host + .item"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":host ~ .item"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":host + :has(*)"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":is(:host) + .item"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":where(:host) ~ .item"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":is(:host) + :has(*)"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":is(.foo, :host) + .item"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":where(.foo, :host(.active)) + .item"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":is(:where(:host)) + .item"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":is(:host + .item)"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":where(:host ~ .item)"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":is(:host + .item) > .child"sv));
|
||||
EXPECT(CSS::selector_may_match_light_dom_outside_shadow_host(":is(.foo, :where(:host + .item))"sv));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,6 @@
|
|||
PASS: host child combinator add stays on the host-side tree (2 invalidations)
|
||||
PASS: host descendant combinator add stays on the host-side tree (2 invalidations)
|
||||
PASS: broad host-side stylesheet add avoids document-wide invalidation (2 invalidations)
|
||||
PASS: broad host-side sibling escape reaches root-side invalidation (1 full invalidations)
|
||||
PASS: broad functional host-side selector avoids document-wide invalidation (2 invalidations)
|
||||
PASS: broad functional host-side sibling escape reaches root-side invalidation (1 full invalidations)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ PASS: shadow media mutation activates :host :where selector | styleInvalidations
|
|||
PASS: shadow media mutation deactivates classed :host selector | styleInvalidations=1, fullStyleInvalidations=0, elementStyleRecomputations=5, elementStyleNoopRecomputations=0, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
PASS: shadow media appendMedium activates :host selector | styleInvalidations=1, fullStyleInvalidations=0, elementStyleRecomputations=5, elementStyleNoopRecomputations=0, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
PASS: shadow media deleteMedium deactivates :host selector | styleInvalidations=1, fullStyleInvalidations=0, elementStyleRecomputations=5, elementStyleNoopRecomputations=0, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
PASS: shadow media mutation activates internal rule guarded by :host | styleInvalidations=0, fullStyleInvalidations=1, elementStyleRecomputations=14, elementStyleNoopRecomputations=13, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
PASS: shadow media mutation activates internal rule guarded by :host | styleInvalidations=0, fullStyleInvalidations=0, elementStyleRecomputations=5, elementStyleNoopRecomputations=4, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
PASS: shadow media mutation activates ::slotted class selector | styleInvalidations=0, fullStyleInvalidations=0, elementStyleRecomputations=5, elementStyleNoopRecomputations=4, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
PASS: shadow media mutation activates ::slotted type and class selector | styleInvalidations=0, fullStyleInvalidations=0, elementStyleRecomputations=5, elementStyleNoopRecomputations=4, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
PASS: shadow media mutation activates ::slotted attribute selector | styleInvalidations=0, fullStyleInvalidations=0, elementStyleRecomputations=5, elementStyleNoopRecomputations=4, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
|
|
@ -20,5 +20,5 @@ PASS: shadow media mutation activates named slot inheritance | styleInvalidation
|
|||
PASS: shadow media mutation activates descendant slot inheritance | styleInvalidations=0, fullStyleInvalidations=0, elementStyleRecomputations=5, elementStyleNoopRecomputations=3, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
PASS: shadow media mutation activates nested host structure | styleInvalidations=1, fullStyleInvalidations=0, elementStyleRecomputations=5, elementStyleNoopRecomputations=0, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
PASS: shadow media mutation activates paragraph assigned node | styleInvalidations=0, fullStyleInvalidations=0, elementStyleRecomputations=5, elementStyleNoopRecomputations=4, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
PASS: shadow media mutation activates compound host and slotted selectors | styleInvalidations=1, fullStyleInvalidations=1, elementStyleRecomputations=14, elementStyleNoopRecomputations=9, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
PASS: shadow media mutation activates compound host and slotted selectors | styleInvalidations=0, fullStyleInvalidations=0, elementStyleRecomputations=5, elementStyleNoopRecomputations=0, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
PASS: shadow media mutation activates inner and slotted selectors together | styleInvalidations=0, fullStyleInvalidations=0, elementStyleRecomputations=5, elementStyleNoopRecomputations=3, elementInheritedStyleRecomputations=0, elementInheritedStyleNoopRecomputations=0, hasAncestorWalkInvocations=0, hasInvalidationMetadataCandidates=0, hasMatchInvocations=0, hasResultCacheHits=0, hasResultCacheMisses=0
|
||||
|
|
|
|||
|
|
@ -10,11 +10,19 @@
|
|||
}
|
||||
|
||||
function verifyInvalidationsStayHostSide(label, maxInvalidations) {
|
||||
const invalidations = internals.getStyleInvalidationCounters().styleInvalidations;
|
||||
if (invalidations > 0 && invalidations <= maxInvalidations)
|
||||
println(`PASS: ${label} (${invalidations} invalidations)`);
|
||||
const counters = internals.getStyleInvalidationCounters();
|
||||
if (counters.fullStyleInvalidations == 0 && counters.styleInvalidations > 0 && counters.styleInvalidations <= maxInvalidations)
|
||||
println(`PASS: ${label} (${counters.styleInvalidations} invalidations)`);
|
||||
else
|
||||
println(`FAIL: ${label} (${invalidations} invalidations)`);
|
||||
println(`FAIL: ${label} (${counters.styleInvalidations} invalidations, ${counters.fullStyleInvalidations} full invalidations)`);
|
||||
}
|
||||
|
||||
function verifyInvalidationsReachRootSide(label) {
|
||||
const counters = internals.getStyleInvalidationCounters();
|
||||
if (counters.fullStyleInvalidations > 0)
|
||||
println(`PASS: ${label} (${counters.fullStyleInvalidations} full invalidations)`);
|
||||
else
|
||||
println(`FAIL: ${label} (${counters.styleInvalidations} invalidations, ${counters.fullStyleInvalidations} full invalidations)`);
|
||||
}
|
||||
|
||||
function populateShadowBystanders(shadowRoot, prefix) {
|
||||
|
|
@ -52,5 +60,85 @@
|
|||
descendantShadowRoot.appendChild(descendantStyle);
|
||||
getComputedStyle(nestedDescendant).color;
|
||||
verifyInvalidationsStayHostSide("host descendant combinator add stays on the host-side tree", 4);
|
||||
|
||||
const broadHost = document.createElement("div");
|
||||
broadHost.innerHTML = "<span id='broad-target'>broad target</span>";
|
||||
document.body.appendChild(broadHost);
|
||||
for (let i = 0; i < 25; ++i) {
|
||||
const bystander = document.createElement("span");
|
||||
bystander.textContent = `document bystander ${i}`;
|
||||
document.body.appendChild(bystander);
|
||||
}
|
||||
|
||||
const broadTarget = document.getElementById("broad-target");
|
||||
const broadShadowRoot = broadHost.attachShadow({ mode: "open" });
|
||||
settleAndReset(broadTarget);
|
||||
const broadStyle = document.createElement("style");
|
||||
broadStyle.textContent = `
|
||||
@keyframes shadow-root-host-side-broad-invalidation {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
:host * { color: rgb(0, 0, 255); }
|
||||
`;
|
||||
broadShadowRoot.appendChild(broadStyle);
|
||||
getComputedStyle(broadTarget).color;
|
||||
verifyInvalidationsStayHostSide("broad host-side stylesheet add avoids document-wide invalidation", 4);
|
||||
|
||||
const siblingEscapeHost = document.createElement("div");
|
||||
document.body.appendChild(siblingEscapeHost);
|
||||
const siblingEscapeTarget = document.createElement("div");
|
||||
siblingEscapeTarget.innerHTML = "<span>sibling escape target</span>";
|
||||
document.body.appendChild(siblingEscapeTarget);
|
||||
const siblingEscapeShadowRoot = siblingEscapeHost.attachShadow({ mode: "open" });
|
||||
settleAndReset(siblingEscapeTarget);
|
||||
const siblingEscapeStyle = document.createElement("style");
|
||||
siblingEscapeStyle.textContent = `
|
||||
@keyframes shadow-root-host-side-sibling-escape-invalidation {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
:host + :has(*) { color: rgb(255, 0, 0); }
|
||||
`;
|
||||
siblingEscapeShadowRoot.appendChild(siblingEscapeStyle);
|
||||
getComputedStyle(siblingEscapeTarget).color;
|
||||
verifyInvalidationsReachRootSide("broad host-side sibling escape reaches root-side invalidation");
|
||||
|
||||
const functionalHost = document.createElement("div");
|
||||
functionalHost.innerHTML = "<span id='functional-host-target'>functional host target</span>";
|
||||
document.body.appendChild(functionalHost);
|
||||
const functionalHostTarget = document.getElementById("functional-host-target");
|
||||
const functionalHostShadowRoot = functionalHost.attachShadow({ mode: "open" });
|
||||
settleAndReset(functionalHostTarget);
|
||||
const functionalHostStyle = document.createElement("style");
|
||||
functionalHostStyle.textContent = `
|
||||
@keyframes shadow-root-functional-host-side-invalidation {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
:is(:host) * { color: rgb(0, 0, 255); }
|
||||
`;
|
||||
functionalHostShadowRoot.appendChild(functionalHostStyle);
|
||||
getComputedStyle(functionalHostTarget).color;
|
||||
verifyInvalidationsStayHostSide("broad functional host-side selector avoids document-wide invalidation", 4);
|
||||
|
||||
const functionalSiblingEscapeHost = document.createElement("div");
|
||||
document.body.appendChild(functionalSiblingEscapeHost);
|
||||
const functionalSiblingEscapeTarget = document.createElement("div");
|
||||
functionalSiblingEscapeTarget.innerHTML = "<span>functional sibling escape target</span>";
|
||||
document.body.appendChild(functionalSiblingEscapeTarget);
|
||||
const functionalSiblingEscapeShadowRoot = functionalSiblingEscapeHost.attachShadow({ mode: "open" });
|
||||
settleAndReset(functionalSiblingEscapeTarget);
|
||||
const functionalSiblingEscapeStyle = document.createElement("style");
|
||||
functionalSiblingEscapeStyle.textContent = `
|
||||
@keyframes shadow-root-functional-sibling-escape-invalidation {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
:is(:host) + :has(*) { color: rgb(255, 0, 0); }
|
||||
`;
|
||||
functionalSiblingEscapeShadowRoot.appendChild(functionalSiblingEscapeStyle);
|
||||
getComputedStyle(functionalSiblingEscapeTarget).color;
|
||||
verifyInvalidationsReachRootSide("broad functional host-side sibling escape reaches root-side invalidation");
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue