dom/base/ShadowRoot.cpp
author Dave Townsend <dtownsend@oxymoronical.com>
Sun, 13 Jan 2019 17:44:29 -0800
changeset 453676 edca8877b0505cd1c31beaf6d907ca32e022aa52
parent 452878 54f18c480711e3cee577e4174f6def86ec2b2a05
child 457729 f6c8336fa7d2f056305fedc4939c51da9a09a74e
permissions -rw-r--r--
Backing out Bug 1518799 (changeset eace4709948c) because the target page is not yet available. a=backout

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/Preferences.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/DocumentFragment.h"
#include "ChildIterator.h"
#include "nsContentUtils.h"
#include "nsIStyleSheetLinkingElement.h"
#include "nsWindowSizes.h"
#include "nsXBLPrototypeBinding.h"
#include "mozilla/dom/DirectionalityUtils.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLSlotElement.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/IdentifierMapEntry.h"
#include "mozilla/ServoStyleRuleMap.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/dom/StyleSheetList.h"

using namespace mozilla;
using namespace mozilla::dom;

NS_IMPL_CYCLE_COLLECTION_CLASS(ShadowRoot)

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot, DocumentFragment)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets)
  for (StyleSheet* sheet : tmp->mStyleSheets) {
    // mServoStyles keeps another reference to it if applicable.
    if (sheet->IsApplicable()) {
      MOZ_ASSERT(tmp->mServoStyles);
      NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mServoStyles->sheets[i]");
      cb.NoteXPCOMChild(sheet);
    }
  }
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStyleSheets)
  for (auto iter = tmp->mIdentifierMap.ConstIter(); !iter.Done(); iter.Next()) {
    iter.Get()->Traverse(&cb);
  }
  DocumentOrShadowRoot::Traverse(tmp, cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ShadowRoot)
  if (tmp->GetHost()) {
    tmp->GetHost()->RemoveMutationObserver(tmp);
  }
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStyleSheets)
  tmp->mIdentifierMap.Clear();
  DocumentOrShadowRoot::Unlink(tmp);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DocumentFragment)

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ShadowRoot)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
  NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
  NS_INTERFACE_MAP_ENTRY(nsIRadioGroupContainer)
NS_INTERFACE_MAP_END_INHERITING(DocumentFragment)

NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment)
NS_IMPL_RELEASE_INHERITED(ShadowRoot, DocumentFragment)

ShadowRoot::ShadowRoot(Element* aElement, ShadowRootMode aMode,
                       already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
    : DocumentFragment(std::move(aNodeInfo)),
      DocumentOrShadowRoot(*this),
      mMode(aMode),
      mIsUAWidget(false) {
  SetHost(aElement);

  // Nodes in a shadow tree should never store a value
  // in the subtree root pointer, nodes in the shadow tree
  // track the subtree root using GetContainingShadow().
  ClearSubtreeRootPointer();

  SetFlags(NODE_IS_IN_SHADOW_TREE);
  Bind();

  ExtendedDOMSlots()->mBindingParent = aElement;
  ExtendedDOMSlots()->mContainingShadow = this;

  // Add the ShadowRoot as a mutation observer on the host to watch
  // for mutations because the insertion points in this ShadowRoot
  // may need to be updated when the host children are modified.
  GetHost()->AddMutationObserver(this);
}

ShadowRoot::~ShadowRoot() {
  if (auto* host = GetHost()) {
    // mHost may have been unlinked.
    host->RemoveMutationObserver(this);
  }

  if (IsInComposedDoc()) {
    OwnerDoc()->RemoveComposedDocShadowRoot(*this);
  }

  MOZ_DIAGNOSTIC_ASSERT(!OwnerDoc()->IsComposedDocShadowRoot(*this));

  UnsetFlags(NODE_IS_IN_SHADOW_TREE);

  // nsINode destructor expects mSubtreeRoot == this.
  SetSubtreeRootPointer(this);
}

MOZ_DEFINE_MALLOC_SIZE_OF(ShadowRootAuthorStylesMallocSizeOf)
MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(ShadowRootAuthorStylesMallocEnclosingSizeOf)

void ShadowRoot::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
                                        size_t* aNodeSize) const {
  DocumentFragment::AddSizeOfExcludingThis(aSizes, aNodeSize);
  DocumentOrShadowRoot::AddSizeOfExcludingThis(aSizes);
  aSizes.mLayoutShadowDomAuthorStyles += Servo_AuthorStyles_SizeOfIncludingThis(
      ShadowRootAuthorStylesMallocSizeOf,
      ShadowRootAuthorStylesMallocEnclosingSizeOf, mServoStyles.get());
}

JSObject* ShadowRoot::WrapObject(JSContext* aCx,
                                 JS::Handle<JSObject*> aGivenProto) {
  return mozilla::dom::ShadowRoot_Binding::Wrap(aCx, this, aGivenProto);
}

void ShadowRoot::CloneInternalDataFrom(ShadowRoot* aOther) {
  size_t sheetCount = aOther->SheetCount();
  for (size_t i = 0; i < sheetCount; ++i) {
    StyleSheet* sheet = aOther->SheetAt(i);
    if (sheet->IsApplicable()) {
      RefPtr<StyleSheet> clonedSheet =
          sheet->Clone(nullptr, nullptr, this, nullptr);
      if (clonedSheet) {
        AppendStyleSheet(*clonedSheet.get());
      }
    }
  }
}

nsresult ShadowRoot::Bind() {
  MOZ_ASSERT(!IsInComposedDoc(), "Forgot to unbind?");
  if (Host()->IsInComposedDoc()) {
    SetIsConnected(true);
    OwnerDoc()->AddComposedDocShadowRoot(*this);
  }

  for (nsIContent* child = GetFirstChild(); child;
       child = child->GetNextSibling()) {
    nsresult rv = child->BindToTree(nullptr, this, Host());
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return NS_OK;
}

void ShadowRoot::Unbind() {
  if (IsInComposedDoc()) {
    SetIsConnected(false);
    OwnerDoc()->RemoveComposedDocShadowRoot(*this);
  }

  for (nsIContent* child = GetFirstChild(); child;
       child = child->GetNextSibling()) {
    child->UnbindFromTree(true, false);
  }
}

void ShadowRoot::Unattach() {
  MOZ_ASSERT(!HasSlots(), "Won't work!");
  if (!GetHost()) {
    // It is possible that we've been unlinked already. In such case host
    // should have called Unbind and ShadowRoot's own unlink
    // RemoveMutationObserver.
    return;
  }

  Unbind();
  GetHost()->RemoveMutationObserver(this);
  SetHost(nullptr);
}

void ShadowRoot::InvalidateStyleAndLayoutOnSubtree(Element* aElement) {
  MOZ_ASSERT(aElement);
  Document* doc = GetComposedDoc();
  if (!doc) {
    return;
  }

  nsIPresShell* shell = doc->GetShell();
  if (!shell) {
    return;
  }

  shell->DestroyFramesForAndRestyle(aElement);
}

void ShadowRoot::AddSlot(HTMLSlotElement* aSlot) {
  MOZ_ASSERT(aSlot);

  // Note that if name attribute missing, the slot is a default slot.
  nsAutoString name;
  aSlot->GetName(name);

  nsTArray<HTMLSlotElement*>* currentSlots = mSlotMap.LookupOrAdd(name);
  MOZ_ASSERT(currentSlots);

  HTMLSlotElement* oldSlot = currentSlots->SafeElementAt(0);

  TreeOrderComparator comparator;
  currentSlots->InsertElementSorted(aSlot, comparator);

  HTMLSlotElement* currentSlot = currentSlots->ElementAt(0);
  if (currentSlot != aSlot) {
    return;
  }

  if (oldSlot && oldSlot != currentSlot) {
    // Move assigned nodes from old slot to new slot.
    InvalidateStyleAndLayoutOnSubtree(oldSlot);
    const nsTArray<RefPtr<nsINode>>& assignedNodes = oldSlot->AssignedNodes();
    bool doEnqueueSlotChange = false;
    while (assignedNodes.Length() > 0) {
      nsINode* assignedNode = assignedNodes[0];

      oldSlot->RemoveAssignedNode(assignedNode);
      currentSlot->AppendAssignedNode(assignedNode);
      doEnqueueSlotChange = true;
    }

    if (doEnqueueSlotChange) {
      oldSlot->EnqueueSlotChangeEvent();
      currentSlot->EnqueueSlotChangeEvent();
      SlotStateChanged(oldSlot);
      SlotStateChanged(currentSlot);
    }
  } else {
    bool doEnqueueSlotChange = false;
    // Otherwise add appropriate nodes to this slot from the host.
    for (nsIContent* child = GetHost()->GetFirstChild(); child;
         child = child->GetNextSibling()) {
      nsAutoString slotName;
      if (child->IsElement()) {
        child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::slot,
                                    slotName);
      }
      if (!child->IsSlotable() || !slotName.Equals(name)) {
        continue;
      }
      doEnqueueSlotChange = true;
      currentSlot->AppendAssignedNode(child);
    }

    if (doEnqueueSlotChange) {
      currentSlot->EnqueueSlotChangeEvent();
      SlotStateChanged(currentSlot);
    }
  }
}

void ShadowRoot::RemoveSlot(HTMLSlotElement* aSlot) {
  MOZ_ASSERT(aSlot);

  nsAutoString name;
  aSlot->GetName(name);

  SlotArray* currentSlots = mSlotMap.Get(name);
  MOZ_DIAGNOSTIC_ASSERT(currentSlots && currentSlots->Contains(aSlot),
                        "Slot to deregister wasn't found?");
  if (currentSlots->Length() == 1) {
    MOZ_ASSERT(currentSlots->ElementAt(0) == aSlot);

    InvalidateStyleAndLayoutOnSubtree(aSlot);

    mSlotMap.Remove(name);
    if (!aSlot->AssignedNodes().IsEmpty()) {
      aSlot->ClearAssignedNodes();
      aSlot->EnqueueSlotChangeEvent();
    }

    return;
  }

  const bool wasFirstSlot = currentSlots->ElementAt(0) == aSlot;
  currentSlots->RemoveElement(aSlot);

  // Move assigned nodes from removed slot to the next slot in
  // tree order with the same name.
  if (!wasFirstSlot) {
    return;
  }

  InvalidateStyleAndLayoutOnSubtree(aSlot);
  HTMLSlotElement* replacementSlot = currentSlots->ElementAt(0);
  const nsTArray<RefPtr<nsINode>>& assignedNodes = aSlot->AssignedNodes();
  bool slottedNodesChanged = !assignedNodes.IsEmpty();
  while (!assignedNodes.IsEmpty()) {
    nsINode* assignedNode = assignedNodes[0];

    aSlot->RemoveAssignedNode(assignedNode);
    replacementSlot->AppendAssignedNode(assignedNode);
  }

  if (slottedNodesChanged) {
    aSlot->EnqueueSlotChangeEvent();
    replacementSlot->EnqueueSlotChangeEvent();
  }
}

// FIXME(emilio): There's a bit of code duplication between this and the
// equivalent ServoStyleSet methods, it'd be nice to not duplicate it...
void ShadowRoot::RuleAdded(StyleSheet& aSheet, css::Rule& aRule) {
  if (!aSheet.IsApplicable()) {
    return;
  }

  MOZ_ASSERT(mServoStyles);
  if (mStyleRuleMap) {
    mStyleRuleMap->RuleAdded(aSheet, aRule);
  }
  Servo_AuthorStyles_ForceDirty(mServoStyles.get());
  ApplicableRulesChanged();
}

void ShadowRoot::RuleRemoved(StyleSheet& aSheet, css::Rule& aRule) {
  if (!aSheet.IsApplicable()) {
    return;
  }

  MOZ_ASSERT(mServoStyles);
  if (mStyleRuleMap) {
    mStyleRuleMap->RuleRemoved(aSheet, aRule);
  }
  Servo_AuthorStyles_ForceDirty(mServoStyles.get());
  ApplicableRulesChanged();
}

void ShadowRoot::RuleChanged(StyleSheet& aSheet, css::Rule*) {
  if (!aSheet.IsApplicable()) {
    return;
  }

  MOZ_ASSERT(mServoStyles);
  Servo_AuthorStyles_ForceDirty(mServoStyles.get());
  ApplicableRulesChanged();
}

void ShadowRoot::ApplicableRulesChanged() {
  Document* doc = GetComposedDoc();
  if (!doc) {
    return;
  }

  if (nsIPresShell* shell = doc->GetShell()) {
    shell->RecordShadowStyleChange(*this);
  }
}

void ShadowRoot::InsertSheetAt(size_t aIndex, StyleSheet& aSheet) {
  DocumentOrShadowRoot::InsertSheetAt(aIndex, aSheet);
  if (aSheet.IsApplicable()) {
    InsertSheetIntoAuthorData(aIndex, aSheet);
  }
}

void ShadowRoot::InsertSheetIntoAuthorData(size_t aIndex, StyleSheet& aSheet) {
  MOZ_ASSERT(SheetAt(aIndex) == &aSheet);
  MOZ_ASSERT(aSheet.IsApplicable());

  if (!mServoStyles) {
    mServoStyles.reset(Servo_AuthorStyles_Create());
  }

  if (mStyleRuleMap) {
    mStyleRuleMap->SheetAdded(aSheet);
  }

  for (size_t i = aIndex + 1; i < SheetCount(); ++i) {
    StyleSheet* beforeSheet = SheetAt(i);
    if (!beforeSheet->IsApplicable()) {
      continue;
    }

    Servo_AuthorStyles_InsertStyleSheetBefore(mServoStyles.get(), &aSheet,
                                              beforeSheet);
    ApplicableRulesChanged();
    return;
  }

  Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), &aSheet);
  ApplicableRulesChanged();
}

// FIXME(emilio): This needs to notify document observers and such,
// presumably.
void ShadowRoot::StyleSheetApplicableStateChanged(StyleSheet& aSheet,
                                                  bool aApplicable) {
  int32_t index = IndexOfSheet(aSheet);
  if (index < 0) {
    // NOTE(emilio): @import sheets are handled in the relevant RuleAdded
    // notification, which only notifies after the sheet is loaded.
    //
    // This setup causes weirdness in other places, we may want to fix this in
    // bug 1465031.
    MOZ_DIAGNOSTIC_ASSERT(aSheet.GetParentSheet(),
                          "It'd better be an @import sheet");
    return;
  }
  if (aApplicable) {
    InsertSheetIntoAuthorData(size_t(index), aSheet);
  } else {
    MOZ_ASSERT(mServoStyles);
    if (mStyleRuleMap) {
      mStyleRuleMap->SheetRemoved(aSheet);
    }
    Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), &aSheet);
    ApplicableRulesChanged();
  }
}

void ShadowRoot::RemoveSheet(StyleSheet* aSheet) {
  MOZ_ASSERT(aSheet);
  RefPtr<StyleSheet> sheet = DocumentOrShadowRoot::RemoveSheet(*aSheet);
  MOZ_ASSERT(sheet);
  if (sheet->IsApplicable()) {
    MOZ_ASSERT(mServoStyles);
    if (mStyleRuleMap) {
      mStyleRuleMap->SheetRemoved(*sheet);
    }
    Servo_AuthorStyles_RemoveStyleSheet(mServoStyles.get(), sheet);
    ApplicableRulesChanged();
  }
}

void ShadowRoot::AddToIdTable(Element* aElement, nsAtom* aId) {
  IdentifierMapEntry* entry = mIdentifierMap.PutEntry(aId);
  if (entry) {
    entry->AddIdElement(aElement);
  }
}

void ShadowRoot::RemoveFromIdTable(Element* aElement, nsAtom* aId) {
  IdentifierMapEntry* entry = mIdentifierMap.GetEntry(aId);
  if (entry) {
    entry->RemoveIdElement(aElement);
    if (entry->IsEmpty()) {
      mIdentifierMap.RemoveEntry(entry);
    }
  }
}

void ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
  aVisitor.mCanHandle = true;
  aVisitor.mRootOfClosedTree = IsClosed();
  // Inform that we're about to exit the current scope.
  aVisitor.mRelatedTargetRetargetedInCurrentScope = false;

  // https://dom.spec.whatwg.org/#ref-for-get-the-parent%E2%91%A6
  if (!aVisitor.mEvent->mFlags.mComposed) {
    nsCOMPtr<nsIContent> originalTarget =
        do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
    if (originalTarget->GetContainingShadow() == this) {
      // If we do stop propagation, we still want to propagate
      // the event to chrome (nsPIDOMWindow::GetParentTarget()).
      // The load event is special in that we don't ever propagate it
      // to chrome.
      nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow();
      EventTarget* parentTarget = win && aVisitor.mEvent->mMessage != eLoad
                                      ? win->GetParentTarget()
                                      : nullptr;

      aVisitor.SetParentTarget(parentTarget, true);
      return;
    }
  }

  nsIContent* shadowHost = GetHost();
  aVisitor.SetParentTarget(shadowHost, false);

  nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mEvent->mTarget));
  if (content && content->GetBindingParent() == shadowHost) {
    aVisitor.mEventTargetAtParent = shadowHost;
  }
}

ShadowRoot::SlotAssignment ShadowRoot::SlotAssignmentFor(nsIContent* aContent) {
  nsAutoString slotName;
  // Note that if slot attribute is missing, assign it to the first default
  // slot, if exists.
  if (aContent->IsElement()) {
    aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::slot,
                                   slotName);
  }

  nsTArray<HTMLSlotElement*>* slots = mSlotMap.Get(slotName);
  if (!slots) {
    return {};
  }

  HTMLSlotElement* slot = slots->ElementAt(0);
  MOZ_ASSERT(slot);

  // Find the appropriate position in the assigned node list for the
  // newly assigned content.
  const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes();
  nsIContent* currentContent = GetHost()->GetFirstChild();
  Maybe<uint32_t> insertionIndex;
  for (uint32_t i = 0; i < assignedNodes.Length(); i++) {
    // Seek through the host's explicit children until the
    // assigned content is found.
    while (currentContent && currentContent != assignedNodes[i]) {
      if (currentContent == aContent) {
        insertionIndex.emplace(i);
        break;
      }

      currentContent = currentContent->GetNextSibling();
    }

    if (insertionIndex) {
      break;
    }
  }

  return {slot, insertionIndex};
}

void ShadowRoot::MaybeReassignElement(Element* aElement) {
  MOZ_ASSERT(aElement->GetParent() == GetHost());
  HTMLSlotElement* oldSlot = aElement->GetAssignedSlot();
  SlotAssignment assignment = SlotAssignmentFor(aElement);

  if (assignment.mSlot == oldSlot) {
    // Nothing to do here.
    return;
  }

  if (Document* doc = GetComposedDoc()) {
    if (nsIPresShell* shell = doc->GetShell()) {
      shell->SlotAssignmentWillChange(*aElement, oldSlot, assignment.mSlot);
    }
  }

  if (oldSlot) {
    oldSlot->RemoveAssignedNode(aElement);
    oldSlot->EnqueueSlotChangeEvent();
  }

  if (assignment.mSlot) {
    if (assignment.mIndex) {
      assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aElement);
    } else {
      assignment.mSlot->AppendAssignedNode(aElement);
    }
    assignment.mSlot->EnqueueSlotChangeEvent();
  }

  SlotStateChanged(oldSlot);
  SlotStateChanged(assignment.mSlot);
}

Element* ShadowRoot::GetActiveElement() {
  return GetRetargetedFocusedElement();
}

void ShadowRoot::GetInnerHTML(nsAString& aInnerHTML) {
  GetMarkup(false, aInnerHTML);
}

void ShadowRoot::SetInnerHTML(const nsAString& aInnerHTML,
                              ErrorResult& aError) {
  SetInnerHTMLInternal(aInnerHTML, aError);
}

nsINode* ShadowRoot::ImportNodeAndAppendChildAt(nsINode& aParentNode,
                                                nsINode& aNode, bool aDeep,
                                                mozilla::ErrorResult& rv) {
  MOZ_ASSERT(mIsUAWidget);

  if (!aParentNode.IsInUAWidget()) {
    rv.Throw(NS_ERROR_INVALID_ARG);
    return nullptr;
  }

  RefPtr<nsINode> node = OwnerDoc()->ImportNode(aNode, aDeep, rv);
  if (rv.Failed()) {
    return nullptr;
  }

  return aParentNode.AppendChild(*node, rv);
}

nsINode* ShadowRoot::CreateElementAndAppendChildAt(nsINode& aParentNode,
                                                   const nsAString& aTagName,
                                                   mozilla::ErrorResult& rv) {
  MOZ_ASSERT(mIsUAWidget);
  MOZ_ASSERT(OwnerDoc());

  if (!aParentNode.IsInUAWidget()) {
    rv.Throw(NS_ERROR_INVALID_ARG);
    return nullptr;
  }

  // This option is not exposed to UA Widgets
  ElementCreationOptionsOrString options;

  RefPtr<nsINode> node = OwnerDoc()->CreateElement(aTagName, options, rv);
  if (rv.Failed()) {
    return nullptr;
  }

  return aParentNode.AppendChild(*node, rv);
}

void ShadowRoot::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
                                  nsAtom* aAttribute, int32_t aModType,
                                  const nsAttrValue* aOldValue) {
  if (aNameSpaceID != kNameSpaceID_None || aAttribute != nsGkAtoms::slot) {
    return;
  }

  if (aElement->GetParent() != GetHost()) {
    return;
  }

  MaybeReassignElement(aElement);
}

void ShadowRoot::ContentAppended(nsIContent* aFirstNewContent) {
  for (nsIContent* content = aFirstNewContent; content;
       content = content->GetNextSibling()) {
    ContentInserted(content);
  }
}

void ShadowRoot::ContentInserted(nsIContent* aChild) {
  // Check to ensure that the child not an anonymous subtree root because
  // even though its parent could be the host it may not be in the host's child
  // list.
  if (aChild->IsRootOfAnonymousSubtree()) {
    return;
  }

  if (!aChild->IsSlotable()) {
    return;
  }

  if (aChild->GetParent() == GetHost()) {
    SlotAssignment assignment = SlotAssignmentFor(aChild);
    if (!assignment.mSlot) {
      return;
    }

    // Fallback content will go away, let layout know.
    if (assignment.mSlot->AssignedNodes().IsEmpty()) {
      InvalidateStyleAndLayoutOnSubtree(assignment.mSlot);
    }

    if (assignment.mIndex) {
      assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aChild);
    } else {
      assignment.mSlot->AppendAssignedNode(aChild);
    }
    assignment.mSlot->EnqueueSlotChangeEvent();

    SlotStateChanged(assignment.mSlot);
    return;
  }

  // If parent's root is a shadow root, and parent is a slot whose assigned
  // nodes is the empty list, then run signal a slot change for parent.
  HTMLSlotElement* slot = HTMLSlotElement::FromNodeOrNull(aChild->GetParent());
  if (slot && slot->GetContainingShadow() == this &&
      slot->AssignedNodes().IsEmpty()) {
    slot->EnqueueSlotChangeEvent();
  }
}

void ShadowRoot::ContentRemoved(nsIContent* aChild,
                                nsIContent* aPreviousSibling) {
  // Check to ensure that the child not an anonymous subtree root because
  // even though its parent could be the host it may not be in the host's child
  // list.
  if (aChild->IsRootOfAnonymousSubtree()) {
    return;
  }

  if (!aChild->IsSlotable()) {
    return;
  }

  if (aChild->GetParent() == GetHost()) {
    if (HTMLSlotElement* slot = aChild->GetAssignedSlot()) {
      // If the slot is going to start showing fallback content, we need to tell
      // layout about it.
      if (slot->AssignedNodes().Length() == 1) {
        InvalidateStyleAndLayoutOnSubtree(slot);
      }
      slot->RemoveAssignedNode(aChild);
      slot->EnqueueSlotChangeEvent();
    }
    return;
  }

  // If parent's root is a shadow root, and parent is a slot whose assigned
  // nodes is the empty list, then run signal a slot change for parent.
  HTMLSlotElement* slot = HTMLSlotElement::FromNodeOrNull(aChild->GetParent());
  if (slot && slot->GetContainingShadow() == this &&
      slot->AssignedNodes().IsEmpty()) {
    slot->EnqueueSlotChangeEvent();
  }
}

ServoStyleRuleMap& ShadowRoot::ServoStyleRuleMap() {
  if (!mStyleRuleMap) {
    mStyleRuleMap = MakeUnique<mozilla::ServoStyleRuleMap>();
  }
  mStyleRuleMap->EnsureTable(*this);
  return *mStyleRuleMap;
}

nsresult ShadowRoot::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const {
  *aResult = nullptr;
  return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}