content/base/src/DirectionalityUtils.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ts=2 sw=2 et tw=78: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 /*
     8   Implementation description from https://etherpad.mozilla.org/dir-auto
    10   Static case
    11   ===========
    12   When we see a new content node with @dir=auto from the parser, we set the
    13   NodeHasDirAuto flag on the node.  We won't have enough information to
    14   decide the directionality of the node at this point.
    16   When we bind a new content node to the document, if its parent has either of
    17   the NodeAncestorHasDirAuto or NodeHasDirAuto flags, we set the
    18   NodeAncestorHasDirAuto flag on the node.
    20   When a new input with @type=text/search/tel/url/email and @dir=auto is added
    21   from the parser, we resolve the directionality based on its @value.
    23   When a new text node with non-neutral content is appended to a textarea
    24   element with NodeHasDirAuto, if the directionality of the textarea element
    25   is still unresolved, it is resolved based on the value of the text node.
    26   Elements with unresolved directionality behave as LTR.
    28   When a new text node with non-neutral content is appended to an element that
    29   is not a textarea but has either of the NodeAncestorHasDirAuto or
    30   NodeHasDirAuto flags, we walk up the parent chain while the
    31   NodeAncestorHasDirAuto flag is present, and when we reach an element with
    32   NodeHasDirAuto and no resolved directionality, we resolve the directionality
    33   based on the contents of the text node and cease walking the parent chain.
    34   Note that we should ignore elements with NodeHasDirAuto with resolved
    35   directionality, so that the second text node in this example tree doesn't
    36   affect the directionality of the div:
    38   <div dir=auto>
    39     <span>foo</span>
    40     <span>بار</span>
    41   </div>
    43   The parent chain walk will be aborted if we hit a script or style element, or
    44   if we hit an element with @dir=ltr or @dir=rtl.
    46   I will call this algorithm "upward propagation".
    48   Each text node should maintain a list of elements which have their
    49   directionality determined by the first strong character of that text node.
    50   This is useful to make dynamic changes more efficient.  One way to implement
    51   this is to have a per-document hash table mapping a text node to a set of
    52   elements.  I'll call this data structure TextNodeDirectionalityMap. The
    53   algorithm for appending a new text node above needs to update this data
    54   structure.
    56   *IMPLEMENTATION NOTE*
    57   In practice, the implementation uses two per-node properties:
    59   dirAutoSetBy, which is set on a node with auto-directionality, and points to
    60   the textnode that contains the strong character which determines the
    61   directionality of the node.
    63   textNodeDirectionalityMap, which is set on a text node and points to a hash
    64   table listing the nodes whose directionality is determined by the text node.
    66   Handling dynamic changes
    67   ========================
    69   We need to handle the following cases:
    71   1. When the value of an input element with @type=text/search/tel/url/email is
    72   changed, if it has NodeHasDirAuto, we update the resolved directionality.
    74   2. When the dir attribute is changed from something else (including the case
    75   where it doesn't exist) to auto on a textarea or an input element with
    76   @type=text/search/tel/url/email, we set the NodeHasDirAuto flag and resolve
    77   the directionality based on the value of the element.
    79   3. When the dir attribute is changed from something else (including the case
    80   where it doesn't exist) to auto on any element except case 1 above and the bdi
    81   element, we run the following algorithm:
    82   * We set the NodeHasDirAuto flag.
    83   * If the element doesn't have the NodeAncestorHasDirAuto flag, we set the
    84   NodeAncestorHasDirAuto flag on all of its child nodes.  (Note that if the
    85   element does have NodeAncestorHasDirAuto, all of its children should
    86   already have this flag too.  We can assert this in debug builds.)
    87   * To resolve the directionality of the element, we run the algorithm explained
    88   in http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-dir-attribute
    89   (I'll call this the "downward propagation algorithm".) by walking the child
    90   subtree in tree order.  Note that an element with @dir=auto should not affect
    91   other elements in its document with @dir=auto.  So there is no need to walk up
    92   the parent chain in this case.  TextNodeDirectionalityMap needs to be updated
    93   as appropriate.
    95   3a. When the dir attribute is set to any valid value on an element that didn't
    96   have a valid dir attribute before, this means that any descendant of that
    97   element will not affect the directionality of any of its ancestors. So we need
    98   to check whether any text node descendants of the element are listed in
    99   TextNodeDirectionalityMap, and whether the elements whose direction they set
   100   are ancestors of the element. If so, we need to rerun the downward propagation
   101   algorithm for those ancestors.
   103   4.  When the dir attribute is changed from auto to something else (including
   104   the case where it gets removed) on a textarea or an input element with
   105   @type=text/search/tel/url/email, we unset the NodeHasDirAuto flag and
   106   resolve the directionality based on the directionality of the value of the @dir
   107   attribute on element itself or its parent element.
   109   5. When the dir attribute is changed from auto to something else (including the
   110   case where it gets removed) on any element except case 4 above and the bdi
   111   element, we run the following algorithm:
   112   * We unset the NodeHasDirAuto flag.
   113   * If the element does not have the NodeAncestorHasDirAuto flag, we unset
   114   the NodeAncestorHasDirAuto flag on all of its child nodes, except those
   115   who are a descendant of another element with NodeHasDirAuto.  (Note that if
   116   the element has the NodeAncestorHasDirAuto flag, all of its child nodes
   117   should still retain the same flag.)
   118   * We resolve the directionality of the element based on the value of the @dir
   119   attribute on the element itself or its parent element.
   120   TextNodeDirectionalityMap needs to be updated as appropriate.
   122   5a. When the dir attribute is removed or set to an invalid value on any
   123   element (except a bdi element) with the NodeAncestorHasDirAuto flag which
   124   previously had a valid dir attribute, it might have a text node descendant that
   125   did not previously affect the directionality of any of its ancestors but should
   126   now begin to affect them.
   127   We run the following algorithm:
   128   * Walk up the parent chain from the element.
   129   * For any element that appears in the TextNodeDirectionalityMap, remove the
   130     element from the map and rerun the downward propagation algorithm
   131     (see section 3).
   132   * If we reach an element without either of the NodeHasDirAuto or
   133     NodeAncestorHasDirAuto flags, abort the parent chain walk.
   135   6. When an element with @dir=auto is added to the document, we should handle it
   136   similar to the case 2/3 above.
   138   7. When an element with NodeHasDirAuto or NodeAncestorHasDirAuto is
   139   removed from the document, we should handle it similar to the case 4/5 above,
   140   except that we don't need to handle anything in the child subtree.  We should
   141   also remove all of the occurrences of that node and its descendants from
   142   TextNodeDirectionalityMap. (This is the conceptual description of what needs to
   143   happen but in the implementation UnbindFromTree is going to be called on all of
   144   the descendants so we don't need to descend into the child subtree).
   146   8. When the contents of a text node is changed either from script or by the
   147   user, we need to run the following algorithm:
   148   * If the change has happened after the first character with strong
   149   directionality in the text node, do nothing.
   150   * If the text node is a child of a bdi, script or style element, do nothing.
   151   * If the text node belongs to a textarea with NodeHasDirAuto, we need to
   152   update the directionality of the textarea.
   153   * Grab a list of elements affected by this text node from
   154   TextNodeDirectionalityMap and re-resolve the directionality of each one of them
   155   based on the new contents of the text node.
   156   * If the text node does not exist in TextNodeDirectionalityMap, and it has the
   157   NodeAncestorHasDirAuto flag set, this could potentially be a text node
   158   which is going to start affecting the directionality of its parent @dir=auto
   159   elements. In this case, we need to fall back to the (potentially expensive)
   160   "upward propagation algorithm".  The TextNodeDirectionalityMap data structure
   161   needs to be update during this algorithm.
   162   * If the new contents of the text node do not have any strong characters, and
   163   the old contents used to, and the text node used to exist in
   164   TextNodeDirectionalityMap and it has the NodeAncestorHasDirAuto flag set,
   165   the elements associated with this text node inside TextNodeDirectionalityMap
   166   will now get their directionality from another text node.  In this case, for
   167   each element in the list retrieved from TextNodeDirectionalityMap, run the
   168   downward propagation algorithm (section 3), and remove the text node from
   169   TextNodeDirectionalityMap.
   171   9. When a new text node is injected into a document, we need to run the
   172   following algorithm:
   173   * If the contents of the text node do not have any characters with strong
   174   direction, do nothing.
   175   * If the text node is a child of a bdi, script or style element, do nothing.
   176   * If the text node is appended to a textarea element with NodeHasDirAuto, we
   177   need to update the directionality of the textarea.
   178   * If the text node has NodeAncestorHasDirAuto, we need to run the "upward
   179   propagation algorithm".  The TextNodeDirectionalityMap data structure needs to
   180   be update during this algorithm.
   182   10. When a text node is removed from a document, we need to run the following
   183   algorithm:
   184   * If the contents of the text node do not have any characters with strong
   185   direction, do nothing.
   186   * If the text node is a child of a bdi, script or style element, do nothing.
   187   * If the text node is removed from a textarea element with NodeHasDirAuto,
   188   set the directionality to "ltr". (This is what the spec currently says, but I'm
   189   filing a spec bug to get it fixed -- the directionality should depend on the
   190   parent element here.)
   191   * If the text node has NodeAncestorHasDirAuto, we need to look at the list
   192   of elements being affected by this text node from TextNodeDirectionalityMap,
   193   run the "downward propagation algorithm" (section 3) for each one of them,
   194   while updating TextNodeDirectionalityMap along the way.
   196   11. If the value of the @dir attribute on a bdi element is changed to an
   197   invalid value (or if it's removed), determine the new directionality similar
   198   to the case 3 above.
   200   == Implemention Notes ==
   201   When a new node gets bound to the tree, the BindToTree function gets called.
   202   The reverse case is UnbindFromTree.
   203   When the contents of a text node change, nsGenericDOMDataNode::SetTextInternal
   204   gets called.
   205   */
   207 #include "mozilla/dom/DirectionalityUtils.h"
   209 #include "nsINode.h"
   210 #include "nsIContent.h"
   211 #include "nsIDocument.h"
   212 #include "mozilla/DebugOnly.h"
   213 #include "mozilla/dom/Element.h"
   214 #include "nsIDOMHTMLDocument.h"
   215 #include "nsUnicodeProperties.h"
   216 #include "nsTextFragment.h"
   217 #include "nsAttrValue.h"
   218 #include "nsTextNode.h"
   219 #include "nsCheapSets.h"
   221 namespace mozilla {
   223 using mozilla::dom::Element;
   225 /**
   226  * Returns true if aElement is one of the elements whose text content should not
   227  * affect its own direction, nor the direction of ancestors with dir=auto.
   228  *
   229  * Note that this does not include <bdi>, whose content does affect its own
   230  * direction when it has dir=auto (which it has by default), so one needs to
   231  * test for it separately, e.g. with DoesNotAffectDirectionOfAncestors.
   232  * It *does* include textarea, because even if a textarea has dir=auto, it has
   233  * unicode-bidi: plaintext and is handled automatically in bidi resolution.
   234  */
   235 static bool
   236 DoesNotParticipateInAutoDirection(const Element* aElement)
   237 {
   238   nsINodeInfo* nodeInfo = aElement->NodeInfo();
   239   return (!aElement->IsHTML() ||
   240           nodeInfo->Equals(nsGkAtoms::script) ||
   241           nodeInfo->Equals(nsGkAtoms::style) ||
   242           nodeInfo->Equals(nsGkAtoms::textarea) ||
   243           aElement->IsInAnonymousSubtree());
   244 }
   246 static inline bool
   247 IsBdiWithoutDirAuto(const Element* aElement)
   248 {
   249   // We are testing for bdi elements without explicit dir="auto", so we can't
   250   // use the HasDirAuto() flag, since that will return true for bdi element with
   251   // no dir attribute or an invalid dir attribute
   252   return (aElement->IsHTML(nsGkAtoms::bdi) &&
   253           (!aElement->HasValidDir() || aElement->HasFixedDir()));
   254 }
   256 /**
   257  * Returns true if aElement is one of the element whose text content should not
   258  * affect the direction of ancestors with dir=auto (though it may affect its own
   259  * direction, e.g. <bdi>)
   260  */
   261 static bool
   262 DoesNotAffectDirectionOfAncestors(const Element* aElement)
   263 {
   264   return (DoesNotParticipateInAutoDirection(aElement) ||
   265           IsBdiWithoutDirAuto(aElement) ||
   266           aElement->HasFixedDir());
   267 }
   269 /**
   270  * Returns the directionality of a Unicode character
   271  */
   272 static Directionality
   273 GetDirectionFromChar(uint32_t ch)
   274 {
   275   switch(mozilla::unicode::GetBidiCat(ch)) {
   276     case eCharType_RightToLeft:
   277     case eCharType_RightToLeftArabic:
   278       return eDir_RTL;
   280     case eCharType_LeftToRight:
   281       return eDir_LTR;
   283     default:
   284       return eDir_NotSet;
   285   }
   286 }
   288 inline static bool NodeAffectsDirAutoAncestor(nsINode* aTextNode)
   289 {
   290   Element* parent = aTextNode->GetParentElement();
   291   return (parent &&
   292           !DoesNotParticipateInAutoDirection(parent) &&
   293           parent->NodeOrAncestorHasDirAuto());
   294 }
   296 /**
   297  * Various methods for returning the directionality of a string using the
   298  * first-strong algorithm defined in http://unicode.org/reports/tr9/#P2
   299  *
   300  * @param[out] aFirstStrong the offset to the first character in the string with
   301  *             strong directionality, or UINT32_MAX if there is none (return
   302                value is eDir_NotSet).
   303  * @return the directionality of the string
   304  */
   305 static Directionality
   306 GetDirectionFromText(const char16_t* aText, const uint32_t aLength,
   307                      uint32_t* aFirstStrong = nullptr)
   308 {
   309   const char16_t* start = aText;
   310   const char16_t* end = aText + aLength;
   312   while (start < end) {
   313     uint32_t current = start - aText;
   314     uint32_t ch = *start++;
   316     if (NS_IS_HIGH_SURROGATE(ch) &&
   317         start < end &&
   318         NS_IS_LOW_SURROGATE(*start)) {
   319       ch = SURROGATE_TO_UCS4(ch, *start++);
   320       current++;
   321     }
   323     // Just ignore lone surrogates
   324     if (!IS_SURROGATE(ch)) {
   325       Directionality dir = GetDirectionFromChar(ch);
   326       if (dir != eDir_NotSet) {
   327         if (aFirstStrong) {
   328           *aFirstStrong = current;
   329         }
   330         return dir;
   331       }
   332     }
   333   }
   335   if (aFirstStrong) {
   336     *aFirstStrong = UINT32_MAX;
   337   }
   338   return eDir_NotSet;
   339 }
   341 static Directionality
   342 GetDirectionFromText(const char* aText, const uint32_t aLength,
   343                         uint32_t* aFirstStrong = nullptr)
   344 {
   345   const char* start = aText;
   346   const char* end = aText + aLength;
   348   while (start < end) {
   349     uint32_t current = start - aText;
   350     unsigned char ch = (unsigned char)*start++;
   352     Directionality dir = GetDirectionFromChar(ch);
   353     if (dir != eDir_NotSet) {
   354       if (aFirstStrong) {
   355         *aFirstStrong = current;
   356       }
   357       return dir;
   358     }
   359   }
   361   if (aFirstStrong) {
   362     *aFirstStrong = UINT32_MAX;
   363   }
   364   return eDir_NotSet;
   365 }
   367 static Directionality
   368 GetDirectionFromText(const nsTextFragment* aFrag,
   369                      uint32_t* aFirstStrong = nullptr)
   370 {
   371   if (aFrag->Is2b()) {
   372     return GetDirectionFromText(aFrag->Get2b(), aFrag->GetLength(),
   373                                    aFirstStrong);
   374   }
   376   return GetDirectionFromText(aFrag->Get1b(), aFrag->GetLength(),
   377                                  aFirstStrong);
   378 }
   380 /**
   381  * Set the directionality of a node with dir=auto as defined in
   382  * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality
   383  *
   384  * @param[in] changedNode If we call this method because the content of a text
   385  *            node is about to change, pass in the changed node, so that we
   386  *            know not to return it
   387  * @return the text node containing the character that determined the direction
   388  */
   389 static nsINode*
   390 WalkDescendantsSetDirectionFromText(Element* aElement, bool aNotify = true,
   391                                     nsINode* aChangedNode = nullptr)
   392 {
   393   MOZ_ASSERT(aElement, "Must have an element");
   394   MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto");
   396   if (DoesNotParticipateInAutoDirection(aElement)) {
   397     return nullptr;
   398   }
   400   nsIContent* child = aElement->GetFirstChild();
   401   while (child) {
   402     if (child->IsElement() &&
   403         DoesNotAffectDirectionOfAncestors(child->AsElement())) {
   404       child = child->GetNextNonChildNode(aElement);
   405       continue;
   406     }
   408     if (child->NodeType() == nsIDOMNode::TEXT_NODE &&
   409         child != aChangedNode) {
   410       Directionality textNodeDir = GetDirectionFromText(child->GetText());
   411       if (textNodeDir != eDir_NotSet) {
   412         // We found a descendant text node with strong directional characters.
   413         // Set the directionality of aElement to the corresponding value.
   414         aElement->SetDirectionality(textNodeDir, aNotify);
   415         return child;
   416       }
   417     }
   418     child = child->GetNextNode(aElement);
   419   }
   421   // We walked all the descendants without finding a text node with strong
   422   // directional characters. Set the directionality to LTR
   423   aElement->SetDirectionality(eDir_LTR, aNotify);
   424   return nullptr;
   425 }
   427 class nsTextNodeDirectionalityMap
   428 {
   429   static void
   430   nsTextNodeDirectionalityMapDtor(void *aObject, nsIAtom* aPropertyName,
   431                                   void *aPropertyValue, void* aData)
   432   {
   433     nsINode* textNode = static_cast<nsINode * >(aObject);
   434     textNode->ClearHasTextNodeDirectionalityMap();
   436     nsTextNodeDirectionalityMap* map =
   437       reinterpret_cast<nsTextNodeDirectionalityMap * >(aPropertyValue);
   438     map->EnsureMapIsClear(textNode);
   439     delete map;
   440   }
   442 public:
   443   nsTextNodeDirectionalityMap(nsINode* aTextNode)
   444   {
   445     MOZ_ASSERT(aTextNode, "Null text node");
   446     MOZ_COUNT_CTOR(nsTextNodeDirectionalityMap);
   447     aTextNode->SetProperty(nsGkAtoms::textNodeDirectionalityMap, this,
   448                            nsTextNodeDirectionalityMapDtor);
   449     aTextNode->SetHasTextNodeDirectionalityMap();
   450   }
   452   ~nsTextNodeDirectionalityMap()
   453   {
   454     MOZ_COUNT_DTOR(nsTextNodeDirectionalityMap);
   455   }
   457   void AddEntry(nsINode* aTextNode, Element* aElement)
   458   {
   459     if (!mElements.Contains(aElement)) {
   460       mElements.Put(aElement);
   461       aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode);
   462       aElement->SetHasDirAutoSet();
   463     }
   464   }
   466   void RemoveEntry(nsINode* aTextNode, Element* aElement)
   467   {
   468     NS_ASSERTION(mElements.Contains(aElement),
   469                  "element already removed from map");
   471     mElements.Remove(aElement);
   472     aElement->ClearHasDirAutoSet();
   473     aElement->UnsetProperty(nsGkAtoms::dirAutoSetBy);
   474   }
   476 private:
   477   nsCheapSet<nsPtrHashKey<Element> > mElements;
   479   static nsTextNodeDirectionalityMap* GetDirectionalityMap(nsINode* aTextNode)
   480   {
   481     MOZ_ASSERT(aTextNode->NodeType() == nsIDOMNode::TEXT_NODE,
   482                "Must be a text node");
   483     nsTextNodeDirectionalityMap* map = nullptr;
   485     if (aTextNode->HasTextNodeDirectionalityMap()) {
   486       map = static_cast<nsTextNodeDirectionalityMap * >
   487         (aTextNode->GetProperty(nsGkAtoms::textNodeDirectionalityMap));
   488     }
   490     return map;
   491   }
   493   static PLDHashOperator SetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aDir)
   494   {
   495     MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
   496     aEntry->GetKey()->SetDirectionality(*reinterpret_cast<Directionality*>(aDir),
   497                                         true);
   498     return PL_DHASH_NEXT;
   499   }
   501   static PLDHashOperator ResetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aData)
   502   {
   503     MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
   504     // run the downward propagation algorithm
   505     // and remove the text node from the map
   506     nsINode* oldTextNode = static_cast<Element*>(aData);
   507     Element* rootNode = aEntry->GetKey();
   508     nsINode* newTextNode = nullptr;
   509     if (oldTextNode && rootNode->HasDirAuto()) {
   510       newTextNode = WalkDescendantsSetDirectionFromText(rootNode, true,
   511                                                         oldTextNode);
   512     }
   513     if (newTextNode) {
   514       nsTextNodeDirectionalityMap::AddEntryToMap(newTextNode, rootNode);
   515     } else {
   516       rootNode->ClearHasDirAutoSet();
   517       rootNode->UnsetProperty(nsGkAtoms::dirAutoSetBy);
   518     }
   519     return PL_DHASH_REMOVE;
   520   }
   522   static PLDHashOperator ClearEntry(nsPtrHashKey<Element>* aEntry, void* aData)
   523   {
   524     Element* rootNode = aEntry->GetKey();
   525     rootNode->ClearHasDirAutoSet();
   526     rootNode->UnsetProperty(nsGkAtoms::dirAutoSetBy);
   527     return PL_DHASH_REMOVE;
   528   }
   530 public:
   531   void UpdateAutoDirection(Directionality aDir)
   532   {
   533     mElements.EnumerateEntries(SetNodeDirection, &aDir);
   534   }
   536   void ClearAutoDirection()
   537   {
   538     mElements.EnumerateEntries(ResetNodeDirection, nullptr);
   539   }
   541   void ResetAutoDirection(nsINode* aTextNode)
   542   {
   543     mElements.EnumerateEntries(ResetNodeDirection, aTextNode);
   544   }
   546   void EnsureMapIsClear(nsINode* aTextNode)
   547   {
   548     DebugOnly<uint32_t> clearedEntries =
   549       mElements.EnumerateEntries(ClearEntry, aTextNode);
   550     MOZ_ASSERT(clearedEntries == 0, "Map should be empty already");
   551   }
   553   static void RemoveElementFromMap(nsINode* aTextNode, Element* aElement)
   554   {
   555     if (aTextNode->HasTextNodeDirectionalityMap()) {
   556       GetDirectionalityMap(aTextNode)->RemoveEntry(aTextNode, aElement);
   557     }
   558   }
   560   static void AddEntryToMap(nsINode* aTextNode, Element* aElement)
   561   {
   562     nsTextNodeDirectionalityMap* map = GetDirectionalityMap(aTextNode);
   563     if (!map) {
   564       map = new nsTextNodeDirectionalityMap(aTextNode);
   565     }
   567     map->AddEntry(aTextNode, aElement);
   568   }
   570   static void UpdateTextNodeDirection(nsINode* aTextNode, Directionality aDir)
   571   {
   572     MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
   573                "Map missing in UpdateTextNodeDirection");
   574     GetDirectionalityMap(aTextNode)->UpdateAutoDirection(aDir);
   575   }
   577   static void ClearTextNodeDirection(nsINode* aTextNode)
   578   {
   579     MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
   580                "Map missing in ResetTextNodeDirection");
   581     GetDirectionalityMap(aTextNode)->ClearAutoDirection();
   582   }
   584   static void ResetTextNodeDirection(nsINode* aTextNode)
   585   {
   586     MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
   587                "Map missing in ResetTextNodeDirection");
   588     GetDirectionalityMap(aTextNode)->ResetAutoDirection(aTextNode);
   589   }
   591   static void EnsureMapIsClearFor(nsINode* aTextNode)
   592   {
   593     if (aTextNode->HasTextNodeDirectionalityMap()) {
   594       GetDirectionalityMap(aTextNode)->EnsureMapIsClear(aTextNode);
   595     }
   596   }
   597 };
   599 Directionality
   600 RecomputeDirectionality(Element* aElement, bool aNotify)
   601 {
   602   MOZ_ASSERT(!aElement->HasDirAuto(),
   603              "RecomputeDirectionality called with dir=auto");
   605   Directionality dir = eDir_LTR;
   607   if (aElement->HasValidDir()) {
   608     dir = aElement->GetDirectionality();
   609   } else {
   610     Element* parent = aElement->GetParentElement();
   611     if (parent) {
   612       // If the element doesn't have an explicit dir attribute with a valid
   613       // value, the directionality is the same as the parent element (but
   614       // don't propagate the parent directionality if it isn't set yet).
   615       Directionality parentDir = parent->GetDirectionality();
   616       if (parentDir != eDir_NotSet) {
   617         dir = parentDir;
   618       }
   619     } else {
   620       // If there is no parent element and no dir attribute, the directionality
   621       // is LTR.
   622       dir = eDir_LTR;
   623     }
   625     aElement->SetDirectionality(dir, aNotify);
   626   }
   627   return dir;
   628 }
   630 void
   631 SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
   632                                bool aNotify)
   633 {
   634   for (nsIContent* child = aElement->GetFirstChild(); child; ) {
   635     if (!child->IsElement()) {
   636       child = child->GetNextNode(aElement);
   637       continue;
   638     }
   640     Element* element = child->AsElement();
   641     if (element->HasValidDir() || element->HasDirAuto()) {
   642       child = child->GetNextNonChildNode(aElement);
   643       continue;
   644     }
   645     element->SetDirectionality(aDir, aNotify);
   646     child = child->GetNextNode(aElement);
   647   }
   648 }
   650 /**
   651  * Walk the parent chain of a text node whose dir attribute has been removed and
   652  * reset the direction of any of its ancestors which have dir=auto and whose
   653  * directionality is determined by a text node descendant.
   654  */
   655 void
   656 WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify)
   657 {
   658   nsINode* setByNode;
   659   Element* parent = aElement->GetParentElement();
   661   while (parent && parent->NodeOrAncestorHasDirAuto()) {
   662     if (parent->HasDirAutoSet()) {
   663       // If the parent has the DirAutoSet flag, its direction is determined by
   664       // some text node descendant.
   665       // Remove it from the map and reset its direction by the downward
   666       // propagation algorithm
   667       setByNode =
   668         static_cast<nsINode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
   669       if (setByNode) {
   670         nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, parent);
   671       }
   672     }
   673     if (parent->HasDirAuto()) {
   674       setByNode = WalkDescendantsSetDirectionFromText(parent, aNotify);
   675       if (setByNode) {
   676         nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parent);
   677       }
   678       break;
   679     }
   680     parent = parent->GetParentElement();
   681   }
   682 }
   684 void
   685 WalkDescendantsResetAutoDirection(Element* aElement)
   686 {
   687   nsIContent* child = aElement->GetFirstChild();
   688   while (child) {
   689     if (child->HasDirAuto()) {
   690       child = child->GetNextNonChildNode(aElement);
   691       continue;
   692     }
   694     if (child->HasTextNodeDirectionalityMap()) {
   695       nsTextNodeDirectionalityMap::ResetTextNodeDirection(child);
   696       nsTextNodeDirectionalityMap::EnsureMapIsClearFor(child);
   697     }
   698     child = child->GetNextNode(aElement);
   699   }
   700 }
   702 void
   703 WalkDescendantsSetDirAuto(Element* aElement, bool aNotify)
   704 {
   705   // Only test for DoesNotParticipateInAutoDirection -- in other words, if
   706   // aElement is a <bdi> which is having its dir attribute set to auto (or
   707   // removed or set to an invalid value, which are equivalent to dir=auto for
   708   // <bdi>, we *do* want to set AncestorHasDirAuto on its descendants, unlike
   709   // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a <bdi>
   710   // being bound to an existing node with dir=auto.
   711   if (!DoesNotParticipateInAutoDirection(aElement)) {
   713     bool setAncestorDirAutoFlag =
   714 #ifdef DEBUG
   715       true;
   716 #else
   717       !aElement->AncestorHasDirAuto();
   718 #endif
   720     if (setAncestorDirAutoFlag) {
   721       nsIContent* child = aElement->GetFirstChild();
   722       while (child) {
   723         if (child->IsElement() &&
   724             DoesNotAffectDirectionOfAncestors(child->AsElement())) {
   725           child = child->GetNextNonChildNode(aElement);
   726           continue;
   727         }
   729         MOZ_ASSERT(!aElement->AncestorHasDirAuto() ||
   730                    child->AncestorHasDirAuto(),
   731                    "AncestorHasDirAuto set on node but not its children");
   732         child->SetAncestorHasDirAuto();
   733         child = child->GetNextNode(aElement);
   734       }
   735     }
   736   }
   738   nsINode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
   739   if (textNode) {
   740     nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement);
   741   }
   742 }
   744 void
   745 WalkDescendantsClearAncestorDirAuto(Element* aElement)
   746 {
   747   nsIContent* child = aElement->GetFirstChild();
   748   while (child) {
   749     if (child->HasDirAuto()) {
   750       child = child->GetNextNonChildNode(aElement);
   751       continue;
   752     }
   754     child->ClearAncestorHasDirAuto();
   755     child = child->GetNextNode(aElement);
   756   }
   757 }
   759 void SetAncestorDirectionIfAuto(nsINode* aTextNode, Directionality aDir,
   760                                 bool aNotify = true)
   761 {
   762   MOZ_ASSERT(aTextNode->NodeType() == nsIDOMNode::TEXT_NODE,
   763              "Must be a text node");
   765   Element* parent = aTextNode->GetParentElement();
   766   while (parent && parent->NodeOrAncestorHasDirAuto()) {
   767     if (DoesNotParticipateInAutoDirection(parent) || parent->HasFixedDir()) {
   768       break;
   769     }
   771     if (parent->HasDirAuto()) {
   772       bool resetDirection = false;
   773       nsINode* directionWasSetByTextNode =
   774         static_cast<nsINode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
   776       if (!parent->HasDirAutoSet()) {
   777         // Fast path if parent's direction is not yet set by any descendant
   778         MOZ_ASSERT(!directionWasSetByTextNode,
   779                    "dirAutoSetBy property should be null");
   780         resetDirection = true;
   781       } else {
   782         // If parent's direction is already set, we need to know if
   783         // aTextNode is before or after the text node that had set it.
   784         // We will walk parent's descendants in tree order starting from
   785         // aTextNode to optimize for the most common case where text nodes are
   786         // being appended to tree.
   787         if (!directionWasSetByTextNode) {
   788           resetDirection = true;
   789         } else if (directionWasSetByTextNode != aTextNode) {
   790           nsIContent* child = aTextNode->GetNextNode(parent);
   791           while (child) {
   792             if (child->IsElement() &&
   793                 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
   794               child = child->GetNextNonChildNode(parent);
   795               continue;
   796             }
   798             if (child == directionWasSetByTextNode) {
   799               // we found the node that set the element's direction after our
   800               // text node, so we need to reset the direction
   801               resetDirection = true;
   802               break;
   803             }
   805             child = child->GetNextNode(parent);
   806           }
   807         }
   808       }
   810       if (resetDirection) {
   811         if (directionWasSetByTextNode) {
   812           nsTextNodeDirectionalityMap::RemoveElementFromMap(
   813             directionWasSetByTextNode, parent
   814           );
   815         }
   816         parent->SetDirectionality(aDir, aNotify);
   817         nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parent);
   818         SetDirectionalityOnDescendants(parent, aDir, aNotify);
   819       }
   821       // Since we found an element with dir=auto, we can stop walking the
   822       // parent chain: none of its ancestors will have their direction set by
   823       // any of its descendants.
   824       return;
   825     }
   826     parent = parent->GetParentElement();
   827   }
   828 }
   830 bool
   831 TextNodeWillChangeDirection(nsIContent* aTextNode, Directionality* aOldDir,
   832                             uint32_t aOffset)
   833 {
   834   if (!NodeAffectsDirAutoAncestor(aTextNode)) {
   835     nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
   836     return false;
   837   }
   839   uint32_t firstStrong;
   840   *aOldDir = GetDirectionFromText(aTextNode->GetText(), &firstStrong);
   841   return (aOffset <= firstStrong);
   842 }
   844 void
   845 TextNodeChangedDirection(nsIContent* aTextNode, Directionality aOldDir,
   846                          bool aNotify)
   847 {
   848   Directionality newDir = GetDirectionFromText(aTextNode->GetText());
   849   if (newDir == eDir_NotSet) {
   850     if (aOldDir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
   851       // This node used to have a strong directional character but no
   852       // longer does. ResetTextNodeDirection() will re-resolve the
   853       // directionality of any elements whose directionality was
   854       // determined by this node.
   855       nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode);
   856     }
   857   } else {
   858     // This node has a strong directional character. If it has a
   859     // TextNodeDirectionalityMap property, it already determines the
   860     // directionality of some element(s), so call UpdateTextNodeDirection to
   861     // reresolve their directionality. Otherwise call
   862     // SetAncestorDirectionIfAuto to find ancestor elements which should
   863     // have their directionality determined by this node.
   864     if (aTextNode->HasTextNodeDirectionalityMap()) {
   865       nsTextNodeDirectionalityMap::UpdateTextNodeDirection(aTextNode, newDir);
   866     } else {
   867       SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify);
   868     }
   869   }
   870 }
   872 void
   873 SetDirectionFromNewTextNode(nsIContent* aTextNode)
   874 {
   875   if (!NodeAffectsDirAutoAncestor(aTextNode)) {
   876     return;
   877   }
   879   Element* parent = aTextNode->GetParentElement();
   880   if (parent && parent->NodeOrAncestorHasDirAuto()) {
   881     aTextNode->SetAncestorHasDirAuto();
   882   }
   884   Directionality dir = GetDirectionFromText(aTextNode->GetText());
   885   if (dir != eDir_NotSet) {
   886     SetAncestorDirectionIfAuto(aTextNode, dir);
   887   }
   888 }
   890 void
   891 ResetDirectionSetByTextNode(nsTextNode* aTextNode, bool aNullParent)
   892 {
   893   if (!NodeAffectsDirAutoAncestor(aTextNode)) {
   894     nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
   895     return;
   896   }
   898   Directionality dir = GetDirectionFromText(aTextNode->GetText());
   899   if (dir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
   900     if (aNullParent) {
   901       nsTextNodeDirectionalityMap::ClearTextNodeDirection(aTextNode);
   902     } else {
   903       nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode);
   904     }
   905   }
   906 }
   908 void
   909 SetDirectionalityFromValue(Element* aElement, const nsAString& value,
   910                            bool aNotify)
   911 {
   912   Directionality dir = GetDirectionFromText(PromiseFlatString(value).get(),
   913                                             value.Length());
   914   if (dir == eDir_NotSet) {
   915     dir = eDir_LTR;
   916   }
   918   aElement->SetDirectionality(dir, aNotify);
   919 }
   921 void
   922 OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue,
   923              bool hadValidDir, bool hadDirAuto, bool aNotify)
   924 {
   925   if (aElement->IsHTML(nsGkAtoms::input)) {
   926     return;
   927   }
   929   if (aElement->AncestorHasDirAuto()) {
   930     if (!hadValidDir) {
   931       // The element is a descendant of an element with dir = auto, is
   932       // having its dir attribute set, and previously didn't have a valid dir
   933       // attribute.
   934       // Check whether any of its text node descendants determine the
   935       // direction of any of its ancestors, and redetermine their direction
   936       WalkDescendantsResetAutoDirection(aElement);
   937     } else if (!aElement->HasValidDir()) {
   938       // The element is a descendant of an element with dir = auto and is
   939       // having its dir attribute removed or set to an invalid value.
   940       // Reset the direction of any of its ancestors whose direction is
   941       // determined by a text node descendant
   942       WalkAncestorsResetAutoDirection(aElement, aNotify);
   943     }
   944   } else if (hadDirAuto && !aElement->HasDirAuto()) {
   945     // The element isn't a descendant of an element with dir = auto, and is
   946     // having its dir attribute set to something other than auto.
   947     // Walk the descendant tree and clear the AncestorHasDirAuto flag.
   948     //
   949     // N.B: For elements other than <bdi> it would be enough to test that the
   950     //      current value of dir was "auto" in BeforeSetAttr to know that we
   951     //      were unsetting dir="auto". For <bdi> things are more complicated,
   952     //      since it behaves like dir="auto" whenever the dir attribute is
   953     //      empty or invalid, so we would have to check whether the old value
   954     //      was not either "ltr" or "rtl", and the new value was either "ltr"
   955     //      or "rtl". Element::HasDirAuto() encapsulates all that, so doing it
   956     //      here is simpler.
   957     WalkDescendantsClearAncestorDirAuto(aElement);
   958   }
   960   if (aElement->HasDirAuto()) {
   961     WalkDescendantsSetDirAuto(aElement, aNotify);
   962   } else {
   963     if (aElement->HasDirAutoSet()) {
   964       nsINode* setByNode =
   965         static_cast<nsINode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
   966       nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
   967     }
   968     SetDirectionalityOnDescendants(aElement,
   969                                    RecomputeDirectionality(aElement, aNotify),
   970                                    aNotify);
   971   }
   972 }
   974 void
   975 SetDirOnBind(mozilla::dom::Element* aElement, nsIContent* aParent)
   976 {
   977   // Set the AncestorHasDirAuto flag, unless this element shouldn't affect
   978   // ancestors that have dir=auto
   979   if (!DoesNotParticipateInAutoDirection(aElement) &&
   980       !aElement->IsHTML(nsGkAtoms::bdi) &&
   981       aParent && aParent->NodeOrAncestorHasDirAuto()) {
   982     aElement->SetAncestorHasDirAuto();
   984     nsIContent* child = aElement->GetFirstChild();
   985     if (child) {
   986       // If we are binding an element to the tree that already has descendants,
   987       // and the parent has NodeHasDirAuto or NodeAncestorHasDirAuto, we need
   988       // to set NodeAncestorHasDirAuto on all the element's descendants, except
   989       // for nodes that don't affect the direction of their ancestors.
   990       do {
   991         if (child->IsElement() &&
   992             DoesNotAffectDirectionOfAncestors(child->AsElement())) {
   993           child = child->GetNextNonChildNode(aElement);
   994           continue;
   995         }
   997         child->SetAncestorHasDirAuto();
   998         child = child->GetNextNode(aElement);
   999       } while (child);
  1001       // We may also need to reset the direction of an ancestor with dir=auto
  1002       WalkAncestorsResetAutoDirection(aElement, true);
  1006   if (!aElement->HasDirAuto()) {
  1007     // if the element doesn't have dir=auto, set its own directionality from
  1008     // the dir attribute or by inheriting from its ancestors.
  1009     RecomputeDirectionality(aElement, false);
  1013 void ResetDir(mozilla::dom::Element* aElement)
  1015   if (aElement->HasDirAutoSet()) {
  1016     nsINode* setByNode =
  1017       static_cast<nsINode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
  1018     nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
  1021   if (!aElement->HasDirAuto()) {
  1022     RecomputeDirectionality(aElement, false);
  1026 } // end namespace mozilla

mercurial