Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/base/DirectionalityUtils.cpp
Line
Count
Source (jump to first uncovered line)
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 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.
94
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.
102
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.
108
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.
121
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.
134
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.
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 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).
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 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.
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 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.
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 "nsIDocument.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 "nsUnicodeProperties.h"
219
#include "nsTextFragment.h"
220
#include "nsAttrValue.h"
221
#include "nsTextNode.h"
222
#include "nsCheapSets.h"
223
224
namespace mozilla {
225
226
using mozilla::dom::Element;
227
using mozilla::dom::ShadowRoot;
228
229
static nsIContent*
230
GetParentOrHostOrSlot(nsIContent* aContent,
231
                      bool* aCrossedShadowBoundary = nullptr)
232
0
{
233
0
  HTMLSlotElement* slot = aContent->GetAssignedSlot();
234
0
  if (slot) {
235
0
    if (aCrossedShadowBoundary) {
236
0
      *aCrossedShadowBoundary = true;
237
0
    }
238
0
    return slot;
239
0
  }
240
0
241
0
  nsIContent* parent = aContent->GetParent();
242
0
  if (parent) {
243
0
    return parent;
244
0
  }
245
0
246
0
  ShadowRoot* sr = ShadowRoot::FromNode(aContent);
247
0
  if (sr) {
248
0
    if (aCrossedShadowBoundary) {
249
0
      *aCrossedShadowBoundary = true;
250
0
    }
251
0
    return sr->Host();
252
0
  }
253
0
254
0
  return nullptr;
255
0
}
256
257
static bool
258
AncestorChainCrossesShadowBoundary(nsIContent* aDescendant,
259
                                   nsIContent* aAncestor)
260
0
{
261
0
  bool crossedShadowBoundary = false;
262
0
  nsIContent* content = aDescendant;
263
0
  while(content && content != aAncestor) {
264
0
    content = GetParentOrHostOrSlot(content, &crossedShadowBoundary);
265
0
    if (crossedShadowBoundary) {
266
0
      return true;
267
0
    }
268
0
  }
269
0
270
0
  return false;
271
0
}
272
273
/**
274
 * Returns true if aElement is one of the elements whose text content should not
275
 * affect its own direction, nor the direction of ancestors with dir=auto.
276
 *
277
 * Note that this does not include <bdi>, whose content does affect its own
278
 * direction when it has dir=auto (which it has by default), so one needs to
279
 * test for it separately, e.g. with DoesNotAffectDirectionOfAncestors.
280
 * It *does* include textarea, because even if a textarea has dir=auto, it has
281
 * unicode-bidi: plaintext and is handled automatically in bidi resolution.
282
 */
283
static bool
284
DoesNotParticipateInAutoDirection(const nsIContent* aContent)
285
0
{
286
0
  mozilla::dom::NodeInfo* nodeInfo = aContent->NodeInfo();
287
0
  return ((!aContent->IsHTMLElement() ||
288
0
           nodeInfo->Equals(nsGkAtoms::script) ||
289
0
           nodeInfo->Equals(nsGkAtoms::style) ||
290
0
           nodeInfo->Equals(nsGkAtoms::textarea) ||
291
0
           aContent->IsInAnonymousSubtree())) &&
292
0
          !aContent->IsShadowRoot();
293
0
}
294
295
/**
296
 * Returns true if aElement is one of the element whose text content should not
297
 * affect the direction of ancestors with dir=auto (though it may affect its own
298
 * direction, e.g. <bdi>)
299
 */
300
static bool
301
DoesNotAffectDirectionOfAncestors(const Element* aElement)
302
0
{
303
0
  return (DoesNotParticipateInAutoDirection(aElement) ||
304
0
          aElement->IsHTMLElement(nsGkAtoms::bdi) ||
305
0
          aElement->HasFixedDir());
306
0
}
307
308
/**
309
 * Returns the directionality of a Unicode character
310
 */
311
static Directionality
312
GetDirectionFromChar(uint32_t ch)
313
{
314
  switch(mozilla::unicode::GetBidiCat(ch)) {
315
    case eCharType_RightToLeft:
316
    case eCharType_RightToLeftArabic:
317
      return eDir_RTL;
318
319
    case eCharType_LeftToRight:
320
      return eDir_LTR;
321
322
    default:
323
      return eDir_NotSet;
324
  }
325
}
326
327
inline static bool
328
NodeAffectsDirAutoAncestor(nsIContent* aTextNode)
329
0
{
330
0
  nsIContent* parent = GetParentOrHostOrSlot(aTextNode);
331
0
  return (parent &&
332
0
          !DoesNotParticipateInAutoDirection(parent) &&
333
0
          parent->NodeOrAncestorHasDirAuto() &&
334
0
          !aTextNode->IsInAnonymousSubtree());
335
0
}
336
337
Directionality
338
GetDirectionFromText(const char16_t* aText, const uint32_t aLength,
339
                     uint32_t* aFirstStrong)
340
0
{
341
0
  const char16_t* start = aText;
342
0
  const char16_t* end = aText + aLength;
343
0
344
0
  while (start < end) {
345
0
    uint32_t current = start - aText;
346
0
    uint32_t ch = *start++;
347
0
348
0
    if (NS_IS_HIGH_SURROGATE(ch) &&
349
0
        start < end &&
350
0
        NS_IS_LOW_SURROGATE(*start)) {
351
0
      ch = SURROGATE_TO_UCS4(ch, *start++);
352
0
      current++;
353
0
    }
354
0
355
0
    // Just ignore lone surrogates
356
0
    if (!IS_SURROGATE(ch)) {
357
0
      Directionality dir = GetDirectionFromChar(ch);
358
0
      if (dir != eDir_NotSet) {
359
0
        if (aFirstStrong) {
360
0
          *aFirstStrong = current;
361
0
        }
362
0
        return dir;
363
0
      }
364
0
    }
365
0
  }
366
0
367
0
  if (aFirstStrong) {
368
0
    *aFirstStrong = UINT32_MAX;
369
0
  }
370
0
  return eDir_NotSet;
371
0
}
372
373
static Directionality
374
GetDirectionFromText(const char* aText, const uint32_t aLength,
375
                        uint32_t* aFirstStrong = nullptr)
376
0
{
377
0
  const char* start = aText;
378
0
  const char* end = aText + aLength;
379
0
380
0
  while (start < end) {
381
0
    uint32_t current = start - aText;
382
0
    unsigned char ch = (unsigned char)*start++;
383
0
384
0
    Directionality dir = GetDirectionFromChar(ch);
385
0
    if (dir != eDir_NotSet) {
386
0
      if (aFirstStrong) {
387
0
        *aFirstStrong = current;
388
0
      }
389
0
      return dir;
390
0
    }
391
0
  }
392
0
393
0
  if (aFirstStrong) {
394
0
    *aFirstStrong = UINT32_MAX;
395
0
  }
396
0
  return eDir_NotSet;
397
0
}
398
399
static Directionality
400
GetDirectionFromText(const nsTextFragment* aFrag,
401
                     uint32_t* aFirstStrong = nullptr)
402
0
{
403
0
  if (aFrag->Is2b()) {
404
0
    return GetDirectionFromText(aFrag->Get2b(), aFrag->GetLength(),
405
0
                                   aFirstStrong);
406
0
  }
407
0
408
0
  return GetDirectionFromText(aFrag->Get1b(), aFrag->GetLength(),
409
0
                                 aFirstStrong);
410
0
}
411
412
static nsTextNode*
413
WalkDescendantsAndGetDirectionFromText(nsINode* aRoot,
414
                                       nsINode* aSkip,
415
                                       Directionality* aDirectionality)
416
0
{
417
0
  nsIContent* child = aRoot->GetFirstChild();
418
0
  while (child) {
419
0
    if ((child->IsElement() &&
420
0
         DoesNotAffectDirectionOfAncestors(child->AsElement())) ||
421
0
        child->GetAssignedSlot()) {
422
0
      child = child->GetNextNonChildNode(aRoot);
423
0
      continue;
424
0
    }
425
0
426
0
    HTMLSlotElement* slot = HTMLSlotElement::FromNode(child);
427
0
    if (slot) {
428
0
      const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
429
0
      for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
430
0
        nsIContent* assignedNode = assignedNodes[i]->AsContent();
431
0
        if (assignedNode->NodeType() == nsINode::TEXT_NODE) {
432
0
          if (assignedNode != aSkip) {
433
0
            Directionality textNodeDir = GetDirectionFromText(assignedNode->GetText());
434
0
            if (textNodeDir != eDir_NotSet) {
435
0
              *aDirectionality = textNodeDir;
436
0
              return static_cast<nsTextNode*>(assignedNode);
437
0
            }
438
0
          }
439
0
        } else if (assignedNode->IsElement() &&
440
0
                   !DoesNotAffectDirectionOfAncestors(assignedNode->AsElement())) {
441
0
          nsTextNode* text = WalkDescendantsAndGetDirectionFromText(
442
0
            assignedNode, aSkip, aDirectionality);
443
0
          if (text) {
444
0
            return text;
445
0
          }
446
0
        }
447
0
      }
448
0
    }
449
0
450
0
    if (child->NodeType() == nsINode::TEXT_NODE &&
451
0
        child != aSkip) {
452
0
      Directionality textNodeDir = GetDirectionFromText(child->GetText());
453
0
      if (textNodeDir != eDir_NotSet) {
454
0
        *aDirectionality = textNodeDir;
455
0
        return static_cast<nsTextNode*>(child);
456
0
      }
457
0
    }
458
0
    child = child->GetNextNode(aRoot);
459
0
  }
460
0
461
0
  return nullptr;
462
0
}
463
464
/**
465
 * Set the directionality of a node with dir=auto as defined in
466
 * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality
467
 *
468
 * @param[in] changedNode If we call this method because the content of a text
469
 *            node is about to change, pass in the changed node, so that we
470
 *            know not to return it
471
 * @return the text node containing the character that determined the direction
472
 */
473
static nsTextNode*
474
WalkDescendantsSetDirectionFromText(Element* aElement, bool aNotify,
475
                                    nsINode* aChangedNode = nullptr)
476
0
{
477
0
  MOZ_ASSERT(aElement, "Must have an element");
478
0
  MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto");
479
0
480
0
  if (DoesNotParticipateInAutoDirection(aElement)) {
481
0
    return nullptr;
482
0
  }
483
0
484
0
  Directionality textNodeDir = eDir_NotSet;
485
0
486
0
  // Check the text in Shadow DOM.
487
0
  if (ShadowRoot* shadowRoot = aElement->GetShadowRoot()) {
488
0
    nsTextNode* text = WalkDescendantsAndGetDirectionFromText(shadowRoot,
489
0
                                                              aChangedNode,
490
0
                                                              &textNodeDir);
491
0
    if (text) {
492
0
      aElement->SetDirectionality(textNodeDir, aNotify);
493
0
      return text;
494
0
    }
495
0
  }
496
0
497
0
  // Check the text in light DOM.
498
0
  nsTextNode* text = WalkDescendantsAndGetDirectionFromText(aElement,
499
0
                                                            aChangedNode,
500
0
                                                            &textNodeDir);
501
0
  if (text) {
502
0
    aElement->SetDirectionality(textNodeDir, aNotify);
503
0
    return text;
504
0
  }
505
0
506
0
  // We walked all the descendants without finding a text node with strong
507
0
  // directional characters. Set the directionality to LTR
508
0
  aElement->SetDirectionality(eDir_LTR, aNotify);
509
0
  return nullptr;
510
0
}
511
512
class nsTextNodeDirectionalityMap
513
{
514
  static void
515
  nsTextNodeDirectionalityMapDtor(void *aObject, nsAtom* aPropertyName,
516
                                  void *aPropertyValue, void* aData)
517
0
  {
518
0
    nsINode* textNode = static_cast<nsINode * >(aObject);
519
0
    textNode->ClearHasTextNodeDirectionalityMap();
520
0
521
0
    nsTextNodeDirectionalityMap* map =
522
0
      reinterpret_cast<nsTextNodeDirectionalityMap * >(aPropertyValue);
523
0
    map->EnsureMapIsClear();
524
0
    delete map;
525
0
  }
526
527
public:
528
  explicit nsTextNodeDirectionalityMap(nsINode* aTextNode)
529
    : mElementToBeRemoved(nullptr)
530
0
  {
531
0
    MOZ_ASSERT(aTextNode, "Null text node");
532
0
    MOZ_COUNT_CTOR(nsTextNodeDirectionalityMap);
533
0
    aTextNode->SetProperty(nsGkAtoms::textNodeDirectionalityMap, this,
534
0
                           nsTextNodeDirectionalityMapDtor);
535
0
    aTextNode->SetHasTextNodeDirectionalityMap();
536
0
  }
537
538
  ~nsTextNodeDirectionalityMap()
539
0
  {
540
0
    MOZ_COUNT_DTOR(nsTextNodeDirectionalityMap);
541
0
  }
542
543
  static void
544
  nsTextNodeDirectionalityMapPropertyDestructor(void* aObject,
545
                                                nsAtom* aProperty,
546
                                                void* aPropertyValue,
547
                                                void* aData)
548
0
  {
549
0
    nsTextNode* textNode =
550
0
      static_cast<nsTextNode*>(aPropertyValue);
551
0
    nsTextNodeDirectionalityMap* map = GetDirectionalityMap(textNode);
552
0
    if (map) {
553
0
      map->RemoveEntryForProperty(static_cast<Element*>(aObject));
554
0
    }
555
0
    NS_RELEASE(textNode);
556
0
  }
557
558
  void AddEntry(nsTextNode* aTextNode, Element* aElement)
559
0
  {
560
0
    if (!mElements.Contains(aElement)) {
561
0
      mElements.Put(aElement);
562
0
      NS_ADDREF(aTextNode);
563
0
      aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode,
564
0
                            nsTextNodeDirectionalityMapPropertyDestructor);
565
0
      aElement->SetHasDirAutoSet();
566
0
    }
567
0
  }
568
569
  void RemoveEntry(nsTextNode* aTextNode, Element* aElement)
570
0
  {
571
0
    NS_ASSERTION(mElements.Contains(aElement),
572
0
                 "element already removed from map");
573
0
574
0
    mElements.Remove(aElement);
575
0
    aElement->ClearHasDirAutoSet();
576
0
    aElement->DeleteProperty(nsGkAtoms::dirAutoSetBy);
577
0
  }
578
579
  void RemoveEntryForProperty(Element* aElement)
580
0
  {
581
0
    if (mElementToBeRemoved != aElement) {
582
0
      mElements.Remove(aElement);
583
0
    }
584
0
    aElement->ClearHasDirAutoSet();
585
0
  }
586
587
private:
588
  nsCheapSet<nsPtrHashKey<Element>> mElements;
589
  // Only used for comparison.
590
  Element* mElementToBeRemoved;
591
592
  static nsTextNodeDirectionalityMap* GetDirectionalityMap(nsINode* aTextNode)
593
0
  {
594
0
    MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE,
595
0
               "Must be a text node");
596
0
    nsTextNodeDirectionalityMap* map = nullptr;
597
0
598
0
    if (aTextNode->HasTextNodeDirectionalityMap()) {
599
0
      map = static_cast<nsTextNodeDirectionalityMap * >
600
0
        (aTextNode->GetProperty(nsGkAtoms::textNodeDirectionalityMap));
601
0
    }
602
0
603
0
    return map;
604
0
  }
605
606
  static nsCheapSetOperator SetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aDir)
607
0
  {
608
0
    aEntry->GetKey()->SetDirectionality(*reinterpret_cast<Directionality*>(aDir),
609
0
                                        true);
610
0
    return OpNext;
611
0
  }
612
613
  struct nsTextNodeDirectionalityMapAndElement
614
  {
615
    nsTextNodeDirectionalityMap* mMap;
616
    nsCOMPtr<nsINode> mNode;
617
  };
618
619
  static nsCheapSetOperator ResetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aData)
620
0
  {
621
0
    // run the downward propagation algorithm
622
0
    // and remove the text node from the map
623
0
    nsTextNodeDirectionalityMapAndElement* data =
624
0
      static_cast<nsTextNodeDirectionalityMapAndElement*>(aData);
625
0
    nsINode* oldTextNode = data->mNode;
626
0
    Element* rootNode = aEntry->GetKey();
627
0
    nsTextNode* newTextNode = nullptr;
628
0
    if (rootNode->GetParentNode() && rootNode->HasDirAuto()) {
629
0
      newTextNode = WalkDescendantsSetDirectionFromText(rootNode, true,
630
0
                                                        oldTextNode);
631
0
    }
632
0
633
0
    AutoRestore<Element*> restore(data->mMap->mElementToBeRemoved);
634
0
    data->mMap->mElementToBeRemoved = rootNode;
635
0
    if (newTextNode) {
636
0
      nsINode* oldDirAutoSetBy =
637
0
        static_cast<nsTextNode*>(rootNode->GetProperty(nsGkAtoms::dirAutoSetBy));
638
0
      if (oldDirAutoSetBy == newTextNode) {
639
0
        // We're already registered.
640
0
        return OpNext;
641
0
      }
642
0
      nsTextNodeDirectionalityMap::AddEntryToMap(newTextNode, rootNode);
643
0
    } else {
644
0
      rootNode->ClearHasDirAutoSet();
645
0
      rootNode->DeleteProperty(nsGkAtoms::dirAutoSetBy);
646
0
    }
647
0
    return OpRemove;
648
0
  }
649
650
  static nsCheapSetOperator TakeEntries(nsPtrHashKey<Element>* aEntry, void* aData)
651
0
  {
652
0
    AutoTArray<Element*, 8>* entries =
653
0
      static_cast<AutoTArray<Element*, 8>*>(aData);
654
0
    entries->AppendElement(aEntry->GetKey());
655
0
    return OpRemove;
656
0
  }
657
658
public:
659
  uint32_t UpdateAutoDirection(Directionality aDir)
660
0
  {
661
0
    return mElements.EnumerateEntries(SetNodeDirection, &aDir);
662
0
  }
663
664
  void ResetAutoDirection(nsINode* aTextNode)
665
0
  {
666
0
    nsTextNodeDirectionalityMapAndElement data = { this, aTextNode };
667
0
    mElements.EnumerateEntries(ResetNodeDirection, &data);
668
0
  }
669
670
  void EnsureMapIsClear()
671
0
  {
672
0
    AutoRestore<Element*> restore(mElementToBeRemoved);
673
0
    AutoTArray<Element*, 8> entries;
674
0
    mElements.EnumerateEntries(TakeEntries, &entries);
675
0
    for (Element* el : entries) {
676
0
      el->ClearHasDirAutoSet();
677
0
      el->DeleteProperty(nsGkAtoms::dirAutoSetBy);
678
0
    }
679
0
  }
680
681
  static void RemoveElementFromMap(nsTextNode* aTextNode, Element* aElement)
682
0
  {
683
0
    if (aTextNode->HasTextNodeDirectionalityMap()) {
684
0
      GetDirectionalityMap(aTextNode)->RemoveEntry(aTextNode, aElement);
685
0
    }
686
0
  }
687
688
  static void AddEntryToMap(nsTextNode* aTextNode, Element* aElement)
689
0
  {
690
0
    nsTextNodeDirectionalityMap* map = GetDirectionalityMap(aTextNode);
691
0
    if (!map) {
692
0
      map = new nsTextNodeDirectionalityMap(aTextNode);
693
0
    }
694
0
695
0
    map->AddEntry(aTextNode, aElement);
696
0
  }
697
698
  static uint32_t UpdateTextNodeDirection(nsINode* aTextNode,
699
                                          Directionality aDir)
700
0
  {
701
0
    MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
702
0
               "Map missing in UpdateTextNodeDirection");
703
0
    return GetDirectionalityMap(aTextNode)->UpdateAutoDirection(aDir);
704
0
  }
705
706
  static void ResetTextNodeDirection(nsTextNode* aTextNode,
707
                                     nsTextNode* aChangedTextNode)
708
0
  {
709
0
    MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
710
0
               "Map missing in ResetTextNodeDirection");
711
0
    RefPtr<nsTextNode> textNode = aTextNode;
712
0
    GetDirectionalityMap(textNode)->ResetAutoDirection(aChangedTextNode);
713
0
  }
714
715
  static void EnsureMapIsClearFor(nsINode* aTextNode)
716
0
  {
717
0
    if (aTextNode->HasTextNodeDirectionalityMap()) {
718
0
      GetDirectionalityMap(aTextNode)->EnsureMapIsClear();
719
0
    }
720
0
  }
721
};
722
723
Directionality
724
RecomputeDirectionality(Element* aElement, bool aNotify)
725
0
{
726
0
  MOZ_ASSERT(!aElement->HasDirAuto(),
727
0
             "RecomputeDirectionality called with dir=auto");
728
0
729
0
  if (aElement->HasValidDir()) {
730
0
    return aElement->GetDirectionality();
731
0
  }
732
0
733
0
  Directionality dir = eDir_LTR;
734
0
  if (nsIContent* parent = GetParentOrHostOrSlot(aElement)) {
735
0
    if (ShadowRoot* shadow = ShadowRoot::FromNode(parent)) {
736
0
      parent = shadow->GetHost();
737
0
    }
738
0
739
0
    if (parent && parent->IsElement()) {
740
0
      // If the node doesn't have an explicit dir attribute with a valid value,
741
0
      // the directionality is the same as the parent element (but don't propagate
742
0
      // the parent directionality if it isn't set yet).
743
0
      Directionality parentDir = parent->AsElement()->GetDirectionality();
744
0
      if (parentDir != eDir_NotSet) {
745
0
        dir = parentDir;
746
0
      }
747
0
    }
748
0
  }
749
0
750
0
  aElement->SetDirectionality(dir, aNotify);
751
0
  return dir;
752
0
}
753
754
static void
755
SetDirectionalityOnDescendantsInternal(nsINode* aNode,
756
                                       Directionality aDir,
757
                                       bool aNotify)
758
0
{
759
0
  if (Element* element = Element::FromNode(aNode)) {
760
0
    if (ShadowRoot* shadow = element->GetShadowRoot()) {
761
0
      SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify);
762
0
    }
763
0
  }
764
0
765
0
  for (nsIContent* child = aNode->GetFirstChild(); child; ) {
766
0
    if (!child->IsElement()) {
767
0
      child = child->GetNextNode(aNode);
768
0
      continue;
769
0
    }
770
0
771
0
    Element* element = child->AsElement();
772
0
    if (element->HasValidDir() || element->HasDirAuto() ||
773
0
        element->GetAssignedSlot()) {
774
0
      child = child->GetNextNonChildNode(aNode);
775
0
      continue;
776
0
    }
777
0
    if (ShadowRoot* shadow = element->GetShadowRoot()) {
778
0
      SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify);
779
0
    }
780
0
781
0
    HTMLSlotElement* slot = HTMLSlotElement::FromNode(child);
782
0
    if (slot) {
783
0
      const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
784
0
      for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
785
0
        nsINode* node = assignedNodes[i];
786
0
        Element* assignedElement =
787
0
          node->IsElement() ? node->AsElement() : nullptr;
788
0
        if (assignedElement && !assignedElement->HasValidDir() &&
789
0
            !assignedElement->HasDirAuto()) {
790
0
          assignedElement->SetDirectionality(aDir, aNotify);
791
0
          SetDirectionalityOnDescendantsInternal(assignedElement, aDir, aNotify);
792
0
        }
793
0
      }
794
0
    }
795
0
796
0
    element->SetDirectionality(aDir, aNotify);
797
0
798
0
    child = child->GetNextNode(aNode);
799
0
  }
800
0
}
801
802
// We want the public version of this only to acc
803
void
804
SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
805
                               bool aNotify)
806
0
{
807
0
  return SetDirectionalityOnDescendantsInternal(aElement, aDir, aNotify);
808
0
}
809
810
static void
811
ResetAutoDirection(Element* aElement, bool aNotify)
812
0
{
813
0
  if (aElement->HasDirAutoSet()) {
814
0
    // If the parent has the DirAutoSet flag, its direction is determined by
815
0
    // some text node descendant.
816
0
    // Remove it from the map and reset its direction by the downward
817
0
    // propagation algorithm
818
0
    nsTextNode* setByNode =
819
0
      static_cast<nsTextNode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
820
0
    if (setByNode) {
821
0
      nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode,
822
0
                                                        aElement);
823
0
    }
824
0
  }
825
0
826
0
  if (aElement->HasDirAuto()) {
827
0
    nsTextNode* setByNode =
828
0
      WalkDescendantsSetDirectionFromText(aElement, aNotify);
829
0
    if (setByNode) {
830
0
      nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, aElement);
831
0
    }
832
0
    SetDirectionalityOnDescendants(aElement, aElement->GetDirectionality(),
833
0
                                   aNotify);
834
0
  }
835
0
}
836
837
/**
838
 * Walk the parent chain of a text node whose dir attribute has been removed and
839
 * reset the direction of any of its ancestors which have dir=auto and whose
840
 * directionality is determined by a text node descendant.
841
 */
842
void
843
WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify)
844
0
{
845
0
  nsTextNode* setByNode;
846
0
  nsIContent* parent = GetParentOrHostOrSlot(aElement);
847
0
  while (parent && parent->NodeOrAncestorHasDirAuto()) {
848
0
    if (!parent->IsElement()) {
849
0
      parent = GetParentOrHostOrSlot(parent);
850
0
      continue;
851
0
    }
852
0
853
0
    Element* parentElement = parent->AsElement();
854
0
    if (parent->HasDirAutoSet()) {
855
0
      // If the parent has the DirAutoSet flag, its direction is determined by
856
0
      // some text node descendant.
857
0
      // Remove it from the map and reset its direction by the downward
858
0
      // propagation algorithm
859
0
      setByNode =
860
0
        static_cast<nsTextNode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
861
0
      if (setByNode) {
862
0
        nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode,
863
0
                                                          parentElement);
864
0
      }
865
0
    }
866
0
    if (parentElement->HasDirAuto()) {
867
0
      setByNode = WalkDescendantsSetDirectionFromText(parentElement, aNotify);
868
0
      if (setByNode) {
869
0
        nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parentElement);
870
0
      }
871
0
      SetDirectionalityOnDescendants(parentElement, parentElement->GetDirectionality(),
872
0
                                     aNotify);
873
0
      break;
874
0
    }
875
0
    parent = GetParentOrHostOrSlot(parent);
876
0
  }
877
0
}
878
879
void
880
SlotStateChanged(HTMLSlotElement* aSlot)
881
0
{
882
0
  if (!aSlot) {
883
0
    return;
884
0
  }
885
0
  if (aSlot->HasDirAuto()) {
886
0
    ResetAutoDirection(aSlot, true);
887
0
  }
888
0
  if (aSlot->NodeOrAncestorHasDirAuto()) {
889
0
    WalkAncestorsResetAutoDirection(aSlot, true);
890
0
  }
891
0
892
0
  const nsTArray<RefPtr<nsINode>>& assignedNodes = aSlot->AssignedNodes();
893
0
  for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
894
0
    nsINode* node = assignedNodes[i];
895
0
    Element* assignedElement =
896
0
      node->IsElement() ? node->AsElement() : nullptr;
897
0
    // Try to optimize out state changes when possible.
898
0
    if (assignedElement && !assignedElement->HasValidDir() &&
899
0
        !assignedElement->HasDirAuto() &&
900
0
        assignedElement->GetDirectionality() !=
901
0
          aSlot->GetDirectionality()) {
902
0
      assignedElement->SetDirectionality(aSlot->GetDirectionality(),
903
0
                                         true);
904
0
      SetDirectionalityOnDescendantsInternal(assignedElement,
905
0
                                             aSlot->GetDirectionality(),
906
0
                                             true);
907
0
    }
908
0
  }
909
0
}
910
911
void
912
WalkDescendantsResetAutoDirection(Element* aElement)
913
0
{
914
0
  nsIContent* child = aElement->GetFirstChild();
915
0
  while (child) {
916
0
    if (child->IsElement() && child->AsElement()->HasDirAuto()) {
917
0
      child = child->GetNextNonChildNode(aElement);
918
0
      continue;
919
0
    }
920
0
921
0
    if (child->NodeType() == nsINode::TEXT_NODE &&
922
0
        child->HasTextNodeDirectionalityMap()) {
923
0
      nsTextNodeDirectionalityMap::ResetTextNodeDirection(static_cast<nsTextNode*>(child), nullptr);
924
0
      // Don't call nsTextNodeDirectionalityMap::EnsureMapIsClearFor(child)
925
0
      // since ResetTextNodeDirection may have kept elements in child's
926
0
      // DirectionalityMap.
927
0
    }
928
0
    child = child->GetNextNode(aElement);
929
0
  }
930
0
}
931
932
static void
933
SetAncestorHasDirAutoOnDescendants(nsINode* aRoot);
934
935
static void
936
MaybeSetAncestorHasDirAutoOnShadowDOM(nsINode* aNode)
937
0
{
938
0
  if (aNode->IsElement()) {
939
0
    if (ShadowRoot* sr = aNode->AsElement()->GetShadowRoot()) {
940
0
      sr->SetAncestorHasDirAuto();
941
0
      SetAncestorHasDirAutoOnDescendants(sr);
942
0
    }
943
0
  }
944
0
}
945
946
static void
947
SetAncestorHasDirAutoOnDescendants(nsINode* aRoot)
948
0
{
949
0
  MaybeSetAncestorHasDirAutoOnShadowDOM(aRoot);
950
0
951
0
  nsIContent* child = aRoot->GetFirstChild();
952
0
  while (child) {
953
0
    if (child->IsElement() &&
954
0
        DoesNotAffectDirectionOfAncestors(child->AsElement())) {
955
0
      child = child->GetNextNonChildNode(aRoot);
956
0
      continue;
957
0
    }
958
0
959
0
    // If the child is assigned to a slot, it should inherit the state from
960
0
    // that.
961
0
    if (!child->GetAssignedSlot()) {
962
0
      MaybeSetAncestorHasDirAutoOnShadowDOM(child);
963
0
      child->SetAncestorHasDirAuto();
964
0
      HTMLSlotElement* slot = HTMLSlotElement::FromNode(child);
965
0
      if (slot) {
966
0
        const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
967
0
        for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
968
0
          assignedNodes[i]->SetAncestorHasDirAuto();
969
0
          SetAncestorHasDirAutoOnDescendants(assignedNodes[i]);
970
0
        }
971
0
      }
972
0
    }
973
0
    child = child->GetNextNode(aRoot);
974
0
  }
975
0
}
976
977
void
978
WalkDescendantsSetDirAuto(Element* aElement, bool aNotify)
979
0
{
980
0
  // Only test for DoesNotParticipateInAutoDirection -- in other words, if
981
0
  // aElement is a <bdi> which is having its dir attribute set to auto (or
982
0
  // removed or set to an invalid value, which are equivalent to dir=auto for
983
0
  // <bdi>, we *do* want to set AncestorHasDirAuto on its descendants, unlike
984
0
  // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a <bdi>
985
0
  // being bound to an existing node with dir=auto.
986
0
  if (!DoesNotParticipateInAutoDirection(aElement) &&
987
0
      !aElement->AncestorHasDirAuto()) {
988
0
    SetAncestorHasDirAutoOnDescendants(aElement);
989
0
  }
990
0
991
0
  nsTextNode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
992
0
  if (textNode) {
993
0
    nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement);
994
0
  }
995
0
}
996
997
void
998
WalkDescendantsClearAncestorDirAuto(nsIContent* aContent)
999
0
{
1000
0
  if (aContent->IsElement()) {
1001
0
    if (ShadowRoot* shadowRoot = aContent->AsElement()->GetShadowRoot()) {
1002
0
      shadowRoot->ClearAncestorHasDirAuto();
1003
0
      WalkDescendantsClearAncestorDirAuto(shadowRoot);
1004
0
    }
1005
0
  }
1006
0
1007
0
  nsIContent* child = aContent->GetFirstChild();
1008
0
  while (child) {
1009
0
    if (child->GetAssignedSlot()) {
1010
0
      // If the child node is assigned to a slot, nodes state is inherited from
1011
0
      // the slot, not from element's parent.
1012
0
      child = child->GetNextNonChildNode(aContent);
1013
0
      continue;
1014
0
    }
1015
0
    if (child->IsElement()) {
1016
0
      if (child->AsElement()->HasDirAuto()) {
1017
0
        child = child->GetNextNonChildNode(aContent);
1018
0
        continue;
1019
0
      }
1020
0
1021
0
      HTMLSlotElement* slot = HTMLSlotElement::FromNode(child);
1022
0
      if (slot) {
1023
0
        const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
1024
0
        for (uint32_t i = 0; i < assignedNodes.Length(); ++i) {
1025
0
          if (assignedNodes[i]->IsElement()) {
1026
0
            Element* slottedElement = assignedNodes[i]->AsElement();
1027
0
            if (slottedElement->HasDirAuto()) {
1028
0
              continue;
1029
0
            }
1030
0
          }
1031
0
1032
0
          nsIContent* content = assignedNodes[i]->AsContent();
1033
0
          content->ClearAncestorHasDirAuto();
1034
0
          WalkDescendantsClearAncestorDirAuto(content);
1035
0
        }
1036
0
      }
1037
0
    }
1038
0
1039
0
    child->ClearAncestorHasDirAuto();
1040
0
    child = child->GetNextNode(aContent);
1041
0
  }
1042
0
}
1043
1044
void
1045
SetAncestorDirectionIfAuto(nsTextNode* aTextNode, Directionality aDir,
1046
                           bool aNotify = true)
1047
0
{
1048
0
  MOZ_ASSERT(aTextNode->NodeType() == nsINode::TEXT_NODE,
1049
0
             "Must be a text node");
1050
0
1051
0
  bool crossedShadowBoundary = false;
1052
0
  nsIContent* parent = GetParentOrHostOrSlot(aTextNode, &crossedShadowBoundary);
1053
0
  while (parent && parent->NodeOrAncestorHasDirAuto()) {
1054
0
    if (!parent->IsElement()) {
1055
0
      parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary);
1056
0
      continue;
1057
0
    }
1058
0
1059
0
    Element* parentElement = parent->AsElement();
1060
0
    if (DoesNotParticipateInAutoDirection(parentElement) ||
1061
0
        parentElement->HasFixedDir()) {
1062
0
      break;
1063
0
    }
1064
0
1065
0
    if (parentElement->HasDirAuto()) {
1066
0
      bool resetDirection = false;
1067
0
      nsTextNode* directionWasSetByTextNode =
1068
0
        static_cast<nsTextNode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
1069
0
1070
0
      if (!parent->HasDirAutoSet()) {
1071
0
        // Fast path if parent's direction is not yet set by any descendant
1072
0
        MOZ_ASSERT(!directionWasSetByTextNode,
1073
0
                   "dirAutoSetBy property should be null");
1074
0
        resetDirection = true;
1075
0
      } else {
1076
0
        // If parent's direction is already set, we need to know if
1077
0
        // aTextNode is before or after the text node that had set it.
1078
0
        // We will walk parent's descendants in tree order starting from
1079
0
        // aTextNode to optimize for the most common case where text nodes are
1080
0
        // being appended to tree.
1081
0
        if (!directionWasSetByTextNode) {
1082
0
          resetDirection = true;
1083
0
        } else if (directionWasSetByTextNode != aTextNode) {
1084
0
          if (crossedShadowBoundary ||
1085
0
              AncestorChainCrossesShadowBoundary(directionWasSetByTextNode,
1086
0
                                                 parent)) {
1087
0
            // Need to take the slow path when the path from either the old or
1088
0
            // new text node to the dir=auto element crosses shadow boundary.
1089
0
            ResetAutoDirection(parentElement, aNotify);
1090
0
            return;
1091
0
          }
1092
0
1093
0
          nsIContent* child = aTextNode->GetNextNode(parent);
1094
0
          while (child) {
1095
0
            if (child->IsElement() &&
1096
0
                DoesNotAffectDirectionOfAncestors(child->AsElement())) {
1097
0
              child = child->GetNextNonChildNode(parent);
1098
0
              continue;
1099
0
            }
1100
0
1101
0
            if (child == directionWasSetByTextNode) {
1102
0
              // we found the node that set the element's direction after our
1103
0
              // text node, so we need to reset the direction
1104
0
              resetDirection = true;
1105
0
              break;
1106
0
            }
1107
0
1108
0
            child = child->GetNextNode(parent);
1109
0
          }
1110
0
        }
1111
0
      }
1112
0
1113
0
      if (resetDirection) {
1114
0
        if (directionWasSetByTextNode) {
1115
0
          nsTextNodeDirectionalityMap::RemoveElementFromMap(
1116
0
            directionWasSetByTextNode, parentElement
1117
0
          );
1118
0
        }
1119
0
        parentElement->SetDirectionality(aDir, aNotify);
1120
0
        nsTextNodeDirectionalityMap::AddEntryToMap(aTextNode, parentElement);
1121
0
        SetDirectionalityOnDescendants(parentElement, aDir, aNotify);
1122
0
      }
1123
0
1124
0
      // Since we found an element with dir=auto, we can stop walking the
1125
0
      // parent chain: none of its ancestors will have their direction set by
1126
0
      // any of its descendants.
1127
0
      return;
1128
0
    }
1129
0
    parent = GetParentOrHostOrSlot(parent, &crossedShadowBoundary);
1130
0
  }
1131
0
}
1132
1133
bool
1134
TextNodeWillChangeDirection(nsIContent* aTextNode, Directionality* aOldDir,
1135
                            uint32_t aOffset)
1136
0
{
1137
0
  if (!NodeAffectsDirAutoAncestor(aTextNode)) {
1138
0
    nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
1139
0
    return false;
1140
0
  }
1141
0
1142
0
  uint32_t firstStrong;
1143
0
  *aOldDir = GetDirectionFromText(aTextNode->GetText(), &firstStrong);
1144
0
  return (aOffset <= firstStrong);
1145
0
}
1146
1147
void
1148
TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir,
1149
                         bool aNotify)
1150
0
{
1151
0
  Directionality newDir = GetDirectionFromText(aTextNode->GetText());
1152
0
  if (newDir == eDir_NotSet) {
1153
0
    if (aOldDir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
1154
0
      // This node used to have a strong directional character but no
1155
0
      // longer does. ResetTextNodeDirection() will re-resolve the
1156
0
      // directionality of any elements whose directionality was
1157
0
      // determined by this node.
1158
0
      nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
1159
0
    }
1160
0
  } else {
1161
0
    // This node has a strong directional character. If it has a
1162
0
    // TextNodeDirectionalityMap property, it already determines the
1163
0
    // directionality of some element(s), so call UpdateTextNodeDirection to
1164
0
    // reresolve their directionality. If it has no map, or if
1165
0
    // UpdateTextNodeDirection returns zero, indicating that the map is
1166
0
    // empty, call SetAncestorDirectionIfAuto to find ancestor elements
1167
0
    // which should have their directionality determined by this node.
1168
0
    if (aTextNode->HasTextNodeDirectionalityMap() &&
1169
0
        nsTextNodeDirectionalityMap::UpdateTextNodeDirection(aTextNode,
1170
0
                                                             newDir)) {
1171
0
      return;
1172
0
    }
1173
0
    SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify);
1174
0
  }
1175
0
}
1176
1177
void
1178
SetDirectionFromNewTextNode(nsTextNode* aTextNode)
1179
0
{
1180
0
  if (!NodeAffectsDirAutoAncestor(aTextNode)) {
1181
0
    return;
1182
0
  }
1183
0
1184
0
  nsIContent* parent = GetParentOrHostOrSlot(aTextNode);
1185
0
  if (parent && parent->NodeOrAncestorHasDirAuto()) {
1186
0
    aTextNode->SetAncestorHasDirAuto();
1187
0
  }
1188
0
1189
0
  Directionality dir = GetDirectionFromText(aTextNode->GetText());
1190
0
  if (dir != eDir_NotSet) {
1191
0
    SetAncestorDirectionIfAuto(aTextNode, dir);
1192
0
  }
1193
0
}
1194
1195
void
1196
ResetDirectionSetByTextNode(nsTextNode* aTextNode)
1197
0
{
1198
0
  if (!NodeAffectsDirAutoAncestor(aTextNode)) {
1199
0
    nsTextNodeDirectionalityMap::EnsureMapIsClearFor(aTextNode);
1200
0
    return;
1201
0
  }
1202
0
1203
0
  Directionality dir = GetDirectionFromText(aTextNode->GetText());
1204
0
  if (dir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
1205
0
    nsTextNodeDirectionalityMap::ResetTextNodeDirection(aTextNode, aTextNode);
1206
0
  }
1207
0
}
1208
1209
void
1210
SetDirectionalityFromValue(Element* aElement, const nsAString& value,
1211
                           bool aNotify)
1212
0
{
1213
0
  Directionality dir = GetDirectionFromText(value.BeginReading(),
1214
0
                                            value.Length());
1215
0
  if (dir == eDir_NotSet) {
1216
0
    dir = eDir_LTR;
1217
0
  }
1218
0
1219
0
  aElement->SetDirectionality(dir, aNotify);
1220
0
}
1221
1222
void
1223
OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue,
1224
             bool hadValidDir, bool hadDirAuto, bool aNotify)
1225
0
{
1226
0
  if (aElement->IsHTMLElement(nsGkAtoms::input)) {
1227
0
    return;
1228
0
  }
1229
0
1230
0
  if (aElement->AncestorHasDirAuto()) {
1231
0
    if (!hadValidDir) {
1232
0
      // The element is a descendant of an element with dir = auto, is
1233
0
      // having its dir attribute set, and previously didn't have a valid dir
1234
0
      // attribute.
1235
0
      // Check whether any of its text node descendants determine the
1236
0
      // direction of any of its ancestors, and redetermine their direction
1237
0
      WalkDescendantsResetAutoDirection(aElement);
1238
0
    } else if (!aElement->HasValidDir()) {
1239
0
      // The element is a descendant of an element with dir = auto and is
1240
0
      // having its dir attribute removed or set to an invalid value.
1241
0
      // Reset the direction of any of its ancestors whose direction is
1242
0
      // determined by a text node descendant
1243
0
      WalkAncestorsResetAutoDirection(aElement, aNotify);
1244
0
    }
1245
0
  } else if (hadDirAuto && !aElement->HasDirAuto()) {
1246
0
    // The element isn't a descendant of an element with dir = auto, and is
1247
0
    // having its dir attribute set to something other than auto.
1248
0
    // Walk the descendant tree and clear the AncestorHasDirAuto flag.
1249
0
    //
1250
0
    // N.B: For elements other than <bdi> it would be enough to test that the
1251
0
    //      current value of dir was "auto" in BeforeSetAttr to know that we
1252
0
    //      were unsetting dir="auto". For <bdi> things are more complicated,
1253
0
    //      since it behaves like dir="auto" whenever the dir attribute is
1254
0
    //      empty or invalid, so we would have to check whether the old value
1255
0
    //      was not either "ltr" or "rtl", and the new value was either "ltr"
1256
0
    //      or "rtl". Element::HasDirAuto() encapsulates all that, so doing it
1257
0
    //      here is simpler.
1258
0
    WalkDescendantsClearAncestorDirAuto(aElement);
1259
0
  }
1260
0
1261
0
  if (aElement->HasDirAuto()) {
1262
0
    WalkDescendantsSetDirAuto(aElement, aNotify);
1263
0
  } else {
1264
0
    if (aElement->HasDirAutoSet()) {
1265
0
      nsTextNode* setByNode =
1266
0
        static_cast<nsTextNode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
1267
0
      nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
1268
0
    }
1269
0
    SetDirectionalityOnDescendants(aElement,
1270
0
                                   RecomputeDirectionality(aElement, aNotify),
1271
0
                                   aNotify);
1272
0
  }
1273
0
}
1274
1275
void
1276
SetDirOnBind(Element* aElement, nsIContent* aParent)
1277
0
{
1278
0
  // Set the AncestorHasDirAuto flag, unless this element shouldn't affect
1279
0
  // ancestors that have dir=auto
1280
0
  if (!DoesNotParticipateInAutoDirection(aElement) &&
1281
0
      !aElement->IsHTMLElement(nsGkAtoms::bdi) &&
1282
0
      aParent && aParent->NodeOrAncestorHasDirAuto()) {
1283
0
    aElement->SetAncestorHasDirAuto();
1284
0
1285
0
    SetAncestorHasDirAutoOnDescendants(aElement);
1286
0
1287
0
    if (aElement->GetFirstChild() || aElement->GetShadowRoot()) {
1288
0
1289
0
      // We may also need to reset the direction of an ancestor with dir=auto
1290
0
      WalkAncestorsResetAutoDirection(aElement, true);
1291
0
    }
1292
0
  }
1293
0
1294
0
  if (!aElement->HasDirAuto()) {
1295
0
    // if the element doesn't have dir=auto, set its own directionality from
1296
0
    // the dir attribute or by inheriting from its ancestors.
1297
0
    RecomputeDirectionality(aElement, false);
1298
0
  }
1299
0
}
1300
1301
void
1302
ResetDir(Element* aElement)
1303
0
{
1304
0
  if (aElement->HasDirAutoSet()) {
1305
0
    nsTextNode* setByNode =
1306
0
      static_cast<nsTextNode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
1307
0
    nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
1308
0
  }
1309
0
1310
0
  if (!aElement->HasDirAuto()) {
1311
0
    RecomputeDirectionality(aElement, false);
1312
0
  }
1313
0
}
1314
1315
} // end namespace mozilla
1316