diff --git a/content/base/public/DirectionalityUtils.h b/content/base/public/DirectionalityUtils.h new file mode 100644 index 000000000000..195f7009de2f --- /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___ */ diff --git a/content/base/public/Element.h b/content/base/public/Element.h index 4d1b51b1acbd..c76bfe03ca95 100644 --- a/content/base/public/Element.h +++ b/content/base/public/Element.h @@ -10,6 +10,7 @@ #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; @@ -216,6 +217,54 @@ public: */ 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 diff --git a/content/base/public/Makefile.in b/content/base/public/Makefile.in index 49c521897795..5d92d5876f2b 100644 --- a/content/base/public/Makefile.in +++ b/content/base/public/Makefile.in @@ -48,6 +48,7 @@ $(NULL) EXPORTS_NAMESPACES = mozilla/dom mozilla EXPORTS_mozilla/dom = \ + DirectionalityUtils.h \ Element.h \ FragmentOrElement.h \ FromParser.h \ diff --git a/content/base/public/nsIDocument.h b/content/base/public/nsIDocument.h index 16307c8d5cf1..0845a2954312 100644 --- a/content/base/public/nsIDocument.h +++ b/content/base/public/nsIDocument.h @@ -23,6 +23,7 @@ #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; @@ -397,6 +398,10 @@ public: { mBidiOptions = aBidiOptions; } + + inline mozilla::directionality::Directionality GetDocumentDirectionality() { + return mDirectionality; + } /** * Access HTTP header data (this may also get set from other @@ -1853,6 +1858,9 @@ protected: // defined in nsBidiUtils.h PRUint32 mBidiOptions; + // The root directionality of this document. + mozilla::directionality::Directionality mDirectionality; + nsCString mContentLanguage; private: nsCString mContentType; diff --git a/content/base/public/nsINode.h b/content/base/public/nsINode.h index 0d79acf33736..189d64265507 100644 --- a/content/base/public/nsINode.h +++ b/content/base/public/nsINode.h @@ -145,8 +145,16 @@ enum { // 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 }; /** @@ -1279,6 +1287,8 @@ private: 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 }; @@ -1346,6 +1356,9 @@ public: 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); } diff --git a/content/base/src/DirectionalityUtils.cpp b/content/base/src/DirectionalityUtils.cpp new file mode 100644 index 000000000000..8523e25a333a --- /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 + diff --git a/content/base/src/Makefile.in b/content/base/src/Makefile.in index fdec933a82b7..c7c1d95097a6 100644 --- a/content/base/src/Makefile.in +++ b/content/base/src/Makefile.in @@ -53,6 +53,7 @@ LOCAL_INCLUDES = \ $(NULL) CPPSRCS = \ + DirectionalityUtils.cpp \ nsAtomListUtils.cpp \ nsAttrAndChildArray.cpp \ nsAttrValue.cpp \ diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 643fb2e05b44..5a1f4c200094 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -91,6 +91,7 @@ #include "nsXMLEventsManager.h" #include "nsBidiUtils.h" +#include "mozilla/dom/DirectionalityUtils.h" #include "nsIDOMUserDataHandler.h" #include "nsIDOMXPathEvaluator.h" @@ -170,6 +171,7 @@ using namespace mozilla; using namespace mozilla::dom; +using namespace mozilla::directionality; typedef nsTArray LinkArray; @@ -1510,6 +1512,7 @@ nsIDocument::nsIDocument() mAllowDNSPrefetch(true), mIsBeingUsedAsImage(false), mHasLinksToUpdate(false), + mDirectionality(eDir_LTR), mPartID(0) { SetInDocument(); @@ -5583,6 +5586,15 @@ nsDocument::SetDir(const nsAString& aDirection) // 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; diff --git a/content/base/src/nsDocument.h b/content/base/src/nsDocument.h index bbe012601d80..3ef75fda92b2 100644 --- a/content/base/src/nsDocument.h +++ b/content/base/src/nsDocument.h @@ -1048,6 +1048,12 @@ protected: nsIContent* GetFirstBaseNodeWithHref(); nsresult SetFirstBaseNodeWithHref(nsIContent *node); + inline void + SetDocumentDirectionality(mozilla::directionality::Directionality aDir) + { + mDirectionality = aDir; + } + // Get the first element with the given IsNodeOfType type, or // return null if there isn't one nsIContent* GetTitleContent(PRUint32 aNodeType); diff --git a/content/base/src/nsGenericElement.cpp b/content/base/src/nsGenericElement.cpp index e591e7ed11af..a421bd5299f0 100644 --- a/content/base/src/nsGenericElement.cpp +++ b/content/base/src/nsGenericElement.cpp @@ -51,6 +51,7 @@ #include "nsIDOMMutationEvent.h" #include "nsMutationEvent.h" #include "nsNodeUtils.h" +#include "mozilla/dom/DirectionalityUtils.h" #include "nsDocument.h" #include "nsAttrValueOrString.h" #ifdef MOZ_XUL @@ -128,6 +129,7 @@ using namespace mozilla; using namespace mozilla::dom; +using namespace mozilla::directionality; nsEventStates Element::IntrinsicState() const @@ -1358,6 +1360,13 @@ nsGenericElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, 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; @@ -1543,6 +1552,13 @@ nsGenericElement::UnbindFromTree(bool aDeep, bool aNullParent) } } + // 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 diff --git a/content/base/src/nsGkAtomList.h b/content/base/src/nsGkAtomList.h index e2d75736b662..2049e2847306 100644 --- a/content/base/src/nsGkAtomList.h +++ b/content/base/src/nsGkAtomList.h @@ -270,6 +270,7 @@ 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") diff --git a/content/html/content/src/nsGenericHTMLElement.cpp b/content/html/content/src/nsGenericHTMLElement.cpp index 2f502f79a2cd..04ec37904b40 100644 --- a/content/html/content/src/nsGenericHTMLElement.cpp +++ b/content/html/content/src/nsGenericHTMLElement.cpp @@ -53,6 +53,7 @@ #include "nsHTMLParts.h" #include "nsContentUtils.h" +#include "mozilla/dom/DirectionalityUtils.h" #include "nsString.h" #include "nsUnicharUtils.h" #include "nsGkAtoms.h" @@ -96,6 +97,7 @@ using namespace mozilla; using namespace mozilla::dom; +using namespace mozilla::directionality; class nsINodeInfo; class nsIDOMNodeList; @@ -1687,6 +1689,24 @@ nsGenericHTMLElement::UpdateEditableState(bool aNotify) 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, @@ -1889,6 +1909,20 @@ nsGenericHTMLElement::AfterSetAttr(PRInt32 aNamespaceID, nsIAtom* aName, 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, diff --git a/content/html/content/src/nsGenericHTMLElement.h b/content/html/content/src/nsGenericHTMLElement.h index b1e04ea37824..a4bbd99db54b 100644 --- a/content/html/content/src/nsGenericHTMLElement.h +++ b/content/html/content/src/nsGenericHTMLElement.h @@ -50,6 +50,7 @@ public: 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. **/ @@ -203,6 +204,8 @@ public: 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);