1
0
mirror of https://github.com/roytam1/UXP.git synced 2026-05-26 13:58:49 +00:00

Issue #3024 - First pass support for :host and :host-context

This commit is contained in:
Matt A. Tobin
2021-12-05 20:16:02 -05:00
committed by roytam1
parent 3bb1c962eb
commit 4cc56c2cb6
12 changed files with 358 additions and 34 deletions
+216 -14
View File
@@ -48,6 +48,7 @@
#include "nsCSSRules.h"
#include "nsStyleSet.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ShadowRoot.h"
#include "nsNthIndexCache.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/EventStates.h"
@@ -1342,9 +1343,17 @@ struct NodeMatchContext {
// mForStyling is false, we have to assume we don't know.)
const bool mIsRelevantLink;
NodeMatchContext(EventStates aStateMask, bool aIsRelevantLink)
// If the node should be considered featureless (as specified in
// selectors 4), then mIsFeature should be set to true to prevent
// matching unless the selector is a special pseudo class or pseudo
// element that matches featureless elements.
const bool mIsFeatureless;
NodeMatchContext(EventStates aStateMask, bool aIsRelevantLink,
bool aIsFeatureless = false)
: mStateMask(aStateMask)
, mIsRelevantLink(aIsRelevantLink)
, mIsFeatureless(aIsFeatureless)
{
}
};
@@ -1631,6 +1640,11 @@ StateSelectorMatches(Element* aElement,
return true;
}
static bool AnySelectorInArgListMatches(Element* aElement,
nsPseudoClassList* aList,
NodeMatchContext& aNodeMatchContext,
TreeMatchContext& aTreeMatchContext);
static bool
StateSelectorMatches(Element* aElement,
nsCSSSelector* aSelector,
@@ -1652,6 +1666,26 @@ StateSelectorMatches(Element* aElement,
return true;
}
// Returns whether aSelector can match featureless elements.
static bool CanMatchFeaturelessElement(nsCSSSelector* aSelector)
{
if (aSelector->HasFeatureSelectors()) {
return false;
}
for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
pseudoClass;
pseudoClass = pseudoClass->mNext) {
if (pseudoClass->mType == CSSPseudoClassType::host ||
pseudoClass->mType == CSSPseudoClassType::hostContext) {
return true;
}
}
return false;
}
// |aDependence| has two functions:
// * when non-null, it indicates that we're processing a negation,
// which is done only when SelectorMatches calls itself recursively
@@ -1672,6 +1706,11 @@ static bool SelectorMatches(Element* aElement,
"is false since we don't know how to set it correctly in "
"Has(Attribute|State)DependentStyle");
if (aNodeMatchContext.mIsFeatureless &&
!CanMatchFeaturelessElement(aSelector)) {
return false;
}
// namespace/tag match
// optimization : bail out early if we can
if ((kNameSpaceID_Unknown != aSelector->mNameSpace &&
@@ -1870,18 +1909,89 @@ static bool SelectorMatches(Element* aElement,
case CSSPseudoClassType::any:
{
nsCSSSelectorList *l;
for (l = pseudoClass->u.mSelectors; l; l = l->mNext) {
nsCSSSelector *s = l->mSelectors;
MOZ_ASSERT(!s->mNext && !s->IsPseudoElement(),
"parser failed");
if (SelectorMatches(
aElement, s, aNodeMatchContext, aTreeMatchContext,
SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT)) {
if (!AnySelectorInArgListMatches(aElement, pseudoClass,
aNodeMatchContext,
aTreeMatchContext)) {
return false;
}
}
break;
case CSSPseudoClassType::host:
{
// In order to match :host, the element must be a shadow root host,
// we must be matching only against host pseudo selectors, and the
// selector's context must be the shadow root (the selector must be
// featureless, the left-most selector, and be in a shadow root
// style). The :host selector may also be be functional, with a
// compound selector. If this is the case, then also ensure that the
// host element matches against the compound
// selector.
// We match automatically if GetParent() and GetShadowRoot() have
// the same result. Without special casing this ahead of all other
// selector matching, it fails. Have not determined the cause.
if (aElement->GetParent() == aElement->GetShadowRoot()) {
break;
}
// Match if any selector in the argument list matches.
NodeMatchContext nodeContext(EventStates(),
nsCSSRuleProcessor::IsLink(aElement));
if (AnySelectorInArgListMatches(aElement, pseudoClass,
nodeContext,
aTreeMatchContext)) {
break;
}
// Finally, with the exception of the two above cases, make sure we
// don't match if GetContainingShadow() returns null. For whatever
// reason, we can't test for this case first.
if (aElement->GetContainingShadow() == nullptr) {
return false;
}
}
break;
case CSSPseudoClassType::hostContext:
{
// In order to match host-context, the element must be a
// shadow root host and the selector's context must be the
// shadow root (aTreeMatchContext.mScopedRoot is set to the
// host of the shadow root where the style is contained,
// thus the element must be mScopedRoot). If the UNKNOWN
// selector flag is set, relax the shadow root host
// requirement because this pseudo class walks through
// ancestors looking for a match, thus the selector can be
// dependant on aElement even though it is not the host. The
// dependency would otherwise be missed because when UNKNOWN
// is set, selector matching may not have started from the top.
if (!((aElement->GetShadowRoot() &&
aElement == aTreeMatchContext.mScopedRoot) ||
aSelectorFlags & SelectorMatchesFlags::UNKNOWN)) {
return false;
}
Element* currentElement = aElement;
while (currentElement) {
NodeMatchContext nodeContext(EventStates(),
nsCSSRuleProcessor::IsLink(currentElement));
if (AnySelectorInArgListMatches(currentElement, pseudoClass,
nodeContext,
aTreeMatchContext)) {
break;
}
nsIContent* flattenedParent = currentElement->GetFlattenedTreeParent();
currentElement = flattenedParent && flattenedParent->IsElement() ?
flattenedParent->AsElement() : nullptr;
}
if (!l) {
if (!currentElement) {
return false;
}
}
@@ -2263,6 +2373,26 @@ static bool SelectorMatches(Element* aElement,
return result;
}
static bool AnySelectorInArgListMatches(Element* aElement,
nsPseudoClassList* aList,
NodeMatchContext& aNodeMatchContext,
TreeMatchContext& aTreeMatchContext)
{
nsCSSSelectorList *l;
for (l = aList->u.mSelectors; l; l = l->mNext) {
nsCSSSelector *s = l->mSelectors;
MOZ_ASSERT(!s->mNext && !s->IsPseudoElement(),
"parser failed");
if (SelectorMatches(
aElement, s, aNodeMatchContext, aTreeMatchContext,
SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT)) {
break;
}
}
return !!l;
}
#undef STATE_CHECK
#ifdef DEBUG
@@ -2352,7 +2482,9 @@ SelectorMatchesTree(Element* aPrevElement,
MOZ_ASSERT(!aSelector || !aSelector->IsPseudoElement());
nsCSSSelector* selector = aSelector;
Element* prevElement = aPrevElement;
bool crossedShadowRootBoundary = false;
while (selector) { // check compound selectors
bool contentIsFeatureless = false;
NS_ASSERTION(!selector->mNext ||
selector->mNext->mOperator != char16_t(0),
"compound selector without combinator");
@@ -2383,6 +2515,20 @@ SelectorMatchesTree(Element* aPrevElement,
// to test against is the parent
else {
nsIContent *content = prevElement->GetParent();
// In the shadow tree, the shadow host behaves as if it
// is a featureless parent of top-level elements of the shadow
// tree. Only cross shadow root boundary when the selector is the
// left most selector because ancestors of the host are not in
// the selector match list.
ShadowRoot* shadowRoot = content ?
ShadowRoot::FromNode(content) : nullptr;
if (shadowRoot && !selector->mNext && !crossedShadowRootBoundary) {
content = shadowRoot->GetHost();
crossedShadowRootBoundary = true;
contentIsFeatureless = true;
}
// GetParent could return a document fragment; we only want
// element parents.
if (content && content->IsElement()) {
@@ -2434,7 +2580,8 @@ SelectorMatchesTree(Element* aPrevElement,
}
const bool isRelevantLink = (aFlags & eLookForRelevantLink) &&
nsCSSRuleProcessor::IsLink(element);
NodeMatchContext nodeContext(EventStates(), isRelevantLink);
NodeMatchContext nodeContext(EventStates(), isRelevantLink, contentIsFeatureless);
if (isRelevantLink) {
// If we find an ancestor of the matched node that is a link
// during the matching process, then it's the relevant link (see
@@ -2504,6 +2651,29 @@ void ContentEnumFunc(const RuleValue& value, nsCSSSelector* aSelector,
// We won't match; nothing else to do here
return;
}
// If mOnlyMatchHostPseudo is set, then we only want to match against
// selectors that contain a :host-context pseudo class.
if (data->mTreeMatchContext.mOnlyMatchHostPseudo) {
nsCSSSelector* selector = aSelector;
while (selector && selector->mNext != nullptr) {
selector = selector->mNext;
}
bool seenHostPseudo = false;
for (nsPseudoClassList* pseudoClass = selector->mPseudoClassList;
pseudoClass;
pseudoClass = pseudoClass->mNext) {
if (pseudoClass->mType == CSSPseudoClassType::host ||
pseudoClass->mType == CSSPseudoClassType::hostContext) {
seenHostPseudo = true;
break;
}
}
if (!seenHostPseudo) {
return;
}
}
if (!data->mTreeMatchContext.SetStyleScopeForSelectorMatching(data->mElement,
data->mScope)) {
// The selector is for a rule in a scoped style sheet, and the subject
@@ -2563,7 +2733,8 @@ nsCSSRuleProcessor::RulesMatching(ElementRuleProcessorData *aData)
if (cascade) {
NodeMatchContext nodeContext(EventStates(),
nsCSSRuleProcessor::IsLink(aData->mElement));
nsCSSRuleProcessor::IsLink(aData->mElement),
aData->mElementIsFeatureless);
cascade->mRuleHash.EnumerateAllRules(aData->mElement, aData, nodeContext);
}
}
@@ -2847,6 +3018,34 @@ AttributeEnumFunc(nsCSSSelector* aSelector,
nsRestyleHint possibleChange =
RestyleHintForSelectorWithAttributeChange(aData->change,
aSelector, aRightmostSelector);
// If mOnlyMatchHostPseudo is set, then we only want to match against
// selectors that contain a :host-context pseudo class.
if (data->mTreeMatchContext.mOnlyMatchHostPseudo) {
nsCSSSelector* selector = aSelector;
while (selector && selector->mNext != nullptr) {
selector = selector->mNext;
}
bool seenHostPseudo = false;
for (nsPseudoClassList* pseudoClass = selector->mPseudoClassList;
pseudoClass;
pseudoClass = pseudoClass->mNext) {
if (pseudoClass->mType == CSSPseudoClassType::host ||
pseudoClass->mType == CSSPseudoClassType::hostContext) {
// :host-context will walk ancestors looking for a match of a compound
// selector, thus any changes to ancestors may require restyling the
// subtree.
possibleChange |= eRestyle_Subtree;
seenHostPseudo = true;
break;
}
}
if (!seenHostPseudo) {
return;
}
}
// If, ignoring eRestyle_SomeDescendants, enumData->change already includes
// all the bits of possibleChange, don't bother calling SelectorMatches, since
@@ -3318,10 +3517,12 @@ AddSelector(RuleCascadeData* aCascade,
}
}
// Recur through any :-moz-any selectors
// Recur through any :-moz-any or :host-context selectors
for (nsPseudoClassList* pseudoClass = negation->mPseudoClassList;
pseudoClass; pseudoClass = pseudoClass->mNext) {
if (pseudoClass->mType == CSSPseudoClassType::any) {
if (pseudoClass->mType == CSSPseudoClassType::any ||
pseudoClass->mType == CSSPseudoClassType::host ||
pseudoClass->mType == CSSPseudoClassType::hostContext) {
for (nsCSSSelectorList *l = pseudoClass->u.mSelectors; l; l = l->mNext) {
nsCSSSelector *s = l->mSelectors;
if (!AddSelector(aCascade, aSelectorInTopLevel, s,
@@ -3970,6 +4171,7 @@ TreeMatchContext::InitAncestors(Element *aElement)
for (uint32_t i = ancestors.Length(); i-- != 0; ) {
mAncestorFilter.PushAncestor(ancestors[i]);
PushStyleScope(ancestors[i]);
PushShadowHost(ancestors[i]);
}
}
}