1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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/. */
6 
7 /*
8   Implementation description from https://etherpad.mozilla.org/dir-auto
9 
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.
15 
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.
19 
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.
22 
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.
27 
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:
37 
38   <div dir=auto>
39     <span>foo</span>
40     <span>بار</span>
41   </div>
42 
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.
45 
46   I will call this algorithm "upward propagation".
47 
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.
55 
56   *IMPLEMENTATION NOTE*
57   In practice, the implementation uses two per-node properties:
58 
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.
62 
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.
65 
66   Handling dynamic changes
67   ========================
68 
69   We need to handle the following cases:
70 
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.
73 
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.
78 
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
89   http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-dir-attribute
90   (I'll call this the "downward propagation algorithm".) by walking the child
91   subtree in tree order.  Note that an element with @dir=auto should not affect
92   other elements in its document with @dir=auto.  So there is no need to walk up
93   the parent chain in this case.  TextNodeDirectionalityMap needs to be updated
94   as appropriate.
95 
96   3a. When the dir attribute is set to any valid value on an element that didn't
97   have a valid dir attribute before, this means that any descendant of that
98   element will not affect the directionality of any of its ancestors. So we need
99   to check whether any text node descendants of the element are listed in
100   TextNodeDirectionalityMap, and whether the elements whose direction they set
101   are ancestors of the element. If so, we need to rerun the downward propagation
102   algorithm for those ancestors.
103 
104   4.  When the dir attribute is changed from auto to something else (including
105   the case where it gets removed) on a textarea or an input element with
106   @type=text/search/tel/url/email, we unset the NodeHasDirAuto flag and
107   resolve the directionality based on the directionality of the value of the
108   @dir attribute on element itself or its parent element.
109 
110   5. When the dir attribute is changed from auto to something else (including
111   the case where it gets removed) on any element except case 4 above and the bdi
112   element, we run the following algorithm:
113   * We unset the NodeHasDirAuto flag.
114   * If the element does not have the NodeAncestorHasDirAuto flag, we unset
115   the NodeAncestorHasDirAuto flag on all of its child nodes, except those
116   who are a descendant of another element with NodeHasDirAuto.  (Note that if
117   the element has the NodeAncestorHasDirAuto flag, all of its child nodes
118   should still retain the same flag.)
119   * We resolve the directionality of the element based on the value of the @dir
120   attribute on the element itself or its parent element.
121   TextNodeDirectionalityMap needs to be updated as appropriate.
122 
123   5a. When the dir attribute is removed or set to an invalid value on any
124   element (except a bdi element) with the NodeAncestorHasDirAuto flag which
125   previously had a valid dir attribute, it might have a text node descendant
126   that did not previously affect the directionality of any of its ancestors but
127   should now begin to affect them. 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.
134 
135   6. When an element with @dir=auto is added to the document, we should handle
136   it similar to the case 2/3 above.
137 
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
143   to happen but in the implementation UnbindFromTree is going to be called on
144   all of the descendants so we don't need to descend into the child subtree).
145 
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
155   them 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.
170 
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.
181 
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
189   I'm filing a spec bug to get it fixed -- the directionality should depend on
190   the 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.
195 
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.
199 
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, CharacterData::SetTextInternal
204   gets called.
205   */
206 
207 #include "mozilla/dom/DirectionalityUtils.h"
208 
209 #include "nsINode.h"
210 #include "nsIContent.h"
211 #include "nsIContentInlines.h"
212 #include "mozilla/dom/Document.h"
213 #include "mozilla/AutoRestore.h"
214 #include "mozilla/DebugOnly.h"
215 #include "mozilla/dom/Element.h"
216 #include "mozilla/dom/HTMLSlotElement.h"
217 #include "mozilla/dom/ShadowRoot.h"
218 #include "mozilla/intl/UnicodeProperties.h"
219 #include "nsUnicodeProperties.h"
220 #include "nsTextFragment.h"
221 #include "nsAttrValue.h"
222 #include "nsTextNode.h"
223 #include "nsCheapSets.h"
224 
225 namespace mozilla {
226 
227 using mozilla::dom::Element;
228 using mozilla::dom::HTMLInputElement;
229 using mozilla::dom::HTMLSlotElement;
230 using mozilla::dom::ShadowRoot;
231 
GetParentOrHostOrSlot(nsIContent * aContent,bool * aCrossedShadowBoundary=nullptr)232 static nsIContent* GetParentOrHostOrSlot(
233     nsIContent* aContent, bool* aCrossedShadowBoundary = nullptr) {
234   if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) {
235     if (aCrossedShadowBoundary) {
236       *aCrossedShadowBoundary = true;
237     }
238     return slot;
239   }
240 
241   nsIContent* parent = aContent->GetParent();
242   if (parent) {
243     return parent;
244   }
245 
246   ShadowRoot* sr = ShadowRoot::FromNode(aContent);
247   if (sr) {
248     if (aCrossedShadowBoundary) {
249       *aCrossedShadowBoundary = true;
250     }
251     return sr->Host();
252   }
253 
254   return nullptr;
255 }
256 
AncestorChainCrossesShadowBoundary(nsIContent * aDescendant,nsIContent * aAncestor)257 static bool AncestorChainCrossesShadowBoundary(nsIContent* aDescendant,
258                                                nsIContent* aAncestor) {
259   bool crossedShadowBoundary = false;
260   nsIContent* content = aDescendant;
261   while (content && content != aAncestor) {
262     content = GetParentOrHostOrSlot(content, &crossedShadowBoundary);
263     if (crossedShadowBoundary) {
264       return true;
265     }
266   }
267 
268   return false;
269 }
270 
271 /**
272  * Returns true if aElement is one of the elements whose text content should not
273  * affect its own direction, nor the direction of ancestors with dir=auto.
274  *
275  * Note that this does not include <bdi>, whose content does affect its own
276  * direction when it has dir=auto (which it has by default), so one needs to
277  * test for it separately, e.g. with DoesNotAffectDirectionOfAncestors.
278  * It *does* include textarea, because even if a textarea has dir=auto, it has
279  * unicode-bidi: plaintext and is handled automatically in bidi resolution.
280  * It also includes `input`, because it takes the `dir` value from its value
281  * attribute, instead of the child nodes.
282  */
DoesNotParticipateInAutoDirection(const nsIContent * aContent)283 static bool DoesNotParticipateInAutoDirection(const nsIContent* aContent) {
284   mozilla::dom::NodeInfo* nodeInfo = aContent->NodeInfo();
285   return ((!aContent->IsHTMLElement() || nodeInfo->Equals(nsGkAtoms::script) ||
286            nodeInfo->Equals(nsGkAtoms::style) ||
287            nodeInfo->Equals(nsGkAtoms::input) ||
288            nodeInfo->Equals(nsGkAtoms::textarea) ||
289            aContent->IsInNativeAnonymousSubtree())) &&
290          !aContent->IsShadowRoot();
291 }
292 
293 /**
294  * Returns true if aElement is one of the element whose text content should not
295  * affect the direction of ancestors with dir=auto (though it may affect its own
296  * direction, e.g. <bdi>)
297  */
DoesNotAffectDirectionOfAncestors(const Element * aElement)298 static bool DoesNotAffectDirectionOfAncestors(const Element* aElement) {
299   return (DoesNotParticipateInAutoDirection(aElement) ||
300           aElement->IsHTMLElement(nsGkAtoms::bdi) || aElement->HasFixedDir());
301 }
302 
303 /**
304  * Returns the directionality of a Unicode character
305  */
GetDirectionFromChar(uint32_t ch)306 static Directionality GetDirectionFromChar(uint32_t ch) {
307   switch (intl::UnicodeProperties::GetBidiClass(ch)) {
308     case intl::BidiClass::RightToLeft:
309     case intl::BidiClass::RightToLeftArabic:
310       return eDir_RTL;
311 
312     case intl::BidiClass::LeftToRight:
313       return eDir_LTR;
314 
315     default:
316       return eDir_NotSet;
317   }
318 }
319 
NodeAffectsDirAutoAncestor(nsIContent * aTextNode)320 inline static bool NodeAffectsDirAutoAncestor(nsIContent* aTextNode) {
321   nsIContent* parent = GetParentOrHostOrSlot(aTextNode);
322   return (parent && !DoesNotParticipateInAutoDirection(parent) &&
323           parent->NodeOrAncestorHasDirAuto() &&
324           !aTextNode->IsInNativeAnonymousSubtree());
325 }
326 
GetDirectionFromText(const char16_t * aText,const uint32_t aLength,uint32_t * aFirstStrong)327 Directionality GetDirectionFromText(const char16_t* aText,
328                                     const uint32_t aLength,
329                                     uint32_t* aFirstStrong) {
330   const char16_t* start = aText;
331   const char16_t* end = aText + aLength;
332 
333   while (start < end) {
334     uint32_t current = start - aText;
335     uint32_t ch = *start++;
336 
337     if (start < end && NS_IS_SURROGATE_PAIR(ch, *start)) {
338       ch = SURROGATE_TO_UCS4(ch, *start++);
339       current++;
340     }
341 
342     // Just ignore lone surrogates
343     if (!IS_SURROGATE(ch)) {
344       Directionality dir = GetDirectionFromChar(ch);
345       if (dir != eDir_NotSet) {
346         if (aFirstStrong) {
347           *aFirstStrong = current;
348         }
349         return dir;
350       }
351     }
352   }
353 
354   if (aFirstStrong) {
355     *aFirstStrong = UINT32_MAX;
356   }
357   return eDir_NotSet;
358 }
359 
GetDirectionFromText(const char * aText,const uint32_t aLength,uint32_t * aFirstStrong=nullptr)360 static Directionality GetDirectionFromText(const char* aText,
361                                            const uint32_t aLength,
362                                            uint32_t* aFirstStrong = nullptr) {
363   const char* start = aText;
364   const char* end = aText + aLength;
365 
366   while (start < end) {
367     uint32_t current = start - aText;
368     unsigned char ch = (unsigned char)*start++;
369 
370     Directionality dir = GetDirectionFromChar(ch);
371     if (dir != eDir_NotSet) {
372       if (aFirstStrong) {
373         *aFirstStrong = current;
374       }
375       return dir;
376     }
377   }
378 
379   if (aFirstStrong) {
380     *aFirstStrong = UINT32_MAX;
381   }
382   return eDir_NotSet;
383 }
384 
GetDirectionFromText(const mozilla::dom::Text * aTextNode,uint32_t * aFirstStrong=nullptr)385 static Directionality GetDirectionFromText(const mozilla::dom::Text* aTextNode,
386                                            uint32_t* aFirstStrong = nullptr) {
387   const nsTextFragment* frag = &aTextNode->TextFragment();
388   if (frag->Is2b()) {
389     return GetDirectionFromText(frag->Get2b(), frag->GetLength(), aFirstStrong);
390   }
391 
392   return GetDirectionFromText(frag->Get1b(), frag->GetLength(), aFirstStrong);
393 }
394 
WalkDescendantsAndGetDirectionFromText(nsINode * aRoot,nsINode * aSkip,Directionality * aDirectionality)395 static nsTextNode* WalkDescendantsAndGetDirectionFromText(
396     nsINode* aRoot, nsINode* aSkip, Directionality* aDirectionality) {
397   nsIContent* child = aRoot->GetFirstChild();
398   while (child) {
399     if ((child->IsElement() &&
400          DoesNotAffectDirectionOfAncestors(child->AsElement())) ||
401         child->GetAssignedSlot()) {
402       child = child->GetNextNonChildNode(aRoot);
403       continue;
404     }
405 
406     if (auto* slot = HTMLSlotElement::FromNode(child)) {
407       const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
408       for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
409         nsIContent* assignedNode = assignedNodes[i]->AsContent();
410         if (assignedNode->NodeType() == nsINode::TEXT_NODE) {
411           auto text = static_cast<nsTextNode*>(assignedNode);
412           if (assignedNode != aSkip) {
413             Directionality textNodeDir = GetDirectionFromText(text);
414             if (textNodeDir != eDir_NotSet) {
415               *aDirectionality = textNodeDir;
416               return text;
417             }
418           }
419         } else if (assignedNode->IsElement() &&
420                    !DoesNotAffectDirectionOfAncestors(
421                        assignedNode->AsElement())) {
422           nsTextNode* text = WalkDescendantsAndGetDirectionFromText(
423               assignedNode, aSkip, aDirectionality);
424           if (text) {
425             return text;
426           }
427         }
428       }
429     }
430 
431     if (child->NodeType() == nsINode::TEXT_NODE && child != aSkip) {
432       auto text = static_cast<nsTextNode*>(child);
433       Directionality textNodeDir = GetDirectionFromText(text);
434       if (textNodeDir != eDir_NotSet) {
435         *aDirectionality = textNodeDir;
436         return text;
437       }
438     }
439     child = child->GetNextNode(aRoot);
440   }
441 
442   return nullptr;
443 }
444 
445 /**
446  * Set the directionality of a node with dir=auto as defined in
447  * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality
448  *
449  * @param[in] changedNode If we call this method because the content of a text
450  *            node is about to change, pass in the changed node, so that we
451  *            know not to return it
452  * @return the text node containing the character that determined the direction
453  */
WalkDescendantsSetDirectionFromText(Element * aElement,bool aNotify,nsINode * aChangedNode=nullptr)454 static nsTextNode* WalkDescendantsSetDirectionFromText(
455     Element* aElement, bool aNotify, nsINode* aChangedNode = nullptr) {
456   MOZ_ASSERT(aElement, "Must have an element");
457   MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto");
458 
459   if (DoesNotParticipateInAutoDirection(aElement)) {
460     return nullptr;
461   }
462 
463   Directionality textNodeDir = eDir_NotSet;
464 
465   // Check the text in Shadow DOM.
466   if (ShadowRoot* shadowRoot = aElement->GetShadowRoot()) {
467     nsTextNode* text = WalkDescendantsAndGetDirectionFromText(
468         shadowRoot, aChangedNode, &textNodeDir);
469     if (text) {
470       aElement->SetDirectionality(textNodeDir, aNotify);
471       return text;
472     }
473   }
474 
475   // Check the text in light DOM.
476   nsTextNode* text = WalkDescendantsAndGetDirectionFromText(
477       aElement, aChangedNode, &textNodeDir);
478   if (text) {
479     aElement->SetDirectionality(textNodeDir, aNotify);
480     return text;
481   }
482 
483   // We walked all the descendants without finding a text node with strong
484   // directional characters. Set the directionality to LTR
485   aElement->SetDirectionality(eDir_LTR, aNotify);
486   return nullptr;
487 }
488 
489 class nsTextNodeDirectionalityMap {
nsTextNodeDirectionalityMapDtor(void * aObject,nsAtom * aPropertyName,void * aPropertyValue,void * aData)490   static void nsTextNodeDirectionalityMapDtor(void* aObject,
491                                               nsAtom* aPropertyName,
492                                               void* aPropertyValue,
493                                               void* aData) {
494     nsINode* textNode = static_cast<nsINode*>(aObject);
495     textNode->ClearHasTextNodeDirectionalityMap();
496 
497     nsTextNodeDirectionalityMap* map =
498         reinterpret_cast<nsTextNodeDirectionalityMap*>(aPropertyValue);
499     map->EnsureMapIsClear();
500     delete map;
501   }
502 
503  public:
nsTextNodeDirectionalityMap(nsINode * aTextNode)504   explicit nsTextNodeDirectionalityMap(nsINode* aTextNode)
505       : mElementToBeRemoved(nullptr) {
506     MOZ_ASSERT(aTextNode, "Null text node");
507     MOZ_COUNT_CTOR(nsTextNodeDirectionalityMap);
508     aTextNode->SetProperty(nsGkAtoms::textNodeDirectionalityMap, this,
509                            nsTextNodeDirectionalityMapDtor);
510     aTextNode->SetHasTextNodeDirectionalityMap();
511   }
512 
MOZ_COUNTED_DTOR(nsTextNodeDirectionalityMap)513   MOZ_COUNTED_DTOR(nsTextNodeDirectionalityMap)
514 
515   static void nsTextNodeDirectionalityMapPropertyDestructor(
516       void* aObject, nsAtom* aProperty, void* aPropertyValue, void* aData) {
517     nsTextNode* textNode = static_cast<nsTextNode*>(aPropertyValue);
518     nsTextNodeDirectionalityMap* map = GetDirectionalityMap(textNode);
519     if (map) {
520       map->RemoveEntryForProperty(static_cast<Element*>(aObject));
521     }
522     NS_RELEASE(textNode);
523   }
524 
AddEntry(nsTextNode * aTextNode,Element * aElement)525   void AddEntry(nsTextNode* aTextNode, Element* aElement) {
526     if (!mElements.Contains(aElement)) {
527       mElements.Put(aElement);
528       NS_ADDREF(aTextNode);
529       aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode,
530                             nsTextNodeDirectionalityMapPropertyDestructor);
531       aElement->SetHasDirAutoSet();
532     }
533   }
534 
RemoveEntry(nsTextNode * aTextNode,Element * aElement)535   void RemoveEntry(nsTextNode* aTextNode, Element* aElement) {
536     NS_ASSERTION(mElements.Contains(aElement),
537                  "element already removed from map");
538 
539     mElements.Remove(aElement);
540     aElement->ClearHasDirAutoSet();
541     aElement->RemoveProperty(nsGkAtoms::dirAutoSetBy);
542   }
543 
RemoveEntryForProperty(Element * aElement)544   void RemoveEntryForProperty(Element* aElement) {
545     if (mElementToBeRemoved != aElement) {
546       mElements.Remove(aElement);
547     }
548     aElement->ClearHasDirAutoSet();
549   }
550 
551  private:
552   nsCheapSet<nsPtrHashKey<Element>> mElements;
553   // Only used for comparison.
554   Element* mElementToBeRemoved;
555 
GetDirectionalityMap(nsINode * aTextNode)556   static nsTextNodeDirectionalityMap* GetDirectionalityMap(nsINode* aTextNode) {
557     MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE,
558                "Must be a text node");
559     nsTextNodeDirectionalityMap* map = nullptr;
560 
561     if (aTextNode->HasTextNodeDirectionalityMap()) {
562       map = static_cast<nsTextNodeDirectionalityMap*>(
563           aTextNode->GetProperty(nsGkAtoms::textNodeDirectionalityMap));
564     }
565 
566     return map;
567   }
568 
SetNodeDirection(nsPtrHashKey<Element> * aEntry,void * aDir)569   static nsCheapSetOperator SetNodeDirection(nsPtrHashKey<Element>* aEntry,
570                                              void* aDir) {
571     aEntry->GetKey()->SetDirectionality(
572         *reinterpret_cast<Directionality*>(aDir), true);
573     return OpNext;
574   }
575 
576   struct nsTextNodeDirectionalityMapAndElement {
577     nsTextNodeDirectionalityMap* mMap;
578     nsCOMPtr<nsINode> mNode;
579   };
580 
ResetNodeDirection(nsPtrHashKey<Element> * aEntry,void * aData)581   static nsCheapSetOperator ResetNodeDirection(nsPtrHashKey<Element>* aEntry,
582                                                void* aData) {
583     // run the downward propagation algorithm
584     // and remove the text node from the map
585     nsTextNodeDirectionalityMapAndElement* data =
586         static_cast<nsTextNodeDirectionalityMapAndElement*>(aData);
587     nsINode* oldTextNode = data->mNode;
588     Element* rootNode = aEntry->GetKey();
589     nsTextNode* newTextNode = nullptr;
590     if (rootNode->GetParentNode() && rootNode->HasDirAuto()) {
591       newTextNode =
592           WalkDescendantsSetDirectionFromText(rootNode, true, oldTextNode);
593     }
594 
595     AutoRestore<Element*> restore(data->mMap->mElementToBeRemoved);
596     data->mMap->mElementToBeRemoved = rootNode;
597     if (newTextNode) {
598       nsINode* oldDirAutoSetBy = static_cast<nsTextNode*>(
599           rootNode->GetProperty(nsGkAtoms::dirAutoSetBy));
600       if (oldDirAutoSetBy == newTextNode) {
601         // We're already registered.
602         return OpNext;
603       }
604       nsTextNodeDirectionalityMap::AddEntryToMap(newTextNode, rootNode);
605     } else {
606       rootNode->ClearHasDirAutoSet();
607       rootNode->RemoveProperty(nsGkAtoms::dirAutoSetBy);
608     }
609     return OpRemove;
610   }
611 
TakeEntries(nsPtrHashKey<Element> * aEntry,void * aData)612   static nsCheapSetOperator TakeEntries(nsPtrHashKey<Element>* aEntry,
613                                         void* aData) {
614     AutoTArray<Element*, 8>* entries =
615         static_cast<AutoTArray<Element*, 8>*>(aData);
616     entries->AppendElement(aEntry->GetKey());
617     return OpRemove;
618   }
619 
620  public:
UpdateAutoDirection(Directionality aDir)621   uint32_t UpdateAutoDirection(Directionality aDir) {
622     return mElements.EnumerateEntries(SetNodeDirection, &aDir);
623   }
624 
ResetAutoDirection(nsINode * aTextNode)625   void ResetAutoDirection(nsINode* aTextNode) {
626     nsTextNodeDirectionalityMapAndElement data = {this, aTextNode};
627     mElements.EnumerateEntries(ResetNodeDirection, &data);
628   }
629 
EnsureMapIsClear()630   void EnsureMapIsClear() {
631     AutoRestore<Element*> restore(mElementToBeRemoved);
632     AutoTArray<Element*, 8> entries;
633     mElements.EnumerateEntries(TakeEntries, &entries);
634     for (Element* el : entries) {
635       el->ClearHasDirAutoSet();
636       el->RemoveProperty(nsGkAtoms::dirAutoSetBy);
637     }
638   }
639 
RemoveElementFromMap(nsTextNode * aTextNode,Element * aElement)640   static void RemoveElementFromMap(nsTextNode* aTextNode, Element* aElement) {
641     if (aTextNode->HasTextNodeDirectionalityMap()) {
642       GetDirectionalityMap(aTextNode)->RemoveEntry(aTextNode, aElement);
643     }
644   }
645 
AddEntryToMap(nsTextNode * aTextNode,Element * aElement)646   static void AddEntryToMap(nsTextNode* aTextNode, Element* aElement) {
647     nsTextNodeDirectionalityMap* map = GetDirectionalityMap(aTextNode);
648     if (!map) {
649       map = new nsTextNodeDirectionalityMap(aTextNode);
650     }
651 
652     map->AddEntry(aTextNode, aElement);
653   }
654 
UpdateTextNodeDirection(nsINode * aTextNode,Directionality aDir)655   static uint32_t UpdateTextNodeDirection(nsINode* aTextNode,
656                                           Directionality aDir) {
657     MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
658                "Map missing in UpdateTextNodeDirection");
659     return GetDirectionalityMap(aTextNode)->UpdateAutoDirection(aDir);
660   }
661 
ResetTextNodeDirection(nsTextNode * aTextNode,nsTextNode * aChangedTextNode)662   static void ResetTextNodeDirection(nsTextNode* aTextNode,
663                                      nsTextNode* aChangedTextNode) {
664     MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
665                "Map missing in ResetTextNodeDirection");
666     RefPtr<nsTextNode> textNode = aTextNode;
667     GetDirectionalityMap(textNode)->ResetAutoDirection(aChangedTextNode);
668   }
669 
EnsureMapIsClearFor(nsINode * aTextNode)670   static void EnsureMapIsClearFor(nsINode* aTextNode) {
671     if (aTextNode->HasTextNodeDirectionalityMap()) {
672       GetDirectionalityMap(aTextNode)->EnsureMapIsClear();
673     }
674   }
675 };
676 
RecomputeDirectionality(Element * aElement,bool aNotify)677 Directionality RecomputeDirectionality(Element* aElement, bool aNotify) {
678   MOZ_ASSERT(!aElement->HasDirAuto(),
679              "RecomputeDirectionality called with dir=auto");
680 
681   if (aElement->HasValidDir()) {
682     return aElement->GetDirectionality();
683   }
684 
685   Directionality dir = eDir_LTR;
686   if (nsIContent* parent = GetParentOrHostOrSlot(aElement)) {
687     if (ShadowRoot* shadow = ShadowRoot::FromNode(parent)) {
688       parent = shadow->GetHost();
689     }
690 
691     if (parent && parent->IsElement()) {
692       // If the node doesn't have an explicit dir attribute with a valid value,
693       // the directionality is the same as the parent element (but don't
694       // propagate the parent directionality if it isn't set yet).
695       Directionality parentDir = parent->AsElement()->GetDirectionality();
696       if (parentDir != eDir_NotSet) {
697         dir = parentDir;
698       }
699     }
700   }
701 
702   aElement->SetDirectionality(dir, aNotify);
703   return dir;
704 }
705 
SetDirectionalityOnDescendantsInternal(nsINode * aNode,Directionality aDir,bool aNotify)706 static void SetDirectionalityOnDescendantsInternal(nsINode* aNode,
707                                                    Directionality aDir,
708                                                    bool aNotify) {
709   if (Element* element = Element::FromNode(aNode)) {
710     if (ShadowRoot* shadow = element->GetShadowRoot()) {
711       SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify);
712     }
713   }
714 
715   for (nsIContent* child = aNode->GetFirstChild(); child;) {
716     if (!child->IsElement()) {
717       child = child->GetNextNode(aNode);
718       continue;
719     }
720 
721     Element* element = child->AsElement();
722     if (element->HasValidDir() || element->HasDirAuto() ||
723         element->GetAssignedSlot()) {
724       child = child->GetNextNonChildNode(aNode);
725       continue;
726     }
727     if (ShadowRoot* shadow = element->GetShadowRoot()) {
728       SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify);
729     }
730 
731     if (auto* slot = HTMLSlotElement::FromNode(child)) {
732       const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
733       for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
734         nsINode* node = assignedNodes[i];
735         Element* assignedElement =
736             node->IsElement() ? node->AsElement() : nullptr;
737         if (assignedElement && !assignedElement->HasValidDir() &&
738             !assignedElement->HasDirAuto()) {
739           assignedElement->SetDirectionality(aDir, aNotify);
740           SetDirectionalityOnDescendantsInternal(assignedElement, aDir,
741                                                  aNotify);
742         }
743       }
744     }
745 
746     element->SetDirectionality(aDir, aNotify);
747 
748     child = child->GetNextNode(aNode);
749   }
750 }
751 
752 // We want the public version of this only to acc
SetDirectionalityOnDescendants(Element * aElement,Directionality aDir,bool aNotify)753 void SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
754                                     bool aNotify) {
755   return SetDirectionalityOnDescendantsInternal(aElement, aDir, aNotify);
756 }
757 
ResetAutoDirection(Element * aElement,bool aNotify)758 static void ResetAutoDirection(Element* aElement, bool aNotify) {
759   if (aElement->HasDirAutoSet()) {
760     // If the parent has the DirAutoSet flag, its direction is determined by
761     // some text node descendant.
762     // Remove it from the map and reset its direction by the downward
763     // propagation algorithm
764     nsTextNode* setByNode = static_cast<nsTextNode*>(
765         aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
766     if (setByNode) {
767       nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
768     }
769   }
770 
771   if (aElement->HasDirAuto()) {
772     nsTextNode* setByNode =
773         WalkDescendantsSetDirectionFromText(aElement, aNotify);
774     if (setByNode) {
775       nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, aElement);
776     }
777     SetDirectionalityOnDescendants(aElement, aElement->GetDirectionality(),
778                                    aNotify);
779   }
780 }
781 
782 /**
783  * Walk the parent chain of a text node whose dir attribute has been removed and
784  * reset the direction of any of its ancestors which have dir=auto and whose
785  * directionality is determined by a text node descendant.
786  */
WalkAncestorsResetAutoDirection(Element * aElement,bool aNotify)787 void WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify) {
788   nsTextNode* setByNode;
789   nsIContent* parent = GetParentOrHostOrSlot(aElement);
790   while (parent && parent->NodeOrAncestorHasDirAuto()) {
791     if (!parent->IsElement()) {
792       parent = GetParentOrHostOrSlot(parent);
793       continue;
794     }
795 
796     Element* parentElement = parent->AsElement();
797     if (parent->HasDirAutoSet()) {
798       // If the parent has the DirAutoSet flag, its direction is determined by
799       // some text node descendant.
800       // Remove it from the map and reset its direction by the downward
801       // propagation algorithm
802       setByNode = static_cast<nsTextNode*>(
803           parent->GetProperty(nsGkAtoms::dirAutoSetBy));
804       if (setByNode) {
805         nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode,
806                                                           parentElement);
807       }
808     }
809     if (parentElement->HasDirAuto()) {
810       setByNode = WalkDescendantsSetDirectionFromText(parentElement, aNotify);
811       if (setByNode) {
812         nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parentElement);
813       }
814       SetDirectionalityOnDescendants(
815           parentElement, parentElement->GetDirectionality(), aNotify);
816       break;
817     }
818     parent = GetParentOrHostOrSlot(parent);
819   }
820 }
821 
RecomputeSlottedNodeDirection(HTMLSlotElement & aSlot,nsINode & aNode)822 static void RecomputeSlottedNodeDirection(HTMLSlotElement& aSlot,
823                                           nsINode& aNode) {
824   auto* assignedElement = Element::FromNode(aNode);
825   if (!assignedElement) {
826     return;
827   }
828 
829   if (assignedElement->HasValidDir() || assignedElement->HasDirAuto()) {
830     return;
831   }
832 
833   // Try to optimize out state changes when possible.
834   if (assignedElement->GetDirectionality() == aSlot.GetDirectionality()) {
835     return;
836   }
837 
838   assignedElement->SetDirectionality(aSlot.GetDirectionality(), true);
839   SetDirectionalityOnDescendantsInternal(assignedElement,
840                                          aSlot.GetDirectionality(), true);
841 }
842 
SlotAssignedNodeChanged(HTMLSlotElement * aSlot,nsIContent & aAssignedNode)843 void SlotAssignedNodeChanged(HTMLSlotElement* aSlot,
844                              nsIContent& aAssignedNode) {
845   if (!aSlot) {
846     return;
847   }
848 
849   if (aSlot->NodeOrAncestorHasDirAuto()) {
850     // The directionality of the assigned node may impact the directionality of
851     // the slot. So recompute everything.
852     SlotStateChanged(aSlot, /* aAllAssignedNodesChanged = */ false);
853   }
854 
855   if (aAssignedNode.GetAssignedSlot() == aSlot) {
856     RecomputeSlottedNodeDirection(*aSlot, aAssignedNode);
857   }
858 }
859 
SlotStateChanged(HTMLSlotElement * aSlot,bool aAllAssignedNodesChanged)860 void SlotStateChanged(HTMLSlotElement* aSlot, bool aAllAssignedNodesChanged) {
861   if (!aSlot) {
862     return;
863   }
864 
865   Directionality oldDir = aSlot->GetDirectionality();
866 
867   if (aSlot->HasDirAuto()) {
868     ResetAutoDirection(aSlot, true);
869   }
870 
871   if (aSlot->NodeOrAncestorHasDirAuto()) {
872     WalkAncestorsResetAutoDirection(aSlot, true);
873   }
874 
875   if (aAllAssignedNodesChanged || oldDir != aSlot->GetDirectionality()) {
876     for (nsINode* node : aSlot->AssignedNodes()) {
877       RecomputeSlottedNodeDirection(*aSlot, *node);
878     }
879   }
880 }
881 
WalkDescendantsResetAutoDirection(Element * aElement)882 void WalkDescendantsResetAutoDirection(Element* aElement) {
883   nsIContent* child = aElement->GetFirstChild();
884   while (child) {
885     if (child->IsElement() && child->AsElement()->HasDirAuto()) {
886       child = child->GetNextNonChildNode(aElement);
887       continue;
888     }
889 
890     if (child->NodeType() == nsINode::TEXT_NODE &&
891         child->HasTextNodeDirectionalityMap()) {
892       nsTextNodeDirectionalityMap::ResetTextNodeDirection(
893           static_cast<nsTextNode*>(child), nullptr);
894       // Don't call nsTextNodeDirectionalityMap::EnsureMapIsClearFor(child)
895       // since ResetTextNodeDirection may have kept elements in child's
896       // DirectionalityMap.
897     }
898     child = child->GetNextNode(aElement);
899   }
900 }
901 
902 static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot);
903 
MaybeSetAncestorHasDirAutoOnShadowDOM(nsINode * aNode)904 static void MaybeSetAncestorHasDirAutoOnShadowDOM(nsINode* aNode) {
905   if (aNode->IsElement()) {
906     if (ShadowRoot* sr = aNode->AsElement()->GetShadowRoot()) {
907       sr->SetAncestorHasDirAuto();
908       SetAncestorHasDirAutoOnDescendants(sr);
909     }
910   }
911 }
912 
SetAncestorHasDirAutoOnDescendants(nsINode * aRoot)913 static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot) {
914   MaybeSetAncestorHasDirAutoOnShadowDOM(aRoot);
915 
916   nsIContent* child = aRoot->GetFirstChild();
917   while (child) {
918     if (child->IsElement() &&
919         DoesNotAffectDirectionOfAncestors(child->AsElement())) {
920       child = child->GetNextNonChildNode(aRoot);
921       continue;
922     }
923 
924     // If the child is assigned to a slot, it should inherit the state from
925     // that.
926     if (!child->GetAssignedSlot()) {
927       MaybeSetAncestorHasDirAutoOnShadowDOM(child);
928       child->SetAncestorHasDirAuto();
929       if (auto* slot = HTMLSlotElement::FromNode(child)) {
930         const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
931         for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
932           assignedNodes[i]->SetAncestorHasDirAuto();
933           SetAncestorHasDirAutoOnDescendants(assignedNodes[i]);
934         }
935       }
936     }
937     child = child->GetNextNode(aRoot);
938   }
939 }
940 
WalkDescendantsSetDirAuto(Element * aElement,bool aNotify)941 void WalkDescendantsSetDirAuto(Element* aElement, bool aNotify) {
942   // Only test for DoesNotParticipateInAutoDirection -- in other words, if
943   // aElement is a <bdi> which is having its dir attribute set to auto (or
944   // removed or set to an invalid value, which are equivalent to dir=auto for
945   // <bdi>, we *do* want to set AncestorHasDirAuto on its descendants, unlike
946   // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a <bdi>
947   // being bound to an existing node with dir=auto.
948   if (!DoesNotParticipateInAutoDirection(aElement) &&
949       !aElement->AncestorHasDirAuto()) {
950     SetAncestorHasDirAutoOnDescendants(aElement);
951   }
952 
953   nsTextNode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
954   if (textNode) {
955     nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement);
956   }
957 }
958 
WalkDescendantsClearAncestorDirAuto(nsIContent * aContent)959 void WalkDescendantsClearAncestorDirAuto(nsIContent* aContent) {
960   if (aContent->IsElement()) {
961     if (ShadowRoot* shadowRoot = aContent->AsElement()->GetShadowRoot()) {
962       shadowRoot->ClearAncestorHasDirAuto();
963       WalkDescendantsClearAncestorDirAuto(shadowRoot);
964     }
965   }
966 
967   nsIContent* child = aContent->GetFirstChild();
968   while (child) {
969     if (child->GetAssignedSlot()) {
970       // If the child node is assigned to a slot, nodes state is inherited from
971       // the slot, not from element's parent.
972       child = child->GetNextNonChildNode(aContent);
973       continue;
974     }
975     if (child->IsElement()) {
976       if (child->AsElement()->HasDirAuto()) {
977         child = child->GetNextNonChildNode(aContent);
978         continue;
979       }
980 
981       if (auto* slot = HTMLSlotElement::FromNode(child)) {
982         const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
983         for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
984           if (assignedNodes[i]->IsElement()) {
985             Element* slottedElement = assignedNodes[i]->AsElement();
986             if (slottedElement->HasDirAuto()) {
987               continue;
988             }
989           }
990 
991           nsIContent* content = assignedNodes[i]->AsContent();
992           content->ClearAncestorHasDirAuto();
993           WalkDescendantsClearAncestorDirAuto(content);
994         }
995       }
996     }
997 
998     child->ClearAncestorHasDirAuto();
999     child = child->GetNextNode(aContent);
1000   }
1001 }
1002 
SetAncestorDirectionIfAuto(nsTextNode * aTextNode,Directionality aDir,bool aNotify=true)1003 void SetAncestorDirectionIfAuto(nsTextNode* aTextNode, Directionality aDir,
1004                                 bool aNotify = true) {
1005   MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE,
1006              "Must be a text node");
1007 
1008   bool crossedShadowBoundary = false;
1009   nsIContent* parent = GetParentOrHostOrSlot(aTextNode, &crossedShadowBoundary);
1010   while (parent && parent->NodeOrAncestorHasDirAuto()) {
1011     if (!parent->IsElement()) {
1012       parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary);
1013       continue;
1014     }
1015 
1016     Element* parentElement = parent->AsElement();
1017     if (DoesNotParticipateInAutoDirection(parentElement) ||
1018         parentElement->HasFixedDir()) {
1019       break;
1020     }
1021 
1022     if (parentElement->HasDirAuto()) {
1023       bool resetDirection = false;
1024       nsTextNode* directionWasSetByTextNode = static_cast<nsTextNode*>(
1025           parent->GetProperty(nsGkAtoms::dirAutoSetBy));
1026 
1027       if (!parent->HasDirAutoSet()) {
1028         // Fast path if parent's direction is not yet set by any descendant
1029         MOZ_ASSERT(!directionWasSetByTextNode,
1030                    "dirAutoSetBy property should be null");
1031         resetDirection = true;
1032       } else {
1033         // If parent's direction is already set, we need to know if
1034         // aTextNode is before or after the text node that had set it.
1035         // We will walk parent's descendants in tree order starting from
1036         // aTextNode to optimize for the most common case where text nodes are
1037         // being appended to tree.
1038         if (!directionWasSetByTextNode) {
1039           resetDirection = true;
1040         } else if (directionWasSetByTextNode != aTextNode) {
1041           if (crossedShadowBoundary || AncestorChainCrossesShadowBoundary(
1042                                            directionWasSetByTextNode, parent)) {
1043             // Need to take the slow path when the path from either the old or
1044             // new text node to the dir=auto element crosses shadow boundary.
1045             ResetAutoDirection(parentElement, aNotify);
1046             return;
1047           }
1048 
1049           nsIContent* child = aTextNode->GetNextNode(parent);
1050           while (child) {
1051             if (child->IsElement() &&
1052                 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
1053               child = child->GetNextNonChildNode(parent);
1054               continue;
1055             }
1056 
1057             if (child == directionWasSetByTextNode) {
1058               // we found the node that set the element's direction after our
1059               // text node, so we need to reset the direction
1060               resetDirection = true;
1061               break;
1062             }
1063 
1064             child = child->GetNextNode(parent);
1065           }
1066         }
1067       }
1068 
1069       if (resetDirection) {
1070         if (directionWasSetByTextNode) {
1071           nsTextNodeDirectionalityMap::RemoveElementFromMap(
1072               directionWasSetByTextNode, parentElement);
1073         }
1074         parentElement->SetDirectionality(aDir, aNotify);
1075         nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parentElement);
1076         SetDirectionalityOnDescendants(parentElement, aDir, aNotify);
1077       }
1078 
1079       // Since we found an element with dir=auto, we can stop walking the
1080       // parent chain: none of its ancestors will have their direction set by
1081       // any of its descendants.
1082       return;
1083     }
1084     parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary);
1085   }
1086 }
1087 
TextNodeWillChangeDirection(nsTextNode * aTextNode,Directionality * aOldDir,uint32_t aOffset)1088 bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir,
1089                                  uint32_t aOffset) {
1090   if (!NodeAffectsDirAutoAncestor(aTextNode)) {
1091     nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
1092     return false;
1093   }
1094 
1095   uint32_t firstStrong;
1096   *aOldDir = GetDirectionFromText(aTextNode, &firstStrong);
1097   return (aOffset <= firstStrong);
1098 }
1099 
TextNodeChangedDirection(nsTextNode * aTextNode,Directionality aOldDir,bool aNotify)1100 void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir,
1101                               bool aNotify) {
1102   Directionality newDir = GetDirectionFromText(aTextNode);
1103   if (newDir == eDir_NotSet) {
1104     if (aOldDir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
1105       // This node used to have a strong directional character but no
1106       // longer does. ResetTextNodeDirection() will re-resolve the
1107       // directionality of any elements whose directionality was
1108       // determined by this node.
1109       nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
1110     }
1111   } else {
1112     // This node has a strong directional character. If it has a
1113     // TextNodeDirectionalityMap property, it already determines the
1114     // directionality of some element(s), so call UpdateTextNodeDirection to
1115     // reresolve their directionality. If it has no map, or if
1116     // UpdateTextNodeDirection returns zero, indicating that the map is
1117     // empty, call SetAncestorDirectionIfAuto to find ancestor elements
1118     // which should have their directionality determined by this node.
1119     if (aTextNode->HasTextNodeDirectionalityMap() &&
1120         nsTextNodeDirectionalityMap::UpdateTextNodeDirection(aTextNode,
1121                                                              newDir)) {
1122       return;
1123     }
1124     SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify);
1125   }
1126 }
1127 
SetDirectionFromNewTextNode(nsTextNode * aTextNode)1128 void SetDirectionFromNewTextNode(nsTextNode* aTextNode) {
1129   if (!NodeAffectsDirAutoAncestor(aTextNode)) {
1130     return;
1131   }
1132 
1133   nsIContent* parent = GetParentOrHostOrSlot(aTextNode);
1134   if (parent && parent->NodeOrAncestorHasDirAuto()) {
1135     aTextNode->SetAncestorHasDirAuto();
1136   }
1137 
1138   Directionality dir = GetDirectionFromText(aTextNode);
1139   if (dir != eDir_NotSet) {
1140     SetAncestorDirectionIfAuto(aTextNode, dir);
1141   }
1142 }
1143 
ResetDirectionSetByTextNode(nsTextNode * aTextNode)1144 void ResetDirectionSetByTextNode(nsTextNode* aTextNode) {
1145   if (!NodeAffectsDirAutoAncestor(aTextNode)) {
1146     nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
1147     return;
1148   }
1149 
1150   Directionality dir = GetDirectionFromText(aTextNode);
1151   if (dir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
1152     nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
1153   }
1154 }
1155 
SetDirectionalityFromValue(Element * aElement,const nsAString & value,bool aNotify)1156 void SetDirectionalityFromValue(Element* aElement, const nsAString& value,
1157                                 bool aNotify) {
1158   Directionality dir =
1159       GetDirectionFromText(value.BeginReading(), value.Length());
1160   if (dir == eDir_NotSet) {
1161     dir = eDir_LTR;
1162   }
1163 
1164   aElement->SetDirectionality(dir, aNotify);
1165 }
1166 
OnSetDirAttr(Element * aElement,const nsAttrValue * aNewValue,bool hadValidDir,bool hadDirAuto,bool aNotify)1167 void OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue,
1168                   bool hadValidDir, bool hadDirAuto, bool aNotify) {
1169   if (aElement->IsHTMLElement(nsGkAtoms::input)) {
1170     return;
1171   }
1172 
1173   if (aElement->AncestorHasDirAuto()) {
1174     if (!hadValidDir) {
1175       // The element is a descendant of an element with dir = auto, is
1176       // having its dir attribute set, and previously didn't have a valid dir
1177       // attribute.
1178       // Check whether any of its text node descendants determine the
1179       // direction of any of its ancestors, and redetermine their direction
1180       WalkDescendantsResetAutoDirection(aElement);
1181     } else if (!aElement->HasValidDir()) {
1182       // The element is a descendant of an element with dir = auto and is
1183       // having its dir attribute removed or set to an invalid value.
1184       // Reset the direction of any of its ancestors whose direction is
1185       // determined by a text node descendant
1186       WalkAncestorsResetAutoDirection(aElement, aNotify);
1187     }
1188   } else if (hadDirAuto && !aElement->HasDirAuto()) {
1189     // The element isn't a descendant of an element with dir = auto, and is
1190     // having its dir attribute set to something other than auto.
1191     // Walk the descendant tree and clear the AncestorHasDirAuto flag.
1192     //
1193     // N.B: For elements other than <bdi> it would be enough to test that the
1194     //      current value of dir was "auto" in BeforeSetAttr to know that we
1195     //      were unsetting dir="auto". For <bdi> things are more complicated,
1196     //      since it behaves like dir="auto" whenever the dir attribute is
1197     //      empty or invalid, so we would have to check whether the old value
1198     //      was not either "ltr" or "rtl", and the new value was either "ltr"
1199     //      or "rtl". Element::HasDirAuto() encapsulates all that, so doing it
1200     //      here is simpler.
1201     WalkDescendantsClearAncestorDirAuto(aElement);
1202   }
1203 
1204   if (aElement->HasDirAuto()) {
1205     WalkDescendantsSetDirAuto(aElement, aNotify);
1206   } else {
1207     if (aElement->HasDirAutoSet()) {
1208       nsTextNode* setByNode = static_cast<nsTextNode*>(
1209           aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
1210       nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
1211     }
1212     SetDirectionalityOnDescendants(
1213         aElement, RecomputeDirectionality(aElement, aNotify), aNotify);
1214   }
1215 }
1216 
SetDirOnBind(Element * aElement,nsIContent * aParent)1217 void SetDirOnBind(Element* aElement, nsIContent* aParent) {
1218   // Set the AncestorHasDirAuto flag, unless this element shouldn't affect
1219   // ancestors that have dir=auto
1220   if (!DoesNotParticipateInAutoDirection(aElement) &&
1221       !aElement->IsHTMLElement(nsGkAtoms::bdi) && aParent &&
1222       aParent->NodeOrAncestorHasDirAuto()) {
1223     aElement->SetAncestorHasDirAuto();
1224 
1225     SetAncestorHasDirAutoOnDescendants(aElement);
1226 
1227     if (aElement->GetFirstChild() || aElement->GetShadowRoot()) {
1228       // We may also need to reset the direction of an ancestor with dir=auto
1229       WalkAncestorsResetAutoDirection(aElement, true);
1230     }
1231   }
1232 
1233   if (!aElement->HasDirAuto()) {
1234     // if the element doesn't have dir=auto, set its own directionality from
1235     // the dir attribute or by inheriting from its ancestors.
1236     RecomputeDirectionality(aElement, false);
1237   }
1238 }
1239 
ResetDir(Element * aElement)1240 void ResetDir(Element* aElement) {
1241   if (aElement->HasDirAutoSet()) {
1242     nsTextNode* setByNode = static_cast<nsTextNode*>(
1243         aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
1244     nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
1245   }
1246 
1247   if (!aElement->HasDirAuto()) {
1248     RecomputeDirectionality(aElement, false);
1249   }
1250 }
1251 
1252 }  // end namespace mozilla
1253