tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DirectionalityUtils.cpp (33920B)


      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  This file contains most of the code to implement html directionality.
      9  This includes default direction, inheritance, and auto directionality.
     10 
     11  A useful perspective is separating the static and dynamic case.
     12  In the static case, directionality is computed based on the current DOM,
     13  closely following the specification, e.g. in ComputeAutoDirectionality.
     14  Dynamic changes, e.g. OnSetDirAttr, are propagated to the impacted nodes,
     15  for which the static case is re-run.
     16 
     17  To minimize searching for dir=auto nodes impacted by a dynamic change, several
     18  flags are maintained (see their declaration for documentation):
     19  - NodeAncestorHasDirAuto and NodeAffectsDirAutoSlot apply to all nodes.
     20    They are set when a node is placed somewhere in the tree and set or cleared
     21    when a dir attribute changes.
     22  - NS_MAY_SET_DIR_AUTO applies to text. It is set whenever a text node might be
     23    responsible for the auto directionality of a dir=auto element. It is cleared
     24    when the element is unbound.
     25 */
     26 
     27 #include "mozilla/dom/DirectionalityUtils.h"
     28 
     29 #include "mozilla/Maybe.h"
     30 #include "mozilla/dom/CharacterDataBuffer.h"
     31 #include "mozilla/dom/Document.h"
     32 #include "mozilla/dom/Element.h"
     33 #include "mozilla/dom/HTMLInputElement.h"
     34 #include "mozilla/dom/HTMLSlotElement.h"
     35 #include "mozilla/dom/HTMLTextAreaElement.h"
     36 #include "mozilla/dom/ShadowRoot.h"
     37 #include "mozilla/dom/Text.h"
     38 #include "mozilla/dom/UnbindContext.h"
     39 #include "mozilla/intl/UnicodeProperties.h"
     40 #include "nsAttrValue.h"
     41 #include "nsIContent.h"
     42 #include "nsIContentInlines.h"
     43 #include "nsINode.h"
     44 #include "nsUnicodeProperties.h"
     45 
     46 namespace mozilla {
     47 
     48 using mozilla::dom::Element;
     49 using mozilla::dom::HTMLInputElement;
     50 using mozilla::dom::HTMLSlotElement;
     51 using mozilla::dom::ShadowRoot;
     52 using mozilla::dom::Text;
     53 
     54 /**
     55 * Returns true if aElement is one of the elements whose text content should
     56 * affect its own direction, or the direction of ancestors with dir=auto.
     57 *
     58 * Note that this does not include <bdi>, whose content does affect its own
     59 * direction when it has dir=auto (which it has by default), so one needs to
     60 * test for it separately, e.g. with EstablishesOwnDirection.
     61 * It *does* include textarea, because even if a textarea has dir=auto, it has
     62 * unicode-bidi: plaintext and is handled automatically in bidi resolution.
     63 * It also includes `input`, because it takes the `dir` value from its value
     64 * attribute, instead of the child nodes.
     65 */
     66 static bool ParticipatesInAutoDirection(const nsIContent* aContent) {
     67  if (aContent->IsInNativeAnonymousSubtree()) {
     68    return false;
     69  }
     70  if (aContent->IsShadowRoot()) {
     71    return true;
     72  }
     73  return !aContent->IsAnyOfHTMLElements(nsGkAtoms::script, nsGkAtoms::style,
     74                                        nsGkAtoms::input, nsGkAtoms::textarea);
     75 }
     76 
     77 static bool IsAutoDirectionalityFormAssociatedElement(Element* aElement) {
     78  if (HTMLInputElement* input = HTMLInputElement::FromNode(aElement)) {
     79    return input->IsAutoDirectionalityAssociated();
     80  }
     81  return aElement->IsHTMLElement(nsGkAtoms::textarea);
     82 }
     83 
     84 static Maybe<nsAutoString> GetValueIfFormAssociatedElement(Element* aElement) {
     85  Maybe<nsAutoString> result;
     86  if (HTMLInputElement* input = HTMLInputElement::FromNode(aElement)) {
     87    if (input->IsAutoDirectionalityAssociated()) {
     88      // It's unclear if per spec we should use the sanitized or unsanitized
     89      // value to set the directionality. But input may provide a known value
     90      // to us, which is unsanitized, so be consistent. Using what the user is
     91      // seeing to determine directionality instead of the sanitized
     92      // (empty if invalid) value probably makes more sense.
     93      result.emplace();
     94      input->GetValueInternal(*result, dom::CallerType::System);
     95    }
     96  } else if (dom::HTMLTextAreaElement* ta =
     97                 dom::HTMLTextAreaElement::FromNode(aElement)) {
     98    result.emplace();
     99    ta->GetValue(*result);
    100  }
    101  return result;
    102 }
    103 
    104 /**
    105 * Returns the directionality of a Unicode character
    106 */
    107 static Directionality GetDirectionFromChar(uint32_t ch) {
    108  switch (intl::UnicodeProperties::GetBidiClass(ch)) {
    109    case intl::BidiClass::RightToLeft:
    110    case intl::BidiClass::RightToLeftArabic:
    111      return Directionality::Rtl;
    112 
    113    case intl::BidiClass::LeftToRight:
    114      return Directionality::Ltr;
    115 
    116    default:
    117      return Directionality::Unset;
    118  }
    119 }
    120 
    121 /**
    122 * Returns true if aElement establishes its own direction or does not have one.
    123 *
    124 * From https://html.spec.whatwg.org/#auto-directionality step 3.1., this is
    125 * bdi, script, style, textarea, and elements with auto, ltr or rtl dir.
    126 * Additionally, it includes input as the class handles directionality itself.
    127 */
    128 inline static bool EstablishesOwnDirection(const Element* aElement) {
    129  return !ParticipatesInAutoDirection(aElement) ||
    130         aElement->IsHTMLElement(nsGkAtoms::bdi) || aElement->HasFixedDir() ||
    131         aElement->HasDirAuto();
    132 }
    133 
    134 /**
    135 * Returns true if aContent is dir=auto, affects a dir=auto ancestor, is
    136 * assigned to a dir=auto slot, or has an ancestor assigned to a dir=auto slot.
    137 *
    138 * It's false for input and textarea as they handle their directionality
    139 * themselves. We are concerned about steps 2 and 3 of
    140 * https://html.spec.whatwg.org/#auto-directionality
    141 */
    142 inline static bool AffectsDirAutoElement(nsIContent* aContent) {
    143  return aContent && ParticipatesInAutoDirection(aContent) &&
    144         (aContent->NodeOrAncestorHasDirAuto() ||
    145          aContent->AffectsDirAutoSlot());
    146 }
    147 
    148 Directionality GetDirectionFromText(const char16_t* aText,
    149                                    const uint32_t aLength,
    150                                    uint32_t* aFirstStrong) {
    151  const char16_t* start = aText;
    152  const char16_t* end = aText + aLength;
    153 
    154  while (start < end) {
    155    uint32_t current = start - aText;
    156    uint32_t ch = *start++;
    157 
    158    if (start < end && NS_IS_SURROGATE_PAIR(ch, *start)) {
    159      ch = SURROGATE_TO_UCS4(ch, *start++);
    160      current++;
    161    }
    162 
    163    // Just ignore lone surrogates
    164    if (!IS_SURROGATE(ch)) {
    165      Directionality dir = GetDirectionFromChar(ch);
    166      if (dir != Directionality::Unset) {
    167        if (aFirstStrong) {
    168          *aFirstStrong = current;
    169        }
    170        return dir;
    171      }
    172    }
    173  }
    174 
    175  if (aFirstStrong) {
    176    *aFirstStrong = UINT32_MAX;
    177  }
    178  return Directionality::Unset;
    179 }
    180 
    181 static Directionality GetDirectionFromText(const char* aText,
    182                                           const uint32_t aLength,
    183                                           uint32_t* aFirstStrong = nullptr) {
    184  const char* start = aText;
    185  const char* end = aText + aLength;
    186 
    187  while (start < end) {
    188    uint32_t current = start - aText;
    189    unsigned char ch = (unsigned char)*start++;
    190 
    191    Directionality dir = GetDirectionFromChar(ch);
    192    if (dir != Directionality::Unset) {
    193      if (aFirstStrong) {
    194        *aFirstStrong = current;
    195      }
    196      return dir;
    197    }
    198  }
    199 
    200  if (aFirstStrong) {
    201    *aFirstStrong = UINT32_MAX;
    202  }
    203  return Directionality::Unset;
    204 }
    205 
    206 static Directionality GetDirectionFromText(const Text* aTextNode,
    207                                           uint32_t* aFirstStrong = nullptr) {
    208  const dom::CharacterDataBuffer* characterDataBuffer =
    209      &aTextNode->DataBuffer();
    210  if (characterDataBuffer->Is2b()) {
    211    return GetDirectionFromText(characterDataBuffer->Get2b(),
    212                                characterDataBuffer->GetLength(), aFirstStrong);
    213  }
    214 
    215  return GetDirectionFromText(characterDataBuffer->Get1b(),
    216                              characterDataBuffer->GetLength(), aFirstStrong);
    217 }
    218 
    219 /**
    220 * Compute auto direction for aRoot. If aCanExcludeRoot is true and aRoot
    221 * establishes its own directionality, return early.
    222 * https://html.spec.whatwg.org/#contained-text-auto-directionality
    223 */
    224 Directionality ContainedTextAutoDirectionality(nsINode* aRoot,
    225                                               bool aCanExcludeRoot) {
    226  MOZ_ASSERT_IF(aCanExcludeRoot, aRoot->IsElement());
    227  if (aCanExcludeRoot && EstablishesOwnDirection(aRoot->AsElement())) {
    228    return Directionality::Unset;
    229  }
    230 
    231  nsIContent* child = aRoot->GetFirstChild();
    232  while (child) {
    233    if (child->IsElement() && EstablishesOwnDirection(child->AsElement())) {
    234      child = child->GetNextNonChildNode(aRoot);
    235      continue;
    236    }
    237 
    238    // Step 1.2. If descendant is a slot element whose root is a shadow root,
    239    // then return the directionality of that shadow root's host.
    240    if (auto* slot = HTMLSlotElement::FromNode(child)) {
    241      if (const ShadowRoot* sr = slot->GetContainingShadow()) {
    242        Element* host = sr->GetHost();
    243        MOZ_ASSERT(host);
    244        return host->GetDirectionality();
    245      }
    246    }
    247 
    248    // Step 1.3-5. If descendant is a Text node, return its
    249    // text node directionality.
    250    if (auto* text = Text::FromNode(child)) {
    251      Directionality textNodeDir = GetDirectionFromText(text);
    252      if (textNodeDir != Directionality::Unset) {
    253        text->SetMaySetDirAuto();
    254        return textNodeDir;
    255      }
    256    }
    257    child = child->GetNextNode(aRoot);
    258  }
    259 
    260  return Directionality::Unset;
    261 }
    262 
    263 static Directionality ComputeAutoDirectionality(Element* aElement,
    264                                                bool aNotify);
    265 
    266 /**
    267 * Compute auto direction aSlot should have based on assigned nodes
    268 * https://html.spec.whatwg.org/#auto-directionality step 2
    269 */
    270 Directionality ComputeAutoDirectionFromAssignedNodes(
    271    HTMLSlotElement* aSlot, Span<const RefPtr<nsINode>> aAssignedNodes,
    272    bool aNotify) {
    273  // Step 2.1. For each node child of element's assigned nodes:
    274  for (const RefPtr<nsINode>& assignedNode : aAssignedNodes) {
    275    // Step 2.1.1. Let childDirection be null.
    276    Directionality childDirection = Directionality::Unset;
    277 
    278    // Step 2.1.2. If child is a Text node...
    279    if (auto* text = Text::FromNode(assignedNode)) {
    280      childDirection = GetDirectionFromText(text);
    281      if (childDirection != Directionality::Unset) {
    282        text->SetMaySetDirAuto();
    283      }
    284    } else {
    285      // Step 2.1.3.1. Assert: child is an Element node.
    286      Element* assignedElement = Element::FromNode(assignedNode);
    287      MOZ_ASSERT(assignedElement);
    288 
    289      // Step 2.1.3.2.
    290      childDirection = ContainedTextAutoDirectionality(assignedElement, true);
    291    }
    292 
    293    // Step 2.1.4. If childDirection is not null, then return childDirection.
    294    if (childDirection != Directionality::Unset) {
    295      return childDirection;
    296    }
    297  }
    298  // Step 2.2. Return null.
    299  return Directionality::Unset;
    300 }
    301 
    302 /**
    303 * Set the directionality of a node with dir=auto as defined in
    304 * https://html.spec.whatwg.org/#auto-directionality,
    305 * not including step 1: auto-directionality form-associated elements, this is
    306 * implemented by the elements themselves.
    307 *
    308 * Sets NodeMaySetDirAuto on the text node that determined the direction.
    309 */
    310 static Directionality ComputeAutoDirectionality(Element* aElement,
    311                                                bool aNotify) {
    312  MOZ_ASSERT(aElement, "Must have an element");
    313  MOZ_ASSERT(ParticipatesInAutoDirection(aElement),
    314             "Cannot compute auto directionality of this element");
    315 
    316  // Step 2. If element is a slot element whose root is a shadow root and
    317  // element's assigned nodes are not empty:
    318  if (auto* slot = HTMLSlotElement::FromNode(aElement)) {
    319    const Span assignedNodes = slot->AssignedNodes();
    320    if (!assignedNodes.IsEmpty()) {
    321      MOZ_ASSERT(slot->IsInShadowTree());
    322      return ComputeAutoDirectionFromAssignedNodes(slot, assignedNodes,
    323                                                   aNotify);
    324    }
    325  }
    326 
    327  // Step 3. find first text or slot that determines the direction
    328  Directionality nodeDir = ContainedTextAutoDirectionality(aElement, false);
    329  if (nodeDir != Directionality::Unset) {
    330    return nodeDir;
    331  }
    332 
    333  // Step 4. return null
    334  return Directionality::Unset;
    335 }
    336 
    337 Directionality GetParentDirectionality(const Element* aElement) {
    338  if (nsIContent* parent = aElement->GetParent()) {
    339    if (ShadowRoot* shadow = ShadowRoot::FromNode(parent)) {
    340      parent = shadow->GetHost();
    341    }
    342    if (parent && parent->IsElement()) {
    343      // If the node doesn't have an explicit dir attribute with a valid value,
    344      // the directionality is the same as the parent element (but don't
    345      // propagate the parent directionality if it isn't set yet).
    346      Directionality parentDir = parent->AsElement()->GetDirectionality();
    347      if (parentDir != Directionality::Unset) {
    348        return parentDir;
    349      }
    350    }
    351  }
    352  return Directionality::Ltr;
    353 }
    354 
    355 Directionality RecomputeDirectionality(Element* aElement, bool aNotify) {
    356  MOZ_ASSERT(!aElement->HasDirAuto(),
    357             "RecomputeDirectionality called with dir=auto");
    358 
    359  if (aElement->HasValidDir()) {
    360    return aElement->GetDirectionality();
    361  }
    362 
    363  // https://html.spec.whatwg.org/multipage/dom.html#the-directionality:
    364  //
    365  // If the element is an input element whose type attribute is in the
    366  // Telephone state, and the dir attribute is not in a defined state
    367  // (i.e. it is not present or has an invalid value)
    368  //
    369  //     The directionality of the element is 'ltr'.
    370  if (auto* input = HTMLInputElement::FromNode(*aElement)) {
    371    if (input->ControlType() == FormControlType::InputTel) {
    372      aElement->SetDirectionality(Directionality::Ltr, aNotify);
    373      return Directionality::Ltr;
    374    }
    375  }
    376 
    377  const Directionality dir = GetParentDirectionality(aElement);
    378  aElement->SetDirectionality(dir, aNotify);
    379  return dir;
    380 }
    381 
    382 // Whether the element establishes its own directionality and the one of its
    383 // descendants.
    384 static inline bool IsBoundary(const Element& aElement) {
    385  return aElement.HasValidDir() || aElement.HasDirAuto();
    386 }
    387 
    388 static void ResetAutoDirection(Element* aElement, bool aNotify);
    389 
    390 /**
    391 * Called when shadow root host changes direction. Reset auto directionality
    392 * for dir=auto descendants whose direction may depend on the host
    393 * directionality through a slot element.
    394 *
    395 * Dynamic update for https://html.spec.whatwg.org/#auto-directionality step 3.2
    396 * If descendant is a slot element whose root is a shadow root, then return
    397 * the directionality of that shadow root's host.
    398 */
    399 static void ResetAutoDirectionForAncestorsOfSlotDescendants(ShadowRoot* aShadow,
    400                                                            Directionality aDir,
    401                                                            bool aNotify) {
    402  // For now, reset auto directionality for all descendants, not only those
    403  // that have a slot descendant.
    404  for (nsIContent* cur = aShadow->GetFirstChild(); cur;
    405       cur = cur->GetNextNode(aShadow)) {
    406    if (Element* element = Element::FromNode(cur)) {
    407      if (element->HasDirAuto() && element->GetDirectionality() != aDir &&
    408          ParticipatesInAutoDirection(element)) {
    409        ResetAutoDirection(element, aNotify);
    410      }
    411    }
    412  }
    413 }
    414 
    415 static void SetDirectionalityOnDescendantsInternal(nsINode* aNode,
    416                                                   Directionality aDir,
    417                                                   bool aNotify) {
    418  if (auto* element = Element::FromNode(aNode)) {
    419    if (ShadowRoot* shadow = element->GetShadowRoot()) {
    420      // host direction changed, propagate it through slots to dir=auto elements
    421      ResetAutoDirectionForAncestorsOfSlotDescendants(shadow, aDir, aNotify);
    422 
    423      SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify);
    424    }
    425  }
    426 
    427  for (nsIContent* child = aNode->GetFirstChild(); child;) {
    428    auto* element = Element::FromNode(child);
    429    if (!element) {
    430      child = child->GetNextNode(aNode);
    431      continue;
    432    }
    433 
    434    if (IsBoundary(*element) || element->GetDirectionality() == aDir) {
    435      // If the element is a directionality boundary, or already
    436      // has the right directionality, then we can skip the whole subtree.
    437      child = child->GetNextNonChildNode(aNode);
    438      continue;
    439    }
    440 
    441    element->SetDirectionality(aDir, aNotify);
    442 
    443    if (ShadowRoot* shadow = element->GetShadowRoot()) {
    444      ResetAutoDirectionForAncestorsOfSlotDescendants(shadow, aDir, aNotify);
    445 
    446      SetDirectionalityOnDescendantsInternal(shadow, aDir, aNotify);
    447    }
    448 
    449    child = child->GetNextNode(aNode);
    450  }
    451 }
    452 
    453 // We want the public version of this only to acc
    454 void SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
    455                                    bool aNotify) {
    456  return SetDirectionalityOnDescendantsInternal(aElement, aDir, aNotify);
    457 }
    458 
    459 static void ResetAutoDirection(Element* aElement, bool aNotify) {
    460  MOZ_ASSERT(aElement->HasDirAuto());
    461  Directionality dir = ComputeAutoDirectionality(aElement, aNotify);
    462  if (dir == Directionality::Unset) {
    463    dir = Directionality::Ltr;
    464  }
    465  if (dir != aElement->GetDirectionality()) {
    466    aElement->SetDirectionality(dir, aNotify);
    467    SetDirectionalityOnDescendants(aElement, aElement->GetDirectionality(),
    468                                   aNotify);
    469  }
    470 }
    471 
    472 /**
    473 * Reset auto direction of the dir=auto elements that aElement might impact.
    474 * Walk the parent chain till a dir=auto element is found, also reset dir=auto
    475 * slots an ancestor might be assigned to.
    476 */
    477 static void WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify) {
    478  for (nsIContent* ancestor = aElement; AffectsDirAutoElement(ancestor);
    479       ancestor = ancestor->GetParent()) {
    480    if (HTMLSlotElement* slot = ancestor->GetAssignedSlot()) {
    481      if (slot->HasDirAuto()) {
    482        ResetAutoDirection(slot, aNotify);
    483      }
    484    }
    485 
    486    auto* ancestorElement = Element::FromNode(*ancestor);
    487    if (ancestorElement && ancestorElement->HasDirAuto()) {
    488      ResetAutoDirection(ancestorElement, aNotify);
    489    }
    490  }
    491 }
    492 
    493 void SlotStateChanged(HTMLSlotElement* aSlot) {
    494  if (aSlot->HasDirAuto()) {
    495    ResetAutoDirection(aSlot, true);
    496  }
    497 }
    498 
    499 static void DownwardPropagateDirAutoFlags(nsINode* aRoot) {
    500  bool affectsAncestor = aRoot->NodeOrAncestorHasDirAuto(),
    501       affectsSlot = aRoot->AffectsDirAutoSlot();
    502  if (!affectsAncestor && !affectsSlot) {
    503    return;
    504  }
    505 
    506  nsIContent* child = aRoot->GetFirstChild();
    507  while (child) {
    508    if (child->IsElement() && EstablishesOwnDirection(child->AsElement())) {
    509      child = child->GetNextNonChildNode(aRoot);
    510      continue;
    511    }
    512 
    513    if (affectsAncestor) {
    514      child->SetAncestorHasDirAuto();
    515    }
    516    if (affectsSlot) {
    517      child->SetAffectsDirAutoSlot();
    518    }
    519    child = child->GetNextNode(aRoot);
    520  }
    521 }
    522 
    523 /**
    524 * aContent no longer affects the auto directionality of it's assigned slot,
    525 * e.g. as it is removed from the slot or the slot no longer has dir=auto.
    526 * Check if aContent impacts another slot and otherwise clear the flag.
    527 */
    528 static void MaybeClearAffectsDirAutoSlot(nsIContent* aContent) {
    529  DebugOnly<HTMLSlotElement*> slot = aContent->GetAssignedSlot();
    530  MOZ_ASSERT(!slot || !slot->HasDirAuto(),
    531             "Function expects aContent not to impact its assigned slot");
    532  // check if aContent still inherits the flag from its parent
    533  if (Element* parent = aContent->GetParentElement()) {
    534    // do not check EstablishesOwnDirection(parent), as it is only true despite
    535    // AffectsDirAutoSlot if parent is directly assigned to a dir=auto slot
    536    if (parent->AffectsDirAutoSlot() &&
    537        !(aContent->IsElement() &&
    538          EstablishesOwnDirection(aContent->AsElement()))) {
    539      MOZ_ASSERT(aContent->AffectsDirAutoSlot());
    540      return;
    541    }
    542  }
    543 
    544  aContent->ClearAffectsDirAutoSlot();
    545 
    546  nsIContent* child = aContent->GetFirstChild();
    547  while (child) {
    548    if (child->IsElement() && EstablishesOwnDirection(child->AsElement())) {
    549      child = child->GetNextNonChildNode(aContent);
    550      continue;
    551    }
    552    if (HTMLSlotElement* slot = child->GetAssignedSlot()) {
    553      if (slot->HasDirAuto()) {
    554        child = child->GetNextNonChildNode(aContent);
    555        continue;
    556      }
    557    }
    558 
    559    child->ClearAffectsDirAutoSlot();
    560    child = child->GetNextNode(aContent);
    561  }
    562 }
    563 
    564 void SlotAssignedNodeAdded(HTMLSlotElement* aSlot, nsIContent& aAssignedNode) {
    565  MOZ_ASSERT(aSlot);
    566  if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled()) {
    567    if (aSlot->IsMaybeSelected()) {
    568      // Normally it's nsRange::ContentAppended's responsibility to
    569      // mark new descendants, however this doesn't work for slotted
    570      // content because nsRange observes the common ancestor of
    571      // start/end, whereas slotted element may not have the same
    572      // ancestor as them.
    573      dom::AbstractRange::UpdateDescendantsInFlattenedTree(
    574          aAssignedNode, true /* aMarkDesendants*/);
    575    }
    576  }
    577 
    578  if (aSlot->HasDirAuto()) {
    579    aAssignedNode.SetAffectsDirAutoSlot();
    580    DownwardPropagateDirAutoFlags(&aAssignedNode);
    581  }
    582  SlotStateChanged(aSlot);
    583 }
    584 
    585 void SlotAssignedNodeRemoved(HTMLSlotElement* aSlot,
    586                             nsIContent& aUnassignedNode) {
    587  if (StaticPrefs::dom_shadowdom_selection_across_boundary_enabled() &&
    588      aUnassignedNode.IsMaybeSelected()) {
    589    // Normally, this shouldn't happen because nsRange::ContentRemoved
    590    // should be called for content removal, and then
    591    // AbstractRange::UnmarkDescendants will be used to clear the flags.
    592    // Though this doesn't work for slotted element because nsRange
    593    // observers the common ancestor of start/end, whereas slotted element
    594    // may not have the same ancestor as them, so we have to clear
    595    // the flags manually here.
    596    dom::AbstractRange::UpdateDescendantsInFlattenedTree(
    597        aUnassignedNode, false /* aMarkDesendants*/);
    598  }
    599 
    600  if (aSlot->HasDirAuto()) {
    601    MaybeClearAffectsDirAutoSlot(&aUnassignedNode);
    602  }
    603  SlotStateChanged(aSlot);
    604 }
    605 
    606 /**
    607 * When dir=auto was set on aElement, reset it's auto direction and set the
    608 * flag on descendants
    609 */
    610 void WalkDescendantsSetDirAuto(Element* aElement, bool aNotify) {
    611  MOZ_ASSERT(aElement->HasDirAuto());
    612  // Only test for ParticipatesInAutoDirection -- in other words, if aElement is
    613  // a <bdi> which is having its dir attribute set to auto (or
    614  // removed or set to an invalid value, which are equivalent to dir=auto for
    615  // <bdi>, we *do* want to set AncestorHasDirAuto on its descendants, unlike
    616  // in SetDirOnBind where we don't propagate AncestorHasDirAuto to a <bdi>
    617  // being bound to an existing node with dir=auto.
    618  if (ParticipatesInAutoDirection(aElement) &&
    619      !aElement->AncestorHasDirAuto()) {
    620    DownwardPropagateDirAutoFlags(aElement);
    621  }
    622 
    623  ResetAutoDirection(aElement, aNotify);
    624 }
    625 
    626 void WalkDescendantsClearAncestorDirAuto(nsIContent* aContent) {
    627  nsIContent* child = aContent->GetFirstChild();
    628  while (child) {
    629    if (child->IsElement() && EstablishesOwnDirection(child->AsElement())) {
    630      child = child->GetNextNonChildNode(aContent);
    631      continue;
    632    }
    633 
    634    child->ClearAncestorHasDirAuto();
    635    child = child->GetNextNode(aContent);
    636  }
    637 }
    638 
    639 /**
    640 * Returns whether answer is definitive, i.e. whether we found all dir=auto
    641 * elements impacted by aContent.
    642 * This is false when we hit the top of an ancestor chain without finding a
    643 * dir=auto element or an element with a fixed direction.
    644 * This is useful when processing node removals, since we might need to look at
    645 * the subtree we're removing from.
    646 */
    647 static bool FindDirAutoElementsFrom(nsIContent* aContent,
    648                                    nsTArray<Element*>& aElements) {
    649  if (!AffectsDirAutoElement(aContent)) {
    650    return true;
    651  }
    652 
    653  for (nsIContent* ancestor = aContent; AffectsDirAutoElement(ancestor);
    654       ancestor = ancestor->GetParent()) {
    655    if (HTMLSlotElement* slot = ancestor->GetAssignedSlot()) {
    656      if (slot->HasDirAuto()) {
    657        aElements.AppendElement(slot);
    658        // need to check whether there are more dir=auto slots or ancestors
    659        nsIContent* parent = ancestor->GetParent();
    660        MOZ_ASSERT(parent, "Slotted content must have a parent");
    661        if (!parent->AffectsDirAutoSlot() &&
    662            !ancestor->NodeOrAncestorHasDirAuto()) {
    663          return true;
    664        }
    665      }
    666    }
    667 
    668    auto* ancestorElement = Element::FromNode(*ancestor);
    669    if (ancestorElement && ancestorElement->HasDirAuto()) {
    670      aElements.AppendElement(ancestorElement);
    671      return true;
    672    }
    673    if (ancestorElement && ancestorElement->IsInShadowTree() &&
    674        ancestorElement->IsHTMLElement(nsGkAtoms::slot)) {
    675      // further ancestors will inherit directionality from shadow host
    676      // https://html.spec.whatwg.org/#auto-directionality step 3.2
    677      // if descendant is a slot in a shadow DOM, return host directionality
    678      return true;
    679    }
    680  }
    681 
    682  return false;
    683 }
    684 
    685 /**
    686 * Reset auto directionality of ancestors of aTextNode
    687 */
    688 static void SetAncestorDirectionIfAuto(Text* aTextNode, Directionality aDir,
    689                                       bool aNotify = true) {
    690  AutoTArray<Element*, 4> autoElements;
    691  FindDirAutoElementsFrom(aTextNode, autoElements);
    692  for (Element* autoElement : autoElements) {
    693    if (autoElement->GetDirectionality() == aDir) {
    694      // If we know that the directionality is already correct, we don't need to
    695      // reset it. But we might be responsible for the directionality of
    696      // parentElement.
    697      MOZ_ASSERT(aDir != Directionality::Unset);
    698      aTextNode->SetMaySetDirAuto();
    699    } else {
    700      // Otherwise recompute the directionality of parentElement.
    701      ResetAutoDirection(autoElement, aNotify);
    702    }
    703  }
    704 }
    705 
    706 bool TextNodeWillChangeDirection(Text* aTextNode, Directionality* aOldDir,
    707                                 uint32_t aOffset) {
    708  if (!AffectsDirAutoElement(aTextNode)) {
    709    return false;
    710  }
    711 
    712  // If the change has happened after the first character with strong
    713  // directionality in the text node, do nothing.
    714  uint32_t firstStrong;
    715  *aOldDir = GetDirectionFromText(aTextNode, &firstStrong);
    716  return (aOffset <= firstStrong);
    717 }
    718 
    719 void TextNodeChangedDirection(Text* aTextNode, Directionality aOldDir,
    720                              bool aNotify) {
    721  MOZ_ASSERT(AffectsDirAutoElement(aTextNode), "Caller should check");
    722  Directionality newDir = GetDirectionFromText(aTextNode);
    723  if (newDir == aOldDir) {
    724    return;
    725  }
    726  // If the old directionality is Unset, we might determine now dir=auto
    727  // ancestor direction now, even if we don't have the MaySetDirAuto flag.
    728  //
    729  // Otherwise we used to have a strong directionality and either no longer
    730  // does, or it changed. We might need to reset the direction.
    731  if (aOldDir == Directionality::Unset || aTextNode->MaySetDirAuto()) {
    732    SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify);
    733  }
    734 }
    735 
    736 void SetDirectionFromNewTextNode(Text* aTextNode) {
    737  // Need to check parent as aTextNode does not yet have flags set
    738  if (!AffectsDirAutoElement(aTextNode->GetParent())) {
    739    return;
    740  }
    741 
    742  nsIContent* parent = aTextNode->GetParent();
    743  MOZ_ASSERT(parent);
    744  if (parent->NodeOrAncestorHasDirAuto()) {
    745    aTextNode->SetAncestorHasDirAuto();
    746  }
    747  if (parent->AffectsDirAutoSlot()) {
    748    aTextNode->SetAffectsDirAutoSlot();
    749  }
    750 
    751  Directionality dir = GetDirectionFromText(aTextNode);
    752  if (dir != Directionality::Unset) {
    753    SetAncestorDirectionIfAuto(aTextNode, dir);
    754  }
    755 }
    756 
    757 /**
    758 * Reset auto directionality for impacted elements when aTextNode is removed
    759 */
    760 void ResetDirectionSetByTextNode(Text* aTextNode,
    761                                 dom::UnbindContext& aContext) {
    762  MOZ_ASSERT(!aTextNode->IsInComposedDoc(), "Should be disconnected already");
    763  if (!aTextNode->MaySetDirAuto()) {
    764    return;
    765  }
    766  AutoTArray<Element*, 4> autoElements;
    767  bool answerIsDefinitive = FindDirAutoElementsFrom(aTextNode, autoElements);
    768 
    769  if (answerIsDefinitive) {
    770    // All dir=auto elements are in our (now detached) subtree. We're done, as
    771    // nothing really changed for our purposes.
    772    return;
    773  }
    774  // The dir=auto element might have been on the element we're unbinding from.
    775  // In any case, this text node is clearly no longer what determines its
    776  // directionality.
    777  aTextNode->ClearMaySetDirAuto();
    778  auto* unboundFrom =
    779      nsIContent::FromNodeOrNull(aContext.GetOriginalSubtreeParent());
    780  if (!unboundFrom || !AffectsDirAutoElement(unboundFrom)) {
    781    return;
    782  }
    783 
    784  Directionality dir = GetDirectionFromText(aTextNode);
    785  if (dir == Directionality::Unset) {
    786    return;
    787  }
    788 
    789  autoElements.Clear();
    790  FindDirAutoElementsFrom(unboundFrom, autoElements);
    791  for (Element* autoElement : autoElements) {
    792    if (autoElement->GetDirectionality() != dir) {
    793      // it's dir was not determined by this text node
    794      continue;
    795    }
    796    ResetAutoDirection(autoElement, /* aNotify = */ true);
    797  }
    798 }
    799 
    800 void ResetDirFormAssociatedElement(Element* aElement, bool aNotify,
    801                                   bool aHasDirAuto,
    802                                   const nsAString* aKnownValue) {
    803  if (aHasDirAuto) {
    804    Directionality dir = Directionality::Unset;
    805 
    806    if (aKnownValue && IsAutoDirectionalityFormAssociatedElement(aElement)) {
    807      dir = GetDirectionFromText(aKnownValue->BeginReading(),
    808                                 aKnownValue->Length());
    809    } else if (!aKnownValue) {
    810      if (Maybe<nsAutoString> maybe =
    811              GetValueIfFormAssociatedElement(aElement)) {
    812        dir = GetDirectionFromText(maybe.value().BeginReading(),
    813                                   maybe.value().Length());
    814      }
    815    }
    816 
    817    // https://html.spec.whatwg.org/#the-directionality
    818    // If auto directionality returns null, then return ltr
    819    if (dir == Directionality::Unset) {
    820      dir = Directionality::Ltr;
    821    }
    822 
    823    if (aElement->GetDirectionality() != dir) {
    824      aElement->SetDirectionality(dir, aNotify);
    825    }
    826  }
    827 
    828  // If aElement is assigned to a dir=auto slot, it might determine its auto
    829  // directionality
    830  if (HTMLSlotElement* slot = aElement->GetAssignedSlot()) {
    831    if (slot->HasDirAuto() &&
    832        slot->GetDirectionality() != aElement->GetDirectionality()) {
    833      ResetAutoDirection(slot, aNotify);
    834    }
    835  }
    836 }
    837 
    838 void OnSetDirAttr(Element* aElement, const nsAttrValue* aNewValue,
    839                  bool hadValidDir, bool hadDirAuto, bool aNotify) {
    840  if (!ParticipatesInAutoDirection(aElement)) {
    841    return;
    842  }
    843 
    844  auto* elementAsSlot = HTMLSlotElement::FromNode(aElement);
    845 
    846  // If element was a boundary but is no more, inherit flags to subtree
    847  if ((hadDirAuto || hadValidDir) && !EstablishesOwnDirection(aElement)) {
    848    if (auto* slot = aElement->GetAssignedSlot()) {
    849      if (slot->HasDirAuto()) {
    850        aElement->SetAffectsDirAutoSlot();
    851      }
    852    }
    853    if (auto* parent = aElement->GetParent()) {
    854      DownwardPropagateDirAutoFlags(parent);
    855    }
    856  }
    857 
    858  if (AffectsDirAutoElement(aElement)) {
    859    // The element is a descendant of an element with dir = auto, is having its
    860    // dir attribute changed. Reset the direction of any of its ancestors whose
    861    // direction might be determined by a text node descendant
    862    WalkAncestorsResetAutoDirection(aElement, aNotify);
    863  } else if (hadDirAuto && !aElement->HasDirAuto()) {
    864    // The element isn't a descendant of an element with dir = auto, and is
    865    // having its dir attribute set to something other than auto.
    866    // Walk the descendant tree and clear the AncestorHasDirAuto flag.
    867    //
    868    // N.B: For elements other than <bdi> it would be enough to test that the
    869    //      current value of dir was "auto" in BeforeSetAttr to know that we
    870    //      were unsetting dir="auto". For <bdi> things are more complicated,
    871    //      since it behaves like dir="auto" whenever the dir attribute is
    872    //      empty or invalid, so we would have to check whether the old value
    873    //      was not either "ltr" or "rtl", and the new value was either "ltr"
    874    //      or "rtl". Element::HasDirAuto() encapsulates all that, so doing it
    875    //      here is simpler.
    876    WalkDescendantsClearAncestorDirAuto(aElement);
    877    if (elementAsSlot) {
    878      for (const auto& assignedNode : elementAsSlot->AssignedNodes()) {
    879        MaybeClearAffectsDirAutoSlot(assignedNode->AsContent());
    880      }
    881    }
    882  }
    883 
    884  if (aElement->HasDirAuto()) {
    885    if (elementAsSlot) {
    886      for (const auto& assignedNode : elementAsSlot->AssignedNodes()) {
    887        assignedNode->SetAffectsDirAutoSlot();
    888        DownwardPropagateDirAutoFlags(assignedNode);
    889      }
    890    }
    891    MaybeClearAffectsDirAutoSlot(aElement);
    892    WalkDescendantsSetDirAuto(aElement, aNotify);
    893  } else {
    894    Directionality oldDir = aElement->GetDirectionality();
    895    Directionality dir = RecomputeDirectionality(aElement, aNotify);
    896    if (oldDir != dir) {
    897      SetDirectionalityOnDescendants(aElement, dir, aNotify);
    898    }
    899  }
    900 }
    901 
    902 void SetDirOnBind(Element* aElement, nsIContent* aParent) {
    903  // Propagate flags from parent to new element
    904  if (!EstablishesOwnDirection(aElement) && AffectsDirAutoElement(aParent)) {
    905    if (aParent->NodeOrAncestorHasDirAuto()) {
    906      aElement->SetAncestorHasDirAuto();
    907    }
    908    if (aParent->AffectsDirAutoSlot()) {
    909      aElement->SetAffectsDirAutoSlot();
    910    }
    911    DownwardPropagateDirAutoFlags(aElement);
    912 
    913    if (aElement->GetFirstChild() ||
    914        (aElement->IsInShadowTree() && !aElement->HasValidDir() &&
    915         aElement->IsHTMLElement(nsGkAtoms::slot))) {
    916      // We may also need to reset the direction of an ancestor with dir=auto
    917      // as we are either an element with possible text descendants
    918      // or a slot that provides it's host directionality
    919      WalkAncestorsResetAutoDirection(aElement, true);
    920    }
    921  }
    922 
    923  if (!aElement->HasDirAuto()) {
    924    // if the element doesn't have dir=auto, set its own directionality from
    925    // the dir attribute or by inheriting from its ancestors.
    926    RecomputeDirectionality(aElement, false);
    927  }
    928 }
    929 
    930 void ResetDir(Element* aElement) {
    931  if (!aElement->HasDirAuto()) {
    932    RecomputeDirectionality(aElement, false);
    933  }
    934 }
    935 
    936 }  // end namespace mozilla