diff options
Diffstat (limited to 'dom/html/HTMLShadowElement.cpp')
-rw-r--r-- | dom/html/HTMLShadowElement.cpp | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/dom/html/HTMLShadowElement.cpp b/dom/html/HTMLShadowElement.cpp new file mode 100644 index 000000000..8824c2875 --- /dev/null +++ b/dom/html/HTMLShadowElement.cpp @@ -0,0 +1,373 @@ +/* -*- 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/dom/ShadowRoot.h" + +#include "ChildIterator.h" +#include "nsContentUtils.h" +#include "nsDocument.h" +#include "mozilla/dom/HTMLShadowElement.h" +#include "mozilla/dom/HTMLUnknownElement.h" +#include "mozilla/dom/HTMLShadowElementBinding.h" + +// Expand NS_IMPL_NS_NEW_HTML_ELEMENT(Shadow) to add check for web components +// being enabled. +nsGenericHTMLElement* +NS_NewHTMLShadowElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, + mozilla::dom::FromParser aFromParser) +{ + // When this check is removed, remove the nsDocument.h and + // HTMLUnknownElement.h includes. Also remove nsINode::IsHTMLShadowElement. + // + // We have to jump through some hoops to be able to produce both NodeInfo* and + // already_AddRefed<NodeInfo>& for our callees. + RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo); + if (!nsDocument::IsWebComponentsEnabled(nodeInfo)) { + already_AddRefed<mozilla::dom::NodeInfo> nodeInfoArg(nodeInfo.forget()); + return new mozilla::dom::HTMLUnknownElement(nodeInfoArg); + } + + already_AddRefed<mozilla::dom::NodeInfo> nodeInfoArg(nodeInfo.forget()); + return new mozilla::dom::HTMLShadowElement(nodeInfoArg); +} + +using namespace mozilla::dom; + +HTMLShadowElement::HTMLShadowElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo) + : nsGenericHTMLElement(aNodeInfo), mIsInsertionPoint(false) +{ +} + +HTMLShadowElement::~HTMLShadowElement() +{ + if (mProjectedShadow) { + mProjectedShadow->RemoveMutationObserver(this); + } +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLShadowElement) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLShadowElement, + nsGenericHTMLElement) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProjectedShadow) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLShadowElement, + nsGenericHTMLElement) + if (tmp->mProjectedShadow) { + tmp->mProjectedShadow->RemoveMutationObserver(tmp); + tmp->mProjectedShadow = nullptr; + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_ADDREF_INHERITED(HTMLShadowElement, Element) +NS_IMPL_RELEASE_INHERITED(HTMLShadowElement, Element) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLShadowElement) +NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement) + +NS_IMPL_ELEMENT_CLONE(HTMLShadowElement) + +JSObject* +HTMLShadowElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) +{ + return HTMLShadowElementBinding::Wrap(aCx, this, aGivenProto); +} + +void +HTMLShadowElement::SetProjectedShadow(ShadowRoot* aProjectedShadow) +{ + if (mProjectedShadow) { + mProjectedShadow->RemoveMutationObserver(this); + + // The currently projected ShadowRoot is going away, + // thus the destination insertion points need to be updated. + ExplicitChildIterator childIterator(mProjectedShadow); + for (nsIContent* content = childIterator.GetNextChild(); + content; + content = childIterator.GetNextChild()) { + ShadowRoot::RemoveDestInsertionPoint(this, content->DestInsertionPoints()); + } + } + + mProjectedShadow = aProjectedShadow; + if (mProjectedShadow) { + // A new ShadowRoot is being projected, thus its explcit + // children will be distributed to this shadow insertion point. + ExplicitChildIterator childIterator(mProjectedShadow); + for (nsIContent* content = childIterator.GetNextChild(); + content; + content = childIterator.GetNextChild()) { + content->DestInsertionPoints().AppendElement(this); + } + + // Watch for mutations on the projected shadow because + // it affects the nodes that are distributed to this shadow + // insertion point. + mProjectedShadow->AddMutationObserver(this); + } +} + +static bool +IsInFallbackContent(nsIContent* aContent) +{ + nsINode* parentNode = aContent->GetParentNode(); + while (parentNode) { + if (parentNode->IsHTMLElement(nsGkAtoms::content)) { + return true; + } + parentNode = parentNode->GetParentNode(); + } + + return false; +} + +nsresult +HTMLShadowElement::BindToTree(nsIDocument* aDocument, + nsIContent* aParent, + nsIContent* aBindingParent, + bool aCompileEventHandlers) +{ + RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow(); + + nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent, + aBindingParent, + aCompileEventHandlers); + NS_ENSURE_SUCCESS(rv, rv); + + ShadowRoot* containingShadow = GetContainingShadow(); + if (containingShadow && !oldContainingShadow) { + // Keep track of all descendant <shadow> elements in tree order so + // that when the current shadow insertion point is removed, the next + // one can be found quickly. + TreeOrderComparator comparator; + containingShadow->ShadowDescendants().InsertElementSorted(this, comparator); + + if (containingShadow->ShadowDescendants()[0] != this) { + // Only the first <shadow> (in tree order) of a ShadowRoot can be an insertion point. + return NS_OK; + } + + if (IsInFallbackContent(this)) { + // If the first shadow element in tree order is invalid (in fallback content), + // the containing ShadowRoot will not have a shadow insertion point. + containingShadow->SetShadowElement(nullptr); + } else { + mIsInsertionPoint = true; + containingShadow->SetShadowElement(this); + } + + containingShadow->SetInsertionPointChanged(); + } + + if (mIsInsertionPoint && containingShadow) { + // Propagate BindToTree calls to projected shadow root children. + ShadowRoot* projectedShadow = containingShadow->GetOlderShadowRoot(); + if (projectedShadow) { + projectedShadow->SetIsComposedDocParticipant(IsInComposedDoc()); + + for (nsIContent* child = projectedShadow->GetFirstChild(); child; + child = child->GetNextSibling()) { + rv = child->BindToTree(nullptr, projectedShadow, + projectedShadow->GetBindingParent(), + aCompileEventHandlers); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + return NS_OK; +} + +void +HTMLShadowElement::UnbindFromTree(bool aDeep, bool aNullParent) +{ + RefPtr<ShadowRoot> oldContainingShadow = GetContainingShadow(); + + if (mIsInsertionPoint && oldContainingShadow) { + // Propagate UnbindFromTree call to previous projected shadow + // root children. + ShadowRoot* projectedShadow = oldContainingShadow->GetOlderShadowRoot(); + if (projectedShadow) { + for (nsIContent* child = projectedShadow->GetFirstChild(); child; + child = child->GetNextSibling()) { + child->UnbindFromTree(true, false); + } + + projectedShadow->SetIsComposedDocParticipant(false); + } + } + + nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent); + + if (oldContainingShadow && !GetContainingShadow() && mIsInsertionPoint) { + nsTArray<HTMLShadowElement*>& shadowDescendants = + oldContainingShadow->ShadowDescendants(); + shadowDescendants.RemoveElement(this); + oldContainingShadow->SetShadowElement(nullptr); + + // Find the next shadow insertion point. + if (shadowDescendants.Length() > 0 && + !IsInFallbackContent(shadowDescendants[0])) { + oldContainingShadow->SetShadowElement(shadowDescendants[0]); + } + + oldContainingShadow->SetInsertionPointChanged(); + + mIsInsertionPoint = false; + } +} + +void +HTMLShadowElement::DistributeSingleNode(nsIContent* aContent) +{ + if (aContent->DestInsertionPoints().Contains(this)) { + // Node has already been distrbuted this this node, + // we are done. + return; + } + + aContent->DestInsertionPoints().AppendElement(this); + + // Handle the case where the shadow element is a child of + // a node with a ShadowRoot. The nodes that have been distributed to + // this shadow insertion point will need to be reprojected into the + // insertion points of the parent's ShadowRoot. + ShadowRoot* parentShadowRoot = GetParent()->GetShadowRoot(); + if (parentShadowRoot) { + parentShadowRoot->DistributeSingleNode(aContent); + return; + } + + // Handle the case where the parent of this shadow element is a ShadowRoot + // that is projected into a shadow insertion point in the younger ShadowRoot. + ShadowRoot* containingShadow = GetContainingShadow(); + ShadowRoot* youngerShadow = containingShadow->GetYoungerShadowRoot(); + if (youngerShadow && GetParent() == containingShadow) { + HTMLShadowElement* youngerShadowElement = youngerShadow->GetShadowElement(); + if (youngerShadowElement) { + youngerShadowElement->DistributeSingleNode(aContent); + } + } +} + +void +HTMLShadowElement::RemoveDistributedNode(nsIContent* aContent) +{ + ShadowRoot::RemoveDestInsertionPoint(this, aContent->DestInsertionPoints()); + + // Handle the case where the shadow element is a child of + // a node with a ShadowRoot. The nodes that have been distributed to + // this shadow insertion point will need to be removed from the + // insertion points of the parent's ShadowRoot. + ShadowRoot* parentShadowRoot = GetParent()->GetShadowRoot(); + if (parentShadowRoot) { + parentShadowRoot->RemoveDistributedNode(aContent); + return; + } + + // Handle the case where the parent of this shadow element is a ShadowRoot + // that is projected into a shadow insertion point in the younger ShadowRoot. + ShadowRoot* containingShadow = GetContainingShadow(); + ShadowRoot* youngerShadow = containingShadow->GetYoungerShadowRoot(); + if (youngerShadow && GetParent() == containingShadow) { + HTMLShadowElement* youngerShadowElement = youngerShadow->GetShadowElement(); + if (youngerShadowElement) { + youngerShadowElement->RemoveDistributedNode(aContent); + } + } +} + +void +HTMLShadowElement::DistributeAllNodes() +{ + // All the explicit children of the projected ShadowRoot are distributed + // into this shadow insertion point so update the destination insertion + // points. + ShadowRoot* containingShadow = GetContainingShadow(); + ShadowRoot* olderShadow = containingShadow->GetOlderShadowRoot(); + if (olderShadow) { + ExplicitChildIterator childIterator(olderShadow); + for (nsIContent* content = childIterator.GetNextChild(); + content; + content = childIterator.GetNextChild()) { + ShadowRoot::RemoveDestInsertionPoint(this, content->DestInsertionPoints()); + content->DestInsertionPoints().AppendElement(this); + } + } + + // Handle the case where the shadow element is a child of + // a node with a ShadowRoot. The nodes that have been distributed to + // this shadow insertion point will need to be reprojected into the + // insertion points of the parent's ShadowRoot. + ShadowRoot* parentShadowRoot = GetParent()->GetShadowRoot(); + if (parentShadowRoot) { + parentShadowRoot->DistributeAllNodes(); + return; + } + + // Handle the case where the parent of this shadow element is a ShadowRoot + // that is projected into a shadow insertion point in the younger ShadowRoot. + ShadowRoot* youngerShadow = containingShadow->GetYoungerShadowRoot(); + if (youngerShadow && GetParent() == containingShadow) { + HTMLShadowElement* youngerShadowElement = youngerShadow->GetShadowElement(); + if (youngerShadowElement) { + youngerShadowElement->DistributeAllNodes(); + } + } +} + +void +HTMLShadowElement::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t aNewIndexInContainer) +{ + // Watch for content appended to the projected shadow (the ShadowRoot that + // will be rendered in place of this shadow insertion point) because the + // nodes may need to be distributed into other insertion points. + nsIContent* currentChild = aFirstNewContent; + while (currentChild) { + if (ShadowRoot::IsPooledNode(currentChild, aContainer, mProjectedShadow)) { + DistributeSingleNode(currentChild); + } + currentChild = currentChild->GetNextSibling(); + } +} + +void +HTMLShadowElement::ContentInserted(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer) +{ + // Watch for content appended to the projected shadow (the ShadowRoot that + // will be rendered in place of this shadow insertion point) because the + // nodes may need to be distributed into other insertion points. + if (!ShadowRoot::IsPooledNode(aChild, aContainer, mProjectedShadow)) { + return; + } + + DistributeSingleNode(aChild); +} + +void +HTMLShadowElement::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + // Watch for content removed from the projected shadow (the ShadowRoot that + // will be rendered in place of this shadow insertion point) because the + // nodes may need to be removed from other insertion points. + if (!ShadowRoot::IsPooledNode(aChild, aContainer, mProjectedShadow)) { + return; + } + + RemoveDistributedNode(aChild); +} + |