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/. */
10 When we see a new content node with @dir=auto from the parser, we set the
11 NodeHasDirAuto flag on the node. We won't have enough information to
12 decide the directionality of the node at this point.
14 When we bind a new content node to the document, if its parent has either of
15 the NodeAncestorHasDirAuto or NodeHasDirAuto flags, we set the
16 NodeAncestorHasDirAuto flag on the node.
18 When a new input with @type=text/search/tel/url/email and @dir=auto is added
19 from the parser, we resolve the directionality based on its @value.
21 When a new text node with non-neutral content is appended to a textarea
22 element with NodeHasDirAuto, if the directionality of the textarea element
23 is still unresolved, it is resolved based on the value of the text node.
24 Elements with unresolved directionality behave as Ltr.
26 When a new text node with non-neutral content is appended to an element that
27 is not a textarea but has either of the NodeAncestorHasDirAuto or
28 NodeHasDirAuto flags, we walk up the parent chain while the
29 NodeAncestorHasDirAuto flag is present, and when we reach an element with
30 NodeHasDirAuto and no resolved directionality, we resolve the directionality
31 based on the contents of the text node and cease walking the parent chain.
32 Note that we should ignore elements with NodeHasDirAuto with resolved
33 directionality, so that the second text node in this example tree doesn't
34 affect the directionality of the div:
41 The parent chain walk will be aborted if we hit a script or style element, or
42 if we hit an element with @dir=ltr or @dir=rtl.
44 I will call this algorithm "upward propagation".
46 Each text node keeps a flag if it might determine the directionality of any
47 ancestor. This is useful to make dynamic changes more efficient.
49 Handling dynamic changes
50 ========================
52 We need to handle the following cases:
54 1. When the value of an input element with @type=text/search/tel/url/email is
55 changed, if it has NodeHasDirAuto, we update the resolved directionality.
57 2. When the dir attribute is changed from something else (including the case
58 where it doesn't exist) to auto on a textarea or an input element with
59 @type=text/search/tel/url/email, we set the NodeHasDirAuto flag and resolve
60 the directionality based on the value of the element.
62 3. When the dir attribute is changed from something else (including the case
63 where it doesn't exist) to auto on any element except case 1 above and the bdi
64 element, we run the following algorithm:
65 * We set the NodeHasDirAuto flag.
66 * If the element doesn't have the NodeAncestorHasDirAuto flag, we set the
67 NodeAncestorHasDirAuto flag on all of its child nodes. (Note that if the
68 element does have NodeAncestorHasDirAuto, all of its children should
69 already have this flag too. We can assert this in debug builds.)
70 * To resolve the directionality of the element, we run the algorithm explained
72 http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-dir-attribute
73 (I'll call this the "downward propagation algorithm".) by walking the child
74 subtree in tree order. Note that an element with @dir=auto should not affect
75 other elements in its document with @dir=auto. So there is no need to walk up
76 the parent chain in this case.
78 3a. When the dir attribute is set to any valid value on an element that didn't
79 have a valid dir attribute before, this means that any descendant of that
80 element will not affect the directionality of any of its ancestors. So we need
81 to check whether any text node descendants of the element can set the dir of
82 any ancestor, and whether the elements whose direction they set are ancestors
83 of the element. If so, we need to rerun the downward propagation algorithm for
84 those ancestors. That's done by OnSetDirAttr.
86 4. When the dir attribute is changed from auto to something else (including
87 the case where it gets removed) on a textarea or an input element with
88 @type=text/search/tel/url/email, we unset the NodeHasDirAuto flag and
89 resolve the directionality based on the directionality of the value of the
90 @dir attribute on element itself or its parent element.
92 5. When the dir attribute is changed from auto to something else (including
93 the case where it gets removed) on any element except case 4 above and the bdi
94 element, we run the following algorithm:
95 * We unset the NodeHasDirAuto flag.
96 * If the element does not have the NodeAncestorHasDirAuto flag, we unset
97 the NodeAncestorHasDirAuto flag on all of its child nodes, except those
98 who are a descendant of another element with NodeHasDirAuto. (Note that if
99 the element has the NodeAncestorHasDirAuto flag, all of its child nodes
100 should still retain the same flag.)
101 * We resolve the directionality of the element based on the value of the @dir
102 attribute on the element itself or its parent element.
104 5a. When the dir attribute is removed or set to an invalid value on any
105 element (except a bdi element) with the NodeAncestorHasDirAuto flag which
106 previously had a valid dir attribute, it might have a text node descendant
107 that did not previously affect the directionality of any of its ancestors but
108 should now begin to affect them. We run OnSetDirAttr.
110 6. When an element with @dir=auto is added to the document, we should handle
111 it similar to the case 2/3 above.
113 7. When an element with NodeHasDirAuto or NodeAncestorHasDirAuto is
114 removed from the document, we should handle it similar to the case 4/5 above,
115 except that we don't need to handle anything in the child subtree.
117 8. When the contents of a text node is changed either from script or by the
118 user, we need to run TextNode{WillChange,Changed}Direction, see inline docs
121 9. When a new text node is injected into a document, we need to run
122 SetDirectionFromNewTextNode.
124 10. When a text node is removed from a document, we need to run
125 ResetDirectionSetByTextNode.
127 11. If the value of the @dir attribute on a bdi element is changed to an
128 invalid value (or if it's removed), determine the new directionality similar
131 == Implemention Notes ==
132 When a new node gets bound to the tree, the BindToTree function gets called.
133 The reverse case is UnbindFromTree.
134 When the contents of a text node change, CharacterData::SetTextInternal
138 #include "mozilla/dom/DirectionalityUtils.h"
141 #include "nsIContent.h"
142 #include "nsIContentInlines.h"
143 #include "mozilla/dom/Document.h"
144 #include "mozilla/dom/Element.h"
145 #include "mozilla/dom/HTMLInputElement.h"
146 #include "mozilla/dom/HTMLSlotElement.h"
147 #include "mozilla/dom/ShadowRoot.h"
148 #include "mozilla/dom/UnbindContext.h"
149 #include "mozilla/intl/UnicodeProperties.h"
150 #include "nsUnicodeProperties.h"
151 #include "nsTextFragment.h"
152 #include "nsAttrValue.h"
153 #include "nsTextNode.h"
157 using mozilla::dom::Element
;
158 using mozilla::dom::HTMLInputElement
;
159 using mozilla::dom::HTMLSlotElement
;
160 using mozilla::dom::ShadowRoot
;
162 static nsIContent
* GetParentOrHostOrSlot(const nsIContent
* aContent
) {
163 if (HTMLSlotElement
* slot
= aContent
->GetAssignedSlot()) {
166 if (nsIContent
* parent
= aContent
->GetParent()) {
169 if (const ShadowRoot
* sr
= ShadowRoot::FromNode(aContent
)) {
170 return sr
->GetHost();
176 * Returns true if aElement is one of the elements whose text content should
177 * affect its own direction, or the direction of ancestors with dir=auto.
179 * Note that this does not include <bdi>, whose content does affect its own
180 * direction when it has dir=auto (which it has by default), so one needs to
181 * test for it separately, e.g. with AffectsDirectionOfAncestors.
182 * It *does* include textarea, because even if a textarea has dir=auto, it has
183 * unicode-bidi: plaintext and is handled automatically in bidi resolution.
184 * It also includes `input`, because it takes the `dir` value from its value
185 * attribute, instead of the child nodes.
187 static bool ParticipatesInAutoDirection(const nsIContent
* aContent
) {
188 if (aContent
->IsInNativeAnonymousSubtree()) {
191 if (aContent
->IsShadowRoot()) {
194 return !aContent
->IsAnyOfHTMLElements(nsGkAtoms::script
, nsGkAtoms::style
,
195 nsGkAtoms::input
, nsGkAtoms::textarea
);
199 * Returns true if aElement is one of the element whose text should affect the
200 * direction of ancestors with dir=auto (though note that even if it returns
201 * false it may affect its own direction, e.g. <bdi> or dir=auto itself)
203 static bool AffectsDirectionOfAncestors(const Element
* aElement
) {
204 return ParticipatesInAutoDirection(aElement
) &&
205 !aElement
->IsHTMLElement(nsGkAtoms::bdi
) && !aElement
->HasFixedDir() &&
206 !aElement
->HasDirAuto();
210 * Returns the directionality of a Unicode character
212 static Directionality
GetDirectionFromChar(uint32_t ch
) {
213 switch (intl::UnicodeProperties::GetBidiClass(ch
)) {
214 case intl::BidiClass::RightToLeft
:
215 case intl::BidiClass::RightToLeftArabic
:
216 return Directionality::Rtl
;
218 case intl::BidiClass::LeftToRight
:
219 return Directionality::Ltr
;
222 return Directionality::Unset
;
226 inline static bool TextChildrenAffectDirAutoAncestor(nsIContent
* aContent
) {
227 return ParticipatesInAutoDirection(aContent
) &&
228 aContent
->NodeOrAncestorHasDirAuto();
231 inline static bool NodeAffectsDirAutoAncestor(nsTextNode
* aTextNode
) {
232 nsIContent
* parent
= GetParentOrHostOrSlot(aTextNode
);
233 return parent
&& TextChildrenAffectDirAutoAncestor(parent
) &&
234 !aTextNode
->IsInNativeAnonymousSubtree();
237 Directionality
GetDirectionFromText(const char16_t
* aText
,
238 const uint32_t aLength
,
239 uint32_t* aFirstStrong
) {
240 const char16_t
* start
= aText
;
241 const char16_t
* end
= aText
+ aLength
;
243 while (start
< end
) {
244 uint32_t current
= start
- aText
;
245 uint32_t ch
= *start
++;
247 if (start
< end
&& NS_IS_SURROGATE_PAIR(ch
, *start
)) {
248 ch
= SURROGATE_TO_UCS4(ch
, *start
++);
252 // Just ignore lone surrogates
253 if (!IS_SURROGATE(ch
)) {
254 Directionality dir
= GetDirectionFromChar(ch
);
255 if (dir
!= Directionality::Unset
) {
257 *aFirstStrong
= current
;
265 *aFirstStrong
= UINT32_MAX
;
267 return Directionality::Unset
;
270 static Directionality
GetDirectionFromText(const char* aText
,
271 const uint32_t aLength
,
272 uint32_t* aFirstStrong
= nullptr) {
273 const char* start
= aText
;
274 const char* end
= aText
+ aLength
;
276 while (start
< end
) {
277 uint32_t current
= start
- aText
;
278 unsigned char ch
= (unsigned char)*start
++;
280 Directionality dir
= GetDirectionFromChar(ch
);
281 if (dir
!= Directionality::Unset
) {
283 *aFirstStrong
= current
;
290 *aFirstStrong
= UINT32_MAX
;
292 return Directionality::Unset
;
295 static Directionality
GetDirectionFromText(const mozilla::dom::Text
* aTextNode
,
296 uint32_t* aFirstStrong
= nullptr) {
297 const nsTextFragment
* frag
= &aTextNode
->TextFragment();
299 return GetDirectionFromText(frag
->Get2b(), frag
->GetLength(), aFirstStrong
);
302 return GetDirectionFromText(frag
->Get1b(), frag
->GetLength(), aFirstStrong
);
305 static nsTextNode
* WalkDescendantsAndGetDirectionFromText(
306 nsINode
* aRoot
, Directionality
* aDirectionality
) {
307 nsIContent
* child
= aRoot
->GetFirstChild();
309 if ((child
->IsElement() &&
310 !AffectsDirectionOfAncestors(child
->AsElement())) ||
311 child
->GetAssignedSlot()) {
312 child
= child
->GetNextNonChildNode(aRoot
);
316 if (auto* slot
= HTMLSlotElement::FromNode(child
)) {
317 const nsTArray
<RefPtr
<nsINode
>>& assignedNodes
= slot
->AssignedNodes();
318 for (uint32_t i
= 0; i
< assignedNodes
.Length(); ++i
) {
319 nsIContent
* assignedNode
= assignedNodes
[i
]->AsContent();
320 if (assignedNode
->NodeType() == nsINode::TEXT_NODE
) {
321 auto* text
= static_cast<nsTextNode
*>(assignedNode
);
322 Directionality textNodeDir
= GetDirectionFromText(text
);
323 if (textNodeDir
!= Directionality::Unset
) {
324 *aDirectionality
= textNodeDir
;
327 } else if (assignedNode
->IsElement() &&
328 AffectsDirectionOfAncestors(assignedNode
->AsElement())) {
329 nsTextNode
* text
= WalkDescendantsAndGetDirectionFromText(
330 assignedNode
, aDirectionality
);
338 if (child
->NodeType() == nsINode::TEXT_NODE
) {
339 auto* text
= static_cast<nsTextNode
*>(child
);
340 Directionality textNodeDir
= GetDirectionFromText(text
);
341 if (textNodeDir
!= Directionality::Unset
) {
342 *aDirectionality
= textNodeDir
;
346 child
= child
->GetNextNode(aRoot
);
353 * Set the directionality of a node with dir=auto as defined in
354 * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality
356 * @return the text node containing the character that determined the direction
358 static nsTextNode
* WalkDescendantsSetDirectionFromText(Element
* aElement
,
360 MOZ_ASSERT(aElement
, "Must have an element");
361 MOZ_ASSERT(aElement
->HasDirAuto(), "Element must have dir=auto");
363 if (!ParticipatesInAutoDirection(aElement
)) {
367 Directionality textNodeDir
= Directionality::Unset
;
369 // Check the text in Shadow DOM.
370 if (ShadowRoot
* shadowRoot
= aElement
->GetShadowRoot()) {
372 WalkDescendantsAndGetDirectionFromText(shadowRoot
, &textNodeDir
);
374 aElement
->SetDirectionality(textNodeDir
, aNotify
);
379 // Check the text in light DOM.
381 WalkDescendantsAndGetDirectionFromText(aElement
, &textNodeDir
);
383 aElement
->SetDirectionality(textNodeDir
, aNotify
);
387 // We walked all the descendants without finding a text node with strong
388 // directional characters. Set the directionality to Ltr
389 aElement
->SetDirectionality(Directionality::Ltr
, aNotify
);
393 Directionality
GetParentDirectionality(const Element
* aElement
) {
394 if (nsIContent
* parent
= GetParentOrHostOrSlot(aElement
)) {
395 if (ShadowRoot
* shadow
= ShadowRoot::FromNode(parent
)) {
396 parent
= shadow
->GetHost();
398 if (parent
&& parent
->IsElement()) {
399 // If the node doesn't have an explicit dir attribute with a valid value,
400 // the directionality is the same as the parent element (but don't
401 // propagate the parent directionality if it isn't set yet).
402 Directionality parentDir
= parent
->AsElement()->GetDirectionality();
403 if (parentDir
!= Directionality::Unset
) {
408 return Directionality::Ltr
;
411 Directionality
RecomputeDirectionality(Element
* aElement
, bool aNotify
) {
412 MOZ_ASSERT(!aElement
->HasDirAuto(),
413 "RecomputeDirectionality called with dir=auto");
415 if (aElement
->HasValidDir()) {
416 return aElement
->GetDirectionality();
419 // https://html.spec.whatwg.org/multipage/dom.html#the-directionality:
421 // If the element is an input element whose type attribute is in the
422 // Telephone state, and the dir attribute is not in a defined state
423 // (i.e. it is not present or has an invalid value)
425 // The directionality of the element is 'ltr'.
426 if (auto* input
= HTMLInputElement::FromNode(*aElement
)) {
427 if (input
->ControlType() == FormControlType::InputTel
) {
428 aElement
->SetDirectionality(Directionality::Ltr
, aNotify
);
429 return Directionality::Ltr
;
433 const Directionality dir
= GetParentDirectionality(aElement
);
434 aElement
->SetDirectionality(dir
, aNotify
);
438 // Whether the element establishes its own directionality and the one of its
440 static inline bool IsBoundary(const Element
& aElement
) {
441 return aElement
.HasValidDir() || aElement
.HasDirAuto();
444 static void SetDirectionalityOnDescendantsInternal(nsINode
* aNode
,
447 if (auto* element
= Element::FromNode(aNode
)) {
448 if (ShadowRoot
* shadow
= element
->GetShadowRoot()) {
449 SetDirectionalityOnDescendantsInternal(shadow
, aDir
, aNotify
);
453 for (nsIContent
* child
= aNode
->GetFirstChild(); child
;) {
454 auto* element
= Element::FromNode(child
);
456 child
= child
->GetNextNode(aNode
);
460 if (IsBoundary(*element
) || element
->GetAssignedSlot() ||
461 element
->GetDirectionality() == aDir
) {
462 // If the element is a directionality boundary, or it's assigned to a slot
463 // (in which case it doesn't inherit the directionality from its direct
464 // parent), or already has the right directionality, then we can skip the
466 child
= child
->GetNextNonChildNode(aNode
);
469 if (ShadowRoot
* shadow
= element
->GetShadowRoot()) {
470 SetDirectionalityOnDescendantsInternal(shadow
, aDir
, aNotify
);
473 if (auto* slot
= HTMLSlotElement::FromNode(child
)) {
474 for (const RefPtr
<nsINode
>& assignedNode
: slot
->AssignedNodes()) {
475 auto* assignedElement
= Element::FromNode(*assignedNode
);
476 if (assignedElement
&& !IsBoundary(*assignedElement
)) {
477 assignedElement
->SetDirectionality(aDir
, aNotify
);
478 SetDirectionalityOnDescendantsInternal(assignedElement
, aDir
,
484 element
->SetDirectionality(aDir
, aNotify
);
485 child
= child
->GetNextNode(aNode
);
489 // We want the public version of this only to acc
490 void SetDirectionalityOnDescendants(Element
* aElement
, Directionality aDir
,
492 return SetDirectionalityOnDescendantsInternal(aElement
, aDir
, aNotify
);
495 static void ResetAutoDirection(Element
* aElement
, bool aNotify
) {
496 MOZ_ASSERT(aElement
->HasDirAuto());
497 nsTextNode
* setByNode
=
498 WalkDescendantsSetDirectionFromText(aElement
, aNotify
);
500 setByNode
->SetMaySetDirAuto();
502 SetDirectionalityOnDescendants(aElement
, aElement
->GetDirectionality(),
507 * Walk the parent chain of a text node whose dir attribute has been removed or
508 * added and reset the direction of any of its ancestors which have dir=auto and
509 * whose directionality is determined by a text node descendant.
511 void WalkAncestorsResetAutoDirection(Element
* aElement
, bool aNotify
) {
512 for (nsIContent
* parent
= GetParentOrHostOrSlot(aElement
);
513 parent
&& parent
->NodeOrAncestorHasDirAuto();
514 parent
= GetParentOrHostOrSlot(parent
)) {
515 auto* parentElement
= Element::FromNode(*parent
);
516 if (!parentElement
|| !parentElement
->HasDirAuto()) {
519 nsTextNode
* setByNode
=
520 WalkDescendantsSetDirectionFromText(parentElement
, aNotify
);
522 setByNode
->SetMaySetDirAuto();
524 SetDirectionalityOnDescendants(parentElement
,
525 parentElement
->GetDirectionality(), aNotify
);
530 static void RecomputeSlottedNodeDirection(HTMLSlotElement
& aSlot
,
532 auto* assignedElement
= Element::FromNode(aNode
);
533 if (!assignedElement
) {
537 if (assignedElement
->HasValidDir() || assignedElement
->HasDirAuto()) {
541 // Try to optimize out state changes when possible.
542 if (assignedElement
->GetDirectionality() == aSlot
.GetDirectionality()) {
546 assignedElement
->SetDirectionality(aSlot
.GetDirectionality(), true);
547 SetDirectionalityOnDescendantsInternal(assignedElement
,
548 aSlot
.GetDirectionality(), true);
551 void SlotAssignedNodeChanged(HTMLSlotElement
* aSlot
,
552 nsIContent
& aAssignedNode
) {
557 if (aSlot
->NodeOrAncestorHasDirAuto()) {
558 // The directionality of the assigned node may impact the directionality of
559 // the slot. So recompute everything.
560 SlotStateChanged(aSlot
, /* aAllAssignedNodesChanged = */ false);
563 if (aAssignedNode
.GetAssignedSlot() == aSlot
) {
564 RecomputeSlottedNodeDirection(*aSlot
, aAssignedNode
);
568 void SlotStateChanged(HTMLSlotElement
* aSlot
, bool aAllAssignedNodesChanged
) {
573 Directionality oldDir
= aSlot
->GetDirectionality();
575 if (aSlot
->HasDirAuto()) {
576 ResetAutoDirection(aSlot
, true);
579 if (aSlot
->NodeOrAncestorHasDirAuto()) {
580 WalkAncestorsResetAutoDirection(aSlot
, true);
583 if (aAllAssignedNodesChanged
|| oldDir
!= aSlot
->GetDirectionality()) {
584 for (nsINode
* node
: aSlot
->AssignedNodes()) {
585 RecomputeSlottedNodeDirection(*aSlot
, *node
);
590 static void SetAncestorHasDirAutoOnDescendants(nsINode
* aRoot
);
592 static void MaybeSetAncestorHasDirAutoOnShadowDOM(nsINode
* aNode
) {
593 if (aNode
->IsElement()) {
594 if (ShadowRoot
* sr
= aNode
->AsElement()->GetShadowRoot()) {
595 sr
->SetAncestorHasDirAuto();
596 SetAncestorHasDirAutoOnDescendants(sr
);
601 static void SetAncestorHasDirAutoOnDescendants(nsINode
* aRoot
) {
602 MaybeSetAncestorHasDirAutoOnShadowDOM(aRoot
);
604 nsIContent
* child
= aRoot
->GetFirstChild();
606 if (child
->IsElement() &&
607 !AffectsDirectionOfAncestors(child
->AsElement())) {
608 child
= child
->GetNextNonChildNode(aRoot
);
612 // If the child is assigned to a slot, it should inherit the state from
614 if (!child
->GetAssignedSlot()) {
615 MaybeSetAncestorHasDirAutoOnShadowDOM(child
);
616 child
->SetAncestorHasDirAuto();
617 if (auto* slot
= HTMLSlotElement::FromNode(child
)) {
618 const nsTArray
<RefPtr
<nsINode
>>& assignedNodes
= slot
->AssignedNodes();
619 for (uint32_t i
= 0; i
< assignedNodes
.Length(); ++i
) {
620 assignedNodes
[i
]->SetAncestorHasDirAuto();
621 SetAncestorHasDirAutoOnDescendants(assignedNodes
[i
]);
625 child
= child
->GetNextNode(aRoot
);
629 void WalkDescendantsSetDirAuto(Element
* aElement
, bool aNotify
) {
630 // Only test for ParticipatesInAutoDirection -- in other words, if aElement is
631 // a <bdi> which is having its dir attribute set to auto (or
632 // removed or set to an invalid value, which are equivalent to dir=auto for
633 // <bdi>, we *do* want to set AncestorHasDirAuto on its descendants, unlike
634 // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a <bdi>
635 // being bound to an existing node with dir=auto.
636 if (ParticipatesInAutoDirection(aElement
) &&
637 !aElement
->AncestorHasDirAuto()) {
638 SetAncestorHasDirAutoOnDescendants(aElement
);
641 nsTextNode
* textNode
= WalkDescendantsSetDirectionFromText(aElement
, aNotify
);
643 textNode
->SetMaySetDirAuto();
647 void WalkDescendantsClearAncestorDirAuto(nsIContent
* aContent
) {
648 if (aContent
->IsElement()) {
649 if (ShadowRoot
* shadowRoot
= aContent
->AsElement()->GetShadowRoot()) {
650 shadowRoot
->ClearAncestorHasDirAuto();
651 WalkDescendantsClearAncestorDirAuto(shadowRoot
);
655 nsIContent
* child
= aContent
->GetFirstChild();
657 if (child
->GetAssignedSlot()) {
658 // If the child node is assigned to a slot, nodes state is inherited from
659 // the slot, not from element's parent.
660 child
= child
->GetNextNonChildNode(aContent
);
663 if (child
->IsElement()) {
664 if (child
->AsElement()->HasDirAuto()) {
665 child
= child
->GetNextNonChildNode(aContent
);
669 if (auto* slot
= HTMLSlotElement::FromNode(child
)) {
670 const nsTArray
<RefPtr
<nsINode
>>& assignedNodes
= slot
->AssignedNodes();
671 for (uint32_t i
= 0; i
< assignedNodes
.Length(); ++i
) {
672 if (assignedNodes
[i
]->IsElement()) {
673 Element
* slottedElement
= assignedNodes
[i
]->AsElement();
674 if (slottedElement
->HasDirAuto()) {
679 nsIContent
* content
= assignedNodes
[i
]->AsContent();
680 content
->ClearAncestorHasDirAuto();
681 WalkDescendantsClearAncestorDirAuto(content
);
686 child
->ClearAncestorHasDirAuto();
687 child
= child
->GetNextNode(aContent
);
691 struct DirAutoElementResult
{
692 Element
* mElement
= nullptr;
693 // This is false when we hit the top of the ancestor chain without finding a
694 // dir=auto element or an element with a fixed direction. This is useful when
695 // processing node removals, since we might need to look at the subtree we're
697 bool mAnswerIsDefinitive
= false;
700 static DirAutoElementResult
FindDirAutoElementFrom(nsIContent
* aContent
) {
701 for (nsIContent
* parent
= aContent
;
702 parent
&& parent
->NodeOrAncestorHasDirAuto();
703 parent
= GetParentOrHostOrSlot(parent
)) {
704 auto* parentElement
= Element::FromNode(*parent
);
705 if (!parentElement
) {
708 if (!ParticipatesInAutoDirection(parentElement
) ||
709 parentElement
->HasFixedDir()) {
710 return {nullptr, true};
712 if (parentElement
->HasDirAuto()) {
713 return {parentElement
, true};
716 return {nullptr, false};
719 static DirAutoElementResult
FindDirAutoElementForText(nsTextNode
* aTextNode
) {
720 MOZ_ASSERT(aTextNode
->NodeType() == nsINode::TEXT_NODE
,
721 "Must be a text node");
722 return FindDirAutoElementFrom(GetParentOrHostOrSlot(aTextNode
));
725 static DirAutoElementResult
SetAncestorDirectionIfAuto(nsTextNode
* aTextNode
,
727 bool aNotify
= true) {
728 auto result
= FindDirAutoElementForText(aTextNode
);
729 if (Element
* parentElement
= result
.mElement
) {
730 if (parentElement
->GetDirectionality() == aDir
) {
731 // If we know that the directionality is already correct, we don't need to
732 // reset it. But we might be responsible for the directionality of
734 MOZ_ASSERT(aDir
!= Directionality::Unset
);
735 aTextNode
->SetMaySetDirAuto();
737 // Otherwise recompute the directionality of parentElement.
738 ResetAutoDirection(parentElement
, aNotify
);
744 bool TextNodeWillChangeDirection(nsTextNode
* aTextNode
, Directionality
* aOldDir
,
746 if (!NodeAffectsDirAutoAncestor(aTextNode
)) {
750 // If the change has happened after the first character with strong
751 // directionality in the text node, do nothing.
752 uint32_t firstStrong
;
753 *aOldDir
= GetDirectionFromText(aTextNode
, &firstStrong
);
754 return (aOffset
<= firstStrong
);
757 void TextNodeChangedDirection(nsTextNode
* aTextNode
, Directionality aOldDir
,
759 MOZ_ASSERT(NodeAffectsDirAutoAncestor(aTextNode
), "Caller should check");
760 Directionality newDir
= GetDirectionFromText(aTextNode
);
761 if (newDir
== aOldDir
) {
764 // If the old directionality is Unset, we might determine now dir=auto
765 // ancestor direction now, even if we don't have the MaySetDirAuto flag.
767 // Otherwise we used to have a strong directionality and either no longer
768 // does, or it changed. We might need to reset the direction.
769 if (aOldDir
== Directionality::Unset
|| aTextNode
->MaySetDirAuto()) {
770 SetAncestorDirectionIfAuto(aTextNode
, newDir
, aNotify
);
774 void SetDirectionFromNewTextNode(nsTextNode
* aTextNode
) {
775 if (!NodeAffectsDirAutoAncestor(aTextNode
)) {
779 nsIContent
* parent
= GetParentOrHostOrSlot(aTextNode
);
780 if (parent
&& parent
->NodeOrAncestorHasDirAuto()) {
781 aTextNode
->SetAncestorHasDirAuto();
784 Directionality dir
= GetDirectionFromText(aTextNode
);
785 if (dir
!= Directionality::Unset
) {
786 SetAncestorDirectionIfAuto(aTextNode
, dir
);
790 void ResetDirectionSetByTextNode(nsTextNode
* aTextNode
,
791 dom::UnbindContext
& aContext
) {
792 MOZ_ASSERT(!aTextNode
->IsInComposedDoc(), "Should be disconnected already");
793 if (!aTextNode
->MaySetDirAuto()) {
796 auto result
= FindDirAutoElementForText(aTextNode
);
797 if (result
.mAnswerIsDefinitive
) {
798 // The dir=auto element is in our (now detached) subtree. We're done, as
799 // nothing really changed for our purposes.
802 MOZ_ASSERT(!result
.mElement
);
803 // The dir=auto element might have been on the element we're unbinding from.
804 // In any case, this text node is clearly no longer what determines its
806 aTextNode
->ClearMaySetDirAuto();
808 nsIContent::FromNodeOrNull(aContext
.GetOriginalSubtreeParent());
809 if (!unboundFrom
|| !TextChildrenAffectDirAutoAncestor(unboundFrom
)) {
813 Directionality dir
= GetDirectionFromText(aTextNode
);
814 if (dir
== Directionality::Unset
) {
818 result
= FindDirAutoElementFrom(unboundFrom
);
819 if (!result
.mElement
|| result
.mElement
->GetDirectionality() != dir
) {
822 ResetAutoDirection(result
.mElement
, /* aNotify = */ true);
825 void SetDirectionalityFromValue(Element
* aElement
, const nsAString
& value
,
828 GetDirectionFromText(value
.BeginReading(), value
.Length());
829 if (dir
== Directionality::Unset
) {
830 dir
= Directionality::Ltr
;
833 if (aElement
->GetDirectionality() != dir
) {
834 aElement
->SetDirectionality(dir
, aNotify
);
838 void OnSetDirAttr(Element
* aElement
, const nsAttrValue
* aNewValue
,
839 bool hadValidDir
, bool hadDirAuto
, bool aNotify
) {
840 if (aElement
->IsAnyOfHTMLElements(nsGkAtoms::input
, nsGkAtoms::textarea
)) {
844 if (aElement
->AncestorHasDirAuto()) {
845 // The element is a descendant of an element with dir = auto, is having its
846 // dir attribute changed. Reset the direction of any of its ancestors whose
847 // direction might be determined by a text node descendant
848 WalkAncestorsResetAutoDirection(aElement
, aNotify
);
849 } else if (hadDirAuto
&& !aElement
->HasDirAuto()) {
850 // The element isn't a descendant of an element with dir = auto, and is
851 // having its dir attribute set to something other than auto.
852 // Walk the descendant tree and clear the AncestorHasDirAuto flag.
854 // N.B: For elements other than <bdi> it would be enough to test that the
855 // current value of dir was "auto" in BeforeSetAttr to know that we
856 // were unsetting dir="auto". For <bdi> things are more complicated,
857 // since it behaves like dir="auto" whenever the dir attribute is
858 // empty or invalid, so we would have to check whether the old value
859 // was not either "ltr" or "rtl", and the new value was either "ltr"
860 // or "rtl". Element::HasDirAuto() encapsulates all that, so doing it
862 WalkDescendantsClearAncestorDirAuto(aElement
);
865 if (aElement
->HasDirAuto()) {
866 WalkDescendantsSetDirAuto(aElement
, aNotify
);
868 SetDirectionalityOnDescendants(
869 aElement
, RecomputeDirectionality(aElement
, aNotify
), aNotify
);
873 void SetDirOnBind(Element
* aElement
, nsIContent
* aParent
) {
874 // Set the AncestorHasDirAuto flag, unless this element shouldn't affect
875 // ancestors that have dir=auto
876 if (ParticipatesInAutoDirection(aElement
) &&
877 !aElement
->IsHTMLElement(nsGkAtoms::bdi
) && aParent
&&
878 aParent
->NodeOrAncestorHasDirAuto()) {
879 aElement
->SetAncestorHasDirAuto();
881 SetAncestorHasDirAutoOnDescendants(aElement
);
883 if (aElement
->GetFirstChild() || aElement
->GetShadowRoot()) {
884 // We may also need to reset the direction of an ancestor with dir=auto
885 WalkAncestorsResetAutoDirection(aElement
, true);
889 if (!aElement
->HasDirAuto()) {
890 // if the element doesn't have dir=auto, set its own directionality from
891 // the dir attribute or by inheriting from its ancestors.
892 RecomputeDirectionality(aElement
, false);
896 void ResetDir(Element
* aElement
) {
897 if (!aElement
->HasDirAuto()) {
898 RecomputeDirectionality(aElement
, false);
902 } // end namespace mozilla