Bug 1852740: add tests for the `fetchpriority` attribute in Link headers. r=necko...
[gecko.git] / dom / base / DirectionalityUtils.cpp
blobaabd946448a507213c086842062e426a61e18642
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 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 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 eDir_RTL;
313 case intl::BidiClass::LeftToRight:
314 return eDir_LTR;
316 default:
317 return eDir_NotSet;
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 != eDir_NotSet) {
347 if (aFirstStrong) {
348 *aFirstStrong = current;
350 return dir;
355 if (aFirstStrong) {
356 *aFirstStrong = UINT32_MAX;
358 return eDir_NotSet;
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 != eDir_NotSet) {
373 if (aFirstStrong) {
374 *aFirstStrong = current;
376 return dir;
380 if (aFirstStrong) {
381 *aFirstStrong = UINT32_MAX;
383 return eDir_NotSet;
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 != eDir_NotSet) {
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 != eDir_NotSet) {
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 = eDir_NotSet;
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(eDir_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 RecomputeDirectionality(Element* aElement, bool aNotify) {
679 MOZ_ASSERT(!aElement->HasDirAuto(),
680 "RecomputeDirectionality called with dir=auto");
682 if (aElement->HasValidDir()) {
683 return aElement->GetDirectionality();
686 Directionality dir = eDir_LTR;
688 // https://html.spec.whatwg.org/multipage/dom.html#the-directionality:
690 // If the element is an input element whose type attribute is in the
691 // Telephone state, and the dir attribute is not in a defined state
692 // (i.e. it is not present or has an invalid value)
694 // The directionality of the element is 'ltr'.
695 if (auto* input = HTMLInputElement::FromNode(*aElement)) {
696 if (input->ControlType() == FormControlType::InputTel) {
697 aElement->SetDirectionality(dir, aNotify);
698 return dir;
702 if (nsIContent* parent = GetParentOrHostOrSlot(aElement)) {
703 if (ShadowRoot* shadow = ShadowRoot::FromNode(parent)) {
704 parent = shadow->GetHost();
707 if (parent && parent->IsElement()) {
708 // If the node doesn't have an explicit dir attribute with a valid value,
709 // the directionality is the same as the parent element (but don't
710 // propagate the parent directionality if it isn't set yet).
711 Directionality parentDir = parent->AsElement()->GetDirectionality();
712 if (parentDir != eDir_NotSet) {
713 dir = parentDir;
718 aElement->SetDirectionality(dir, aNotify);
719 return dir;
722 static void SetDirectionalityOnDescendantsInternal(nsINode* aNode,
723 Directionality aDir,
724 bool aNotify) {
725 if (Element* element = Element::FromNode(aNode)) {
726 if (ShadowRoot* shadow = element->GetShadowRoot()) {
727 SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify);
731 for (nsIContent* child = aNode->GetFirstChild(); child;) {
732 if (!child->IsElement()) {
733 child = child->GetNextNode(aNode);
734 continue;
737 Element* element = child->AsElement();
738 if (element->HasValidDir() || element->HasDirAuto() ||
739 element->GetAssignedSlot()) {
740 child = child->GetNextNonChildNode(aNode);
741 continue;
743 if (ShadowRoot* shadow = element->GetShadowRoot()) {
744 SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify);
747 if (auto* slot = HTMLSlotElement::FromNode(child)) {
748 const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
749 for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
750 nsINode* node = assignedNodes[i];
751 Element* assignedElement =
752 node->IsElement() ? node->AsElement() : nullptr;
753 if (assignedElement && !assignedElement->HasValidDir() &&
754 !assignedElement->HasDirAuto()) {
755 assignedElement->SetDirectionality(aDir, aNotify);
756 SetDirectionalityOnDescendantsInternal(assignedElement, aDir,
757 aNotify);
762 element->SetDirectionality(aDir, aNotify);
764 child = child->GetNextNode(aNode);
768 // We want the public version of this only to acc
769 void SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
770 bool aNotify) {
771 return SetDirectionalityOnDescendantsInternal(aElement, aDir, aNotify);
774 static void ResetAutoDirection(Element* aElement, bool aNotify) {
775 if (aElement->HasDirAutoSet()) {
776 // If the parent has the DirAutoSet flag, its direction is determined by
777 // some text node descendant.
778 // Remove it from the map and reset its direction by the downward
779 // propagation algorithm
780 nsTextNode* setByNode = static_cast<nsTextNode*>(
781 aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
782 if (setByNode) {
783 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
787 if (aElement->HasDirAuto()) {
788 nsTextNode* setByNode =
789 WalkDescendantsSetDirectionFromText(aElement, aNotify);
790 if (setByNode) {
791 nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, aElement);
793 SetDirectionalityOnDescendants(aElement, aElement->GetDirectionality(),
794 aNotify);
799 * Walk the parent chain of a text node whose dir attribute has been removed and
800 * reset the direction of any of its ancestors which have dir=auto and whose
801 * directionality is determined by a text node descendant.
803 void WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify) {
804 nsTextNode* setByNode;
805 nsIContent* parent = GetParentOrHostOrSlot(aElement);
806 while (parent && parent->NodeOrAncestorHasDirAuto()) {
807 if (!parent->IsElement()) {
808 parent = GetParentOrHostOrSlot(parent);
809 continue;
812 Element* parentElement = parent->AsElement();
813 if (parent->HasDirAutoSet()) {
814 // If the parent has the DirAutoSet flag, its direction is determined by
815 // some text node descendant.
816 // Remove it from the map and reset its direction by the downward
817 // propagation algorithm
818 setByNode = static_cast<nsTextNode*>(
819 parent->GetProperty(nsGkAtoms::dirAutoSetBy));
820 if (setByNode) {
821 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode,
822 parentElement);
825 if (parentElement->HasDirAuto()) {
826 setByNode = WalkDescendantsSetDirectionFromText(parentElement, aNotify);
827 if (setByNode) {
828 nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parentElement);
830 SetDirectionalityOnDescendants(
831 parentElement, parentElement->GetDirectionality(), aNotify);
832 break;
834 parent = GetParentOrHostOrSlot(parent);
838 static void RecomputeSlottedNodeDirection(HTMLSlotElement& aSlot,
839 nsINode& aNode) {
840 auto* assignedElement = Element::FromNode(aNode);
841 if (!assignedElement) {
842 return;
845 if (assignedElement->HasValidDir() || assignedElement->HasDirAuto()) {
846 return;
849 // Try to optimize out state changes when possible.
850 if (assignedElement->GetDirectionality() == aSlot.GetDirectionality()) {
851 return;
854 assignedElement->SetDirectionality(aSlot.GetDirectionality(), true);
855 SetDirectionalityOnDescendantsInternal(assignedElement,
856 aSlot.GetDirectionality(), true);
859 void SlotAssignedNodeChanged(HTMLSlotElement* aSlot,
860 nsIContent& aAssignedNode) {
861 if (!aSlot) {
862 return;
865 if (aSlot->NodeOrAncestorHasDirAuto()) {
866 // The directionality of the assigned node may impact the directionality of
867 // the slot. So recompute everything.
868 SlotStateChanged(aSlot, /* aAllAssignedNodesChanged = */ false);
871 if (aAssignedNode.GetAssignedSlot() == aSlot) {
872 RecomputeSlottedNodeDirection(*aSlot, aAssignedNode);
876 void SlotStateChanged(HTMLSlotElement* aSlot, bool aAllAssignedNodesChanged) {
877 if (!aSlot) {
878 return;
881 Directionality oldDir = aSlot->GetDirectionality();
883 if (aSlot->HasDirAuto()) {
884 ResetAutoDirection(aSlot, true);
887 if (aSlot->NodeOrAncestorHasDirAuto()) {
888 WalkAncestorsResetAutoDirection(aSlot, true);
891 if (aAllAssignedNodesChanged || oldDir != aSlot->GetDirectionality()) {
892 for (nsINode* node : aSlot->AssignedNodes()) {
893 RecomputeSlottedNodeDirection(*aSlot, *node);
898 void WalkDescendantsResetAutoDirection(Element* aElement) {
899 nsIContent* child = aElement->GetFirstChild();
900 while (child) {
901 if (child->IsElement() && child->AsElement()->HasDirAuto()) {
902 child = child->GetNextNonChildNode(aElement);
903 continue;
906 if (child->NodeType() == nsINode::TEXT_NODE &&
907 child->HasTextNodeDirectionalityMap()) {
908 nsTextNodeDirectionalityMap::ResetTextNodeDirection(
909 static_cast<nsTextNode*>(child), nullptr);
910 // Don't call nsTextNodeDirectionalityMap::EnsureMapIsClearFor(child)
911 // since ResetTextNodeDirection may have kept elements in child's
912 // DirectionalityMap.
914 child = child->GetNextNode(aElement);
918 static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot);
920 static void MaybeSetAncestorHasDirAutoOnShadowDOM(nsINode* aNode) {
921 if (aNode->IsElement()) {
922 if (ShadowRoot* sr = aNode->AsElement()->GetShadowRoot()) {
923 sr->SetAncestorHasDirAuto();
924 SetAncestorHasDirAutoOnDescendants(sr);
929 static void SetAncestorHasDirAutoOnDescendants(nsINode* aRoot) {
930 MaybeSetAncestorHasDirAutoOnShadowDOM(aRoot);
932 nsIContent* child = aRoot->GetFirstChild();
933 while (child) {
934 if (child->IsElement() &&
935 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
936 child = child->GetNextNonChildNode(aRoot);
937 continue;
940 // If the child is assigned to a slot, it should inherit the state from
941 // that.
942 if (!child->GetAssignedSlot()) {
943 MaybeSetAncestorHasDirAutoOnShadowDOM(child);
944 child->SetAncestorHasDirAuto();
945 if (auto* slot = HTMLSlotElement::FromNode(child)) {
946 const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
947 for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
948 assignedNodes[i]->SetAncestorHasDirAuto();
949 SetAncestorHasDirAutoOnDescendants(assignedNodes[i]);
953 child = child->GetNextNode(aRoot);
957 void WalkDescendantsSetDirAuto(Element* aElement, bool aNotify) {
958 // Only test for DoesNotParticipateInAutoDirection -- in other words, if
959 // aElement is a <bdi> which is having its dir attribute set to auto (or
960 // removed or set to an invalid value, which are equivalent to dir=auto for
961 // <bdi>, we *do* want to set AncestorHasDirAuto on its descendants, unlike
962 // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a <bdi>
963 // being bound to an existing node with dir=auto.
964 if (!DoesNotParticipateInAutoDirection(aElement) &&
965 !aElement->AncestorHasDirAuto()) {
966 SetAncestorHasDirAutoOnDescendants(aElement);
969 nsTextNode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
970 if (textNode) {
971 nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement);
975 void WalkDescendantsClearAncestorDirAuto(nsIContent* aContent) {
976 if (aContent->IsElement()) {
977 if (ShadowRoot* shadowRoot = aContent->AsElement()->GetShadowRoot()) {
978 shadowRoot->ClearAncestorHasDirAuto();
979 WalkDescendantsClearAncestorDirAuto(shadowRoot);
983 nsIContent* child = aContent->GetFirstChild();
984 while (child) {
985 if (child->GetAssignedSlot()) {
986 // If the child node is assigned to a slot, nodes state is inherited from
987 // the slot, not from element's parent.
988 child = child->GetNextNonChildNode(aContent);
989 continue;
991 if (child->IsElement()) {
992 if (child->AsElement()->HasDirAuto()) {
993 child = child->GetNextNonChildNode(aContent);
994 continue;
997 if (auto* slot = HTMLSlotElement::FromNode(child)) {
998 const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
999 for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
1000 if (assignedNodes[i]->IsElement()) {
1001 Element* slottedElement = assignedNodes[i]->AsElement();
1002 if (slottedElement->HasDirAuto()) {
1003 continue;
1007 nsIContent* content = assignedNodes[i]->AsContent();
1008 content->ClearAncestorHasDirAuto();
1009 WalkDescendantsClearAncestorDirAuto(content);
1014 child->ClearAncestorHasDirAuto();
1015 child = child->GetNextNode(aContent);
1019 void SetAncestorDirectionIfAuto(nsTextNode* aTextNode, Directionality aDir,
1020 bool aNotify = true) {
1021 MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE,
1022 "Must be a text node");
1024 bool crossedShadowBoundary = false;
1025 nsIContent* parent = GetParentOrHostOrSlot(aTextNode, &crossedShadowBoundary);
1026 while (parent && parent->NodeOrAncestorHasDirAuto()) {
1027 if (!parent->IsElement()) {
1028 parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary);
1029 continue;
1032 Element* parentElement = parent->AsElement();
1033 if (DoesNotParticipateInAutoDirection(parentElement) ||
1034 parentElement->HasFixedDir()) {
1035 break;
1038 if (parentElement->HasDirAuto()) {
1039 bool resetDirection = false;
1040 nsTextNode* directionWasSetByTextNode = static_cast<nsTextNode*>(
1041 parent->GetProperty(nsGkAtoms::dirAutoSetBy));
1043 if (!parent->HasDirAutoSet()) {
1044 // Fast path if parent's direction is not yet set by any descendant
1045 MOZ_ASSERT(!directionWasSetByTextNode,
1046 "dirAutoSetBy property should be null");
1047 resetDirection = true;
1048 } else {
1049 // If parent's direction is already set, we need to know if
1050 // aTextNode is before or after the text node that had set it.
1051 // We will walk parent's descendants in tree order starting from
1052 // aTextNode to optimize for the most common case where text nodes are
1053 // being appended to tree.
1054 if (!directionWasSetByTextNode) {
1055 resetDirection = true;
1056 } else if (directionWasSetByTextNode != aTextNode) {
1057 if (crossedShadowBoundary || AncestorChainCrossesShadowBoundary(
1058 directionWasSetByTextNode, parent)) {
1059 // Need to take the slow path when the path from either the old or
1060 // new text node to the dir=auto element crosses shadow boundary.
1061 ResetAutoDirection(parentElement, aNotify);
1062 return;
1065 nsIContent* child = aTextNode->GetNextNode(parent);
1066 while (child) {
1067 if (child->IsElement() &&
1068 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
1069 child = child->GetNextNonChildNode(parent);
1070 continue;
1073 if (child == directionWasSetByTextNode) {
1074 // we found the node that set the element's direction after our
1075 // text node, so we need to reset the direction
1076 resetDirection = true;
1077 break;
1080 child = child->GetNextNode(parent);
1085 if (resetDirection) {
1086 if (directionWasSetByTextNode) {
1087 nsTextNodeDirectionalityMap::RemoveElementFromMap(
1088 directionWasSetByTextNode, parentElement);
1090 parentElement->SetDirectionality(aDir, aNotify);
1091 nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parentElement);
1092 SetDirectionalityOnDescendants(parentElement, aDir, aNotify);
1095 // Since we found an element with dir=auto, we can stop walking the
1096 // parent chain: none of its ancestors will have their direction set by
1097 // any of its descendants.
1098 return;
1100 parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary);
1104 bool TextNodeWillChangeDirection(nsTextNode* aTextNode, Directionality* aOldDir,
1105 uint32_t aOffset) {
1106 if (!NodeAffectsDirAutoAncestor(aTextNode)) {
1107 nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
1108 return false;
1111 uint32_t firstStrong;
1112 *aOldDir = GetDirectionFromText(aTextNode, &firstStrong);
1113 return (aOffset <= firstStrong);
1116 void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir,
1117 bool aNotify) {
1118 Directionality newDir = GetDirectionFromText(aTextNode);
1119 if (newDir == eDir_NotSet) {
1120 if (aOldDir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
1121 // This node used to have a strong directional character but no
1122 // longer does. ResetTextNodeDirection() will re-resolve the
1123 // directionality of any elements whose directionality was
1124 // determined by this node.
1125 nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
1127 } else {
1128 // This node has a strong directional character. If it has a
1129 // TextNodeDirectionalityMap property, it already determines the
1130 // directionality of some element(s), so call UpdateTextNodeDirection to
1131 // reresolve their directionality. If it has no map, or if
1132 // UpdateTextNodeDirection returns zero, indicating that the map is
1133 // empty, call SetAncestorDirectionIfAuto to find ancestor elements
1134 // which should have their directionality determined by this node.
1135 if (aTextNode->HasTextNodeDirectionalityMap() &&
1136 nsTextNodeDirectionalityMap::UpdateTextNodeDirection(aTextNode,
1137 newDir)) {
1138 return;
1140 SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify);
1144 void SetDirectionFromNewTextNode(nsTextNode* aTextNode) {
1145 if (!NodeAffectsDirAutoAncestor(aTextNode)) {
1146 return;
1149 nsIContent* parent = GetParentOrHostOrSlot(aTextNode);
1150 if (parent && parent->NodeOrAncestorHasDirAuto()) {
1151 aTextNode->SetAncestorHasDirAuto();
1154 Directionality dir = GetDirectionFromText(aTextNode);
1155 if (dir != eDir_NotSet) {
1156 SetAncestorDirectionIfAuto(aTextNode, dir);
1160 void ResetDirectionSetByTextNode(nsTextNode* aTextNode) {
1161 if (!NodeAffectsDirAutoAncestor(aTextNode)) {
1162 nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
1163 return;
1166 Directionality dir = GetDirectionFromText(aTextNode);
1167 if (dir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
1168 nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
1172 void SetDirectionalityFromValue(Element* aElement, const nsAString& value,
1173 bool aNotify) {
1174 Directionality dir =
1175 GetDirectionFromText(value.BeginReading(), value.Length());
1176 if (dir == eDir_NotSet) {
1177 dir = eDir_LTR;
1180 if (aElement->GetDirectionality() != dir) {
1181 aElement->SetDirectionality(dir, aNotify);
1185 void OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue,
1186 bool hadValidDir, bool hadDirAuto, bool aNotify) {
1187 if (aElement->IsHTMLElement(nsGkAtoms::input) ||
1188 aElement->IsHTMLElement(nsGkAtoms::textarea)) {
1189 return;
1192 if (aElement->AncestorHasDirAuto()) {
1193 if (!hadValidDir) {
1194 // The element is a descendant of an element with dir = auto, is
1195 // having its dir attribute set, and previously didn't have a valid dir
1196 // attribute.
1197 // Check whether any of its text node descendants determine the
1198 // direction of any of its ancestors, and redetermine their direction
1199 WalkDescendantsResetAutoDirection(aElement);
1200 } else if (!aElement->HasValidDir()) {
1201 // The element is a descendant of an element with dir = auto and is
1202 // having its dir attribute removed or set to an invalid value.
1203 // Reset the direction of any of its ancestors whose direction is
1204 // determined by a text node descendant
1205 WalkAncestorsResetAutoDirection(aElement, aNotify);
1207 } else if (hadDirAuto && !aElement->HasDirAuto()) {
1208 // The element isn't a descendant of an element with dir = auto, and is
1209 // having its dir attribute set to something other than auto.
1210 // Walk the descendant tree and clear the AncestorHasDirAuto flag.
1212 // N.B: For elements other than <bdi> it would be enough to test that the
1213 // current value of dir was "auto" in BeforeSetAttr to know that we
1214 // were unsetting dir="auto". For <bdi> things are more complicated,
1215 // since it behaves like dir="auto" whenever the dir attribute is
1216 // empty or invalid, so we would have to check whether the old value
1217 // was not either "ltr" or "rtl", and the new value was either "ltr"
1218 // or "rtl". Element::HasDirAuto() encapsulates all that, so doing it
1219 // here is simpler.
1220 WalkDescendantsClearAncestorDirAuto(aElement);
1223 if (aElement->HasDirAuto()) {
1224 WalkDescendantsSetDirAuto(aElement, aNotify);
1225 } else {
1226 if (aElement->HasDirAutoSet()) {
1227 nsTextNode* setByNode = static_cast<nsTextNode*>(
1228 aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
1229 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
1231 SetDirectionalityOnDescendants(
1232 aElement, RecomputeDirectionality(aElement, aNotify), aNotify);
1236 void SetDirOnBind(Element* aElement, nsIContent* aParent) {
1237 // Set the AncestorHasDirAuto flag, unless this element shouldn't affect
1238 // ancestors that have dir=auto
1239 if (!DoesNotParticipateInAutoDirection(aElement) &&
1240 !aElement->IsHTMLElement(nsGkAtoms::bdi) && aParent &&
1241 aParent->NodeOrAncestorHasDirAuto()) {
1242 aElement->SetAncestorHasDirAuto();
1244 SetAncestorHasDirAutoOnDescendants(aElement);
1246 if (aElement->GetFirstChild() || aElement->GetShadowRoot()) {
1247 // We may also need to reset the direction of an ancestor with dir=auto
1248 WalkAncestorsResetAutoDirection(aElement, true);
1252 if (!aElement->HasDirAuto()) {
1253 // if the element doesn't have dir=auto, set its own directionality from
1254 // the dir attribute or by inheriting from its ancestors.
1255 RecomputeDirectionality(aElement, false);
1259 void ResetDir(Element* aElement) {
1260 if (aElement->HasDirAutoSet()) {
1261 nsTextNode* setByNode = static_cast<nsTextNode*>(
1262 aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
1263 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
1266 if (!aElement->HasDirAuto()) {
1267 RecomputeDirectionality(aElement, false);
1271 } // end namespace mozilla