Skip to content
Permalink
Browse files
Use Style::ScopeOrdinal for finding the right scope for ::part matching
https://bugs.webkit.org/show_bug.cgi?id=232562

Reviewed by Simon Fraser.

We are already passing the scope ordinal to the selector checker so we can use it consistently to find
the right scope.

* css/SelectorChecker.cpp:
(WebCore::SelectorChecker::matchRecursively const):

Find the right scope based on the scope ordinal.
Simplify allowMultiplePseudoElements check, invalid cases are not allowed by the parser.

(WebCore::SelectorChecker::checkOne const):

Find the right scope based on the scope ordinal.

* css/SelectorChecker.h:
* dom/ShadowRoot.h:
* style/ElementRuleCollector.cpp:
(WebCore::Style::ElementRuleCollector::matchPartPseudoElementRulesForScope):

Compute the scope ordinal for nested scopes.
Make iterative instead of recursive.

(WebCore::Style::ElementRuleCollector::ruleMatches):
(WebCore::Style::ElementRuleCollector::matchAllRules):

Flush all remaining rules.

* style/ElementRuleCollector.h:
* style/StyleScope.cpp:
(WebCore::Style::Scope::forOrdinal):
(WebCore::Style::assignedSlotForScopeOrdinal):
(WebCore::Style::hostForScopeOrdinal):

Add helpers.

* style/StyleScope.h:
* style/StyleScopeOrdinal.h:
(WebCore::Style::operator--):

We now use values less than ContainingHost to present enclosing scopes, similar to slotted matching.



Canonical link: https://commits.webkit.org/243828@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@285202 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
anttijk committed Nov 3, 2021
1 parent 7a39a6b commit 414f4e45b0e82bbfcee783d08a9642be1afa8f72
Showing 9 changed files with 115 additions and 54 deletions.
@@ -1,3 +1,50 @@
2021-11-03 Antti Koivisto <antti@apple.com>

Use Style::ScopeOrdinal for finding the right scope for ::part matching
https://bugs.webkit.org/show_bug.cgi?id=232562

Reviewed by Simon Fraser.

We are already passing the scope ordinal to the selector checker so we can use it consistently to find
the right scope.

* css/SelectorChecker.cpp:
(WebCore::SelectorChecker::matchRecursively const):

Find the right scope based on the scope ordinal.
Simplify allowMultiplePseudoElements check, invalid cases are not allowed by the parser.

(WebCore::SelectorChecker::checkOne const):

Find the right scope based on the scope ordinal.

* css/SelectorChecker.h:
* dom/ShadowRoot.h:
* style/ElementRuleCollector.cpp:
(WebCore::Style::ElementRuleCollector::matchPartPseudoElementRulesForScope):

Compute the scope ordinal for nested scopes.
Make iterative instead of recursive.

(WebCore::Style::ElementRuleCollector::ruleMatches):
(WebCore::Style::ElementRuleCollector::matchAllRules):

Flush all remaining rules.

* style/ElementRuleCollector.h:
* style/StyleScope.cpp:
(WebCore::Style::Scope::forOrdinal):
(WebCore::Style::assignedSlotForScopeOrdinal):
(WebCore::Style::hostForScopeOrdinal):

Add helpers.

* style/StyleScope.h:
* style/StyleScopeOrdinal.h:
(WebCore::Style::operator--):

We now use values less than ContainingHost to present enclosing scopes, similar to slotted matching.

2021-11-02 Brady Eidson <beidson@apple.com>

Notifications on iOS enabled at compile-time, disabled at runtime
@@ -316,8 +316,7 @@ SelectorChecker::MatchResult SelectorChecker::matchRecursively(CheckingContext&

nextContext.pseudoId = PseudoId::None;

bool nextIsPart = leftSelector->match() == CSSSelector::PseudoElement && leftSelector->pseudoElementType() == CSSSelector::PseudoElementPart;
bool allowMultiplePseudoElements = relation == CSSSelector::ShadowDescendant && nextIsPart;
bool allowMultiplePseudoElements = relation == CSSSelector::ShadowDescendant;
// Virtual pseudo element is only effective in the rightmost fragment.
if (!allowMultiplePseudoElements)
nextContext.pseudoElementEffective = false;
@@ -409,11 +408,14 @@ SelectorChecker::MatchResult SelectorChecker::matchRecursively(CheckingContext&
}
case CSSSelector::ShadowDescendant:
case CSSSelector::ShadowPartDescendant: {
// When matching foo::part(bar) we skip directly to the tree of element 'foo'.
auto* shadowHost = relation == CSSSelector::ShadowPartDescendant ? checkingContext.shadowHostInPartRuleScope : context.element->shadowHost();
if (!shadowHost)
// Continue matching in the scope where this rule came from.
auto* host = relation == CSSSelector::ShadowPartDescendant
? Style::hostForScopeOrdinal(*context.element, checkingContext.styleScopeOrdinal)
: context.element->shadowHost();
if (!host)
return MatchResult::fails(Match::SelectorFailsCompletely);
nextContext.element = shadowHost;

nextContext.element = host;
nextContext.firstSelectorOfTheFragment = nextContext.selector;
nextContext.isSubjectOrAdjacentElement = false;
PseudoIdSet ignoreDynamicPseudo;
@@ -422,14 +424,9 @@ SelectorChecker::MatchResult SelectorChecker::matchRecursively(CheckingContext&
return MatchResult::updateWithMatchType(result, matchType);
}
case CSSSelector::ShadowSlotted: {
auto* slot = context.element->assignedSlot();
if (!slot)
return MatchResult::fails(Match::SelectorFailsCompletely);
// We continue matching in the scope where this rule came from.
auto scopeDepth = static_cast<int>(checkingContext.styleScopeOrdinal);
while (--scopeDepth && slot->assignedSlot())
slot = slot->assignedSlot();
if (scopeDepth)
auto slot = Style::assignedSlotForScopeOrdinal(*context.element, checkingContext.styleScopeOrdinal);
if (!slot)
return MatchResult::fails(Match::SelectorFailsCompletely);

nextContext.element = slot;
@@ -1171,11 +1168,13 @@ bool SelectorChecker::checkOne(CheckingContext& checkingContext, const LocalCont
case CSSSelector::PseudoElementPart: {
auto translatePartNameToRuleScope = [&](AtomString partName) {
Vector<AtomString, 1> mappedNames { partName };
auto* ruleScopeHost = Style::hostForScopeOrdinal(*context.element, checkingContext.styleScopeOrdinal);

for (auto* shadowRoot = element.containingShadowRoot(); shadowRoot; shadowRoot = shadowRoot->host()->containingShadowRoot()) {
// Apply mappings up to the scope the rules are coming from.
if (shadowRoot->host() == checkingContext.shadowHostInPartRuleScope)
if (shadowRoot->host() == ruleScopeHost)
break;

Vector<AtomString, 1> newMappedNames;
for (auto& name : mappedNames)
newMappedNames.appendVector(shadowRoot->partMappings().get(name));
@@ -95,7 +95,6 @@ class SelectorChecker {
AtomString nameForHightlightPseudoElement;
const ContainerNode* scope { nullptr };
bool isMatchingHostPseudoClass { false };
const Element* shadowHostInPartRuleScope { nullptr };
Style::ScopeOrdinal styleScopeOrdinal { Style::ScopeOrdinal::Element };

// FIXME: It would be nicer to have a separate object for return values. This requires some more work in the selector compiler.
@@ -29,6 +29,7 @@
#include "Document.h"
#include "DocumentFragment.h"
#include "Element.h"
#include "StyleScopeOrdinal.h"
#if ENABLE(PICTURE_IN_PICTURE_API)
#include "HTMLVideoElement.h"
#endif
@@ -306,21 +306,23 @@ void ElementRuleCollector::matchPartPseudoElementRules()

void ElementRuleCollector::matchPartPseudoElementRulesForScope(const ShadowRoot& scopeShadowRoot)
{
auto& shadowHost = *scopeShadowRoot.host();
{
SetForScope<RefPtr<const Element>> partMatchingScope(m_shadowHostInPartRuleScope, &shadowHost);
auto* host = scopeShadowRoot.host();
auto styleScopeOrdinal = ScopeOrdinal::ContainingHost;

auto& hostAuthorRules = Scope::forNode(shadowHost).resolver().ruleSets().authorStyle();
MatchRequest hostAuthorRequest { &hostAuthorRules, ScopeOrdinal::ContainingHost };
collectMatchingRulesForList(&hostAuthorRules.partPseudoElementRules(), hostAuthorRequest);
}
for (; host; host = host->shadowHost(), --styleScopeOrdinal) {
auto& styleScope = Scope::forNode(*host);
if (!styleScope.resolver().ruleSets().isAuthorStyleDefined())
continue;

// Element may be exposed to styling from enclosing scopes via exportparts attributes.
if (scopeShadowRoot.partMappings().isEmpty())
return;
auto& hostAuthorRules = styleScope.resolver().ruleSets().authorStyle();

MatchRequest scopeMatchRequest(&hostAuthorRules, styleScopeOrdinal);
collectMatchingRulesForList(&hostAuthorRules.partPseudoElementRules(), scopeMatchRequest);

if (auto* parentScopeShadowRoot = shadowHost.containingShadowRoot())
matchPartPseudoElementRulesForScope(*parentScopeShadowRoot);
// Element may only be exposed to styling from enclosing scopes via exportparts attributes.
if (host->shadowRoot()->partMappings().isEmpty())
break;
}
}

void ElementRuleCollector::collectMatchingShadowPseudoElementRules(const MatchRequest& matchRequest)
@@ -431,7 +433,6 @@ inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, unsigned
context.scrollbarState = m_pseudoElementRequest.scrollbarState;
context.nameForHightlightPseudoElement = m_pseudoElementRequest.highlightName;
context.isMatchingHostPseudoClass = m_isMatchingHostPseudoClass;
context.shadowHostInPartRuleScope = m_shadowHostInPartRuleScope.get();
context.styleScopeOrdinal = styleScopeOrdinal;

bool selectorMatches;
@@ -549,8 +550,8 @@ void ElementRuleCollector::matchAllRules(bool matchAuthorAndUserStyles, bool inc
// Inline style behaves as if it has higher specificity than any rule.
addElementInlineStyleProperties(includeSMILProperties);

// Rules from the host scope override inline style.
transferMatchedRules(DeclarationOrigin::Author, ScopeOrdinal::ContainingHost);
// Rules from the host scopes override inline style.
transferMatchedRules(DeclarationOrigin::Author);
}
}

@@ -163,7 +163,6 @@ class ElementRuleCollector {
PseudoElementRequest m_pseudoElementRequest { PseudoId::None };
SelectorChecker::Mode m_mode { SelectorChecker::Mode::ResolvingStyle };
bool m_isMatchingHostPseudoClass { false };
RefPtr<const Element> m_shadowHostInPartRuleScope;

Vector<MatchedRule, 64> m_matchedRules;
size_t m_matchedRuleTransferIndex { 0 };
@@ -185,31 +185,18 @@ Scope& Scope::forNode(Node& node)

Scope* Scope::forOrdinal(Element& element, ScopeOrdinal ordinal)
{
switch (ordinal) {
case ScopeOrdinal::Element:
if (ordinal == ScopeOrdinal::Element)
return &forNode(element);
case ScopeOrdinal::ContainingHost: {
auto* containingShadowRoot = element.containingShadowRoot();
if (!containingShadowRoot)
return nullptr;
return &forNode(*containingShadowRoot->host());
}
case ScopeOrdinal::Shadow: {
if (ordinal == ScopeOrdinal::Shadow) {
auto* shadowRoot = element.shadowRoot();
if (!shadowRoot)
return nullptr;
return &shadowRoot->styleScope();
}
default: {
ASSERT(ordinal >= ScopeOrdinal::FirstSlot);
auto slotIndex = ScopeOrdinal::FirstSlot;
for (auto* slot = element.assignedSlot(); slot; slot = slot->assignedSlot(), ++slotIndex) {
if (slotIndex == ordinal)
return &forNode(*slot);
}
return nullptr;
return shadowRoot ? &shadowRoot->styleScope() : nullptr;
}
if (ordinal <= ScopeOrdinal::ContainingHost) {
auto* host = hostForScopeOrdinal(element, ordinal);
return host ? &forNode(*host) : nullptr;
}
auto* slot = assignedSlotForScopeOrdinal(element, ordinal);
return slot ? &forNode(*slot) : nullptr;
}

void Scope::setPreferredStylesheetSetName(const String& name)
@@ -789,5 +776,23 @@ bool Scope::isForUserAgentShadowTree() const
return m_shadowRoot && m_shadowRoot->mode() == ShadowRootMode::UserAgent;
}

HTMLSlotElement* assignedSlotForScopeOrdinal(const Element& element, ScopeOrdinal scopeOrdinal)
{
ASSERT(scopeOrdinal >= ScopeOrdinal::FirstSlot);
auto* slot = element.assignedSlot();
for (auto scopeDepth = ScopeOrdinal::FirstSlot; slot && scopeDepth != scopeOrdinal; ++scopeDepth)
slot = slot->assignedSlot();
return slot;
}

Element* hostForScopeOrdinal(const Element& element, ScopeOrdinal scopeOrdinal)
{
ASSERT(scopeOrdinal <= ScopeOrdinal::ContainingHost);
auto* host = element.shadowHost();
for (auto scopeDepth = ScopeOrdinal::ContainingHost; host && scopeDepth != scopeOrdinal; --scopeDepth)
host = host->shadowHost();
return host;
}

}
}
@@ -45,6 +45,7 @@ namespace WebCore {
class CSSStyleSheet;
class Document;
class Element;
class HTMLSlotElement;
class Node;
class ProcessingInstruction;
class StyleSheet;
@@ -203,6 +204,9 @@ class Scope : public CanMakeCheckedPtr {
HashMap<ResolverSharingKey, Ref<Resolver>> m_sharedShadowTreeResolvers;
};

HTMLSlotElement* assignedSlotForScopeOrdinal(const Element&, ScopeOrdinal);
Element* hostForScopeOrdinal(const Element&, ScopeOrdinal);

inline bool Scope::hasPendingSheets() const
{
return hasPendingSheetsBeforeBody() || !m_elementsInBodyWithPendingSheets.isEmpty();
@@ -30,7 +30,7 @@ namespace Style {
// This is used to identify style scopes that can affect an element.
// Scopes are in tree-of-trees order. Styles from earlier scopes win over later ones (modulo !important).
enum class ScopeOrdinal : int {
ContainingHost = -1, // ::part rules and author-exposed UA pseudo classes from the host tree scope.
ContainingHost = -1, // ::part rules and author-exposed UA pseudo classes from the host tree scope. Values less than ContainingHost indicate enclosing scopes.
Element = 0, // Normal rules in the same tree where the element is.
FirstSlot = 1, // ::slotted rules in the parent's shadow tree. Values greater than FirstSlot indicate subsequent slots in the chain.
Shadow = std::numeric_limits<int>::max(), // :host rules in element's own shadow tree.
@@ -42,5 +42,11 @@ inline ScopeOrdinal& operator++(ScopeOrdinal& ordinal)
return ordinal = static_cast<ScopeOrdinal>(static_cast<int>(ordinal) + 1);
}

inline ScopeOrdinal& operator--(ScopeOrdinal& ordinal)
{
ASSERT(ordinal < ScopeOrdinal::Shadow);
return ordinal = static_cast<ScopeOrdinal>(static_cast<int>(ordinal) - 1);
}

}
}

0 comments on commit 414f4e4

Please sign in to comment.