Bug 562169. Add support for :dir(ltr/rtl) CSS selector, content part, r=bz
authorSimon Montagu <smontagu@smontagu.org>
Tue, 17 Apr 2012 07:03:10 +0300
changeset 108036 51739311572b99d02ec2103bdfbcb32b3a1d6d6b
parent 108035 407bce2f4e97029b61c02d9ef46f471dd3c9f9c2
child 108037 4a08177931d26928812755c21b86fa195a388568
push id214
push userakeybl@mozilla.com
push dateWed, 14 Nov 2012 20:38:59 +0000
treeherdermozilla-release@c8b08ec8e1aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs562169
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
Bug 562169. Add support for :dir(ltr/rtl) CSS selector, content part, r=bz
content/base/public/DirectionalityUtils.h
content/base/public/Element.h
content/base/public/Makefile.in
content/base/public/nsIDocument.h
content/base/public/nsINode.h
content/base/src/DirectionalityUtils.cpp
content/base/src/Makefile.in
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/base/src/nsGenericElement.cpp
content/base/src/nsGkAtomList.h
content/html/content/src/nsGenericHTMLElement.cpp
content/html/content/src/nsGenericHTMLElement.h
new file mode 100644
--- /dev/null
+++ b/content/base/public/DirectionalityUtils.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DirectionalityUtils_h___
+#define DirectionalityUtils_h___
+
+class nsIContent;
+class nsIDocument;
+class nsINode;
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+
+namespace directionality {
+
+enum Directionality {
+  eDir_NotSet = 0,
+  eDir_RTL    = 1,
+  eDir_LTR    = 2
+};
+
+/**
+ * Set the directionality of an element according to the algorithm defined at
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality,
+ * not including elements with auto direction.
+ *
+ * @return the directionality that the element was set to
+ */
+Directionality RecomputeDirectionality(mozilla::dom::Element* aElement,
+                                       bool aNotify = true);
+
+/**
+ * Set the directionality of any descendants of a node that do not themselves
+ * have a dir attribute.
+ * For performance reasons we walk down the descendant tree in the rare case
+ * of setting the dir attribute, rather than walking up the ancestor tree in
+ * the much more common case of getting the element's directionality.
+ */
+void SetDirectionalityOnDescendants(mozilla::dom::Element* aElement, 
+                                    Directionality aDir,
+                                    bool aNotify = true);
+
+} // end namespace directionality
+
+} // end namespace mozilla
+
+#endif /* DirectionalityUtils_h___ */
--- a/content/base/public/Element.h
+++ b/content/base/public/Element.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_Element_h__
 #define mozilla_dom_Element_h__
 
 #include "mozilla/dom/FragmentOrElement.h" // for base class
 #include "nsChangeHint.h"                  // for enum
 #include "nsEventStates.h"                 // for member
+#include "mozilla/dom/DirectionalityUtils.h"
 
 class nsEventStateManager;
 class nsFocusManager;
 class nsGlobalWindow;
 class nsICSSDeclaration;
 class nsISMILAttr;
 
 // Element-specific flags
@@ -211,16 +212,64 @@ public:
 
   /**
    * Returns an atom holding the name of the "class" attribute on this
    * content node (if applicable).  Returns null if there is no
    * "class" attribute for this type of content node.
    */
   virtual nsIAtom *GetClassAttributeName() const = 0;
 
+  inline mozilla::directionality::Directionality GetDirectionality() const {
+    if (HasFlag(NODE_HAS_DIRECTION_RTL)) {
+      return mozilla::directionality::eDir_RTL;
+    }
+
+    if (HasFlag(NODE_HAS_DIRECTION_LTR)) {
+      return mozilla::directionality::eDir_LTR;
+    }
+
+    return mozilla::directionality::eDir_NotSet;
+  }
+
+  inline void SetDirectionality(mozilla::directionality::Directionality aDir,
+                                bool aNotify) {
+    UnsetFlags(NODE_ALL_DIRECTION_FLAGS);
+    if (!aNotify) {
+      RemoveStatesSilently(DIRECTION_STATES);
+    }
+
+    switch (aDir) {
+      case (mozilla::directionality::eDir_RTL):
+        SetFlags(NODE_HAS_DIRECTION_RTL);
+        if (!aNotify) {
+          AddStatesSilently(NS_EVENT_STATE_RTL);
+        }
+        break;
+
+      case(mozilla::directionality::eDir_LTR):
+        SetFlags(NODE_HAS_DIRECTION_LTR);
+        if (!aNotify) {
+          AddStatesSilently(NS_EVENT_STATE_LTR);
+        }
+        break;
+
+      default:
+        break;
+    }
+
+    /* 
+     * Only call UpdateState if we need to notify, because we call
+     * SetDirectionality for every element, and UpdateState is very very slow
+     * for some elements.
+     */
+    if (aNotify) {
+      UpdateState(true);
+    }
+  }
+
 protected:
   /**
    * Method to get the _intrinsic_ content state of this element.  This is the
    * state that is independent of the element's presentation.  To get the full
    * content state, use State().  See nsEventStates.h for
    * the possible bits that could be set here.
    */
   virtual nsEventStates IntrinsicState() const;
--- a/content/base/public/Makefile.in
+++ b/content/base/public/Makefile.in
@@ -43,16 +43,17 @@ nsReferencedElement.h \
 nsTreeSanitizer.h \
 nsXMLNameSpaceMap.h \
 nsIXFormsUtilityService.h \
 $(NULL)
 
 EXPORTS_NAMESPACES = mozilla/dom mozilla
 
 EXPORTS_mozilla/dom = \
+		DirectionalityUtils.h \
 		Element.h \
 		FragmentOrElement.h \
 		FromParser.h \
 		$(NULL)
 
 EXPORTS_mozilla = \
 		CORSMode.h \
 		$(NULL)
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -18,16 +18,17 @@
 #include "nsILoadContext.h"              // for member (in nsCOMPtr)
 #include "nsILoadGroup.h"                // for member (in nsCOMPtr)
 #include "nsINode.h"                     // for base class
 #include "nsIScriptGlobalObject.h"       // for member (in nsCOMPtr)
 #include "nsIStructuredCloneContainer.h" // for member (in nsCOMPtr)
 #include "nsPIDOMWindow.h"               // for use in inline functions
 #include "nsPropertyTable.h"             // for member
 #include "nsTHashtable.h"                // for member
+#include "mozilla/dom/DirectionalityUtils.h"
 
 class imgIRequest;
 class nsAString;
 class nsBindingManager;
 class nsCSSStyleSheet;
 class nsDOMNavigationTiming;
 class nsEventStates;
 class nsFrameLoader;
@@ -392,16 +393,20 @@ public:
    * callers are expected to take action as needed if they want this
    * change to actually change anything immediately.
    * @see nsBidiUtils.h
    */
   void SetBidiOptions(PRUint32 aBidiOptions)
   {
     mBidiOptions = aBidiOptions;
   }
+
+  inline mozilla::directionality::Directionality GetDocumentDirectionality() {
+    return mDirectionality;
+  }
   
   /**
    * Access HTTP header data (this may also get set from other
    * sources, like HTML META tags).
    */
   virtual void GetHeaderData(nsIAtom* aHeaderField, nsAString& aData) const = 0;
   virtual void SetHeaderData(nsIAtom* aheaderField, const nsAString& aData) = 0;
 
@@ -1848,16 +1853,19 @@ protected:
   // If mIsStaticDocument is true, mOriginalDocument points to the original
   // document.
   nsCOMPtr<nsIDocument> mOriginalDocument;
 
   // The bidi options for this document.  What this bitfield means is
   // defined in nsBidiUtils.h
   PRUint32 mBidiOptions;
 
+  // The root directionality of this document.
+  mozilla::directionality::Directionality mDirectionality;
+
   nsCString mContentLanguage;
 private:
   nsCString mContentType;
 protected:
 
   // The document's security info
   nsCOMPtr<nsISupports> mSecurityInfo;
 
--- a/content/base/public/nsINode.h
+++ b/content/base/public/nsINode.h
@@ -140,18 +140,26 @@ enum {
   NODE_HAS_ACCESSKEY           = 0x00020000U,
 
   // Set if the node is handling a click.
   NODE_HANDLING_CLICK          = 0x00040000U,
 
   // Set if the node has had :hover selectors matched against it
   NODE_HAS_RELEVANT_HOVER_RULES = 0x00080000U,
 
+  // Set if the node has right-to-left directionality
+  NODE_HAS_DIRECTION_RTL        = 0x00100000U,
+
+  // Set if the node has left-to-right directionality
+  NODE_HAS_DIRECTION_LTR        = 0x00200000U,
+
+  NODE_ALL_DIRECTION_FLAGS      = NODE_HAS_DIRECTION_LTR | NODE_HAS_DIRECTION_RTL,
+
   // Remaining bits are node type specific.
-  NODE_TYPE_SPECIFIC_BITS_OFFSET =        20
+  NODE_TYPE_SPECIFIC_BITS_OFFSET =        22
 };
 
 /**
  * Class used to detect unexpected mutations. To use the class create an
  * nsMutationGuard on the stack before unexpected mutations could occur.
  * You can then at any time call Mutated to check if any unexpected mutations
  * have occurred.
  *
@@ -1274,16 +1282,18 @@ private:
     // Set if element has pointer locked
     ElementHasPointerLock,
     // Set if the node may have DOMMutationObserver attached to it.
     NodeMayHaveDOMMutationObserver,
     // Set if node is Content
     NodeIsContent,
     // Set if the node has animations or transitions
     ElementHasAnimations,
+    // Set if node has a dir attribute with a valid value (ltr or rtl)
+    NodeHasValidDirAttribute,
     // Guard value
     BooleanFlagCount
   };
 
   void SetBoolFlag(BooleanFlag name, bool value) {
     PR_STATIC_ASSERT(BooleanFlagCount <= 8*sizeof(mBoolFlags));
     mBoolFlags = (mBoolFlags & ~(1 << name)) | (value << name);
   }
@@ -1341,16 +1351,19 @@ public:
   void SetMayHaveDOMMutationObserver()
     { SetBoolFlag(NodeMayHaveDOMMutationObserver, true); }
   bool HasListenerManager() { return HasFlag(NODE_HAS_LISTENERMANAGER); }
   bool HasPointerLock() const { return GetBoolFlag(ElementHasPointerLock); }
   void SetPointerLock() { SetBoolFlag(ElementHasPointerLock); }
   void ClearPointerLock() { ClearBoolFlag(ElementHasPointerLock); }
   bool MayHaveAnimations() { return GetBoolFlag(ElementHasAnimations); }
   void SetMayHaveAnimations() { SetBoolFlag(ElementHasAnimations); }
+  void SetHasValidDir() { SetBoolFlag(NodeHasValidDirAttribute); }
+  void ClearHasValidDir() { ClearBoolFlag(NodeHasValidDirAttribute); }
+  bool HasValidDir() const { return GetBoolFlag(NodeHasValidDirAttribute); }
 protected:
   void SetParentIsContent(bool aValue) { SetBoolFlag(ParentIsContent, aValue); }
   void SetInDocument() { SetBoolFlag(IsInDocument); }
   void SetNodeIsContent() { SetBoolFlag(NodeIsContent); }
   void ClearInDocument() { ClearBoolFlag(IsInDocument); }
   void SetIsElement() { SetBoolFlag(NodeIsElement); }
   void SetHasID() { SetBoolFlag(ElementHasID); }
   void ClearHasID() { ClearBoolFlag(ElementHasID); }
new file mode 100644
--- /dev/null
+++ b/content/base/src/DirectionalityUtils.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=78: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "nsINode.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "mozilla/dom/Element.h"
+#include "nsIDOMNodeFilter.h"
+#include "nsTreeWalker.h"
+#include "nsIDOMHTMLDocument.h"
+
+
+namespace mozilla {
+
+namespace directionality {
+
+typedef mozilla::dom::Element Element;
+
+Directionality
+RecomputeDirectionality(Element* aElement, bool aNotify)
+{
+  Directionality dir = eDir_LTR;
+
+  if (aElement->HasValidDir()) {
+    dir = aElement->GetDirectionality();
+  } else {
+    Element* parent = aElement->GetElementParent();
+    if (parent) {
+      // If the element doesn't have an explicit dir attribute with a valid
+      // value, the directionality is the same as the parent element (but
+      // don't propagate the parent directionality if it isn't set yet).
+      Directionality parentDir = parent->GetDirectionality();
+      if (parentDir != eDir_NotSet) {
+        dir = parentDir;
+      }
+    } else {
+      // If there is no parent element, the directionality is the same as the
+      // document direction.
+      Directionality documentDir =
+        aElement->OwnerDoc()->GetDocumentDirectionality();
+      if (documentDir != eDir_NotSet) {
+        dir = documentDir;
+      }
+    }
+    
+    aElement->SetDirectionality(dir, aNotify);
+  }
+  return dir;
+}
+
+void
+SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
+                               bool aNotify)
+{
+  for (nsIContent* child = aElement->GetFirstChild(); child; ) {
+    if (!child->IsElement()) {
+      child = child->GetNextNode(aElement);
+      continue;
+    }
+
+    Element* element = child->AsElement();
+    if (element->HasValidDir()) {
+      child = child->GetNextNonChildNode(aElement);
+      continue;
+    }
+    element->SetDirectionality(aDir, aNotify);
+    child = child->GetNextNode(aElement);
+  }
+}
+
+} // end namespace directionality
+
+} // end namespace mozilla
+
--- a/content/base/src/Makefile.in
+++ b/content/base/src/Makefile.in
@@ -48,16 +48,17 @@ EXPORTS_NAMESPACES = mozilla/dom
 EXPORTS_mozilla/dom = \
   Link.h \
   $(NULL)
 
 LOCAL_INCLUDES = \
 		$(NULL)
 
 CPPSRCS		= \
+		DirectionalityUtils.cpp \
 		nsAtomListUtils.cpp \
 		nsAttrAndChildArray.cpp \
 		nsAttrValue.cpp \
 		nsAttrValueOrString.cpp \
 		nsCCUncollectableMarker.cpp \
 		nsChannelPolicy.cpp \
 		nsCommentNode.cpp \
 		nsContentAreaDragDrop.cpp \
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -86,16 +86,17 @@
 // for radio group stuff
 #include "nsIDOMHTMLInputElement.h"
 #include "nsIRadioVisitor.h"
 #include "nsIFormControl.h"
 
 #include "nsXMLEventsManager.h"
 
 #include "nsBidiUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
 
 #include "nsIDOMUserDataHandler.h"
 #include "nsIDOMXPathEvaluator.h"
 #include "nsIXPathEvaluatorInternal.h"
 #include "nsIParserService.h"
 #include "nsContentCreatorFunctions.h"
 
 #include "nsIScriptContext.h"
@@ -165,16 +166,17 @@
 
 #include "mozilla/Preferences.h"
 
 #include "imgILoader.h"
 #include "nsWrapperCacheInlines.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::directionality;
 
 typedef nsTArray<Link*> LinkArray;
 
 // Reference to the document which requested DOM full-screen mode.
 nsWeakPtr nsDocument::sFullScreenDoc = nullptr;
 
 // Reference to the root document of the branch containing the document
 // which requested DOM full-screen mode.
@@ -1505,16 +1507,17 @@ nsIDocument::nsIDocument()
     mRemovedFromDocShell(false),
     // mAllowDNSPrefetch starts true, so that we can always reliably && it
     // with various values that might disable it.  Since we never prefetch
     // unless we get a window, and in that case the docshell value will get
     // &&-ed in, this is safe.
     mAllowDNSPrefetch(true),
     mIsBeingUsedAsImage(false),
     mHasLinksToUpdate(false),
+    mDirectionality(eDir_LTR),
     mPartID(0)
 {
   SetInDocument();
 }
 
 // NOTE! nsDocument::operator new() zeroes out all members, so don't
 // bother initializing members to 0.
 
@@ -5578,16 +5581,25 @@ nsDocument::SetDir(const nsAString& aDir
         if (shell) {
           nsPresContext *context = shell->GetPresContext();
           NS_ENSURE_TRUE(context, NS_ERROR_UNEXPECTED);
           context->SetBidi(options, true);
         } else {
           // No presentation; just set it on ourselves
           SetBidiOptions(options);
         }
+        Directionality dir = elt->mValue == IBMBIDI_TEXTDIRECTION_RTL ?
+                               eDir_RTL : eDir_LTR;
+        SetDocumentDirectionality(dir);
+        // Set the directionality of the root element and its descendants, if any
+        Element* rootElement = GetRootElement();
+        if (rootElement) {
+          rootElement->SetDirectionality(dir, true);
+          SetDirectionalityOnDescendants(rootElement, dir);
+        }
       }
 
       break;
     }
   }
 
   return NS_OK;
 }
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -1043,16 +1043,22 @@ protected:
   void DestroyElementMaps();
 
   // Refreshes the hrefs of all the links in the document.
   void RefreshLinkHrefs();
 
   nsIContent* GetFirstBaseNodeWithHref();
   nsresult SetFirstBaseNodeWithHref(nsIContent *node);
 
+  inline void
+  SetDocumentDirectionality(mozilla::directionality::Directionality aDir)
+  {
+    mDirectionality = aDir;
+  }
+
   // Get the first <title> element with the given IsNodeOfType type, or
   // return null if there isn't one
   nsIContent* GetTitleContent(PRUint32 aNodeType);
   // Find the first "title" element in the given IsNodeOfType type and
   // append the concatenation of its text node children to aTitle. Do
   // nothing if there is no such element.
   void GetTitleFromElement(PRUint32 aNodeType, nsAString& aTitle);
 
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -46,16 +46,17 @@
 #include "nsDOMTokenList.h"
 #include "nsXBLPrototypeBinding.h"
 #include "nsDOMError.h"
 #include "nsDOMString.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsMutationEvent.h"
 #include "nsNodeUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
 #include "nsDocument.h"
 #include "nsAttrValueOrString.h"
 #ifdef MOZ_XUL
 #include "nsXULElement.h"
 #endif /* MOZ_XUL */
 #include "nsFrameManager.h"
 #include "nsFrameSelection.h"
 #ifdef DEBUG
@@ -123,16 +124,17 @@
 #include "mozilla/Telemetry.h"
 
 #include "mozilla/CORSMode.h"
 
 #include "nsStyledElement.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::directionality;
 
 nsEventStates
 Element::IntrinsicState() const
 {
   return IsEditable() ? NS_EVENT_STATE_MOZ_READWRITE :
                         NS_EVENT_STATE_MOZ_READONLY;
 }
 
@@ -1353,16 +1355,23 @@ nsGenericElement::BindToTree(nsIDocument
                NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES |
                // And the restyle bits
                ELEMENT_ALL_RESTYLE_FLAGS);
   } else {
     // If we're not in the doc, update our subtree pointer.
     SetSubtreeRootPointer(aParent->SubtreeRoot());
   }
 
+  // This has to be here, rather than in nsGenericHTMLElement::BindToTree, 
+  //  because it has to happen after updating the parent pointer, but before
+  //  recursively binding the kids.
+  if (IsHTML()) {
+    RecomputeDirectionality(this, false);
+  }
+
   // If NODE_FORCE_XBL_BINDINGS was set we might have anonymous children
   // that also need to be told that they are moving.
   nsresult rv;
   if (hadForceXBL) {
     nsBindingManager* bmgr = OwnerDoc()->BindingManager();
 
     // First check if we have a binding...
     nsXBLBinding* contBinding =
@@ -1538,16 +1547,23 @@ nsGenericElement::UnbindFromTree(bool aD
 #endif
   {
     nsDOMSlots *slots = GetExistingDOMSlots();
     if (slots) {
       slots->mBindingParent = nullptr;
     }
   }
 
+  // This has to be here, rather than in nsGenericHTMLElement::UnbindFromTree, 
+  //  because it has to happen after unsetting the parent pointer, but before
+  //  recursively unbinding the kids.
+  if (IsHTML()) {
+    RecomputeDirectionality(this, false);
+  }
+
   if (aDeep) {
     // Do the kids. Don't call GetChildCount() here since that'll force
     // XUL to generate template children, which there is no need for since
     // all we're going to do is unbind them anyway.
     PRUint32 i, n = mAttrsAndChildren.ChildCount();
 
     for (i = 0; i < n; ++i) {
       // Note that we pass false for aNullParent here, since we don't want
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -265,16 +265,17 @@ GK_ATOM(details, "details")
 GK_ATOM(deviceAspectRatio, "device-aspect-ratio")
 GK_ATOM(deviceHeight, "device-height")
 GK_ATOM(deviceWidth, "device-width")
 GK_ATOM(dfn, "dfn")
 GK_ATOM(dialog, "dialog")
 GK_ATOM(difference, "difference")
 GK_ATOM(digit, "digit")
 GK_ATOM(dir, "dir")
+GK_ATOM(directionality, "directionality")
 GK_ATOM(disableOutputEscaping, "disable-output-escaping")
 GK_ATOM(disabled, "disabled")
 GK_ATOM(display, "display")
 GK_ATOM(distinct, "distinct")
 GK_ATOM(div, "div")
 GK_ATOM(dl, "dl")
 GK_ATOM(doctypePublic, "doctype-public")
 GK_ATOM(doctypeSystem, "doctype-system")
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -48,16 +48,17 @@
 #include "nsScriptLoader.h"
 #include "nsRuleData.h"
 
 #include "nsPresState.h"
 #include "nsILayoutHistoryState.h"
 
 #include "nsHTMLParts.h"
 #include "nsContentUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
 #include "nsString.h"
 #include "nsUnicharUtils.h"
 #include "nsGkAtoms.h"
 #include "nsEventStateManager.h"
 #include "nsIDOMEvent.h"
 #include "nsDOMCSSDeclaration.h"
 #include "nsITextControlFrame.h"
 #include "nsIForm.h"
@@ -91,16 +92,17 @@
 #include "HTMLPropertiesCollection.h"
 #include "nsVariant.h"
 #include "nsDOMSettableTokenList.h"
 #include "nsThreadUtils.h"
 #include "nsTextFragment.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::directionality;
 
 class nsINodeInfo;
 class nsIDOMNodeList;
 class nsRuleWalker;
 
 // XXX todo: add in missing out-of-memory checks
 
 //----------------------------------------------------------------------
@@ -1682,16 +1684,34 @@ nsGenericHTMLElement::UpdateEditableStat
   if (value != eInherit) {
     DoSetEditableFlag(!!value, aNotify);
     return;
   }
 
   nsStyledElement::UpdateEditableState(aNotify);
 }
 
+nsEventStates
+nsGenericHTMLElement::IntrinsicState() const
+{
+  nsEventStates state = nsGenericHTMLElementBase::IntrinsicState();
+
+  if (GetDirectionality() == eDir_RTL) {
+    state |= NS_EVENT_STATE_RTL;
+    state &= ~NS_EVENT_STATE_LTR;
+  } else { // at least for HTML, directionality is exclusively LTR or RTL
+    NS_ASSERTION(GetDirectionality() == eDir_LTR,
+                 "HTML element's directionality must be either RTL or LTR");
+    state |= NS_EVENT_STATE_LTR;
+    state &= ~NS_EVENT_STATE_RTL;
+  }
+
+  return state;
+}
+
 nsresult
 nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                  nsIContent* aBindingParent,
                                  bool aCompileEventHandlers)
 {
   nsresult rv = nsGenericHTMLElementBase::BindToTree(aDocument, aParent,
                                                      aBindingParent,
                                                      aCompileEventHandlers);
@@ -1884,16 +1904,30 @@ nsGenericHTMLElement::AfterSetAttr(PRInt
       NS_ABORT_IF_FALSE(aValue->Type() == nsAttrValue::eString,
         "Expected string value for script body");
       nsresult rv = AddScriptEventListener(aName, aValue->GetStringValue());
       NS_ENSURE_SUCCESS(rv, rv);
     }
     else if (aNotify && aName == nsGkAtoms::spellcheck) {
       SyncEditorsOnSubtree(this);
     }
+    else if (aName == nsGkAtoms::dir) {
+      Directionality dir;
+      if (aValue &&
+          (aValue->Equals(nsGkAtoms::ltr, eIgnoreCase) ||
+           aValue->Equals(nsGkAtoms::rtl, eIgnoreCase))) {
+        SetHasValidDir();
+        dir = aValue->Equals(nsGkAtoms::rtl, eIgnoreCase) ? eDir_RTL : eDir_LTR;
+        SetDirectionality(dir, aNotify);
+      } else {
+        ClearHasValidDir();
+        dir = RecomputeDirectionality(this, aNotify);
+      }
+      SetDirectionalityOnDescendants(this, dir, aNotify);
+    }
   }
 
   return nsGenericHTMLElementBase::AfterSetAttr(aNamespaceID, aName,
                                                 aValue, aNotify);
 }
 
 nsEventListenerManager*
 nsGenericHTMLElement::GetEventListenerManagerForAttr(nsIAtom* aAttrName,
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -45,16 +45,17 @@ class nsGenericHTMLElement : public nsGe
 {
 public:
   nsGenericHTMLElement(already_AddRefed<nsINodeInfo> aNodeInfo)
     : nsGenericHTMLElementBase(aNodeInfo)
   {
     NS_ASSERTION(mNodeInfo->NamespaceID() == kNameSpaceID_XHTML,
                  "Unexpected namespace");
     AddStatesSilently(NS_EVENT_STATE_LTR);
+    SetFlags(NODE_HAS_DIRECTION_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;
@@ -198,16 +199,18 @@ public:
   nsresult PostHandleEventForAnchors(nsEventChainPostVisitor& aVisitor);
   bool IsHTMLLink(nsIURI** aURI) const;
 
   // HTML element methods
   void Compact() { mAttrsAndChildren.Compact(); }
 
   virtual void UpdateEditableState(bool aNotify);
 
+  virtual nsEventStates IntrinsicState() const;
+
   // Helper for setting our editable flag and notifying
   void DoSetEditableFlag(bool aEditable, bool aNotify) {
     SetEditableFlag(aEditable);
     UpdateState(aNotify);
   }
 
   virtual bool ParseAttribute(PRInt32 aNamespaceID,
                                 nsIAtom* aAttribute,