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