tor-browser

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

HTMLSlotElement.cpp (13167B)


      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 #include "mozilla/dom/HTMLSlotElement.h"
      8 
      9 #include "mozilla/AppShutdown.h"
     10 #include "mozilla/PresShell.h"
     11 #include "mozilla/dom/DocGroup.h"
     12 #include "mozilla/dom/Document.h"
     13 #include "mozilla/dom/HTMLSlotElementBinding.h"
     14 #include "mozilla/dom/HTMLUnknownElement.h"
     15 #include "mozilla/dom/ShadowRoot.h"
     16 #include "mozilla/dom/Text.h"
     17 #include "nsContentUtils.h"
     18 #include "nsGkAtoms.h"
     19 
     20 nsGenericHTMLElement* NS_NewHTMLSlotElement(
     21    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
     22    mozilla::dom::FromParser aFromParser) {
     23  RefPtr<mozilla::dom::NodeInfo> nodeInfo(std::move(aNodeInfo));
     24  auto* nim = nodeInfo->NodeInfoManager();
     25  return new (nim) mozilla::dom::HTMLSlotElement(nodeInfo.forget());
     26 }
     27 
     28 namespace mozilla::dom {
     29 
     30 HTMLSlotElement::HTMLSlotElement(
     31    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     32    : nsGenericHTMLElement(std::move(aNodeInfo)) {}
     33 
     34 HTMLSlotElement::~HTMLSlotElement() {
     35  for (const auto& node : mManuallyAssignedNodes) {
     36    MOZ_ASSERT(node->AsContent()->GetManualSlotAssignment() == this);
     37    node->AsContent()->SetManualSlotAssignment(nullptr);
     38  }
     39 }
     40 
     41 NS_IMPL_ADDREF_INHERITED(HTMLSlotElement, nsGenericHTMLElement)
     42 NS_IMPL_RELEASE_INHERITED(HTMLSlotElement, nsGenericHTMLElement)
     43 
     44 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLSlotElement, nsGenericHTMLElement,
     45                                   mAssignedNodes)
     46 
     47 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLSlotElement)
     48 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
     49 
     50 NS_IMPL_ELEMENT_CLONE(HTMLSlotElement)
     51 
     52 nsresult HTMLSlotElement::BindToTree(BindContext& aContext, nsINode& aParent) {
     53  RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow();
     54 
     55  nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
     56  NS_ENSURE_SUCCESS(rv, rv);
     57 
     58  ShadowRoot* containingShadow = GetContainingShadow();
     59  mInManualShadowRoot =
     60      containingShadow &&
     61      containingShadow->SlotAssignment() == SlotAssignmentMode::Manual;
     62  if (containingShadow && !oldContainingShadow) {
     63    containingShadow->AddSlot(this);
     64  }
     65 
     66  return NS_OK;
     67 }
     68 
     69 void HTMLSlotElement::UnbindFromTree(UnbindContext& aContext) {
     70  RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow();
     71 
     72  nsGenericHTMLElement::UnbindFromTree(aContext);
     73 
     74  if (oldContainingShadow && !GetContainingShadow()) {
     75    oldContainingShadow->RemoveSlot(this);
     76  }
     77 }
     78 
     79 void HTMLSlotElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
     80                                    const nsAttrValue* aValue, bool aNotify) {
     81  if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::name) {
     82    if (ShadowRoot* containingShadow = GetContainingShadow()) {
     83      containingShadow->RemoveSlot(this);
     84    }
     85  }
     86 
     87  return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
     88                                             aNotify);
     89 }
     90 
     91 void HTMLSlotElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
     92                                   const nsAttrValue* aValue,
     93                                   const nsAttrValue* aOldValue,
     94                                   nsIPrincipal* aSubjectPrincipal,
     95                                   bool aNotify) {
     96  if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::name) {
     97    if (ShadowRoot* containingShadow = GetContainingShadow()) {
     98      containingShadow->AddSlot(this);
     99    }
    100  }
    101 
    102  return nsGenericHTMLElement::AfterSetAttr(
    103      aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
    104 }
    105 
    106 /**
    107 * Flatten assigned nodes given a slot, as in:
    108 * https://dom.spec.whatwg.org/#find-flattened-slotables
    109 */
    110 static void FlattenAssignedNodes(HTMLSlotElement* aSlot,
    111                                 nsTArray<RefPtr<nsINode>>& aNodes) {
    112  if (!aSlot->GetContainingShadow()) {
    113    return;
    114  }
    115 
    116  const Span<const RefPtr<nsINode>> assignedNodes = aSlot->AssignedNodes();
    117 
    118  // If assignedNodes is empty, use children of slot as fallback content.
    119  if (assignedNodes.IsEmpty()) {
    120    for (nsIContent* child = aSlot->GetFirstChild(); child;
    121         child = child->GetNextSibling()) {
    122      if (!child->IsSlotable()) {
    123        continue;
    124      }
    125 
    126      if (auto* slot = HTMLSlotElement::FromNode(child)) {
    127        FlattenAssignedNodes(slot, aNodes);
    128      } else {
    129        aNodes.AppendElement(child);
    130      }
    131    }
    132    return;
    133  }
    134 
    135  for (const RefPtr<nsINode>& assignedNode : assignedNodes) {
    136    auto* slot = HTMLSlotElement::FromNode(assignedNode);
    137    if (slot && slot->GetContainingShadow()) {
    138      FlattenAssignedNodes(slot, aNodes);
    139    } else {
    140      aNodes.AppendElement(assignedNode);
    141    }
    142  }
    143 }
    144 
    145 void HTMLSlotElement::AssignedNodes(const AssignedNodesOptions& aOptions,
    146                                    nsTArray<RefPtr<nsINode>>& aNodes) {
    147  if (aOptions.mFlatten) {
    148    return FlattenAssignedNodes(this, aNodes);
    149  }
    150 
    151  aNodes.AppendElements(mAssignedNodes.AsSpan());
    152 }
    153 
    154 void HTMLSlotElement::AssignedElements(const AssignedNodesOptions& aOptions,
    155                                       nsTArray<RefPtr<Element>>& aElements) {
    156  AutoTArray<RefPtr<nsINode>, 128> assignedNodes;
    157  AssignedNodes(aOptions, assignedNodes);
    158  for (const RefPtr<nsINode>& assignedNode : assignedNodes) {
    159    if (assignedNode->IsElement()) {
    160      aElements.AppendElement(assignedNode->AsElement());
    161    }
    162  }
    163 }
    164 
    165 const nsTArray<nsINode*>& HTMLSlotElement::ManuallyAssignedNodes() const {
    166  return mManuallyAssignedNodes;
    167 }
    168 
    169 void HTMLSlotElement::Assign(const Sequence<OwningElementOrText>& aNodes) {
    170  nsAutoScriptBlocker scriptBlocker;
    171 
    172  // no-op if the input nodes and the assigned nodes are identical
    173  // This also works if the two 'assign' calls are like
    174  //   > slot.assign(node1, node2);
    175  //   > slot.assign(node1, node2, node1, node2);
    176  if (!mAssignedNodes.IsEmpty() && aNodes.Length() >= mAssignedNodes.Length()) {
    177    nsTHashMap<nsPtrHashKey<nsIContent>, size_t> nodeIndexMap;
    178    for (size_t i = 0; i < aNodes.Length(); ++i) {
    179      nsIContent* content;
    180      if (aNodes[i].IsElement()) {
    181        content = aNodes[i].GetAsElement();
    182      } else {
    183        content = aNodes[i].GetAsText();
    184      }
    185      MOZ_ASSERT(content);
    186      // We only care about the first index this content appears
    187      // in the array
    188      nodeIndexMap.LookupOrInsert(content, i);
    189    }
    190 
    191    if (nodeIndexMap.Count() == mAssignedNodes.Length()) {
    192      bool isIdentical = true;
    193      for (size_t i = 0; i < mAssignedNodes.Length(); ++i) {
    194        size_t indexInInputNodes;
    195        if (!nodeIndexMap.Get(mAssignedNodes[i]->AsContent(),
    196                              &indexInInputNodes) ||
    197            indexInInputNodes != i) {
    198          isIdentical = false;
    199          break;
    200        }
    201      }
    202      if (isIdentical) {
    203        return;
    204      }
    205    }
    206  }
    207 
    208  // 1. For each node of this's manually assigned nodes, set node's manual slot
    209  // assignment to null.
    210  for (nsINode* node : mManuallyAssignedNodes) {
    211    MOZ_ASSERT(node->AsContent()->GetManualSlotAssignment() == this);
    212    node->AsContent()->SetManualSlotAssignment(nullptr);
    213  }
    214 
    215  // 2. Let nodesSet be a new ordered set.
    216  mManuallyAssignedNodes.Clear();
    217 
    218  nsIContent* host = nullptr;
    219  ShadowRoot* root = GetContainingShadow();
    220 
    221  // An optimization to keep track which slots need to enqueue
    222  // slotchange event, such that they can be enqueued later in
    223  // tree order.
    224  nsTHashSet<RefPtr<HTMLSlotElement>> changedSlots;
    225 
    226  // Clear out existing assigned nodes
    227  if (mInManualShadowRoot) {
    228    if (!mAssignedNodes.IsEmpty()) {
    229      changedSlots.EnsureInserted(this);
    230      if (root) {
    231        root->InvalidateStyleAndLayoutOnSubtree(this);
    232      }
    233      ClearAssignedNodes();
    234    }
    235 
    236    MOZ_ASSERT(mAssignedNodes.IsEmpty());
    237    host = GetContainingShadowHost();
    238  }
    239 
    240  for (const OwningElementOrText& elementOrText : aNodes) {
    241    nsIContent* content;
    242    if (elementOrText.IsElement()) {
    243      content = elementOrText.GetAsElement();
    244    } else {
    245      content = elementOrText.GetAsText();
    246    }
    247 
    248    MOZ_ASSERT(content);
    249    // XXXsmaug Should we have a helper for
    250    //         https://infra.spec.whatwg.org/#ordered-set?
    251    if (content->GetManualSlotAssignment() != this) {
    252      if (HTMLSlotElement* oldSlot = content->GetAssignedSlot()) {
    253        if (changedSlots.EnsureInserted(oldSlot)) {
    254          if (root) {
    255            MOZ_ASSERT(oldSlot->GetContainingShadow() == root);
    256            root->InvalidateStyleAndLayoutOnSubtree(oldSlot);
    257          }
    258        }
    259      }
    260 
    261      if (changedSlots.EnsureInserted(this)) {
    262        if (root) {
    263          root->InvalidateStyleAndLayoutOnSubtree(this);
    264        }
    265      }
    266      // 3.1 (HTML Spec) If content's manual slot assignment refers to a slot,
    267      // then remove node from that slot's manually assigned nodes. 3.2 (HTML
    268      // Spec) Set content's manual slot assignment to this.
    269      if (HTMLSlotElement* oldSlot = content->GetManualSlotAssignment()) {
    270        oldSlot->RemoveManuallyAssignedNode(*content);
    271      }
    272      content->SetManualSlotAssignment(this);
    273      mManuallyAssignedNodes.AppendElement(content);
    274 
    275      if (root && host && content->GetParent() == host) {
    276        // Equivalent to 4.2.2.4.3 (DOM Spec) `Set slot's assigned nodes to
    277        // slottables`
    278        root->MaybeReassignContent(*content);
    279      }
    280    }
    281  }
    282 
    283  // The `assign slottables` step is completed already at this point,
    284  // however we haven't fired the `slotchange` event yet because this
    285  // needs to be done in tree order.
    286  if (root) {
    287    for (nsIContent* child = root->GetFirstChild(); child;
    288         child = child->GetNextNode()) {
    289      if (HTMLSlotElement* slot = HTMLSlotElement::FromNode(child)) {
    290        if (changedSlots.EnsureRemoved(slot)) {
    291          slot->EnqueueSlotChangeEvent();
    292        }
    293      }
    294    }
    295    MOZ_ASSERT(changedSlots.IsEmpty());
    296  }
    297 }
    298 
    299 void HTMLSlotElement::InsertAssignedNode(uint32_t aIndex, nsIContent& aNode) {
    300  MOZ_ASSERT(!aNode.GetAssignedSlot(), "Losing track of a slot");
    301  mAssignedNodes.InsertElementAt(aIndex, &aNode);
    302  aNode.SetAssignedSlot(this);
    303  RecalculateHasSlottedState();
    304  SlotAssignedNodeAdded(this, aNode);
    305 }
    306 
    307 void HTMLSlotElement::AppendAssignedNode(nsIContent& aNode) {
    308  MOZ_ASSERT(!aNode.GetAssignedSlot(), "Losing track of a slot");
    309  mAssignedNodes.AppendElement(&aNode);
    310  aNode.SetAssignedSlot(this);
    311  RecalculateHasSlottedState();
    312  SlotAssignedNodeAdded(this, aNode);
    313 }
    314 
    315 void HTMLSlotElement::RecalculateHasSlottedState() {
    316  bool hasSlotted = false;
    317  // Find the first node that makes this a slotted element.
    318  for (const RefPtr<nsINode>& assignedNode : mAssignedNodes.AsSpan()) {
    319    if (auto* slot = HTMLSlotElement::FromNode(assignedNode)) {
    320      if (slot->IsInShadowTree() &&
    321          !slot->State().HasState(ElementState::HAS_SLOTTED)) {
    322        continue;
    323      }
    324    }
    325    hasSlotted = true;
    326    break;
    327  }
    328  if (State().HasState(ElementState::HAS_SLOTTED) != hasSlotted) {
    329    SetStates(ElementState::HAS_SLOTTED, hasSlotted);
    330    // If slot is a slotted node itself, the assigned slot needs to
    331    // RecalculateHasSlottedState:
    332    if (auto* slot = GetAssignedSlot()) {
    333      slot->RecalculateHasSlottedState();
    334    }
    335  }
    336 }
    337 
    338 void HTMLSlotElement::RemoveAssignedNode(nsIContent& aNode) {
    339  // This one runs from unlinking, so we can't guarantee that the slot pointer
    340  // hasn't been cleared.
    341  MOZ_ASSERT(!aNode.GetAssignedSlot() || aNode.GetAssignedSlot() == this,
    342             "How exactly?");
    343  mAssignedNodes.RemoveElement(&aNode);
    344  aNode.SetAssignedSlot(nullptr);
    345 
    346  RecalculateHasSlottedState();
    347  SlotAssignedNodeRemoved(this, aNode);
    348 }
    349 
    350 void HTMLSlotElement::ClearAssignedNodes() {
    351  for (RefPtr<nsINode>& node : mAssignedNodes.AsSpan()) {
    352    MOZ_ASSERT(!node->AsContent()->GetAssignedSlot() ||
    353                   node->AsContent()->GetAssignedSlot() == this,
    354               "How exactly?");
    355    node->AsContent()->SetAssignedSlot(nullptr);
    356  }
    357 
    358  mAssignedNodes.Clear();
    359  RecalculateHasSlottedState();
    360 }
    361 
    362 void HTMLSlotElement::EnqueueSlotChangeEvent() {
    363  if (mInSignalSlotList) {
    364    return;
    365  }
    366 
    367  // FIXME(bug 1459704): Need to figure out how to deal with microtasks posted
    368  // during shutdown.
    369  if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads)) {
    370    return;
    371  }
    372 
    373  DocGroup* docGroup = OwnerDoc()->GetDocGroup();
    374  if (!docGroup) {
    375    return;
    376  }
    377 
    378  mInSignalSlotList = true;
    379  docGroup->SignalSlotChange(*this);
    380 }
    381 
    382 void HTMLSlotElement::FireSlotChangeEvent() {
    383  nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, u"slotchange"_ns,
    384                                       CanBubble::eYes, Cancelable::eNo);
    385 }
    386 
    387 void HTMLSlotElement::RemoveManuallyAssignedNode(nsIContent& aNode) {
    388  mManuallyAssignedNodes.RemoveElement(&aNode);
    389  RemoveAssignedNode(aNode);
    390 }
    391 
    392 JSObject* HTMLSlotElement::WrapNode(JSContext* aCx,
    393                                    JS::Handle<JSObject*> aGivenProto) {
    394  return HTMLSlotElement_Binding::Wrap(aCx, this, aGivenProto);
    395 }
    396 
    397 }  // namespace mozilla::dom