mirror of
https://github.com/zebrajr/ladybird.git
synced 2025-12-06 00:19:53 +01:00
LibWeb: Add ::slotted() pseudo-element support
Implements `::slotted()` to enough extent we could pass the imported WPT test and make substantial layout correctness improvement on https://www.rottentomatoes.com/
This commit is contained in:
parent
8986e1f1ec
commit
4c7da460dc
|
|
@ -137,6 +137,13 @@ Selector::Selector(Vector<CompoundSelector>&& compound_selectors)
|
|||
|
||||
void Selector::collect_ancestor_hashes()
|
||||
{
|
||||
if (is_slotted()) {
|
||||
// Ancestor filtering is not supported for slotted selectors, because those
|
||||
// are supposed to be collected for element inside a slot, while being
|
||||
// matched against slot element.
|
||||
return;
|
||||
}
|
||||
|
||||
size_t next_hash_index = 0;
|
||||
auto append_unique_hash = [&](u32 hash) -> bool {
|
||||
if (next_hash_index >= m_ancestor_hashes.size())
|
||||
|
|
|
|||
|
|
@ -228,6 +228,8 @@ public:
|
|||
|
||||
size_t sibling_invalidation_distance() const;
|
||||
|
||||
bool is_slotted() const { return m_pseudo_element.has_value() && m_pseudo_element->type() == PseudoElement::Slotted; }
|
||||
|
||||
private:
|
||||
explicit Selector(Vector<CompoundSelector>&&);
|
||||
|
||||
|
|
|
|||
|
|
@ -1136,6 +1136,10 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, DOM::
|
|||
case CSS::Selector::SimpleSelector::Type::PseudoClass:
|
||||
return matches_pseudo_class(component.pseudo_class(), element, shadow_host, context, scope, selector_kind);
|
||||
case CSS::Selector::SimpleSelector::Type::PseudoElement:
|
||||
if (component.pseudo_element().type() == CSS::PseudoElement::Slotted) {
|
||||
VERIFY(context.slotted_element);
|
||||
return matches(component.pseudo_element().compound_selector(), *context.slotted_element, shadow_host, context);
|
||||
}
|
||||
// Pseudo-element matching/not-matching is handled in the top level matches().
|
||||
return true;
|
||||
case CSS::Selector::SimpleSelector::Type::Nesting:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ enum class SelectorKind {
|
|||
struct MatchContext {
|
||||
GC::Ptr<CSS::CSSStyleSheet const> style_sheet_for_rule {};
|
||||
GC::Ptr<DOM::Element const> subject {};
|
||||
GC::Ptr<DOM::Element const> slotted_element {}; // Only set when matching a ::slotted() pseudo-element
|
||||
bool collect_per_element_selector_involvement_metadata { false };
|
||||
CSS::PseudoClassBitmap attempted_pseudo_class_matches {};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -526,14 +526,17 @@ Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element c
|
|||
auto from_user_agent_or_user_stylesheet = rule_to_run.cascade_origin == CascadeOrigin::UserAgent || rule_to_run.cascade_origin == CascadeOrigin::User;
|
||||
|
||||
// NOTE: Inside shadow trees, we only match rules that are defined in the shadow tree's style sheets.
|
||||
// The key exception is the shadow tree's *shadow host*, which needs to match :host rules from inside the shadow root.
|
||||
// Also note that UA or User style sheets don't have a scope, so they are always relevant.
|
||||
// Exceptions are:
|
||||
// - the shadow tree's *shadow host*, which needs to match :host rules from inside the shadow root.
|
||||
// - ::slotted() rules, which need to match elements assigned to slots from inside the shadow root.
|
||||
// - UA or User style sheets don't have a scope, so they are always relevant.
|
||||
// FIXME: We should reorganize the data so that the document-level StyleComputer doesn't cache *all* rules,
|
||||
// but instead we'd have some kind of "style scope" at the document level, and also one for each shadow root.
|
||||
// Then we could only evaluate rules from the current style scope.
|
||||
bool rule_is_relevant_for_current_scope = rule_root == shadow_root
|
||||
|| (element_shadow_root && rule_root == element_shadow_root)
|
||||
|| from_user_agent_or_user_stylesheet;
|
||||
|| from_user_agent_or_user_stylesheet
|
||||
|| rule_to_run.slotted;
|
||||
|
||||
if (!rule_is_relevant_for_current_scope)
|
||||
return;
|
||||
|
|
@ -554,7 +557,7 @@ Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element c
|
|||
}
|
||||
} else {
|
||||
for (auto const& rule : rules) {
|
||||
if (!rule.contains_pseudo_element && filter_namespace_rule(element_namespace_uri, rule))
|
||||
if ((rule.slotted || !rule.contains_pseudo_element) && filter_namespace_rule(element_namespace_uri, rule))
|
||||
add_rule_to_run(rule);
|
||||
}
|
||||
}
|
||||
|
|
@ -580,6 +583,14 @@ Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element c
|
|||
add_rules_from_cache(*rule_cache);
|
||||
}
|
||||
|
||||
if (auto assigned_slot = element.assigned_slot_internal()) {
|
||||
if (auto const* slot_shadow_root = as_if<DOM::ShadowRoot>(assigned_slot->root())) {
|
||||
if (auto const* rule_cache = rule_cache_for_cascade_origin(cascade_origin, qualified_layer_name, slot_shadow_root)) {
|
||||
add_rules_to_run(rule_cache->slotted_rules);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector<MatchingRule const*> matching_rules;
|
||||
matching_rules.ensure_capacity(rules_to_run.size());
|
||||
|
||||
|
|
@ -602,7 +613,19 @@ Vector<MatchingRule const*> StyleComputer::collect_matching_rules(DOM::Element c
|
|||
ScopeGuard guard = [&] {
|
||||
attempted_pseudo_class_matches |= context.attempted_pseudo_class_matches;
|
||||
};
|
||||
if (!SelectorEngine::matches(selector, element, shadow_host_to_use, context, pseudo_element))
|
||||
if (selector.is_slotted()) {
|
||||
if (!element.assigned_slot_internal())
|
||||
continue;
|
||||
// We're collecting rules for element, which is assigned to a slot.
|
||||
// For ::slotted() matching, slot should be used as a subject instead of element,
|
||||
// while element itself is saved in matching context, so selector engine could
|
||||
// switch back to it when matching inside ::slotted() argument.
|
||||
auto const& slot = *element.assigned_slot_internal();
|
||||
context.slotted_element = &element;
|
||||
context.subject = &slot;
|
||||
if (!SelectorEngine::matches(selector, slot, shadow_host_to_use, context, PseudoElement::Slotted))
|
||||
continue;
|
||||
} else if (!SelectorEngine::matches(selector, element, shadow_host_to_use, context, pseudo_element))
|
||||
continue;
|
||||
matching_rules.append(&rule_to_run);
|
||||
}
|
||||
|
|
@ -2901,6 +2924,7 @@ void StyleComputer::make_rule_cache_for_cascade_origin(CascadeOrigin cascade_ori
|
|||
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoElement) {
|
||||
matching_rule.contains_pseudo_element = true;
|
||||
pseudo_element = simple_selector.pseudo_element().type();
|
||||
matching_rule.slotted = pseudo_element == PseudoElement::Slotted;
|
||||
}
|
||||
}
|
||||
if (!contains_root_pseudo_class) {
|
||||
|
|
@ -3404,6 +3428,10 @@ bool StyleComputer::have_has_selectors() const
|
|||
|
||||
void RuleCache::add_rule(MatchingRule const& matching_rule, Optional<PseudoElement> pseudo_element, bool contains_root_pseudo_class)
|
||||
{
|
||||
if (matching_rule.slotted) {
|
||||
slotted_rules.append(matching_rule);
|
||||
return;
|
||||
}
|
||||
// NOTE: We traverse the simple selectors in reverse order to make sure that class/ID buckets are preferred over tag buckets
|
||||
// in the common case of div.foo or div#foo selectors.
|
||||
auto add_to_id_bucket = [&](FlyString const& name) {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ struct MatchingRule {
|
|||
u32 specificity { 0 };
|
||||
CascadeOrigin cascade_origin;
|
||||
bool contains_pseudo_element { false };
|
||||
bool slotted { false };
|
||||
|
||||
// Helpers to deal with the fact that `rule` might be a CSSStyleRule or a CSSNestedDeclarations
|
||||
CSSStyleProperties const& declaration() const;
|
||||
|
|
@ -117,6 +118,7 @@ struct RuleCache {
|
|||
HashMap<FlyString, Vector<MatchingRule>, AK::ASCIICaseInsensitiveFlyStringTraits> rules_by_attribute_name;
|
||||
Array<Vector<MatchingRule>, to_underlying(CSS::PseudoElement::KnownPseudoElementCount)> rules_by_pseudo_element;
|
||||
Vector<MatchingRule> root_rules;
|
||||
Vector<MatchingRule> slotted_rules;
|
||||
Vector<MatchingRule> other_rules;
|
||||
|
||||
HashMap<FlyString, NonnullRefPtr<Animations::KeyframeEffect::KeyFrameSet>> rules_by_animation_keyframes;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Scoping Module Level 1 - A green box reference</title>
|
||||
<link rel="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"/>
|
||||
</head>
|
||||
<body>
|
||||
<p>Test passes if you see a single 100px by 100px green box below.</p>
|
||||
<div style="width: 100px; height: 100px; background: green;"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CSS Scoping Module Level 1 - :slotted pseudo element must allow selecting elements assigned to a slot element</title>
|
||||
<link rel="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"/>
|
||||
<link rel="help" href="http://www.w3.org/TR/css-scoping-1/#slotted-pseudo">
|
||||
<link rel="match" href="../../../../expected/wpt-import/css/css-scoping/reference/green-box.html"/>
|
||||
</head>
|
||||
<body>
|
||||
<style>
|
||||
my-host {
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
color: red;
|
||||
background: green;
|
||||
}
|
||||
my-host > div, nested-host {
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 25px;
|
||||
}
|
||||
</style>
|
||||
<p>Test passes if you see a single 100px by 100px green box below.</p>
|
||||
<my-host>
|
||||
<div class="green">FAIL1</div>
|
||||
<myelem><span>FAIL2</span></myelem>
|
||||
<nested-host>
|
||||
<span>FAIL3</span>
|
||||
</nested-host>
|
||||
<another-host>
|
||||
<b>FAIL4</b>
|
||||
</another-host>
|
||||
</my-host>
|
||||
<script>
|
||||
|
||||
try {
|
||||
var shadowHost = document.querySelector('my-host');
|
||||
shadowRoot = shadowHost.attachShadow({mode: 'open'});
|
||||
shadowRoot.innerHTML = '<slot></slot><style> ::slotted(.green), ::slotted(myelem) { color:green; } </style>';
|
||||
|
||||
shadowHost = document.querySelector('nested-host');
|
||||
shadowRoot = shadowHost.attachShadow({mode: 'open'});
|
||||
shadowRoot.innerHTML = '<style> .mydiv ::slotted(*) { color:green; } </style><div class=mydiv><slot></slot></div>';
|
||||
|
||||
shadowHost = document.querySelector('another-host');
|
||||
shadowRoot = shadowHost.attachShadow({mode: 'open'});
|
||||
shadowRoot.innerHTML = '<style> ::slotted(*) { color:green; } </style><slot></slot>';
|
||||
} catch (exception) {
|
||||
document.body.appendChild(document.createTextNode(exception));
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user