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 101784 e226e413dd27afb24965180b472aef966f20ec34
parent 101783 9c24b7287c5ba4fce4f692d4ff9dff690a96e8c2
child 101785 841cd6e1e585831a25ab406d48b3e5750c564781
push id23250
push useremorley@mozilla.com
push dateWed, 08 Aug 2012 16:23:03 +0000
treeherdermozilla-central@b99a81e70b06 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs562159
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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,