diff --git a/content/events/public/nsEventStates.h b/content/events/public/nsEventStates.h index ff36910a30d2..f8fbf7d2ab1c 100644 --- a/content/events/public/nsEventStates.h +++ b/content/events/public/nsEventStates.h @@ -248,11 +248,17 @@ private: #define NS_EVENT_STATE_VULNERABLE_NO_UPDATE NS_DEFINE_EVENT_STATE_MACRO(41) // Platform does not support plugin content (some mobile platforms) #define NS_EVENT_STATE_TYPE_UNSUPPORTED_PLATFORM NS_DEFINE_EVENT_STATE_MACRO(42) +// Element is ltr (for :dir pseudo-class) +#define NS_EVENT_STATE_LTR NS_DEFINE_EVENT_STATE_MACRO(43) +// Element is rtl (for :dir pseudo-class) +#define NS_EVENT_STATE_RTL NS_DEFINE_EVENT_STATE_MACRO(44) /** * NOTE: do not go over 63 without updating nsEventStates::InternalType! */ +#define DIRECTION_STATES (NS_EVENT_STATE_LTR | NS_EVENT_STATE_RTL) + #define ESM_MANAGED_STATES (NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_FOCUS | \ NS_EVENT_STATE_HOVER | NS_EVENT_STATE_DRAGOVER | \ NS_EVENT_STATE_URLTARGET | NS_EVENT_STATE_FOCUSRING | \ diff --git a/content/html/content/src/nsGenericHTMLElement.h b/content/html/content/src/nsGenericHTMLElement.h index 4cd54732ab75..b1e04ea37824 100644 --- a/content/html/content/src/nsGenericHTMLElement.h +++ b/content/html/content/src/nsGenericHTMLElement.h @@ -49,6 +49,7 @@ public: { NS_ASSERTION(mNodeInfo->NamespaceID() == kNameSpaceID_XHTML, "Unexpected namespace"); + AddStatesSilently(NS_EVENT_STATE_LTR); } /** Typesafe, non-refcounting cast from nsIContent. Cheaper than QI. **/ diff --git a/dom/locales/en-US/chrome/layout/css.properties b/dom/locales/en-US/chrome/layout/css.properties index c3aed0a20980..bec986fd1110 100644 --- a/dom/locales/en-US/chrome/layout/css.properties +++ b/dom/locales/en-US/chrome/layout/css.properties @@ -114,6 +114,7 @@ PEMQExpectedFeatureValue=Found invalid value for media feature. PEBadFontBlockStart=Expected '{' to begin @font-face rule but found '%1$S'. PEBadFontBlockEnd=Expected '}' to end @font-face rule but found '%1$S'. PEAnonBoxNotAlone=Did not expect anonymous box. +PEBadDirValue=Expected 'ltr' or 'rtl' in direction selector but found '%1$S'. PESupportsConditionStartEOF='not' or '(' PESupportsConditionInParensStartEOF='not', '(' or identifier PESupportsConditionNotEOF='not' diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 7d3f5a28a8e4..e1a747c18182 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -3595,10 +3595,12 @@ CSSParserImpl::ParsePseudoClassWithIdentArg(nsCSSSelector& aSelector, return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') } - // -moz-locale-dir can only have values of 'ltr' or 'rtl'. - if (aType == nsCSSPseudoClasses::ePseudoClass_mozLocaleDir) { + // -moz-locale-dir and :dir can only have values of 'ltr' or 'rtl'. + if (aType == nsCSSPseudoClasses::ePseudoClass_mozLocaleDir || + aType == nsCSSPseudoClasses::ePseudoClass_dir) { if (!mToken.mIdent.EqualsLiteral("ltr") && !mToken.mIdent.EqualsLiteral("rtl")) { + REPORT_UNEXPECTED_TOKEN(PEBadDirValue); return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')') } } diff --git a/layout/style/nsCSSPseudoClassList.h b/layout/style/nsCSSPseudoClassList.h index b87ed3ad5185..23312179cc19 100644 --- a/layout/style/nsCSSPseudoClassList.h +++ b/layout/style/nsCSSPseudoClassList.h @@ -9,27 +9,45 @@ * This file contains the list of nsIAtoms and their values for CSS * pseudo-classes. It is designed to be used as inline input to * nsCSSPseudoClasses.cpp *only* through the magic of C preprocessing. - * All entries must be enclosed in the macros CSS_PSEUDO_CLASS or - * CSS_STATE_PSEUDO_CLASS which will have cruel and unusual things - * done to it. The entries should be kept in some sort of logical - * order. The first argument to CSS_PSEUDO_CLASS is the C++ - * identifier of the atom. The second argument is the string value of - * the atom. CSS_STATE_PSEUDO_CLASS also takes the name of the state - * bits that the class corresponds to. Only one of the bits needs to - * match for the pseudo-class to match. If CSS_STATE_PSEUDO_CLASS is - * not defined, it'll be automatically defined to CSS_PSEUDO_CLASS. + * All entries must be enclosed in the macros CSS_PSEUDO_CLASS, + * CSS_STATE_DEPENDENT_PSEUDO_CLASS, or CSS_STATE_PSEUDO_CLASS which + * will have cruel and unusual things done to them. The entries should + * be kept in some sort of logical order. The first argument to + * CSS_PSEUDO_CLASS is the C++ identifier of the atom. The second + * argument is the string value of the atom. + * CSS_STATE_DEPENDENT_PSEUDO_CLASS and CSS_STATE_PSEUDO_CLASS also take + * the name of the state bits that the class corresponds to. Only one + * of the bits needs to match for a CSS_STATE_PSEUDO_CLASS to match; + * CSS_STATE_DEPENDENT_PSEUDO_CLASS matching depends on a customized per-class + * algorithm which should be defined in SelectorMatches() in + * nsCSSRuleProcessor.cpp. + * + * If CSS_STATE_PSEUDO_CLASS is not defined, it'll be automatically + * defined to CSS_STATE_DEPENDENT_PSEUDO_CLASS; + * if CSS_STATE_DEPENDENT_PSEUDO_CLASS is not defined, it'll be + * automatically defined to CSS_PSEUDO_CLASS. */ // OUTPUT_CLASS=nsCSSPseudoClasses // MACRO_NAME=CSS_PSEUDO_CLASS +#ifdef DEFINED_CSS_STATE_DEPENDENT_PSEUDO_CLASS +#error "CSS_STATE_DEPENDENT_PSEUDO_CLASS shouldn't be defined" +#endif + +#ifndef CSS_STATE_DEPENDENT_PSEUDO_CLASS +#define CSS_STATE_DEPENDENT_PSEUDO_CLASS(_name, _value, _bit) \ + CSS_PSEUDO_CLASS(_name, _value) +#define DEFINED_CSS_STATE_DEPENDENT_PSEUDO_CLASS +#endif + #ifdef DEFINED_CSS_STATE_PSEUDO_CLASS -#error "This shouldn't be defined" +#error "CSS_STATE_PSEUDO_CLASS shouldn't be defined" #endif #ifndef CSS_STATE_PSEUDO_CLASS #define CSS_STATE_PSEUDO_CLASS(_name, _value, _bit) \ - CSS_PSEUDO_CLASS(_name, _value) + CSS_STATE_DEPENDENT_PSEUDO_CLASS(_name, _value, _bit) #define DEFINED_CSS_STATE_PSEUDO_CLASS #endif @@ -90,6 +108,11 @@ CSS_PSEUDO_CLASS(mozTableBorderNonzero, ":-moz-table-border-nonzero") // it doesn't actually get directly matched on in SelectorMatches. CSS_PSEUDO_CLASS(notPseudo, ":not") +// :dir(ltr) and :dir(rtl) match elements whose resolved directionality +// in the markup language is ltr or rtl respectively +CSS_STATE_DEPENDENT_PSEUDO_CLASS(dir, ":dir", + NS_EVENT_STATE_LTR | NS_EVENT_STATE_RTL) + CSS_STATE_PSEUDO_CLASS(link, ":link", NS_EVENT_STATE_UNVISITED) // what matches :link or :visited CSS_STATE_PSEUDO_CLASS(mozAnyLink, ":-moz-any-link", @@ -178,3 +201,8 @@ CSS_STATE_PSEUDO_CLASS(mozMeterSubSubOptimum, ":-moz-meter-sub-sub-optimum", #undef DEFINED_CSS_STATE_PSEUDO_CLASS #undef CSS_STATE_PSEUDO_CLASS #endif + +#ifdef DEFINED_CSS_STATE_DEPENDENT_PSEUDO_CLASS +#undef DEFINED_CSS_STATE_DEPENDENT_PSEUDO_CLASS +#undef CSS_STATE_DEPENDENT_PSEUDO_CLASS +#endif diff --git a/layout/style/nsCSSPseudoClasses.cpp b/layout/style/nsCSSPseudoClasses.cpp index e2046357a049..b2c19e355612 100644 --- a/layout/style/nsCSSPseudoClasses.cpp +++ b/layout/style/nsCSSPseudoClasses.cpp @@ -42,7 +42,8 @@ nsCSSPseudoClasses::HasStringArg(Type aType) return aType == ePseudoClass_lang || aType == ePseudoClass_mozEmptyExceptChildrenWithLocalname || aType == ePseudoClass_mozSystemMetric || - aType == ePseudoClass_mozLocaleDir; + aType == ePseudoClass_mozLocaleDir || + aType == ePseudoClass_dir; } bool diff --git a/layout/style/nsCSSRuleProcessor.cpp b/layout/style/nsCSSRuleProcessor.cpp index f6b57039445d..13f732dc55c3 100644 --- a/layout/style/nsCSSRuleProcessor.cpp +++ b/layout/style/nsCSSRuleProcessor.cpp @@ -1308,7 +1308,7 @@ struct NodeMatchContext { // event-state-dependent selector for any value of that event state. // So mStateMask contains the states that should NOT be tested. // - // NOTE: For |aStateMask| to work correctly, it's important that any + // NOTE: For |mStateMask| to work correctly, it's important that any // change that changes multiple state bits include all those state // bits in the notification. Otherwise, if multiple states change but // we do separate notifications then we might determine the style is @@ -1533,7 +1533,21 @@ checkGenericEmptyMatches(Element* aElement, return (child == nullptr); } -// An array of the states that are relevant for various pseudoclasses. +// Arrays of the states that are relevant for various pseudoclasses. +static const nsEventStates sPseudoClassStateDependences[] = { +#define CSS_PSEUDO_CLASS(_name, _value) \ + nsEventStates(), +#define CSS_STATE_DEPENDENT_PSEUDO_CLASS(_name, _value, _states) \ + _states, +#include "nsCSSPseudoClassList.h" +#undef CSS_STATE_DEPENDENT_PSEUDO_CLASS +#undef CSS_PSEUDO_CLASS + // Add more entries for our fake values to make sure we can't + // index out of bounds into this array no matter what. + nsEventStates(), + nsEventStates() +}; + static const nsEventStates sPseudoClassStates[] = { #define CSS_PSEUDO_CLASS(_name, _value) \ nsEventStates(), @@ -1556,7 +1570,7 @@ MOZ_STATIC_ASSERT(NS_ARRAY_LENGTH(sPseudoClassStates) == // * when non-null, it indicates that we're processing a negation, // which is done only when SelectorMatches calls itself recursively // * what it points to should be set to true whenever a test is skipped -// because of aStateMask +// because of aNodeMatchContent.mStateMask static bool SelectorMatches(Element* aElement, nsCSSSelector* aSelector, NodeMatchContext& aNodeMatchContext, @@ -1997,6 +2011,38 @@ static bool SelectorMatches(Element* aElement, } break; + case nsCSSPseudoClasses::ePseudoClass_dir: + { + if (aDependence) { + nsEventStates states + = sPseudoClassStateDependences[pseudoClass->mType]; + if (aNodeMatchContext.mStateMask.HasAtLeastOneOfStates(states)) { + *aDependence = true; + return false; + } + } + + // if we only had to consider HTML, directionality would be exclusively + // LTR or RTL, and this could be just + // + // if (dirString.EqualsLiteral("rtl") != + // aElement->StyleState().HasState(NS_EVENT_STATE_RTL) + // + // However, in markup languages where there is no direction attribute + // we have to consider the possibility that neither dir(rtl) nor + // dir(ltr) matches. + nsEventStates state = aElement->StyleState(); + bool elementIsRTL = state.HasState(NS_EVENT_STATE_RTL); + bool elementIsLTR = state.HasState(NS_EVENT_STATE_LTR); + nsDependentString dirString(pseudoClass->u.mString); + + if ((dirString.EqualsLiteral("rtl") && !elementIsRTL) || + (dirString.EqualsLiteral("ltr") && !elementIsLTR)) { + return false; + } + } + break; + default: NS_ABORT_IF_FALSE(false, "How did that happen?"); } @@ -2128,10 +2174,10 @@ static bool SelectorMatches(Element* aElement, bool dependence = false; result = !SelectorMatches(aElement, negation, aNodeMatchContext, aTreeMatchContext, &dependence); - // If the selector does match due to the dependence on aStateMask, - // then we want to keep result true so that the final result of - // SelectorMatches is true. Doing so tells StateEnumFunc that - // there is a dependence on the state. + // If the selector does match due to the dependence on + // aNodeMatchContext.mStateMask, then we want to keep result true + // so that the final result of SelectorMatches is true. Doing so + // tells StateEnumFunc that there is a dependence on the state. result = result || dependence; } } @@ -2665,7 +2711,7 @@ nsEventStates ComputeSelectorStateDependence(nsCSSSelector& aSelector) if (pseudoClass->mType >= nsCSSPseudoClasses::ePseudoClass_Count) { continue; } - states |= sPseudoClassStates[pseudoClass->mType]; + states |= sPseudoClassStateDependences[pseudoClass->mType]; } return states; }