Bug 783213 - Part 2: Don't apply the quirk to selectors that use a pseudo-element or are part of a pseudo-class argument. r=dbaron

This commit is contained in:
Brian Marshall 2014-11-13 21:37:42 -08:00
parent c0a3b6e87d
commit ce5f1f5c0d

View File

@ -53,6 +53,8 @@
#include "mozilla/Preferences.h" #include "mozilla/Preferences.h"
#include "mozilla/LookAndFeel.h" #include "mozilla/LookAndFeel.h"
#include "mozilla/Likely.h" #include "mozilla/Likely.h"
#include "mozilla/TypedEnum.h"
#include "mozilla/TypedEnumBits.h"
using namespace mozilla; using namespace mozilla;
using namespace mozilla::dom; using namespace mozilla::dom;
@ -1429,6 +1431,30 @@ struct NodeMatchContext {
} }
}; };
/**
* Additional information about a selector (without combinators) that is
* being matched.
*/
MOZ_BEGIN_ENUM_CLASS(SelectorMatchesFlags, uint8_t)
NONE = 0,
// The selector's flags are unknown. This happens when you don't know
// if you're starting from the top of a selector. Only used in cases
// where it's acceptable for matching to return a false positive.
// (It's not OK to return a false negative.)
UNKNOWN = 1 << 0,
// The selector is part of a compound selector which has been split in
// half, where the other half is a pseudo-element. The current
// selector is not a pseudo-element itself.
HAS_PSEUDO_ELEMENT = 1 << 1,
// The selector is part of an argument to a functional pseudo-class or
// pseudo-element.
IS_PSEUDO_CLASS_ARGUMENT = 1 << 2
MOZ_END_ENUM_CLASS(SelectorMatchesFlags)
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SelectorMatchesFlags)
static bool ValueIncludes(const nsSubstring& aValueList, static bool ValueIncludes(const nsSubstring& aValueList,
const nsSubstring& aValue, const nsSubstring& aValue,
const nsStringComparator& aComparator) const nsStringComparator& aComparator)
@ -1460,10 +1486,18 @@ static bool ValueIncludes(const nsSubstring& aValueList,
// Return whether the selector matches conditions for the :active and // Return whether the selector matches conditions for the :active and
// :hover quirk. // :hover quirk.
static inline bool ActiveHoverQuirkMatches(nsCSSSelector* aSelector) static inline bool ActiveHoverQuirkMatches(nsCSSSelector* aSelector,
SelectorMatchesFlags aSelectorFlags)
{ {
if (aSelector->HasTagSelector() || aSelector->mAttrList || if (aSelector->HasTagSelector() || aSelector->mAttrList ||
aSelector->mIDList || aSelector->mClassList) { aSelector->mIDList || aSelector->mClassList ||
aSelector->IsPseudoElement() ||
// Having this quirk means that some selectors will no longer match,
// so it's better to return false when we aren't sure (i.e., the
// flags are unknown).
aSelectorFlags & (SelectorMatchesFlags::UNKNOWN |
SelectorMatchesFlags::HAS_PSEUDO_ELEMENT |
SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT)) {
return false; return false;
} }
@ -1673,6 +1707,7 @@ StateSelectorMatches(Element* aElement,
nsCSSSelector* aSelector, nsCSSSelector* aSelector,
NodeMatchContext& aNodeMatchContext, NodeMatchContext& aNodeMatchContext,
TreeMatchContext& aTreeMatchContext, TreeMatchContext& aTreeMatchContext,
SelectorMatchesFlags aSelectorFlags,
bool* const aDependence, bool* const aDependence,
EventStates aStatesToCheck) EventStates aStatesToCheck)
{ {
@ -1680,18 +1715,11 @@ StateSelectorMatches(Element* aElement,
"should only need to call StateSelectorMatches if " "should only need to call StateSelectorMatches if "
"aStatesToCheck is not empty"); "aStatesToCheck is not empty");
const bool isNegated = aDependence != nullptr;
// Bit-based pseudo-classes // Bit-based pseudo-classes
if (aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE | if (aStatesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE |
NS_EVENT_STATE_HOVER) && NS_EVENT_STATE_HOVER) &&
aTreeMatchContext.mCompatMode == eCompatibility_NavQuirks && aTreeMatchContext.mCompatMode == eCompatibility_NavQuirks &&
ActiveHoverQuirkMatches(aSelector) && ActiveHoverQuirkMatches(aSelector, aSelectorFlags) &&
// This (or the other way around) both make :not() asymmetric
// in quirks mode (and it's hard to work around since we're
// testing the current mNegations, not the first
// (unnegated)). This at least makes it closer to the spec.
!isNegated &&
aElement->IsHTML() && !nsCSSRuleProcessor::IsLink(aElement)) { aElement->IsHTML() && !nsCSSRuleProcessor::IsLink(aElement)) {
// In quirks mode, only make links sensitive to selectors ":active" // In quirks mode, only make links sensitive to selectors ":active"
// and ":hover". // and ":hover".
@ -1727,14 +1755,16 @@ static bool
StateSelectorMatches(Element* aElement, StateSelectorMatches(Element* aElement,
nsCSSSelector* aSelector, nsCSSSelector* aSelector,
NodeMatchContext& aNodeMatchContext, NodeMatchContext& aNodeMatchContext,
TreeMatchContext& aTreeMatchContext) TreeMatchContext& aTreeMatchContext,
SelectorMatchesFlags aSelectorFlags)
{ {
for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList;
pseudoClass; pseudoClass = pseudoClass->mNext) { pseudoClass; pseudoClass = pseudoClass->mNext) {
EventStates statesToCheck = sPseudoClassStates[pseudoClass->mType]; EventStates statesToCheck = sPseudoClassStates[pseudoClass->mType];
if (!statesToCheck.IsEmpty() && if (!statesToCheck.IsEmpty() &&
!StateSelectorMatches(aElement, aSelector, aNodeMatchContext, !StateSelectorMatches(aElement, aSelector, aNodeMatchContext,
aTreeMatchContext, nullptr, statesToCheck)) { aTreeMatchContext, aSelectorFlags, nullptr,
statesToCheck)) {
return false; return false;
} }
} }
@ -1747,11 +1777,11 @@ StateSelectorMatches(Element* aElement,
// * what it points to should be set to true whenever a test is skipped // * what it points to should be set to true whenever a test is skipped
// because of aNodeMatchContent.mStateMask // because of aNodeMatchContent.mStateMask
static bool SelectorMatches(Element* aElement, static bool SelectorMatches(Element* aElement,
nsCSSSelector* aSelector, nsCSSSelector* aSelector,
NodeMatchContext& aNodeMatchContext, NodeMatchContext& aNodeMatchContext,
TreeMatchContext& aTreeMatchContext, TreeMatchContext& aTreeMatchContext,
bool* const aDependence = nullptr) SelectorMatchesFlags aSelectorFlags,
bool* const aDependence = nullptr)
{ {
NS_PRECONDITION(!aSelector->IsPseudoElement(), NS_PRECONDITION(!aSelector->IsPseudoElement(),
"Pseudo-element snuck into SelectorMatches?"); "Pseudo-element snuck into SelectorMatches?");
@ -1966,8 +1996,9 @@ static bool SelectorMatches(Element* aElement,
nsCSSSelector *s = l->mSelectors; nsCSSSelector *s = l->mSelectors;
NS_ABORT_IF_FALSE(!s->mNext && !s->IsPseudoElement(), NS_ABORT_IF_FALSE(!s->mNext && !s->IsPseudoElement(),
"parser failed"); "parser failed");
if (SelectorMatches(aElement, s, aNodeMatchContext, if (SelectorMatches(
aTreeMatchContext)) { aElement, s, aNodeMatchContext, aTreeMatchContext,
SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT)) {
break; break;
} }
} }
@ -2229,7 +2260,7 @@ static bool SelectorMatches(Element* aElement,
} }
} else { } else {
if (!StateSelectorMatches(aElement, aSelector, aNodeMatchContext, if (!StateSelectorMatches(aElement, aSelector, aNodeMatchContext,
aTreeMatchContext, aDependence, aTreeMatchContext, aSelectorFlags, aDependence,
statesToCheck)) { statesToCheck)) {
return false; return false;
} }
@ -2319,7 +2350,9 @@ static bool SelectorMatches(Element* aElement,
result && negation; negation = negation->mNegations) { result && negation; negation = negation->mNegations) {
bool dependence = false; bool dependence = false;
result = !SelectorMatches(aElement, negation, aNodeMatchContext, result = !SelectorMatches(aElement, negation, aNodeMatchContext,
aTreeMatchContext, &dependence); aTreeMatchContext,
SelectorMatchesFlags::IS_PSEUDO_CLASS_ARGUMENT,
&dependence);
// If the selector does match due to the dependence on // If the selector does match due to the dependence on
// aNodeMatchContext.mStateMask, then we want to keep result true // aNodeMatchContext.mStateMask, then we want to keep result true
// so that the final result of SelectorMatches is true. Doing so // so that the final result of SelectorMatches is true. Doing so
@ -2427,7 +2460,8 @@ static bool SelectorMatchesTree(Element* aPrevElement,
aLookForRelevantLink = false; aLookForRelevantLink = false;
aTreeMatchContext.SetHaveRelevantLink(); aTreeMatchContext.SetHaveRelevantLink();
} }
if (SelectorMatches(element, selector, nodeContext, aTreeMatchContext)) { if (SelectorMatches(element, selector, nodeContext, aTreeMatchContext,
SelectorMatchesFlags::NONE)) {
// to avoid greedy matching, we need to recur if this is a // to avoid greedy matching, we need to recur if this is a
// descendant or general sibling combinator and the next // descendant or general sibling combinator and the next
// combinator is different, but we can make an exception for // combinator is different, but we can make an exception for
@ -2509,13 +2543,19 @@ void ContentEnumFunc(const RuleValue& value, nsCSSSelector* aSelector,
return; return;
} }
if (!StateSelectorMatches(pdata->mPseudoElement, aSelector, nodeContext, if (!StateSelectorMatches(pdata->mPseudoElement, aSelector, nodeContext,
data->mTreeMatchContext)) { data->mTreeMatchContext,
SelectorMatchesFlags::NONE)) {
return; return;
} }
selector = selector->mNext; selector = selector->mNext;
} }
SelectorMatchesFlags selectorFlags = SelectorMatchesFlags::NONE;
if (aSelector->IsPseudoElement()) {
selectorFlags |= SelectorMatchesFlags::HAS_PSEUDO_ELEMENT;
}
if (SelectorMatches(data->mElement, selector, nodeContext, if (SelectorMatches(data->mElement, selector, nodeContext,
data->mTreeMatchContext)) { data->mTreeMatchContext, selectorFlags)) {
nsCSSSelector *next = selector->mNext; nsCSSSelector *next = selector->mNext;
if (!next || SelectorMatchesTree(data->mElement, next, if (!next || SelectorMatchesTree(data->mElement, next,
data->mTreeMatchContext, data->mTreeMatchContext,
@ -2660,6 +2700,7 @@ nsCSSRuleProcessor::HasStateDependentStyle(ElementDependentRuleProcessorData* aD
} }
nsRestyleHint possibleChange = RestyleHintForOp(selector->mOperator); nsRestyleHint possibleChange = RestyleHintForOp(selector->mOperator);
SelectorMatchesFlags selectorFlags = SelectorMatchesFlags::UNKNOWN;
// If hint already includes all the bits of possibleChange, // If hint already includes all the bits of possibleChange,
// don't bother calling SelectorMatches, since even if it returns false // don't bother calling SelectorMatches, since even if it returns false
@ -2689,9 +2730,9 @@ nsCSSRuleProcessor::HasStateDependentStyle(ElementDependentRuleProcessorData* aD
(!isPseudoElement || (!isPseudoElement ||
StateSelectorMatches(aStatefulElement, selectorForPseudo, StateSelectorMatches(aStatefulElement, selectorForPseudo,
nodeContext, aData->mTreeMatchContext, nodeContext, aData->mTreeMatchContext,
nullptr, aStateMask)) && selectorFlags, nullptr, aStateMask)) &&
SelectorMatches(aData->mElement, selector, nodeContext, SelectorMatches(aData->mElement, selector, nodeContext,
aData->mTreeMatchContext) && aData->mTreeMatchContext, selectorFlags) &&
SelectorMatchesTree(aData->mElement, selector->mNext, SelectorMatchesTree(aData->mElement, selector->mNext,
aData->mTreeMatchContext, aData->mTreeMatchContext,
false)) false))
@ -2758,7 +2799,7 @@ AttributeEnumFunc(nsCSSSelector* aSelector, AttributeEnumData* aData)
NodeMatchContext nodeContext(EventStates(), false); NodeMatchContext nodeContext(EventStates(), false);
if ((possibleChange & ~(aData->change)) && if ((possibleChange & ~(aData->change)) &&
SelectorMatches(data->mElement, aSelector, nodeContext, SelectorMatches(data->mElement, aSelector, nodeContext,
data->mTreeMatchContext) && data->mTreeMatchContext, SelectorMatchesFlags::UNKNOWN) &&
SelectorMatchesTree(data->mElement, aSelector->mNext, SelectorMatchesTree(data->mElement, aSelector->mNext,
data->mTreeMatchContext, false)) { data->mTreeMatchContext, false)) {
aData->change = nsRestyleHint(aData->change | possibleChange); aData->change = nsRestyleHint(aData->change | possibleChange);
@ -3575,7 +3616,8 @@ nsCSSRuleProcessor::SelectorListMatches(Element* aElement,
NS_ASSERTION(sel, "Should have *some* selectors"); NS_ASSERTION(sel, "Should have *some* selectors");
NS_ASSERTION(!sel->IsPseudoElement(), "Shouldn't have been called"); NS_ASSERTION(!sel->IsPseudoElement(), "Shouldn't have been called");
NodeMatchContext nodeContext(EventStates(), false); NodeMatchContext nodeContext(EventStates(), false);
if (SelectorMatches(aElement, sel, nodeContext, aTreeMatchContext)) { if (SelectorMatches(aElement, sel, nodeContext, aTreeMatchContext,
SelectorMatchesFlags::NONE)) {
nsCSSSelector* next = sel->mNext; nsCSSSelector* next = sel->mNext;
if (!next || if (!next ||
SelectorMatchesTree(aElement, next, aTreeMatchContext, false)) { SelectorMatchesTree(aElement, next, aTreeMatchContext, false)) {