Add support for :dir(ltr/rtl) CSS selector, layout/style part. Bug 562159, r=bz
authorSimon Montagu <smontagu@smontagu.org>
Tue, 07 Aug 2012 21:44:24 -0700
changeset 104899 e226e413dd27afb24965180b472aef966f20ec34
parent 104898 9c24b7287c5ba4fce4f692d4ff9dff690a96e8c2
child 104900 841cd6e1e585831a25ab406d48b3e5750c564781
push idunknown
push userunknown
push dateunknown
reviewersbz
bugs562159
milestone17.0a1
Add support for :dir(ltr/rtl) CSS selector, layout/style part. Bug 562159, r=bz
content/events/public/nsEventStates.h
content/html/content/src/nsGenericHTMLElement.h
dom/locales/en-US/chrome/layout/css.properties
layout/style/nsCSSParser.cpp
layout/style/nsCSSPseudoClassList.h
layout/style/nsCSSPseudoClasses.cpp
layout/style/nsCSSRuleProcessor.cpp
--- a/content/events/public/nsEventStates.h
+++ b/content/events/public/nsEventStates.h
@@ -243,16 +243,20 @@ private:
 // Content is in the sub-suboptimal region.
 #define NS_EVENT_STATE_SUB_SUB_OPTIMUM NS_DEFINE_EVENT_STATE_MACRO(39)
 // Handler for click to play plugin (vulnerable w/update)
 #define NS_EVENT_STATE_VULNERABLE_UPDATABLE NS_DEFINE_EVENT_STATE_MACRO(40)
 // Handler for click to play plugin (vulnerable w/no update)
 #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 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 | \
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -44,16 +44,17 @@ typedef nsMappedAttributeElement nsGener
 class nsGenericHTMLElement : public nsGenericHTMLElementBase
 {
 public:
   nsGenericHTMLElement(already_AddRefed<nsINodeInfo> aNodeInfo)
     : nsGenericHTMLElementBase(aNodeInfo)
   {
     NS_ASSERTION(mNodeInfo->NamespaceID() == kNameSpaceID_XHTML,
                  "Unexpected namespace");
+    AddStatesSilently(NS_EVENT_STATE_LTR);
   }
 
   /** Typesafe, non-refcounting cast from nsIContent.  Cheaper than QI. **/
   static nsGenericHTMLElement* FromContent(nsIContent *aContent)
   {
     if (aContent->IsHTML())
       return static_cast<nsGenericHTMLElement*>(aContent);
     return nullptr;
--- a/dom/locales/en-US/chrome/layout/css.properties
+++ b/dom/locales/en-US/chrome/layout/css.properties
@@ -109,16 +109,17 @@ PEMQExpectedExpressionStart=Expected '('
 PEMQExpressionEOF=contents of media query expression
 PEMQExpectedFeatureName=Expected media feature name but found '%1$S'.
 PEMQExpectedFeatureNameEnd=Expected ':' or ')' after media feature name but found '%1$S'.
 PEMQNoMinMaxWithoutValue=Media features with min- or max- must have a value.
 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'
 PESupportsConditionExpectedOpenParen=Expected '(' while parsing supports condition but found '%1$S'.
 PESupportsConditionExpectedCloseParen=Expected ')' while parsing supports condition but found '%1$S'.
 PESupportsConditionExpectedStart=Expected 'not' or '(' while parsing supports condition but found '%1$S'.
 PESupportsConditionExpectedNot=Expected 'not' while parsing supports condition but found '%1$S'.
 PESupportsGroupRuleStart=Expected '{' to begin @supports rule but found '%1$S'.
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -3590,20 +3590,22 @@ CSSParserImpl::ParsePseudoClassWithIdent
   }
   // We expect an identifier with a language abbreviation
   if (eCSSToken_Ident != mToken.mType) {
     REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotIdent);
     UngetToken();
     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(')')
     }
   }
 
   // Add the pseudo with the language parameter
   aSelector.AddPseudoClass(aType, mToken.mIdent.get());
 
   // close the parenthesis
--- a/layout/style/nsCSSPseudoClassList.h
+++ b/layout/style/nsCSSPseudoClassList.h
@@ -4,37 +4,55 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* atom list for CSS pseudo-classes */
 
 /*
  * 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
 
 // The CSS_PSEUDO_CLASS entries should all come before the
 // CSS_STATE_PSEUDO_CLASS entries.  The CSS_PSEUDO_CLASS entry order
 // must be the same as the order of cases in SelectorMatches.
 
 CSS_PSEUDO_CLASS(empty, ":empty")
@@ -85,16 +103,21 @@ CSS_PSEUDO_CLASS(mozWindowInactive, ":-m
 // Matches any table elements that have a nonzero border attribute,
 // according to HTML integer attribute parsing rules.
 CSS_PSEUDO_CLASS(mozTableBorderNonzero, ":-moz-table-border-nonzero")
 
 // :not needs to come at the end of the non-bit pseudo-class list, since
 // 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",
                        NS_EVENT_STATE_VISITED | NS_EVENT_STATE_UNVISITED)
 CSS_STATE_PSEUDO_CLASS(visited, ":visited", NS_EVENT_STATE_VISITED)
 
 CSS_STATE_PSEUDO_CLASS(active, ":active", NS_EVENT_STATE_ACTIVE)
 CSS_STATE_PSEUDO_CLASS(checked, ":checked", NS_EVENT_STATE_CHECKED)
@@ -173,8 +196,13 @@ CSS_STATE_PSEUDO_CLASS(mozMeterSubOptimu
                        NS_EVENT_STATE_SUB_OPTIMUM)
 CSS_STATE_PSEUDO_CLASS(mozMeterSubSubOptimum, ":-moz-meter-sub-sub-optimum",
                        NS_EVENT_STATE_SUB_SUB_OPTIMUM)
 
 #ifdef DEFINED_CSS_STATE_PSEUDO_CLASS
 #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
--- a/layout/style/nsCSSPseudoClasses.cpp
+++ b/layout/style/nsCSSPseudoClasses.cpp
@@ -37,17 +37,18 @@ void nsCSSPseudoClasses::AddRefAtoms()
 }
 
 bool
 nsCSSPseudoClasses::HasStringArg(Type aType)
 {
   return aType == ePseudoClass_lang ||
          aType == ePseudoClass_mozEmptyExceptChildrenWithLocalname ||
          aType == ePseudoClass_mozSystemMetric ||
-         aType == ePseudoClass_mozLocaleDir;
+         aType == ePseudoClass_mozLocaleDir ||
+         aType == ePseudoClass_dir;
 }
 
 bool
 nsCSSPseudoClasses::HasNthPairArg(Type aType)
 {
   return aType == ePseudoClass_nthChild ||
          aType == ePseudoClass_nthLastChild ||
          aType == ePseudoClass_nthOfType ||
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -1303,17 +1303,17 @@ nsCSSRuleProcessor::GetContentStateForVi
  * node, but only one possible RuleProcessorData.
  */
 struct NodeMatchContext {
   // In order to implement nsCSSRuleProcessor::HasStateDependentStyle,
   // we need to be able to see if a node might match an
   // 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
   // not state-dependent when it really is (e.g., determining that a
   // :hover:active rule no longer matches when both states are unset).
   const nsEventStates mStateMask;
 
   // Is this link the unique link whose visitedness can affect the style
@@ -1528,17 +1528,31 @@ checkGenericEmptyMatches(Element* aEleme
   do {
     child = aElement->GetChildAt(++index);
     // stop at first non-comment (and non-whitespace for
     // :-moz-only-whitespace) node        
   } while (child && !IsSignificantChild(child, true, isWhitespaceSignificant));
   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(),
 #define CSS_STATE_PSEUDO_CLASS(_name, _value, _states) \
   _states,
 #include "nsCSSPseudoClassList.h"
 #undef CSS_STATE_PSEUDO_CLASS
 #undef CSS_PSEUDO_CLASS
@@ -1551,17 +1565,17 @@ MOZ_STATIC_ASSERT(NS_ARRAY_LENGTH(sPseud
                   nsCSSPseudoClasses::ePseudoClass_NotPseudoClass + 1,
                   "ePseudoClass_NotPseudoClass is no longer at the end of"
                   "sPseudoClassStates");
 
 // |aDependence| has two functions:
 //  * 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,
                               TreeMatchContext& aTreeMatchContext,
                               bool* const aDependence = nullptr)
 
 {
   NS_PRECONDITION(!aSelector->IsPseudoElement(),
@@ -1992,16 +2006,48 @@ static bool SelectorMatches(Element* aEl
           if (!val ||
               (val->Type() == nsAttrValue::eInteger &&
                val->GetIntegerValue() == 0)) {
             return false;
           }
         }
         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?");
       }
     } else {
       // Bit-based pseudo-classes
       if (statesToCheck.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE) &&
           aTreeMatchContext.mCompatMode == eCompatibility_NavQuirks &&
           // global selector:
@@ -2123,20 +2169,20 @@ static bool SelectorMatches(Element* aEl
 
   // apply SelectorMatches to the negated selectors in the chain
   if (!isNegated) {
     for (nsCSSSelector *negation = aSelector->mNegations;
          result && negation; negation = negation->mNegations) {
       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;
     }
   }
   return result;
 }
 
 #undef STATE_CHECK
 
@@ -2660,17 +2706,17 @@ nsEventStates ComputeSelectorStateDepend
   nsEventStates states;
   for (nsPseudoClassList* pseudoClass = aSelector.mPseudoClassList;
        pseudoClass; pseudoClass = pseudoClass->mNext) {
     // Tree pseudo-elements overload mPseudoClassList for things that
     // aren't pseudo-classes.
     if (pseudoClass->mType >= nsCSSPseudoClasses::ePseudoClass_Count) {
       continue;
     }
-    states |= sPseudoClassStates[pseudoClass->mType];
+    states |= sPseudoClassStateDependences[pseudoClass->mType];
   }
   return states;
 }
 
 static bool
 AddSelector(RuleCascadeData* aCascade,
             // The part between combinators at the top level of the selector
             nsCSSSelector* aSelectorInTopLevel,