Bug 1842773 - Part 5: Add ArrayBuffer.prototype.{maxByteLength,resizable} getters...
[gecko.git] / dom / base / DirectionalityUtils.cpp
blob46229cfbd36fc19822ab4269ef2c4584810d898d
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/. */
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
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.
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.
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.
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.
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.
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.
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).
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.
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
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.
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, CharacterData::SetTextInternal
204 gets called.
207 #include "mozilla/dom/DirectionalityUtils.h"
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/HTMLInputElement.h"
217 #include "mozilla/dom/HTMLSlotElement.h"
218 #include "mozilla/dom/ShadowRoot.h"
219 #include "mozilla/intl/UnicodeProperties.h"
220 #include "nsUnicodeProperties.h"
221 #include "nsTextFragment.h"
222 #include "nsAttrValue.h"
223 #include "nsTextNode.h"
224 #include "nsCheapSets.h"
226 namespace mozilla {
228 using mozilla::dom::Element;
229 using mozilla::dom::HTMLInputElement;
230 using mozilla::dom::HTMLSlotElement;
231 using mozilla::dom::ShadowRoot;
233 static nsIContent* GetParentOrHostOrSlot(
234 const nsIContent* aContent, bool* aCrossedShadowBoundary = nullptr) {
235 if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) {
236 if (aCrossedShadowBoundary) {
237 *aCrossedShadowBoundary = true;
239 return slot;
242 nsIContent* parent = aContent->GetParent();
243 if (parent) {
244 return parent;
247 const ShadowRoot* sr = ShadowRoot::FromNode(aContent);
248 if (sr) {
249 if (aCrossedShadowBoundary) {
250 *aCrossedShadowBoundary = true;
252 return sr->Host();
255 return nullptr;
258 static bool AncestorChainCrossesShadowBoundary(nsIContent* aDescendant,
259 nsIContent* aAncestor) {
260 bool crossedShadowBoundary = false;
261 nsIContent* content = aDescendant;
262 while (content && content != aAncestor) {
263 content = GetParentOrHostOrSlot(content, &crossedShadowBoundary);
264 if (crossedShadowBoundary) {
265 return true;
269 return false;
273 * Returns true if aElement is one of the elements whose text content should not
274 * affect its own direction, nor the direction of ancestors with dir=auto.
276 * Note that this does not include <bdi>, whose content does affect its own
277 * direction when it has dir=auto (which it has by default), so one needs to
278 * test for it separately, e.g. with DoesNotAffectDirectionOfAncestors.
279 * It *does* include textarea, because even if a textarea has dir=auto, it has
280 * unicode-bidi: plaintext and is handled automatically in bidi resolution.
281 * It also includes `input`, because it takes the `dir` value from its value
282 * attribute, instead of the child nodes.
284 static bool DoesNotParticipateInAutoDirection(const nsIContent* aContent) {
285 mozilla::dom::NodeInfo* nodeInfo = aContent->NodeInfo();
286 return ((!aContent->IsHTMLElement() || nodeInfo->Equals(nsGkAtoms::script) ||
287 nodeInfo->Equals(nsGkAtoms::style) ||
288 nodeInfo->Equals(nsGkAtoms::input) ||
289 nodeInfo->Equals(nsGkAtoms::textarea) ||
290 aContent->IsInNativeAnonymousSubtree())) &&
291 !aContent->IsShadowRoot();
295 * Returns true if aElement is one of the element whose text content should not
296 * affect the direction of ancestors with dir=auto (though it may affect its own
297 * direction, e.g. <bdi>)
299 static bool DoesNotAffectDirectionOfAncestors(const Element* aElement) {
300 return (DoesNotParticipateInAutoDirection(aElement) ||
301 aElement->IsHTMLElement(nsGkAtoms::bdi) || aElement->HasFixedDir());
305 * Returns the directionality of a Unicode character
307 static Directionality GetDirectionFromChar(uint32_t ch) {
308 switch (intl::UnicodeProperties::GetBidiClass(ch)) {
309 case intl::BidiClass::RightToLeft:
310 case intl::BidiClass::RightToLeftArabic:
311 return Directionality::Rtl;
313 case intl::BidiClass::LeftToRight:
314 return Directionality::Ltr;
316 default:
317 return Directionality::Unset;
321 inline static bool NodeAffectsDirAutoAncestor(nsIContent* aTextNode) {
322 nsIContent* parent = GetParentOrHostOrSlot(aTextNode);
323 return (parent && !DoesNotParticipateInAutoDirection(parent) &&
324 parent->NodeOrAncestorHasDirAuto() &&
325 !aTextNode->IsInNativeAnonymousSubtree());
328 Directionality GetDirectionFromText(const char16_t* aText,
329 const uint32_t aLength,
330 uint32_t* aFirstStrong) {
331 const char16_t* start = aText;
332 const char16_t* end = aText + aLength;
334 while (start < end) {
335 uint32_t current = start - aText;
336 uint32_t ch = *start++;
338 if (start < end && NS_IS_SURROGATE_PAIR(ch, *start)) {
339 ch = SURROGATE_TO_UCS4(ch, *start++);
340 current++;
343 // Just ignore lone surrogates
344 if (!IS_SURROGATE(ch)) {
345 Directionality dir = GetDirectionFromChar(ch);
346 if (dir != Directionality::Unset) {
347 if (aFirstStrong) {
348 *aFirstStrong = current;
350 return dir;
355 if (aFirstStrong) {
356 *aFirstStrong = UINT32_MAX;
358 return Directionality::Unset;
361 static Directionality GetDirectionFromText(const char* aText,
362 const uint32_t aLength,
363 uint32_t* aFirstStrong = nullptr) {
364 const char* start = aText;
365 const char* end = aText + aLength;
367 while (start < end) {
368 uint32_t current = start - aText;
369 unsigned char ch = (unsigned char)*start++;
371 Directionality dir = GetDirectionFromChar(ch);
372 if (dir != Directionality::Unset) {
373 if (aFirstStrong) {
374 *aFirstStrong = current;
376 return dir;
380 if (aFirstStrong) {
381 *aFirstStrong = UINT32_MAX;
383 return Directionality::Unset;
386 static Directionality GetDirectionFromText(const mozilla::dom::Text* aTextNode,
387 uint32_t* aFirstStrong = nullptr) {
388 const nsTextFragment* frag = &aTextNode->TextFragment();
389 if (frag->Is2b()) {
390 return GetDirectionFromText(frag->Get2b(), frag->GetLength(), aFirstStrong);
393 return GetDirectionFromText(frag->Get1b(), frag->GetLength(), aFirstStrong);
396 static nsTextNode* WalkDescendantsAndGetDirectionFromText(
397 nsINode* aRoot, nsINode* aSkip, Directionality* aDirectionality) {
398 nsIContent* child = aRoot->GetFirstChild();
399 while (child) {
400 if ((child->IsElement() &&
401 DoesNotAffectDirectionOfAncestors(child->AsElement())) ||
402 child->GetAssignedSlot()) {
403 child = child->GetNextNonChildNode(aRoot);
404 continue;
407 if (auto* slot = HTMLSlotElement::FromNode(child)) {
408 const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
409 for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
410 nsIContent* assignedNode = assignedNodes[i]->AsContent();
411 if (assignedNode->NodeType() == nsINode::TEXT_NODE) {
412 auto text = static_cast<nsTextNode*>(assignedNode);
413 if (assignedNode != aSkip) {
414 Directionality textNodeDir = GetDirectionFromText(text);
415 if (textNodeDir != Directionality::Unset) {
416 *aDirectionality = textNodeDir;
417 return text;
420 } else if (assignedNode->IsElement() &&
421 !DoesNotAffectDirectionOfAncestors(
422 assignedNode->AsElement())) {
423 nsTextNode* text = WalkDescendantsAndGetDirectionFromText(
424 assignedNode, aSkip, aDirectionality);
425 if (text) {
426 return text;
432 if (child->NodeType() == nsINode::TEXT_NODE && child != aSkip) {
433 auto text = static_cast<nsTextNode*>(child);
434 Directionality textNodeDir = GetDirectionFromText(text);
435 if (textNodeDir != Directionality::Unset) {
436 *aDirectionality = textNodeDir;
437 return text;
440 child = child->GetNextNode(aRoot);
443 return nullptr;
447 * Set the directionality of a node with dir=auto as defined in
448 * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality
450 * @param[in] changedNode If we call this method because the content of a text
451 * node is about to change, pass in the changed node, so that we
452 * know not to return it
453 * @return the text node containing the character that determined the direction
455 static nsTextNode* WalkDescendantsSetDirectionFromText(
456 Element* aElement, bool aNotify, nsINode* aChangedNode = nullptr) {
457 MOZ_ASSERT(aElement, "Must have an element");
458 MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto");
460 if (DoesNotParticipateInAutoDirection(aElement)) {
461 return nullptr;
464 Directionality textNodeDir = Directionality::Unset;
466 // Check the text in Shadow DOM.
467 if (ShadowRoot* shadowRoot = aElement->GetShadowRoot()) {
468 nsTextNode* text = WalkDescendantsAndGetDirectionFromText(
469 shadowRoot, aChangedNode, &textNodeDir);
470 if (text) {
471 aElement->SetDirectionality(textNodeDir, aNotify);
472 return text;
476 // Check the text in light DOM.
477 nsTextNode* text = WalkDescendantsAndGetDirectionFromText(
478 aElement, aChangedNode, &textNodeDir);
479 if (text) {
480 aElement->SetDirectionality(textNodeDir, aNotify);
481 return text;
484 // We walked all the descendants without finding a text node with strong
485 // directional characters. Set the directionality to Ltr
486 aElement->SetDirectionality(Directionality::Ltr, aNotify);
487 return nullptr;
490 class nsTextNodeDirectionalityMap {
491 static void nsTextNodeDirectionalityMapDtor(void* aObject,
492 nsAtom* aPropertyName,
493 void* aPropertyValue,
494 void* aData) {
495 nsINode* textNode = static_cast<nsINode*>(aObject);
496 textNode->ClearHasTextNodeDirectionalityMap();
498 nsTextNodeDirectionalityMap* map =
499 reinterpret_cast<nsTextNodeDirectionalityMap*>(aPropertyValue);
500 map->EnsureMapIsClear();
501 delete map;
504 public:
505 explicit nsTextNodeDirectionalityMap(nsINode* aTextNode)
506 : mElementToBeRemoved(nullptr) {
507 MOZ_ASSERT(aTextNode, "Null text node");
508 MOZ_COUNT_CTOR(nsTextNodeDirectionalityMap);
509 aTextNode->SetProperty(nsGkAtoms::textNodeDirectionalityMap, this,
510 nsTextNodeDirectionalityMapDtor);
511 aTextNode->SetHasTextNodeDirectionalityMap();
514 MOZ_COUNTED_DTOR(nsTextNodeDirectionalityMap)
516 static void nsTextNodeDirectionalityMapPropertyDestructor(
517 void* aObject, nsAtom* aProperty, void* aPropertyValue, void* aData) {
518 nsTextNode* textNode = static_cast<nsTextNode*>(aPropertyValue);
519 nsTextNodeDirectionalityMap* map = GetDirectionalityMap(textNode);
520 if (map) {
521 map->RemoveEntryForProperty(static_cast<Element*>(aObject));
523 NS_RELEASE(textNode);
526 void AddEntry(nsTextNode* aTextNode, Element* aElement) {
527 if (!mElements.Contains(aElement)) {
528 mElements.Put(aElement);
529 NS_ADDREF(aTextNode);
530 aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode,
531 nsTextNodeDirectionalityMapPropertyDestructor);
532 aElement->SetHasDirAutoSet();
536 void RemoveEntry(nsTextNode* aTextNode, Element* aElement) {
537 NS_ASSERTION(mElements.Contains(aElement),
538 "element already removed from map");
540 mElements.Remove(aElement);
541 aElement->ClearHasDirAutoSet();
542 aElement->RemoveProperty(nsGkAtoms::dirAutoSetBy);
545 void RemoveEntryForProperty(Element* aElement) {
546 if (mElementToBeRemoved != aElement) {
547 mElements.Remove(aElement);
549 aElement->ClearHasDirAutoSet();
552 private:
553 nsCheapSet<nsPtrHashKey<Element>> mElements;
554 // Only used for comparison.
555 Element* mElementToBeRemoved;
557 static nsTextNodeDirectionalityMap* GetDirectionalityMap(nsINode* aTextNode) {
558 MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE,
559 "Must be a text node");
560 nsTextNodeDirectionalityMap* map = nullptr;
562 if (aTextNode->HasTextNodeDirectionalityMap()) {
563 map = static_cast<nsTextNodeDirectionalityMap*>(
564 aTextNode->GetProperty(nsGkAtoms::textNodeDirectionalityMap));
567 return map;
570 static nsCheapSetOperator SetNodeDirection(nsPtrHashKey<Element>* aEntry,
571 void* aDir) {
572 aEntry->GetKey()->SetDirectionality(
573 *reinterpret_cast<Directionality*>(aDir), true);
574 return OpNext;
577 struct nsTextNodeDirectionalityMapAndElement {
578 nsTextNodeDirectionalityMap* mMap;
579 nsCOMPtr<nsINode> mNode;
582 static nsCheapSetOperator ResetNodeDirection(nsPtrHashKey<Element>* aEntry,
583 void* aData) {
584 // run the downward propagation algorithm
585 // and remove the text node from the map
586 nsTextNodeDirectionalityMapAndElement* data =
587 static_cast<nsTextNodeDirectionalityMapAndElement*>(aData);
588 nsINode* oldTextNode = data->mNode;
589 Element* rootNode = aEntry->GetKey();
590 nsTextNode* newTextNode = nullptr;
591 if (rootNode->GetParentNode() && rootNode->HasDirAuto()) {
592 newTextNode =
593 WalkDescendantsSetDirectionFromText(rootNode, true, oldTextNode);
596 AutoRestore<Element*> restore(data->mMap->mElementToBeRemoved);
597 data->mMap->mElementToBeRemoved = rootNode;
598 if (newTextNode) {
599 nsINode* oldDirAutoSetBy = static_cast<nsTextNode*>(
600 rootNode->GetProperty(nsGkAtoms::dirAutoSetBy));
601 if (oldDirAutoSetBy == newTextNode) {
602 // We're already registered.
603 return OpNext;
605 nsTextNodeDirectionalityMap::AddEntryToMap(newTextNode, rootNode);
606 } else {
607 rootNode->ClearHasDirAutoSet();
608 rootNode->RemoveProperty(nsGkAtoms::dirAutoSetBy);
610 return OpRemove;
613 static nsCheapSetOperator TakeEntries(nsPtrHashKey<Element>* aEntry,
614 void* aData) {
615 AutoTArray<Element*, 8>* entries =
616 static_cast<AutoTArray<Element*, 8>*>(aData);
617 entries->AppendElement(aEntry->GetKey());
618 return OpRemove;
621 public:
622 uint32_t UpdateAutoDirection(Directionality aDir) {
623 return mElements.EnumerateEntries(SetNodeDirection, &aDir);
626 void ResetAutoDirection(nsINode* aTextNode) {
627 nsTextNodeDirectionalityMapAndElement data = {this, aTextNode};
628 mElements.EnumerateEntries(ResetNodeDirection, &data);
631 void EnsureMapIsClear() {
632 AutoRestore<Element*> restore(mElementToBeRemoved);
633 AutoTArray<Element*, 8> entries;
634 mElements.EnumerateEntries(TakeEntries, &entries);
635 for (Element* el : entries) {
636 el->ClearHasDirAutoSet();
637 el->RemoveProperty(nsGkAtoms::dirAutoSetBy);
641 static void RemoveElementFromMap(nsTextNode* aTextNode, Element* aElement) {
642 if (aTextNode->HasTextNodeDirectionalityMap()) {
643 GetDirectionalityMap(aTextNode)->RemoveEntry(aTextNode, aElement);
647 static void AddEntryToMap(nsTextNode* aTextNode, Element* aElement) {
648 nsTextNodeDirectionalityMap* map = GetDirectionalityMap(aTextNode);
649 if (!map) {
650 map = new nsTextNodeDirectionalityMap(aTextNode);
653 map->AddEntry(aTextNode, aElement);
656 static uint32_t UpdateTextNodeDirection(nsINode* aTextNode,
657 Directionality aDir) {
658 MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
659 "Map missing in UpdateTextNodeDirection");
660 return GetDirectionalityMap(aTextNode)->UpdateAutoDirection(aDir);
663 static void ResetTextNodeDirection(nsTextNode* aTextNode,
664 nsTextNode* aChangedTextNode) {
665 MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
666 "Map missing in ResetTextNodeDirection");
667 RefPtr<nsTextNode> textNode = aTextNode;
668 GetDirectionalityMap(textNode)->ResetAutoDirection(aChangedTextNode);
671 static void EnsureMapIsClearFor(nsINode* aTextNode) {
672 if (aTextNode->HasTextNodeDirectionalityMap()) {
673 GetDirectionalityMap(aTextNode)->EnsureMapIsClear();
678 Directionality GetParentDirectionality(const Element* aElement) {
679 if (nsIContent* parent = GetParentOrHostOrSlot(aElement)) {
680 if (ShadowRoot* shadow = ShadowRoot::FromNode(parent)) {
681 parent = shadow->GetHost();
683 if (parent && parent->IsElement()) {
684 // If the node doesn't have an explicit dir attribute with a valid value,
685 // the directionality is the same as the parent element (but don't
686 // propagate the parent directionality if it isn't set yet).
687 Directionality parentDir = parent->AsElement()->GetDirectionality();
688 if (parentDir != Directionality::Unset) {
689 return parentDir;
693 return Directionality::Ltr;
696 Directionality RecomputeDirectionality(Element* aElement, bool aNotify) {
697 MOZ_ASSERT(!aElement->HasDirAuto(),
698 "RecomputeDirectionality called with dir=auto");
700 if (aElement->HasValidDir()) {
701 return aElement->GetDirectionality();
704 // https://html.spec.whatwg.org/multipage/dom.html#the-directionality:
706 // If the element is an input element whose type attribute is in the
707 // Telephone state, and the dir attribute is not in a defined state
708 // (i.e. it is not present or has an invalid value)
710 // The directionality of the element is 'ltr'.
711 if (auto* input = HTMLInputElement::FromNode(*aElement)) {
712 if (input->ControlType() == FormControlType::InputTel) {
713 aElement->SetDirectionality(Directionality::Ltr, aNotify);
714 return Directionality::Ltr;
718 const Directionality dir = GetParentDirectionality(aElement);
719 aElement->SetDirectionality(dir, aNotify);
720 return dir;
723 // Whether the element establishes its own directionality and the one of its
724 // descendants.
725 static inline bool IsBoundary(const Element& aElement) {
726 return aElement.HasValidDir() || aElement.HasDirAuto();
729 static void SetDirectionalityOnDescendantsInternal(nsINode* aNode,
730 Directionality aDir,
731 bool aNotify) {
732 if (Element* element = Element::FromNode(aNode)) {
733 if (ShadowRoot* shadow = element->GetShadowRoot()) {
734 SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify);
738 for (nsIContent* child = aNode->GetFirstChild(); child;) {
739 auto* element = Element::FromNode(child);
740 if (!element) {
741 child = child->GetNextNode(aNode);
742 continue;
745 if (IsBoundary(*element) || element->GetAssignedSlot() ||
746 element->GetDirectionality() == aDir) {
747 // If the element is a directionality boundary, or it's assigned to a slot
748 // (in which case it doesn't inherit the directionality from its direct
749 // parent), or already has the right directionality, then we can skip the
750 // whole subtree.
751 child = child->GetNextNonChildNode(aNode);
752 continue;
754 if (ShadowRoot* shadow = element->GetShadowRoot()) {
755 SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify);
758 if (auto* slot = HTMLSlotElement::FromNode(child)) {
759 for (const RefPtr<nsINode>& assignedNode : slot->AssignedNodes()) {
760 auto* assignedElement = Element::FromNode(*assignedNode);
761 if (assignedElement && !IsBoundary(*assignedElement)) {
762 assignedElement->SetDirectionality(aDir, aNotify);
763 SetDirectionalityOnDescendantsInternal(assignedElement, aDir,
764 aNotify);
769 element->SetDirectionality(aDir, aNotify);
770 child = child->GetNextNode(aNode);
774 // We want the public version of this only to acc
775 void SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
776 bool aNotify) {
777 return SetDirectionalityOnDescendantsInternal(aElement, aDir, aNotify);
780 static void ResetAutoDirection(Element* aElement, bool aNotify) {
781 if (aElement->HasDirAutoSet()) {
782 // If the parent has the DirAutoSet flag, its direction is determined by
783 // some text node descendant.
784 // Remove it from the map and reset its direction by the downward
785 // propagation algorithm
786 nsTextNode* setByNode = static_cast<nsTextNode*>(
787 aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
788 if (setByNode) {
789 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
793 if (aElement->HasDirAuto()) {
794 nsTextNode* setByNode =
795 WalkDescendantsSetDirectionFromText(aElement, aNotify);
796 if (setByNode) {
797 nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, aElement);
799 SetDirectionalityOnDescendants(aElement, aElement->GetDirectionality(),
800 aNotify);
805 * Walk the parent chain of a text node whose dir attribute has been removed and
806 * reset the direction of any of its ancestors which have dir=auto and whose
807 * directionality is determined by a text node descendant.
809 void WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify) {
810 nsTextNode* setByNode;
811 nsIContent* parent = GetParentOrHostOrSlot(aElement);
812 while (parent && parent->NodeOrAncestorHasDirAuto()) {
813 if (!parent->IsElement()) {
814 parent = GetParentOrHostOrSlot(parent);
815 continue;
818 Element* parentElement = parent->AsElement();
819 if (parent->HasDirAutoSet()) {
820 // If the parent has the DirAutoSet flag, its direction is determined by
821 // some text node descendant.
822 // Remove it from the map and reset its direction by the downward
823 // propagation algorithm
824 setByNode = static_cast<nsTextNode*>(
825 parent->GetProperty(nsGkAtoms::dirAutoSetBy));
826 if (setByNode) {
827 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode,
828 parentElement);
831 if (parentElement->HasDirAuto()) {
832 setByNode = WalkDescendantsSetDirectionFromText(parentElement, aNotify);
833 if (setByNode) {
834 nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parentElement);
836 SetDirectionalityOnDescendants(
837 parentElement, parentElement->GetDirectionality(), aNotify);
838 break;
840 parent = GetParentOrHostOrSlot(parent);
844 static void RecomputeSlottedNodeDirection(HTMLSlotElement& aSlot,
845 nsINode& aNode) {
846 auto* assignedElement = Element::FromNode(aNode);
847 if (!assignedElement) {
848 return;
851 if (assignedElement->HasValidDir() || assignedElement->HasDirAuto()) {
852 return;
855 // Try to optimize out state changes when possible.
856 if (assignedElement->GetDirectionality() == aSlot.GetDirectionality()) {
857 return;
860 assignedElement->SetDirectionality(aSlot.GetDirectionality(), true);
861 SetDirectionalityOnDescendantsInternal(assignedElement,
862 aSlot.GetDirectionality(), true);
865 void SlotAssignedNodeChanged(HTMLSlotElement* aSlot,
866 nsIContent& aAssignedNode) {
867 if (!aSlot) {
868 return;
871 if (aSlot->NodeOrAncestorHasDirAuto()) {
872 // The directionality of the assigned node may impact the directionality of
873 // the slot. So recompute everything.
874 SlotStateChanged(aSlot, /* aAllAssignedNodesChanged = */ false);
877 if (aAssignedNode.GetAssignedSlot() == aSlot) {
878 RecomputeSlottedNodeDirection(*aSlot, aAssignedNode);
882 void SlotStateChanged(HTMLSlotElement* aSlot, bool aAllAssignedNodesChanged) {
883 if (!aSlot) {
884 return;
887 Directionality oldDir = aSlot->GetDirectionality();
889 if (aSlot->HasDirAuto()) {
890 ResetAutoDirection(aSlot, true);
893 if (aSlot->NodeOrAncestorHasDirAuto()) {
894 WalkAncestorsResetAutoDirection(aSlot, true);
897 if (aAllAssignedNodesChanged || oldDir != aSlot->GetDirectionality()) {
898 for (nsINode* node : aSlot->AssignedNodes()) {
899 RecomputeSlottedNodeDirection(*aSlot, *node);
904 void WalkDescendantsResetAutoDirection(Element* aElement) {
905 nsIContent* child = aElement->GetFirstChild();
906 while (child) {
907 if (child->IsElement() && child->AsElement()->HasDirAuto()) {
908 child = child->GetNextNonChildNode(aElement);
909 continue;
912 if (child->NodeType() == nsINode::TEXT_NODE &&
913 child->HasTextNodeDirectionalityMap()) {
914 nsTextNodeDirectionalityMap::ResetTextNodeDirection(
915 static_cast<nsTextNode*>(child), nullptr);
916 // Don't call nsTextNodeDirectionalityMap::EnsureMapIsClearFor(child)
917 // since ResetTextNodeDirection may have kept elements in child's
918 // DirectionalityMap.
920 child = child->GetNextNode(aElement);
924 static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot);
926 static void MaybeSetAncestorHasDirAutoOnShadowDOM(nsINode* aNode) {
927 if (aNode->IsElement()) {
928 if (ShadowRoot* sr = aNode->AsElement()->GetShadowRoot()) {
929 sr->SetAncestorHasDirAuto();
930 SetAncestorHasDirAutoOnDescendants(sr);
935 static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot) {
936 MaybeSetAncestorHasDirAutoOnShadowDOM(aRoot);
938 nsIContent* child = aRoot->GetFirstChild();
939 while (child) {
940 if (child->IsElement() &&
941 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
942 child = child->GetNextNonChildNode(aRoot);
943 continue;
946 // If the child is assigned to a slot, it should inherit the state from
947 // that.
948 if (!child->GetAssignedSlot()) {
949 MaybeSetAncestorHasDirAutoOnShadowDOM(child);
950 child->SetAncestorHasDirAuto();
951 if (auto* slot = HTMLSlotElement::FromNode(child)) {
952 const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
953 for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
954 assignedNodes[i]->SetAncestorHasDirAuto();
955 SetAncestorHasDirAutoOnDescendants(assignedNodes[i]);
959 child = child->GetNextNode(aRoot);
963 void WalkDescendantsSetDirAuto(Element* aElement, bool aNotify) {
964 // Only test for DoesNotParticipateInAutoDirection -- in other words, if
965 // aElement is a <bdi> which is having its dir attribute set to auto (or
966 // removed or set to an invalid value, which are equivalent to dir=auto for
967 // <bdi>, we *do* want to set AncestorHasDirAuto on its descendants, unlike
968 // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a <bdi>
969 // being bound to an existing node with dir=auto.
970 if (!DoesNotParticipateInAutoDirection(aElement) &&
971 !aElement->AncestorHasDirAuto()) {
972 SetAncestorHasDirAutoOnDescendants(aElement);
975 nsTextNode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
976 if (textNode) {
977 nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement);
981 void WalkDescendantsClearAncestorDirAuto(nsIContent* aContent) {
982 if (aContent->IsElement()) {
983 if (ShadowRoot* shadowRoot = aContent->AsElement()->GetShadowRoot()) {
984 shadowRoot->ClearAncestorHasDirAuto();
985 WalkDescendantsClearAncestorDirAuto(shadowRoot);
989 nsIContent* child = aContent->GetFirstChild();
990 while (child) {
991 if (child->GetAssignedSlot()) {
992 // If the child node is assigned to a slot, nodes state is inherited from
993 // the slot, not from element's parent.
994 child = child->GetNextNonChildNode(aContent);
995 continue;
997 if (child->IsElement()) {
998 if (child->AsElement()->HasDirAuto()) {
999 child = child->GetNextNonChildNode(aContent);
1000 continue;
1003 if (auto* slot = HTMLSlotElement::FromNode(child)) {
1004 const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
1005 for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
1006 if (assignedNodes[i]->IsElement()) {
1007 Element* slottedElement = assignedNodes[i]->AsElement();
1008 if (slottedElement->HasDirAuto()) {
1009 continue;
1013 nsIContent* content = assignedNodes[i]->AsContent();
1014 content->ClearAncestorHasDirAuto();
1015 WalkDescendantsClearAncestorDirAuto(content);
1020 child->ClearAncestorHasDirAuto();
1021 child = child->GetNextNode(aContent);
1025 void SetAncestorDirectionIfAuto(nsTextNode* aTextNode, Directionality aDir,
1026 bool aNotify = true) {
1027 MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE,
1028 "Must be a text node");
1030 bool crossedShadowBoundary = false;
1031 nsIContent* parent = GetParentOrHostOrSlot(aTextNode, &crossedShadowBoundary);
1032 while (parent && parent->NodeOrAncestorHasDirAuto()) {
1033 if (!parent->IsElement()) {
1034 parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary);
1035 continue;
1038 Element* parentElement = parent->AsElement();
1039 if (DoesNotParticipateInAutoDirection(parentElement) ||
1040 parentElement->HasFixedDir()) {
1041 break;
1044 if (parentElement->HasDirAuto()) {
1045 bool resetDirection = false;
1046 nsTextNode* directionWasSetByTextNode = static_cast<nsTextNode*>(
1047 parent->GetProperty(nsGkAtoms::dirAutoSetBy));
1049 if (!parent->HasDirAutoSet()) {
1050 // Fast path if parent's direction is not yet set by any descendant
1051 MOZ_ASSERT(!directionWasSetByTextNode,
1052 "dirAutoSetBy property should be null");
1053 resetDirection = true;
1054 } else {
1055 // If parent's direction is already set, we need to know if
1056 // aTextNode is before or after the text node that had set it.
1057 // We will walk parent's descendants in tree order starting from
1058 // aTextNode to optimize for the most common case where text nodes are
1059 // being appended to tree.
1060 if (!directionWasSetByTextNode) {
1061 resetDirection = true;
1062 } else if (directionWasSetByTextNode != aTextNode) {
1063 if (crossedShadowBoundary || AncestorChainCrossesShadowBoundary(
1064 directionWasSetByTextNode, parent)) {
1065 // Need to take the slow path when the path from either the old or
1066 // new text node to the dir=auto element crosses shadow boundary.
1067 ResetAutoDirection(parentElement, aNotify);
1068 return;
1071 nsIContent* child = aTextNode->GetNextNode(parent);
1072 while (child) {
1073 if (child->IsElement() &&
1074 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
1075 child = child->GetNextNonChildNode(parent);
1076 continue;
1079 if (child == directionWasSetByTextNode) {
1080 // we found the node that set the element's direction after our
1081 // text node, so we need to reset the direction
1082 resetDirection = true;
1083 break;
1086 child = child->GetNextNode(parent);
1091 if (resetDirection) {
1092 if (directionWasSetByTextNode) {
1093 nsTextNodeDirectionalityMap::RemoveElementFromMap(
1094 directionWasSetByTextNode, parentElement);
1096 parentElement->SetDirectionality(aDir, aNotify);
1097 nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parentElement);
1098 SetDirectionalityOnDescendants(parentElement, aDir, aNotify);
1101 // Since we found an element with dir=auto, we can stop walking the
1102 // parent chain: none of its ancestors will have their direction set by
1103 // any of its descendants.
1104 return;
1106 parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary);
1110 bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir,
1111 uint32_t aOffset) {
1112 if (!NodeAffectsDirAutoAncestor(aTextNode)) {
1113 nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
1114 return false;
1117 uint32_t firstStrong;
1118 *aOldDir = GetDirectionFromText(aTextNode, &firstStrong);
1119 return (aOffset <= firstStrong);
1122 void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir,
1123 bool aNotify) {
1124 Directionality newDir = GetDirectionFromText(aTextNode);
1125 if (newDir == Directionality::Unset) {
1126 if (aOldDir != Directionality::Unset &&
1127 aTextNode->HasTextNodeDirectionalityMap()) {
1128 // This node used to have a strong directional character but no
1129 // longer does. ResetTextNodeDirection() will re-resolve the
1130 // directionality of any elements whose directionality was
1131 // determined by this node.
1132 nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
1134 } else {
1135 // This node has a strong directional character. If it has a
1136 // TextNodeDirectionalityMap property, it already determines the
1137 // directionality of some element(s), so call UpdateTextNodeDirection to
1138 // reresolve their directionality. If it has no map, or if
1139 // UpdateTextNodeDirection returns zero, indicating that the map is
1140 // empty, call SetAncestorDirectionIfAuto to find ancestor elements
1141 // which should have their directionality determined by this node.
1142 if (aTextNode->HasTextNodeDirectionalityMap() &&
1143 nsTextNodeDirectionalityMap::UpdateTextNodeDirection(aTextNode,
1144 newDir)) {
1145 return;
1147 SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify);
1151 void SetDirectionFromNewTextNode(nsTextNode* aTextNode) {
1152 if (!NodeAffectsDirAutoAncestor(aTextNode)) {
1153 return;
1156 nsIContent* parent = GetParentOrHostOrSlot(aTextNode);
1157 if (parent && parent->NodeOrAncestorHasDirAuto()) {
1158 aTextNode->SetAncestorHasDirAuto();
1161 Directionality dir = GetDirectionFromText(aTextNode);
1162 if (dir != Directionality::Unset) {
1163 SetAncestorDirectionIfAuto(aTextNode, dir);
1167 void ResetDirectionSetByTextNode(nsTextNode* aTextNode) {
1168 if (!NodeAffectsDirAutoAncestor(aTextNode)) {
1169 nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
1170 return;
1173 Directionality dir = GetDirectionFromText(aTextNode);
1174 if (dir != Directionality::Unset &&
1175 aTextNode->HasTextNodeDirectionalityMap()) {
1176 nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
1180 void SetDirectionalityFromValue(Element* aElement, const nsAString& value,
1181 bool aNotify) {
1182 Directionality dir =
1183 GetDirectionFromText(value.BeginReading(), value.Length());
1184 if (dir == Directionality::Unset) {
1185 dir = Directionality::Ltr;
1188 if (aElement->GetDirectionality() != dir) {
1189 aElement->SetDirectionality(dir, aNotify);
1193 void OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue,
1194 bool hadValidDir, bool hadDirAuto, bool aNotify) {
1195 if (aElement->IsHTMLElement(nsGkAtoms::input) ||
1196 aElement->IsHTMLElement(nsGkAtoms::textarea)) {
1197 return;
1200 if (aElement->AncestorHasDirAuto()) {
1201 if (!hadValidDir) {
1202 // The element is a descendant of an element with dir = auto, is
1203 // having its dir attribute set, and previously didn't have a valid dir
1204 // attribute.
1205 // Check whether any of its text node descendants determine the
1206 // direction of any of its ancestors, and redetermine their direction
1207 WalkDescendantsResetAutoDirection(aElement);
1208 } else if (!aElement->HasValidDir()) {
1209 // The element is a descendant of an element with dir = auto and is
1210 // having its dir attribute removed or set to an invalid value.
1211 // Reset the direction of any of its ancestors whose direction is
1212 // determined by a text node descendant
1213 WalkAncestorsResetAutoDirection(aElement, aNotify);
1215 } else if (hadDirAuto && !aElement->HasDirAuto()) {
1216 // The element isn't a descendant of an element with dir = auto, and is
1217 // having its dir attribute set to something other than auto.
1218 // Walk the descendant tree and clear the AncestorHasDirAuto flag.
1220 // N.B: For elements other than <bdi> it would be enough to test that the
1221 // current value of dir was "auto" in BeforeSetAttr to know that we
1222 // were unsetting dir="auto". For <bdi> things are more complicated,
1223 // since it behaves like dir="auto" whenever the dir attribute is
1224 // empty or invalid, so we would have to check whether the old value
1225 // was not either "ltr" or "rtl", and the new value was either "ltr"
1226 // or "rtl". Element::HasDirAuto() encapsulates all that, so doing it
1227 // here is simpler.
1228 WalkDescendantsClearAncestorDirAuto(aElement);
1231 if (aElement->HasDirAuto()) {
1232 WalkDescendantsSetDirAuto(aElement, aNotify);
1233 } else {
1234 if (aElement->HasDirAutoSet()) {
1235 nsTextNode* setByNode = static_cast<nsTextNode*>(
1236 aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
1237 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
1239 SetDirectionalityOnDescendants(
1240 aElement, RecomputeDirectionality(aElement, aNotify), aNotify);
1244 void SetDirOnBind(Element* aElement, nsIContent* aParent) {
1245 // Set the AncestorHasDirAuto flag, unless this element shouldn't affect
1246 // ancestors that have dir=auto
1247 if (!DoesNotParticipateInAutoDirection(aElement) &&
1248 !aElement->IsHTMLElement(nsGkAtoms::bdi) && aParent &&
1249 aParent->NodeOrAncestorHasDirAuto()) {
1250 aElement->SetAncestorHasDirAuto();
1252 SetAncestorHasDirAutoOnDescendants(aElement);
1254 if (aElement->GetFirstChild() || aElement->GetShadowRoot()) {
1255 // We may also need to reset the direction of an ancestor with dir=auto
1256 WalkAncestorsResetAutoDirection(aElement, true);
1260 if (!aElement->HasDirAuto()) {
1261 // if the element doesn't have dir=auto, set its own directionality from
1262 // the dir attribute or by inheriting from its ancestors.
1263 RecomputeDirectionality(aElement, false);
1267 void ResetDir(Element* aElement) {
1268 if (aElement->HasDirAutoSet()) {
1269 nsTextNode* setByNode = static_cast<nsTextNode*>(
1270 aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
1271 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
1274 if (!aElement->HasDirAuto()) {
1275 RecomputeDirectionality(aElement, false);
1279 } // end namespace mozilla