Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
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
88 in http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-dir-attribute
89 (I'll call this the "downward propagation algorithm".) by walking the child
90 subtree in tree order. Note that an element with @dir=auto should not affect
91 other elements in its document with @dir=auto. So there is no need to walk up
92 the parent chain in this case. TextNodeDirectionalityMap needs to be updated
93 as appropriate.
95 3a. When the dir attribute is set to any valid value on an element that didn't
96 have a valid dir attribute before, this means that any descendant of that
97 element will not affect the directionality of any of its ancestors. So we need
98 to check whether any text node descendants of the element are listed in
99 TextNodeDirectionalityMap, and whether the elements whose direction they set
100 are ancestors of the element. If so, we need to rerun the downward propagation
101 algorithm for those ancestors.
103 4. When the dir attribute is changed from auto to something else (including
104 the case where it gets removed) on a textarea or an input element with
105 @type=text/search/tel/url/email, we unset the NodeHasDirAuto flag and
106 resolve the directionality based on the directionality of the value of the @dir
107 attribute on element itself or its parent element.
109 5. When the dir attribute is changed from auto to something else (including the
110 case where it gets removed) on any element except case 4 above and the bdi
111 element, we run the following algorithm:
112 * We unset the NodeHasDirAuto flag.
113 * If the element does not have the NodeAncestorHasDirAuto flag, we unset
114 the NodeAncestorHasDirAuto flag on all of its child nodes, except those
115 who are a descendant of another element with NodeHasDirAuto. (Note that if
116 the element has the NodeAncestorHasDirAuto flag, all of its child nodes
117 should still retain the same flag.)
118 * We resolve the directionality of the element based on the value of the @dir
119 attribute on the element itself or its parent element.
120 TextNodeDirectionalityMap needs to be updated as appropriate.
122 5a. When the dir attribute is removed or set to an invalid value on any
123 element (except a bdi element) with the NodeAncestorHasDirAuto flag which
124 previously had a valid dir attribute, it might have a text node descendant that
125 did not previously affect the directionality of any of its ancestors but should
126 now begin to affect them.
127 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 it
136 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 to
143 happen but in the implementation UnbindFromTree is going to be called on all of
144 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 them
155 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 I'm
189 filing a spec bug to get it fixed -- the directionality should depend on the
190 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, nsGenericDOMDataNode::SetTextInternal
204 gets called.
205 */
207 #include "mozilla/dom/DirectionalityUtils.h"
209 #include "nsINode.h"
210 #include "nsIContent.h"
211 #include "nsIDocument.h"
212 #include "mozilla/DebugOnly.h"
213 #include "mozilla/dom/Element.h"
214 #include "nsIDOMHTMLDocument.h"
215 #include "nsUnicodeProperties.h"
216 #include "nsTextFragment.h"
217 #include "nsAttrValue.h"
218 #include "nsTextNode.h"
219 #include "nsCheapSets.h"
221 namespace mozilla {
223 using mozilla::dom::Element;
225 /**
226 * Returns true if aElement is one of the elements whose text content should not
227 * affect its own direction, nor the direction of ancestors with dir=auto.
228 *
229 * Note that this does not include <bdi>, whose content does affect its own
230 * direction when it has dir=auto (which it has by default), so one needs to
231 * test for it separately, e.g. with DoesNotAffectDirectionOfAncestors.
232 * It *does* include textarea, because even if a textarea has dir=auto, it has
233 * unicode-bidi: plaintext and is handled automatically in bidi resolution.
234 */
235 static bool
236 DoesNotParticipateInAutoDirection(const Element* aElement)
237 {
238 nsINodeInfo* nodeInfo = aElement->NodeInfo();
239 return (!aElement->IsHTML() ||
240 nodeInfo->Equals(nsGkAtoms::script) ||
241 nodeInfo->Equals(nsGkAtoms::style) ||
242 nodeInfo->Equals(nsGkAtoms::textarea) ||
243 aElement->IsInAnonymousSubtree());
244 }
246 static inline bool
247 IsBdiWithoutDirAuto(const Element* aElement)
248 {
249 // We are testing for bdi elements without explicit dir="auto", so we can't
250 // use the HasDirAuto() flag, since that will return true for bdi element with
251 // no dir attribute or an invalid dir attribute
252 return (aElement->IsHTML(nsGkAtoms::bdi) &&
253 (!aElement->HasValidDir() || aElement->HasFixedDir()));
254 }
256 /**
257 * Returns true if aElement is one of the element whose text content should not
258 * affect the direction of ancestors with dir=auto (though it may affect its own
259 * direction, e.g. <bdi>)
260 */
261 static bool
262 DoesNotAffectDirectionOfAncestors(const Element* aElement)
263 {
264 return (DoesNotParticipateInAutoDirection(aElement) ||
265 IsBdiWithoutDirAuto(aElement) ||
266 aElement->HasFixedDir());
267 }
269 /**
270 * Returns the directionality of a Unicode character
271 */
272 static Directionality
273 GetDirectionFromChar(uint32_t ch)
274 {
275 switch(mozilla::unicode::GetBidiCat(ch)) {
276 case eCharType_RightToLeft:
277 case eCharType_RightToLeftArabic:
278 return eDir_RTL;
280 case eCharType_LeftToRight:
281 return eDir_LTR;
283 default:
284 return eDir_NotSet;
285 }
286 }
288 inline static bool NodeAffectsDirAutoAncestor(nsINode* aTextNode)
289 {
290 Element* parent = aTextNode->GetParentElement();
291 return (parent &&
292 !DoesNotParticipateInAutoDirection(parent) &&
293 parent->NodeOrAncestorHasDirAuto());
294 }
296 /**
297 * Various methods for returning the directionality of a string using the
298 * first-strong algorithm defined in http://unicode.org/reports/tr9/#P2
299 *
300 * @param[out] aFirstStrong the offset to the first character in the string with
301 * strong directionality, or UINT32_MAX if there is none (return
302 value is eDir_NotSet).
303 * @return the directionality of the string
304 */
305 static Directionality
306 GetDirectionFromText(const char16_t* aText, const uint32_t aLength,
307 uint32_t* aFirstStrong = nullptr)
308 {
309 const char16_t* start = aText;
310 const char16_t* end = aText + aLength;
312 while (start < end) {
313 uint32_t current = start - aText;
314 uint32_t ch = *start++;
316 if (NS_IS_HIGH_SURROGATE(ch) &&
317 start < end &&
318 NS_IS_LOW_SURROGATE(*start)) {
319 ch = SURROGATE_TO_UCS4(ch, *start++);
320 current++;
321 }
323 // Just ignore lone surrogates
324 if (!IS_SURROGATE(ch)) {
325 Directionality dir = GetDirectionFromChar(ch);
326 if (dir != eDir_NotSet) {
327 if (aFirstStrong) {
328 *aFirstStrong = current;
329 }
330 return dir;
331 }
332 }
333 }
335 if (aFirstStrong) {
336 *aFirstStrong = UINT32_MAX;
337 }
338 return eDir_NotSet;
339 }
341 static Directionality
342 GetDirectionFromText(const char* aText, const uint32_t aLength,
343 uint32_t* aFirstStrong = nullptr)
344 {
345 const char* start = aText;
346 const char* end = aText + aLength;
348 while (start < end) {
349 uint32_t current = start - aText;
350 unsigned char ch = (unsigned char)*start++;
352 Directionality dir = GetDirectionFromChar(ch);
353 if (dir != eDir_NotSet) {
354 if (aFirstStrong) {
355 *aFirstStrong = current;
356 }
357 return dir;
358 }
359 }
361 if (aFirstStrong) {
362 *aFirstStrong = UINT32_MAX;
363 }
364 return eDir_NotSet;
365 }
367 static Directionality
368 GetDirectionFromText(const nsTextFragment* aFrag,
369 uint32_t* aFirstStrong = nullptr)
370 {
371 if (aFrag->Is2b()) {
372 return GetDirectionFromText(aFrag->Get2b(), aFrag->GetLength(),
373 aFirstStrong);
374 }
376 return GetDirectionFromText(aFrag->Get1b(), aFrag->GetLength(),
377 aFirstStrong);
378 }
380 /**
381 * Set the directionality of a node with dir=auto as defined in
382 * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality
383 *
384 * @param[in] changedNode If we call this method because the content of a text
385 * node is about to change, pass in the changed node, so that we
386 * know not to return it
387 * @return the text node containing the character that determined the direction
388 */
389 static nsINode*
390 WalkDescendantsSetDirectionFromText(Element* aElement, bool aNotify = true,
391 nsINode* aChangedNode = nullptr)
392 {
393 MOZ_ASSERT(aElement, "Must have an element");
394 MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto");
396 if (DoesNotParticipateInAutoDirection(aElement)) {
397 return nullptr;
398 }
400 nsIContent* child = aElement->GetFirstChild();
401 while (child) {
402 if (child->IsElement() &&
403 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
404 child = child->GetNextNonChildNode(aElement);
405 continue;
406 }
408 if (child->NodeType() == nsIDOMNode::TEXT_NODE &&
409 child != aChangedNode) {
410 Directionality textNodeDir = GetDirectionFromText(child->GetText());
411 if (textNodeDir != eDir_NotSet) {
412 // We found a descendant text node with strong directional characters.
413 // Set the directionality of aElement to the corresponding value.
414 aElement->SetDirectionality(textNodeDir, aNotify);
415 return child;
416 }
417 }
418 child = child->GetNextNode(aElement);
419 }
421 // We walked all the descendants without finding a text node with strong
422 // directional characters. Set the directionality to LTR
423 aElement->SetDirectionality(eDir_LTR, aNotify);
424 return nullptr;
425 }
427 class nsTextNodeDirectionalityMap
428 {
429 static void
430 nsTextNodeDirectionalityMapDtor(void *aObject, nsIAtom* aPropertyName,
431 void *aPropertyValue, void* aData)
432 {
433 nsINode* textNode = static_cast<nsINode * >(aObject);
434 textNode->ClearHasTextNodeDirectionalityMap();
436 nsTextNodeDirectionalityMap* map =
437 reinterpret_cast<nsTextNodeDirectionalityMap * >(aPropertyValue);
438 map->EnsureMapIsClear(textNode);
439 delete map;
440 }
442 public:
443 nsTextNodeDirectionalityMap(nsINode* aTextNode)
444 {
445 MOZ_ASSERT(aTextNode, "Null text node");
446 MOZ_COUNT_CTOR(nsTextNodeDirectionalityMap);
447 aTextNode->SetProperty(nsGkAtoms::textNodeDirectionalityMap, this,
448 nsTextNodeDirectionalityMapDtor);
449 aTextNode->SetHasTextNodeDirectionalityMap();
450 }
452 ~nsTextNodeDirectionalityMap()
453 {
454 MOZ_COUNT_DTOR(nsTextNodeDirectionalityMap);
455 }
457 void AddEntry(nsINode* aTextNode, Element* aElement)
458 {
459 if (!mElements.Contains(aElement)) {
460 mElements.Put(aElement);
461 aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode);
462 aElement->SetHasDirAutoSet();
463 }
464 }
466 void RemoveEntry(nsINode* aTextNode, Element* aElement)
467 {
468 NS_ASSERTION(mElements.Contains(aElement),
469 "element already removed from map");
471 mElements.Remove(aElement);
472 aElement->ClearHasDirAutoSet();
473 aElement->UnsetProperty(nsGkAtoms::dirAutoSetBy);
474 }
476 private:
477 nsCheapSet<nsPtrHashKey<Element> > mElements;
479 static nsTextNodeDirectionalityMap* GetDirectionalityMap(nsINode* aTextNode)
480 {
481 MOZ_ASSERT(aTextNode->NodeType() == nsIDOMNode::TEXT_NODE,
482 "Must be a text node");
483 nsTextNodeDirectionalityMap* map = nullptr;
485 if (aTextNode->HasTextNodeDirectionalityMap()) {
486 map = static_cast<nsTextNodeDirectionalityMap * >
487 (aTextNode->GetProperty(nsGkAtoms::textNodeDirectionalityMap));
488 }
490 return map;
491 }
493 static PLDHashOperator SetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aDir)
494 {
495 MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
496 aEntry->GetKey()->SetDirectionality(*reinterpret_cast<Directionality*>(aDir),
497 true);
498 return PL_DHASH_NEXT;
499 }
501 static PLDHashOperator ResetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aData)
502 {
503 MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
504 // run the downward propagation algorithm
505 // and remove the text node from the map
506 nsINode* oldTextNode = static_cast<Element*>(aData);
507 Element* rootNode = aEntry->GetKey();
508 nsINode* newTextNode = nullptr;
509 if (oldTextNode && rootNode->HasDirAuto()) {
510 newTextNode = WalkDescendantsSetDirectionFromText(rootNode, true,
511 oldTextNode);
512 }
513 if (newTextNode) {
514 nsTextNodeDirectionalityMap::AddEntryToMap(newTextNode, rootNode);
515 } else {
516 rootNode->ClearHasDirAutoSet();
517 rootNode->UnsetProperty(nsGkAtoms::dirAutoSetBy);
518 }
519 return PL_DHASH_REMOVE;
520 }
522 static PLDHashOperator ClearEntry(nsPtrHashKey<Element>* aEntry, void* aData)
523 {
524 Element* rootNode = aEntry->GetKey();
525 rootNode->ClearHasDirAutoSet();
526 rootNode->UnsetProperty(nsGkAtoms::dirAutoSetBy);
527 return PL_DHASH_REMOVE;
528 }
530 public:
531 void UpdateAutoDirection(Directionality aDir)
532 {
533 mElements.EnumerateEntries(SetNodeDirection, &aDir);
534 }
536 void ClearAutoDirection()
537 {
538 mElements.EnumerateEntries(ResetNodeDirection, nullptr);
539 }
541 void ResetAutoDirection(nsINode* aTextNode)
542 {
543 mElements.EnumerateEntries(ResetNodeDirection, aTextNode);
544 }
546 void EnsureMapIsClear(nsINode* aTextNode)
547 {
548 DebugOnly<uint32_t> clearedEntries =
549 mElements.EnumerateEntries(ClearEntry, aTextNode);
550 MOZ_ASSERT(clearedEntries == 0, "Map should be empty already");
551 }
553 static void RemoveElementFromMap(nsINode* aTextNode, Element* aElement)
554 {
555 if (aTextNode->HasTextNodeDirectionalityMap()) {
556 GetDirectionalityMap(aTextNode)->RemoveEntry(aTextNode, aElement);
557 }
558 }
560 static void AddEntryToMap(nsINode* aTextNode, Element* aElement)
561 {
562 nsTextNodeDirectionalityMap* map = GetDirectionalityMap(aTextNode);
563 if (!map) {
564 map = new nsTextNodeDirectionalityMap(aTextNode);
565 }
567 map->AddEntry(aTextNode, aElement);
568 }
570 static void UpdateTextNodeDirection(nsINode* aTextNode, Directionality aDir)
571 {
572 MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
573 "Map missing in UpdateTextNodeDirection");
574 GetDirectionalityMap(aTextNode)->UpdateAutoDirection(aDir);
575 }
577 static void ClearTextNodeDirection(nsINode* aTextNode)
578 {
579 MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
580 "Map missing in ResetTextNodeDirection");
581 GetDirectionalityMap(aTextNode)->ClearAutoDirection();
582 }
584 static void ResetTextNodeDirection(nsINode* aTextNode)
585 {
586 MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
587 "Map missing in ResetTextNodeDirection");
588 GetDirectionalityMap(aTextNode)->ResetAutoDirection(aTextNode);
589 }
591 static void EnsureMapIsClearFor(nsINode* aTextNode)
592 {
593 if (aTextNode->HasTextNodeDirectionalityMap()) {
594 GetDirectionalityMap(aTextNode)->EnsureMapIsClear(aTextNode);
595 }
596 }
597 };
599 Directionality
600 RecomputeDirectionality(Element* aElement, bool aNotify)
601 {
602 MOZ_ASSERT(!aElement->HasDirAuto(),
603 "RecomputeDirectionality called with dir=auto");
605 Directionality dir = eDir_LTR;
607 if (aElement->HasValidDir()) {
608 dir = aElement->GetDirectionality();
609 } else {
610 Element* parent = aElement->GetParentElement();
611 if (parent) {
612 // If the element doesn't have an explicit dir attribute with a valid
613 // value, the directionality is the same as the parent element (but
614 // don't propagate the parent directionality if it isn't set yet).
615 Directionality parentDir = parent->GetDirectionality();
616 if (parentDir != eDir_NotSet) {
617 dir = parentDir;
618 }
619 } else {
620 // If there is no parent element and no dir attribute, the directionality
621 // is LTR.
622 dir = eDir_LTR;
623 }
625 aElement->SetDirectionality(dir, aNotify);
626 }
627 return dir;
628 }
630 void
631 SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
632 bool aNotify)
633 {
634 for (nsIContent* child = aElement->GetFirstChild(); child; ) {
635 if (!child->IsElement()) {
636 child = child->GetNextNode(aElement);
637 continue;
638 }
640 Element* element = child->AsElement();
641 if (element->HasValidDir() || element->HasDirAuto()) {
642 child = child->GetNextNonChildNode(aElement);
643 continue;
644 }
645 element->SetDirectionality(aDir, aNotify);
646 child = child->GetNextNode(aElement);
647 }
648 }
650 /**
651 * Walk the parent chain of a text node whose dir attribute has been removed and
652 * reset the direction of any of its ancestors which have dir=auto and whose
653 * directionality is determined by a text node descendant.
654 */
655 void
656 WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify)
657 {
658 nsINode* setByNode;
659 Element* parent = aElement->GetParentElement();
661 while (parent && parent->NodeOrAncestorHasDirAuto()) {
662 if (parent->HasDirAutoSet()) {
663 // If the parent has the DirAutoSet flag, its direction is determined by
664 // some text node descendant.
665 // Remove it from the map and reset its direction by the downward
666 // propagation algorithm
667 setByNode =
668 static_cast<nsINode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
669 if (setByNode) {
670 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, parent);
671 }
672 }
673 if (parent->HasDirAuto()) {
674 setByNode = WalkDescendantsSetDirectionFromText(parent, aNotify);
675 if (setByNode) {
676 nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parent);
677 }
678 break;
679 }
680 parent = parent->GetParentElement();
681 }
682 }
684 void
685 WalkDescendantsResetAutoDirection(Element* aElement)
686 {
687 nsIContent* child = aElement->GetFirstChild();
688 while (child) {
689 if (child->HasDirAuto()) {
690 child = child->GetNextNonChildNode(aElement);
691 continue;
692 }
694 if (child->HasTextNodeDirectionalityMap()) {
695 nsTextNodeDirectionalityMap::ResetTextNodeDirection(child);
696 nsTextNodeDirectionalityMap::EnsureMapIsClearFor(child);
697 }
698 child = child->GetNextNode(aElement);
699 }
700 }
702 void
703 WalkDescendantsSetDirAuto(Element* aElement, bool aNotify)
704 {
705 // Only test for DoesNotParticipateInAutoDirection -- in other words, if
706 // aElement is a <bdi> which is having its dir attribute set to auto (or
707 // removed or set to an invalid value, which are equivalent to dir=auto for
708 // <bdi>, we *do* want to set AncestorHasDirAuto on its descendants, unlike
709 // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a <bdi>
710 // being bound to an existing node with dir=auto.
711 if (!DoesNotParticipateInAutoDirection(aElement)) {
713 bool setAncestorDirAutoFlag =
714 #ifdef DEBUG
715 true;
716 #else
717 !aElement->AncestorHasDirAuto();
718 #endif
720 if (setAncestorDirAutoFlag) {
721 nsIContent* child = aElement->GetFirstChild();
722 while (child) {
723 if (child->IsElement() &&
724 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
725 child = child->GetNextNonChildNode(aElement);
726 continue;
727 }
729 MOZ_ASSERT(!aElement->AncestorHasDirAuto() ||
730 child->AncestorHasDirAuto(),
731 "AncestorHasDirAuto set on node but not its children");
732 child->SetAncestorHasDirAuto();
733 child = child->GetNextNode(aElement);
734 }
735 }
736 }
738 nsINode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
739 if (textNode) {
740 nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement);
741 }
742 }
744 void
745 WalkDescendantsClearAncestorDirAuto(Element* aElement)
746 {
747 nsIContent* child = aElement->GetFirstChild();
748 while (child) {
749 if (child->HasDirAuto()) {
750 child = child->GetNextNonChildNode(aElement);
751 continue;
752 }
754 child->ClearAncestorHasDirAuto();
755 child = child->GetNextNode(aElement);
756 }
757 }
759 void SetAncestorDirectionIfAuto(nsINode* aTextNode, Directionality aDir,
760 bool aNotify = true)
761 {
762 MOZ_ASSERT(aTextNode->NodeType() == nsIDOMNode::TEXT_NODE,
763 "Must be a text node");
765 Element* parent = aTextNode->GetParentElement();
766 while (parent && parent->NodeOrAncestorHasDirAuto()) {
767 if (DoesNotParticipateInAutoDirection(parent) || parent->HasFixedDir()) {
768 break;
769 }
771 if (parent->HasDirAuto()) {
772 bool resetDirection = false;
773 nsINode* directionWasSetByTextNode =
774 static_cast<nsINode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
776 if (!parent->HasDirAutoSet()) {
777 // Fast path if parent's direction is not yet set by any descendant
778 MOZ_ASSERT(!directionWasSetByTextNode,
779 "dirAutoSetBy property should be null");
780 resetDirection = true;
781 } else {
782 // If parent's direction is already set, we need to know if
783 // aTextNode is before or after the text node that had set it.
784 // We will walk parent's descendants in tree order starting from
785 // aTextNode to optimize for the most common case where text nodes are
786 // being appended to tree.
787 if (!directionWasSetByTextNode) {
788 resetDirection = true;
789 } else if (directionWasSetByTextNode != aTextNode) {
790 nsIContent* child = aTextNode->GetNextNode(parent);
791 while (child) {
792 if (child->IsElement() &&
793 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
794 child = child->GetNextNonChildNode(parent);
795 continue;
796 }
798 if (child == directionWasSetByTextNode) {
799 // we found the node that set the element's direction after our
800 // text node, so we need to reset the direction
801 resetDirection = true;
802 break;
803 }
805 child = child->GetNextNode(parent);
806 }
807 }
808 }
810 if (resetDirection) {
811 if (directionWasSetByTextNode) {
812 nsTextNodeDirectionalityMap::RemoveElementFromMap(
813 directionWasSetByTextNode, parent
814 );
815 }
816 parent->SetDirectionality(aDir, aNotify);
817 nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parent);
818 SetDirectionalityOnDescendants(parent, aDir, aNotify);
819 }
821 // Since we found an element with dir=auto, we can stop walking the
822 // parent chain: none of its ancestors will have their direction set by
823 // any of its descendants.
824 return;
825 }
826 parent = parent->GetParentElement();
827 }
828 }
830 bool
831 TextNodeWillChangeDirection(nsIContent* aTextNode, Directionality* aOldDir,
832 uint32_t aOffset)
833 {
834 if (!NodeAffectsDirAutoAncestor(aTextNode)) {
835 nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
836 return false;
837 }
839 uint32_t firstStrong;
840 *aOldDir = GetDirectionFromText(aTextNode->GetText(), &firstStrong);
841 return (aOffset <= firstStrong);
842 }
844 void
845 TextNodeChangedDirection(nsIContent* aTextNode, Directionality aOldDir,
846 bool aNotify)
847 {
848 Directionality newDir = GetDirectionFromText(aTextNode->GetText());
849 if (newDir == eDir_NotSet) {
850 if (aOldDir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
851 // This node used to have a strong directional character but no
852 // longer does. ResetTextNodeDirection() will re-resolve the
853 // directionality of any elements whose directionality was
854 // determined by this node.
855 nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode);
856 }
857 } else {
858 // This node has a strong directional character. If it has a
859 // TextNodeDirectionalityMap property, it already determines the
860 // directionality of some element(s), so call UpdateTextNodeDirection to
861 // reresolve their directionality. Otherwise call
862 // SetAncestorDirectionIfAuto to find ancestor elements which should
863 // have their directionality determined by this node.
864 if (aTextNode->HasTextNodeDirectionalityMap()) {
865 nsTextNodeDirectionalityMap::UpdateTextNodeDirection(aTextNode, newDir);
866 } else {
867 SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify);
868 }
869 }
870 }
872 void
873 SetDirectionFromNewTextNode(nsIContent* aTextNode)
874 {
875 if (!NodeAffectsDirAutoAncestor(aTextNode)) {
876 return;
877 }
879 Element* parent = aTextNode->GetParentElement();
880 if (parent && parent->NodeOrAncestorHasDirAuto()) {
881 aTextNode->SetAncestorHasDirAuto();
882 }
884 Directionality dir = GetDirectionFromText(aTextNode->GetText());
885 if (dir != eDir_NotSet) {
886 SetAncestorDirectionIfAuto(aTextNode, dir);
887 }
888 }
890 void
891 ResetDirectionSetByTextNode(nsTextNode* aTextNode, bool aNullParent)
892 {
893 if (!NodeAffectsDirAutoAncestor(aTextNode)) {
894 nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
895 return;
896 }
898 Directionality dir = GetDirectionFromText(aTextNode->GetText());
899 if (dir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
900 if (aNullParent) {
901 nsTextNodeDirectionalityMap::ClearTextNodeDirection(aTextNode);
902 } else {
903 nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode);
904 }
905 }
906 }
908 void
909 SetDirectionalityFromValue(Element* aElement, const nsAString& value,
910 bool aNotify)
911 {
912 Directionality dir = GetDirectionFromText(PromiseFlatString(value).get(),
913 value.Length());
914 if (dir == eDir_NotSet) {
915 dir = eDir_LTR;
916 }
918 aElement->SetDirectionality(dir, aNotify);
919 }
921 void
922 OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue,
923 bool hadValidDir, bool hadDirAuto, bool aNotify)
924 {
925 if (aElement->IsHTML(nsGkAtoms::input)) {
926 return;
927 }
929 if (aElement->AncestorHasDirAuto()) {
930 if (!hadValidDir) {
931 // The element is a descendant of an element with dir = auto, is
932 // having its dir attribute set, and previously didn't have a valid dir
933 // attribute.
934 // Check whether any of its text node descendants determine the
935 // direction of any of its ancestors, and redetermine their direction
936 WalkDescendantsResetAutoDirection(aElement);
937 } else if (!aElement->HasValidDir()) {
938 // The element is a descendant of an element with dir = auto and is
939 // having its dir attribute removed or set to an invalid value.
940 // Reset the direction of any of its ancestors whose direction is
941 // determined by a text node descendant
942 WalkAncestorsResetAutoDirection(aElement, aNotify);
943 }
944 } else if (hadDirAuto && !aElement->HasDirAuto()) {
945 // The element isn't a descendant of an element with dir = auto, and is
946 // having its dir attribute set to something other than auto.
947 // Walk the descendant tree and clear the AncestorHasDirAuto flag.
948 //
949 // N.B: For elements other than <bdi> it would be enough to test that the
950 // current value of dir was "auto" in BeforeSetAttr to know that we
951 // were unsetting dir="auto". For <bdi> things are more complicated,
952 // since it behaves like dir="auto" whenever the dir attribute is
953 // empty or invalid, so we would have to check whether the old value
954 // was not either "ltr" or "rtl", and the new value was either "ltr"
955 // or "rtl". Element::HasDirAuto() encapsulates all that, so doing it
956 // here is simpler.
957 WalkDescendantsClearAncestorDirAuto(aElement);
958 }
960 if (aElement->HasDirAuto()) {
961 WalkDescendantsSetDirAuto(aElement, aNotify);
962 } else {
963 if (aElement->HasDirAutoSet()) {
964 nsINode* setByNode =
965 static_cast<nsINode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
966 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
967 }
968 SetDirectionalityOnDescendants(aElement,
969 RecomputeDirectionality(aElement, aNotify),
970 aNotify);
971 }
972 }
974 void
975 SetDirOnBind(mozilla::dom::Element* aElement, nsIContent* aParent)
976 {
977 // Set the AncestorHasDirAuto flag, unless this element shouldn't affect
978 // ancestors that have dir=auto
979 if (!DoesNotParticipateInAutoDirection(aElement) &&
980 !aElement->IsHTML(nsGkAtoms::bdi) &&
981 aParent && aParent->NodeOrAncestorHasDirAuto()) {
982 aElement->SetAncestorHasDirAuto();
984 nsIContent* child = aElement->GetFirstChild();
985 if (child) {
986 // If we are binding an element to the tree that already has descendants,
987 // and the parent has NodeHasDirAuto or NodeAncestorHasDirAuto, we need
988 // to set NodeAncestorHasDirAuto on all the element's descendants, except
989 // for nodes that don't affect the direction of their ancestors.
990 do {
991 if (child->IsElement() &&
992 DoesNotAffectDirectionOfAncestors(child->AsElement())) {
993 child = child->GetNextNonChildNode(aElement);
994 continue;
995 }
997 child->SetAncestorHasDirAuto();
998 child = child->GetNextNode(aElement);
999 } while (child);
1001 // We may also need to reset the direction of an ancestor with dir=auto
1002 WalkAncestorsResetAutoDirection(aElement, true);
1003 }
1004 }
1006 if (!aElement->HasDirAuto()) {
1007 // if the element doesn't have dir=auto, set its own directionality from
1008 // the dir attribute or by inheriting from its ancestors.
1009 RecomputeDirectionality(aElement, false);
1010 }
1011 }
1013 void ResetDir(mozilla::dom::Element* aElement)
1014 {
1015 if (aElement->HasDirAutoSet()) {
1016 nsINode* setByNode =
1017 static_cast<nsINode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
1018 nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
1019 }
1021 if (!aElement->HasDirAuto()) {
1022 RecomputeDirectionality(aElement, false);
1023 }
1024 }
1026 } // end namespace mozilla