summaryrefslogtreecommitdiffstats
path: root/dom/base/FragmentOrElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/FragmentOrElement.cpp')
-rw-r--r--dom/base/FragmentOrElement.cpp2394
1 files changed, 2394 insertions, 0 deletions
diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp
new file mode 100644
index 000000000..293177ce7
--- /dev/null
+++ b/dom/base/FragmentOrElement.cpp
@@ -0,0 +1,2394 @@
+/* -*- 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/. */
+
+/*
+ * Base class for all element classes; this provides an implementation
+ * of DOM Core's nsIDOMElement, implements nsIContent, provides
+ * utility methods for subclasses, and so forth.
+ */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StaticPtr.h"
+
+#include "mozilla/dom/FragmentOrElement.h"
+
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/DeclarationBlockInlines.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Attr.h"
+#include "nsDOMAttributeMap.h"
+#include "nsIAtom.h"
+#include "mozilla/dom/NodeInfo.h"
+#include "mozilla/dom/Event.h"
+#include "nsIDocumentInlines.h"
+#include "nsIDocumentEncoder.h"
+#include "nsIDOMNodeList.h"
+#include "nsIContentIterator.h"
+#include "nsFocusManager.h"
+#include "nsILinkHandler.h"
+#include "nsIScriptGlobalObject.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsIFrame.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsStyleConsts.h"
+#include "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsIDOMEvent.h"
+#include "nsDOMCID.h"
+#include "nsIServiceManager.h"
+#include "nsIDOMCSSStyleDeclaration.h"
+#include "nsDOMCSSAttrDeclaration.h"
+#include "nsNameSpaceManager.h"
+#include "nsContentList.h"
+#include "nsDOMTokenList.h"
+#include "nsXBLPrototypeBinding.h"
+#include "nsError.h"
+#include "nsDOMString.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIDOMMutationEvent.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/MouseEvents.h"
+#include "nsNodeUtils.h"
+#include "nsDocument.h"
+#include "nsAttrValueOrString.h"
+#ifdef MOZ_XUL
+#include "nsXULElement.h"
+#endif /* MOZ_XUL */
+#include "nsFrameSelection.h"
+#ifdef DEBUG
+#include "nsRange.h"
+#endif
+
+#include "nsBindingManager.h"
+#include "nsXBLBinding.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIBoxObject.h"
+#include "nsSVGUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsGkAtoms.h"
+#include "nsContentUtils.h"
+#include "nsTextFragment.h"
+#include "nsContentCID.h"
+
+#include "nsIDOMEventListener.h"
+#include "nsIWebNavigation.h"
+#include "nsIBaseWindow.h"
+#include "nsIWidget.h"
+
+#include "js/GCAPI.h"
+
+#include "nsNodeInfoManager.h"
+#include "nsICategoryManager.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIEditor.h"
+#include "nsIEditorIMESupport.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsIControllers.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsIScrollableFrame.h"
+#include "ChildIterator.h"
+#include "mozilla/css/StyleRule.h" /* For nsCSSSelectorList */
+#include "nsRuleProcessorData.h"
+#include "nsTextNode.h"
+#include "mozilla/dom/NodeListBinding.h"
+
+#ifdef MOZ_XUL
+#include "nsIXULDocument.h"
+#endif /* MOZ_XUL */
+
+#include "nsCCUncollectableMarker.h"
+
+#include "mozAutoDocUpdate.h"
+
+#include "mozilla/Sprintf.h"
+#include "nsDOMMutationObserver.h"
+#include "nsWrapperCacheInlines.h"
+#include "nsCycleCollector.h"
+#include "xpcpublic.h"
+#include "nsIScriptError.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/CORSMode.h"
+
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/HTMLTemplateElement.h"
+
+#include "nsStyledElement.h"
+#include "nsIContentInlines.h"
+#include "nsChildContentList.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+int32_t nsIContent::sTabFocusModel = eTabFocus_any;
+bool nsIContent::sTabFocusModelAppliesToXUL = false;
+uint64_t nsMutationGuard::sGeneration = 0;
+
+nsIContent*
+nsIContent::FindFirstNonChromeOnlyAccessContent() const
+{
+ // This handles also nested native anonymous content.
+ for (const nsIContent *content = this; content;
+ content = content->GetBindingParent()) {
+ if (!content->ChromeOnlyAccess()) {
+ // Oops, this function signature allows casting const to
+ // non-const. (Then again, so does GetChildAt(0)->GetParent().)
+ return const_cast<nsIContent*>(content);
+ }
+ }
+ return nullptr;
+}
+
+nsINode*
+nsIContent::GetFlattenedTreeParentNodeInternal() const
+{
+ nsINode* parentNode = GetParentNode();
+ if (!parentNode || !parentNode->IsContent()) {
+ MOZ_ASSERT(!parentNode || parentNode == OwnerDoc());
+ return parentNode;
+ }
+ nsIContent* parent = parentNode->AsContent();
+
+ if (parent && nsContentUtils::HasDistributedChildren(parent) &&
+ nsContentUtils::IsInSameAnonymousTree(parent, this)) {
+ // This node is distributed to insertion points, thus we
+ // need to consult the destination insertion points list to
+ // figure out where this node was inserted in the flattened tree.
+ // It may be the case that |parent| distributes its children
+ // but the child does not match any insertion points, thus
+ // the flattened tree parent is nullptr.
+ nsTArray<nsIContent*>* destInsertionPoints = GetExistingDestInsertionPoints();
+ parent = destInsertionPoints && !destInsertionPoints->IsEmpty() ?
+ destInsertionPoints->LastElement()->GetParent() : nullptr;
+ } else if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ nsIContent* insertionParent = GetXBLInsertionParent();
+ if (insertionParent) {
+ parent = insertionParent;
+ }
+ }
+
+ // Shadow roots never shows up in the flattened tree. Return the host
+ // instead.
+ if (parent && parent->IsInShadowTree()) {
+ ShadowRoot* parentShadowRoot = ShadowRoot::FromNode(parent);
+ if (parentShadowRoot) {
+ return parentShadowRoot->GetHost();
+ }
+ }
+
+ return parent;
+}
+
+nsIContent::IMEState
+nsIContent::GetDesiredIMEState()
+{
+ if (!IsEditableInternal()) {
+ // Check for the special case where we're dealing with elements which don't
+ // have the editable flag set, but are readwrite (such as text controls).
+ if (!IsElement() ||
+ !AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) {
+ return IMEState(IMEState::DISABLED);
+ }
+ }
+ // NOTE: The content for independent editors (e.g., input[type=text],
+ // textarea) must override this method, so, we don't need to worry about
+ // that here.
+ nsIContent *editableAncestor = GetEditingHost();
+
+ // This is in another editable content, use the result of it.
+ if (editableAncestor && editableAncestor != this) {
+ return editableAncestor->GetDesiredIMEState();
+ }
+ nsIDocument* doc = GetComposedDoc();
+ if (!doc) {
+ return IMEState(IMEState::DISABLED);
+ }
+ nsIPresShell* ps = doc->GetShell();
+ if (!ps) {
+ return IMEState(IMEState::DISABLED);
+ }
+ nsPresContext* pc = ps->GetPresContext();
+ if (!pc) {
+ return IMEState(IMEState::DISABLED);
+ }
+ nsIEditor* editor = nsContentUtils::GetHTMLEditor(pc);
+ nsCOMPtr<nsIEditorIMESupport> imeEditor = do_QueryInterface(editor);
+ if (!imeEditor) {
+ return IMEState(IMEState::DISABLED);
+ }
+ IMEState state;
+ imeEditor->GetPreferredIMEState(&state);
+ return state;
+}
+
+bool
+nsIContent::HasIndependentSelection()
+{
+ nsIFrame* frame = GetPrimaryFrame();
+ return (frame && frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION);
+}
+
+dom::Element*
+nsIContent::GetEditingHost()
+{
+ // If this isn't editable, return nullptr.
+ if (!IsEditableInternal()) {
+ return nullptr;
+ }
+
+ nsIDocument* doc = GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ // If this is in designMode, we should return <body>
+ if (doc->HasFlag(NODE_IS_EDITABLE) && !IsInShadowTree()) {
+ return doc->GetBodyElement();
+ }
+
+ nsIContent* content = this;
+ for (dom::Element* parent = GetParentElement();
+ parent && parent->HasFlag(NODE_IS_EDITABLE);
+ parent = content->GetParentElement()) {
+ content = parent;
+ }
+ return content->AsElement();
+}
+
+nsresult
+nsIContent::LookupNamespaceURIInternal(const nsAString& aNamespacePrefix,
+ nsAString& aNamespaceURI) const
+{
+ if (aNamespacePrefix.EqualsLiteral("xml")) {
+ // Special-case for xml prefix
+ aNamespaceURI.AssignLiteral("http://www.w3.org/XML/1998/namespace");
+ return NS_OK;
+ }
+
+ if (aNamespacePrefix.EqualsLiteral("xmlns")) {
+ // Special-case for xmlns prefix
+ aNamespaceURI.AssignLiteral("http://www.w3.org/2000/xmlns/");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAtom> name;
+ if (!aNamespacePrefix.IsEmpty()) {
+ name = NS_Atomize(aNamespacePrefix);
+ NS_ENSURE_TRUE(name, NS_ERROR_OUT_OF_MEMORY);
+ }
+ else {
+ name = nsGkAtoms::xmlns;
+ }
+ // Trace up the content parent chain looking for the namespace
+ // declaration that declares aNamespacePrefix.
+ const nsIContent* content = this;
+ do {
+ if (content->GetAttr(kNameSpaceID_XMLNS, name, aNamespaceURI))
+ return NS_OK;
+ } while ((content = content->GetParent()));
+ return NS_ERROR_FAILURE;
+}
+
+already_AddRefed<nsIURI>
+nsIContent::GetBaseURI(bool aTryUseXHRDocBaseURI) const
+{
+ nsIDocument* doc = OwnerDoc();
+ // Start with document base
+ nsCOMPtr<nsIURI> base = doc->GetBaseURI(aTryUseXHRDocBaseURI);
+
+ // Collect array of xml:base attribute values up the parent chain. This
+ // is slightly slower for the case when there are xml:base attributes, but
+ // faster for the far more common case of there not being any such
+ // attributes.
+ // Also check for SVG elements which require special handling
+ AutoTArray<nsString, 5> baseAttrs;
+ nsString attr;
+ const nsIContent *elem = this;
+ do {
+ // First check for SVG specialness (why is this SVG specific?)
+ if (elem->IsSVGElement()) {
+ nsIContent* bindingParent = elem->GetBindingParent();
+ if (bindingParent) {
+ nsXBLBinding* binding = bindingParent->GetXBLBinding();
+ if (binding) {
+ // XXX sXBL/XBL2 issue
+ // If this is an anonymous XBL element use the binding
+ // document for the base URI.
+ // XXX Will fail with xml:base
+ base = binding->PrototypeBinding()->DocURI();
+ break;
+ }
+ }
+ }
+
+ nsIURI* explicitBaseURI = elem->GetExplicitBaseURI();
+ if (explicitBaseURI) {
+ base = explicitBaseURI;
+ break;
+ }
+
+ // Otherwise check for xml:base attribute
+ elem->GetAttr(kNameSpaceID_XML, nsGkAtoms::base, attr);
+ if (!attr.IsEmpty()) {
+ baseAttrs.AppendElement(attr);
+ }
+ elem = elem->GetParent();
+ } while(elem);
+
+ // Now resolve against all xml:base attrs
+ for (uint32_t i = baseAttrs.Length() - 1; i != uint32_t(-1); --i) {
+ nsCOMPtr<nsIURI> newBase;
+ nsresult rv = NS_NewURI(getter_AddRefs(newBase), baseAttrs[i],
+ doc->GetDocumentCharacterSet().get(), base);
+ // Do a security check, almost the same as nsDocument::SetBaseURL()
+ // Only need to do this on the final uri
+ if (NS_SUCCEEDED(rv) && i == 0) {
+ rv = nsContentUtils::GetSecurityManager()->
+ CheckLoadURIWithPrincipal(NodePrincipal(), newBase,
+ nsIScriptSecurityManager::STANDARD);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ base.swap(newBase);
+ }
+ }
+
+ return base.forget();
+}
+
+//----------------------------------------------------------------------
+
+static inline JSObject*
+GetJSObjectChild(nsWrapperCache* aCache)
+{
+ return aCache->PreservingWrapper() ? aCache->GetWrapperPreserveColor() : nullptr;
+}
+
+static bool
+NeedsScriptTraverse(nsINode* aNode)
+{
+ return aNode->PreservingWrapper() && aNode->GetWrapperPreserveColor() &&
+ !aNode->IsBlackAndDoesNotNeedTracing(aNode);
+}
+
+//----------------------------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsChildContentList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsChildContentList)
+
+// If nsChildContentList is changed so that any additional fields are
+// traversed by the cycle collector, then CAN_SKIP must be updated to
+// check that the additional fields are null.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsChildContentList)
+
+// nsChildContentList only ever has a single child, its wrapper, so if
+// the wrapper is black, the list can't be part of a garbage cycle.
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsChildContentList)
+ return tmp->IsBlack();
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsChildContentList)
+ return tmp->IsBlackAndDoesNotNeedTracing(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+// CanSkipThis returns false to avoid problems with incomplete unlinking.
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsChildContentList)
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+NS_INTERFACE_TABLE_HEAD(nsChildContentList)
+ NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
+ NS_INTERFACE_TABLE(nsChildContentList, nsINodeList, nsIDOMNodeList)
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsChildContentList)
+NS_INTERFACE_MAP_END
+
+JSObject*
+nsChildContentList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return NodeListBinding::Wrap(cx, this, aGivenProto);
+}
+
+NS_IMETHODIMP
+nsChildContentList::GetLength(uint32_t* aLength)
+{
+ *aLength = mNode ? mNode->GetChildCount() : 0;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsChildContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
+{
+ nsINode* node = Item(aIndex);
+ if (!node) {
+ *aReturn = nullptr;
+
+ return NS_OK;
+ }
+
+ return CallQueryInterface(node, aReturn);
+}
+
+nsIContent*
+nsChildContentList::Item(uint32_t aIndex)
+{
+ if (mNode) {
+ return mNode->GetChildAt(aIndex);
+ }
+
+ return nullptr;
+}
+
+int32_t
+nsChildContentList::IndexOf(nsIContent* aContent)
+{
+ if (mNode) {
+ return mNode->IndexOf(aContent);
+ }
+
+ return -1;
+}
+
+//----------------------------------------------------------------------
+
+nsIHTMLCollection*
+FragmentOrElement::Children()
+{
+ FragmentOrElement::nsDOMSlots *slots = DOMSlots();
+
+ if (!slots->mChildrenList) {
+ slots->mChildrenList = new nsContentList(this, kNameSpaceID_Wildcard,
+ nsGkAtoms::_asterisk, nsGkAtoms::_asterisk,
+ false);
+ }
+
+ return slots->mChildrenList;
+}
+
+
+//----------------------------------------------------------------------
+
+
+NS_IMPL_ISUPPORTS(nsNodeWeakReference,
+ nsIWeakReference)
+
+nsNodeWeakReference::~nsNodeWeakReference()
+{
+ if (mNode) {
+ NS_ASSERTION(mNode->Slots()->mWeakReference == this,
+ "Weak reference has wrong value");
+ mNode->Slots()->mWeakReference = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsNodeWeakReference::QueryReferent(const nsIID& aIID, void** aInstancePtr)
+{
+ return mNode ? mNode->QueryInterface(aIID, aInstancePtr) :
+ NS_ERROR_NULL_POINTER;
+}
+
+size_t
+nsNodeWeakReference::SizeOfOnlyThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this);
+}
+
+
+NS_IMPL_CYCLE_COLLECTION(nsNodeSupportsWeakRefTearoff, mNode)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNodeSupportsWeakRefTearoff)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_AGGREGATED(mNode)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNodeSupportsWeakRefTearoff)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNodeSupportsWeakRefTearoff)
+
+NS_IMETHODIMP
+nsNodeSupportsWeakRefTearoff::GetWeakReference(nsIWeakReference** aInstancePtr)
+{
+ nsINode::nsSlots* slots = mNode->Slots();
+ if (!slots->mWeakReference) {
+ slots->mWeakReference = new nsNodeWeakReference(mNode);
+ }
+
+ NS_ADDREF(*aInstancePtr = slots->mWeakReference);
+
+ return NS_OK;
+}
+
+//----------------------------------------------------------------------
+FragmentOrElement::nsDOMSlots::nsDOMSlots()
+ : nsINode::nsSlots(),
+ mDataset(nullptr),
+ mBindingParent(nullptr)
+{
+}
+
+FragmentOrElement::nsDOMSlots::~nsDOMSlots()
+{
+ if (mAttributeMap) {
+ mAttributeMap->DropReference();
+ }
+}
+
+void
+FragmentOrElement::nsDOMSlots::Traverse(nsCycleCollectionTraversalCallback &cb, bool aIsXUL)
+{
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mStyle");
+ cb.NoteXPCOMChild(mStyle.get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mSMILOverrideStyle");
+ cb.NoteXPCOMChild(mSMILOverrideStyle.get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mAttributeMap");
+ cb.NoteXPCOMChild(mAttributeMap.get());
+
+ if (aIsXUL) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mControllers");
+ cb.NoteXPCOMChild(mControllers);
+ }
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mXBLBinding");
+ cb.NoteNativeChild(mXBLBinding, NS_CYCLE_COLLECTION_PARTICIPANT(nsXBLBinding));
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mXBLInsertionParent");
+ cb.NoteXPCOMChild(mXBLInsertionParent.get());
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mShadowRoot");
+ cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mShadowRoot));
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mContainingShadow");
+ cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mContainingShadow));
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mChildrenList");
+ cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMNodeList*, mChildrenList));
+
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mClassList");
+ cb.NoteXPCOMChild(mClassList.get());
+
+ if (mCustomElementData) {
+ for (uint32_t i = 0; i < mCustomElementData->mCallbackQueue.Length(); i++) {
+ mCustomElementData->mCallbackQueue[i]->Traverse(cb);
+ }
+ }
+}
+
+void
+FragmentOrElement::nsDOMSlots::Unlink(bool aIsXUL)
+{
+ mStyle = nullptr;
+ mSMILOverrideStyle = nullptr;
+ if (mAttributeMap) {
+ mAttributeMap->DropReference();
+ mAttributeMap = nullptr;
+ }
+ if (aIsXUL)
+ NS_IF_RELEASE(mControllers);
+
+ MOZ_ASSERT(!mXBLBinding);
+
+ mXBLInsertionParent = nullptr;
+ mShadowRoot = nullptr;
+ mContainingShadow = nullptr;
+ mChildrenList = nullptr;
+ mCustomElementData = nullptr;
+ mClassList = nullptr;
+}
+
+size_t
+FragmentOrElement::nsDOMSlots::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = aMallocSizeOf(this);
+
+ if (mAttributeMap) {
+ n += mAttributeMap->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - Superclass members (nsINode::nsSlots)
+ // - mStyle
+ // - mDataSet
+ // - mSMILOverrideStyle
+ // - mSMILOverrideStyleDeclaration
+ // - mChildrenList
+ // - mClassList
+
+ // The following members are not measured:
+ // - mBindingParent / mControllers: because they're non-owning
+ return n;
+}
+
+FragmentOrElement::FragmentOrElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsIContent(aNodeInfo)
+{
+}
+
+FragmentOrElement::FragmentOrElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
+ : nsIContent(aNodeInfo)
+{
+}
+
+FragmentOrElement::~FragmentOrElement()
+{
+ NS_PRECONDITION(!IsInUncomposedDoc(),
+ "Please remove this from the document properly");
+ if (GetParent()) {
+ NS_RELEASE(mParent);
+ }
+}
+
+already_AddRefed<nsINodeList>
+FragmentOrElement::GetChildren(uint32_t aFilter)
+{
+ RefPtr<nsSimpleContentList> list = new nsSimpleContentList(this);
+ AllChildrenIterator iter(this, aFilter);
+ while (nsIContent* kid = iter.GetNextChild()) {
+ list->AppendElement(kid);
+ }
+
+ return list.forget();
+}
+
+static nsIContent*
+FindChromeAccessOnlySubtreeOwner(nsIContent* aContent)
+{
+ if (aContent->ChromeOnlyAccess()) {
+ bool chromeAccessOnly = false;
+ while (aContent && !chromeAccessOnly) {
+ chromeAccessOnly = aContent->IsRootOfChromeAccessOnlySubtree();
+ aContent = aContent->GetParent();
+ }
+ }
+ return aContent;
+}
+
+nsresult
+nsIContent::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ //FIXME! Document how this event retargeting works, Bug 329124.
+ aVisitor.mCanHandle = true;
+ aVisitor.mMayHaveListenerManager = HasListenerManager();
+
+ // Don't propagate mouseover and mouseout events when mouse is moving
+ // inside chrome access only content.
+ bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree();
+ if ((aVisitor.mEvent->mMessage == eMouseOver ||
+ aVisitor.mEvent->mMessage == eMouseOut ||
+ aVisitor.mEvent->mMessage == ePointerOver ||
+ aVisitor.mEvent->mMessage == ePointerOut) &&
+ // Check if we should stop event propagation when event has just been
+ // dispatched or when we're about to propagate from
+ // chrome access only subtree or if we are about to propagate out of
+ // a shadow root to a shadow root host.
+ ((this == aVisitor.mEvent->mOriginalTarget &&
+ !ChromeOnlyAccess()) || isAnonForEvents || GetShadowRoot())) {
+ nsCOMPtr<nsIContent> relatedTarget =
+ do_QueryInterface(aVisitor.mEvent->AsMouseEvent()->relatedTarget);
+ if (relatedTarget &&
+ relatedTarget->OwnerDoc() == OwnerDoc()) {
+
+ // In the web components case, we may need to stop propagation of events
+ // at shadow root host.
+ if (GetShadowRoot()) {
+ nsIContent* adjustedTarget =
+ Event::GetShadowRelatedTarget(this, relatedTarget);
+ if (this == adjustedTarget) {
+ aVisitor.mParentTarget = nullptr;
+ aVisitor.mCanHandle = false;
+ return NS_OK;
+ }
+ }
+
+ // If current target is anonymous for events or we know that related
+ // target is descendant of an element which is anonymous for events,
+ // we may want to stop event propagation.
+ // If this is the original target, aVisitor.mRelatedTargetIsInAnon
+ // must be updated.
+ if (isAnonForEvents || aVisitor.mRelatedTargetIsInAnon ||
+ (aVisitor.mEvent->mOriginalTarget == this &&
+ (aVisitor.mRelatedTargetIsInAnon =
+ relatedTarget->ChromeOnlyAccess()))) {
+ nsIContent* anonOwner = FindChromeAccessOnlySubtreeOwner(this);
+ if (anonOwner) {
+ nsIContent* anonOwnerRelated =
+ FindChromeAccessOnlySubtreeOwner(relatedTarget);
+ if (anonOwnerRelated) {
+ // Note, anonOwnerRelated may still be inside some other
+ // native anonymous subtree. The case where anonOwner is still
+ // inside native anonymous subtree will be handled when event
+ // propagates up in the DOM tree.
+ while (anonOwner != anonOwnerRelated &&
+ anonOwnerRelated->ChromeOnlyAccess()) {
+ anonOwnerRelated = FindChromeAccessOnlySubtreeOwner(anonOwnerRelated);
+ }
+ if (anonOwner == anonOwnerRelated) {
+#ifdef DEBUG_smaug
+ nsCOMPtr<nsIContent> originalTarget =
+ do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
+ nsAutoString ot, ct, rt;
+ if (originalTarget) {
+ originalTarget->NodeInfo()->NameAtom()->ToString(ot);
+ }
+ NodeInfo()->NameAtom()->ToString(ct);
+ relatedTarget->NodeInfo()->NameAtom()->ToString(rt);
+ printf("Stopping %s propagation:"
+ "\n\toriginalTarget=%s \n\tcurrentTarget=%s %s"
+ "\n\trelatedTarget=%s %s \n%s",
+ (aVisitor.mEvent->mMessage == eMouseOver)
+ ? "mouseover" : "mouseout",
+ NS_ConvertUTF16toUTF8(ot).get(),
+ NS_ConvertUTF16toUTF8(ct).get(),
+ isAnonForEvents
+ ? "(is native anonymous)"
+ : (ChromeOnlyAccess()
+ ? "(is in native anonymous subtree)" : ""),
+ NS_ConvertUTF16toUTF8(rt).get(),
+ relatedTarget->ChromeOnlyAccess()
+ ? "(is in native anonymous subtree)" : "",
+ (originalTarget &&
+ relatedTarget->FindFirstNonChromeOnlyAccessContent() ==
+ originalTarget->FindFirstNonChromeOnlyAccessContent())
+ ? "" : "Wrong event propagation!?!\n");
+#endif
+ aVisitor.mParentTarget = nullptr;
+ // Event should not propagate to non-anon content.
+ aVisitor.mCanHandle = isAnonForEvents;
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ nsIContent* parent = GetParent();
+
+ // Web components have a special event chain that need to account
+ // for destination insertion points where nodes have been distributed.
+ nsTArray<nsIContent*>* destPoints = GetExistingDestInsertionPoints();
+ if (destPoints && !destPoints->IsEmpty()) {
+ // Push destination insertion points to aVisitor.mDestInsertionPoints
+ // excluding shadow insertion points.
+ bool didPushNonShadowInsertionPoint = false;
+ for (uint32_t i = 0; i < destPoints->Length(); i++) {
+ nsIContent* point = destPoints->ElementAt(i);
+ if (!ShadowRoot::IsShadowInsertionPoint(point)) {
+ aVisitor.mDestInsertionPoints.AppendElement(point);
+ didPushNonShadowInsertionPoint = true;
+ }
+ }
+
+ // Next node in the event path is the final destination
+ // (non-shadow) insertion point that was pushed.
+ if (didPushNonShadowInsertionPoint) {
+ parent = aVisitor.mDestInsertionPoints.LastElement();
+ aVisitor.mDestInsertionPoints.SetLength(
+ aVisitor.mDestInsertionPoints.Length() - 1);
+ }
+ }
+
+ ShadowRoot* thisShadowRoot = ShadowRoot::FromNode(this);
+ if (thisShadowRoot) {
+ if (!aVisitor.mEvent->mFlags.mComposed) {
+ // 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.mParentTarget = parentTarget;
+ return NS_OK;
+ }
+
+ if (!aVisitor.mDestInsertionPoints.IsEmpty()) {
+ parent = aVisitor.mDestInsertionPoints.LastElement();
+ aVisitor.mDestInsertionPoints.SetLength(
+ aVisitor.mDestInsertionPoints.Length() - 1);
+ } else {
+ // The pool host for the youngest shadow root is shadow DOM host,
+ // for older shadow roots, it is the shadow insertion point
+ // where the shadow root is projected, nullptr if none exists.
+ parent = thisShadowRoot->GetPoolHost();
+ }
+ }
+
+ // Event may need to be retargeted if this is the root of a native
+ // anonymous content subtree or event is dispatched somewhere inside XBL.
+ if (isAnonForEvents) {
+#ifdef DEBUG
+ // If a DOM event is explicitly dispatched using node.dispatchEvent(), then
+ // all the events are allowed even in the native anonymous content..
+ nsCOMPtr<nsIContent> t =
+ do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
+ NS_ASSERTION(!t || !t->ChromeOnlyAccess() ||
+ aVisitor.mEvent->mClass != eMutationEventClass ||
+ aVisitor.mDOMEvent,
+ "Mutation event dispatched in native anonymous content!?!");
+#endif
+ aVisitor.mEventTargetAtParent = parent;
+ } else if (parent && aVisitor.mOriginalTargetIsInAnon) {
+ nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mEvent->mTarget));
+ if (content && content->GetBindingParent() == parent) {
+ aVisitor.mEventTargetAtParent = parent;
+ }
+ }
+
+ // check for an anonymous parent
+ // XXX XBL2/sXBL issue
+ if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ nsIContent* insertionParent = GetXBLInsertionParent();
+ NS_ASSERTION(!(aVisitor.mEventTargetAtParent && insertionParent &&
+ aVisitor.mEventTargetAtParent != insertionParent),
+ "Retargeting and having insertion parent!");
+ if (insertionParent) {
+ parent = insertionParent;
+ }
+ }
+
+ if (!aVisitor.mEvent->mFlags.mComposedInNativeAnonymousContent &&
+ IsRootOfNativeAnonymousSubtree() && OwnerDoc() &&
+ OwnerDoc()->GetWindow()) {
+ aVisitor.mParentTarget = OwnerDoc()->GetWindow()->GetParentTarget();
+ } else if (parent) {
+ aVisitor.mParentTarget = parent;
+ } else {
+ aVisitor.mParentTarget = GetComposedDoc();
+ }
+ return NS_OK;
+}
+
+bool
+nsIContent::GetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAString& aResult) const
+{
+ if (IsElement()) {
+ return AsElement()->GetAttr(aNameSpaceID, aName, aResult);
+ }
+ aResult.Truncate();
+ return false;
+}
+
+bool
+nsIContent::HasAttr(int32_t aNameSpaceID, nsIAtom* aName) const
+{
+ return IsElement() && AsElement()->HasAttr(aNameSpaceID, aName);
+}
+
+bool
+nsIContent::AttrValueIs(int32_t aNameSpaceID,
+ nsIAtom* aName,
+ const nsAString& aValue,
+ nsCaseTreatment aCaseSensitive) const
+{
+ return IsElement() &&
+ AsElement()->AttrValueIs(aNameSpaceID, aName, aValue, aCaseSensitive);
+}
+
+bool
+nsIContent::AttrValueIs(int32_t aNameSpaceID,
+ nsIAtom* aName,
+ nsIAtom* aValue,
+ nsCaseTreatment aCaseSensitive) const
+{
+ return IsElement() &&
+ AsElement()->AttrValueIs(aNameSpaceID, aName, aValue, aCaseSensitive);
+}
+
+bool
+nsIContent::IsFocusable(int32_t* aTabIndex, bool aWithMouse)
+{
+ bool focusable = IsFocusableInternal(aTabIndex, aWithMouse);
+ // Ensure that the return value and aTabIndex are consistent in the case
+ // we're in userfocusignored context.
+ if (focusable || (aTabIndex && *aTabIndex != -1)) {
+ if (nsContentUtils::IsUserFocusIgnored(this)) {
+ if (aTabIndex) {
+ *aTabIndex = -1;
+ }
+ return false;
+ }
+ return focusable;
+ }
+ return false;
+}
+
+bool
+nsIContent::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse)
+{
+ if (aTabIndex) {
+ *aTabIndex = -1; // Default, not tabbable
+ }
+ return false;
+}
+
+NS_IMETHODIMP
+FragmentOrElement::WalkContentStyleRules(nsRuleWalker* aRuleWalker)
+{
+ return NS_OK;
+}
+
+bool
+FragmentOrElement::IsLink(nsIURI** aURI) const
+{
+ *aURI = nullptr;
+ return false;
+}
+
+nsIContent*
+FragmentOrElement::GetBindingParent() const
+{
+ nsDOMSlots *slots = GetExistingDOMSlots();
+
+ if (slots) {
+ return slots->mBindingParent;
+ }
+ return nullptr;
+}
+
+nsXBLBinding*
+FragmentOrElement::GetXBLBinding() const
+{
+ if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ return slots->mXBLBinding;
+ }
+ }
+
+ return nullptr;
+}
+
+void
+FragmentOrElement::SetXBLBinding(nsXBLBinding* aBinding,
+ nsBindingManager* aOldBindingManager)
+{
+ nsBindingManager* bindingManager;
+ if (aOldBindingManager) {
+ MOZ_ASSERT(!aBinding, "aOldBindingManager should only be provided "
+ "when removing a binding.");
+ bindingManager = aOldBindingManager;
+ } else {
+ bindingManager = OwnerDoc()->BindingManager();
+ }
+
+ // After this point, aBinding will be the most-derived binding for aContent.
+ // If we already have a binding for aContent, make sure to
+ // remove it from the attached stack. Otherwise we might end up firing its
+ // constructor twice (if aBinding inherits from it) or firing its constructor
+ // after aContent has been deleted (if aBinding is null and the content node
+ // dies before we process mAttachedStack).
+ RefPtr<nsXBLBinding> oldBinding = GetXBLBinding();
+ if (oldBinding) {
+ bindingManager->RemoveFromAttachedQueue(oldBinding);
+ }
+
+ if (aBinding) {
+ SetFlags(NODE_MAY_BE_IN_BINDING_MNGR);
+ nsDOMSlots *slots = DOMSlots();
+ slots->mXBLBinding = aBinding;
+ bindingManager->AddBoundContent(this);
+ } else {
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ slots->mXBLBinding = nullptr;
+ }
+ bindingManager->RemoveBoundContent(this);
+ if (oldBinding) {
+ oldBinding->SetBoundElement(nullptr);
+ }
+ }
+}
+
+nsIContent*
+FragmentOrElement::GetXBLInsertionParent() const
+{
+ if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ return slots->mXBLInsertionParent;
+ }
+ }
+
+ return nullptr;
+}
+
+ShadowRoot*
+FragmentOrElement::GetContainingShadow() const
+{
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ return slots->mContainingShadow;
+ }
+ return nullptr;
+}
+
+void
+FragmentOrElement::SetShadowRoot(ShadowRoot* aShadowRoot)
+{
+ nsDOMSlots *slots = DOMSlots();
+ slots->mShadowRoot = aShadowRoot;
+}
+
+nsTArray<nsIContent*>&
+FragmentOrElement::DestInsertionPoints()
+{
+ nsDOMSlots *slots = DOMSlots();
+ return slots->mDestInsertionPoints;
+}
+
+nsTArray<nsIContent*>*
+FragmentOrElement::GetExistingDestInsertionPoints() const
+{
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ return &slots->mDestInsertionPoints;
+ }
+ return nullptr;
+}
+
+void
+FragmentOrElement::SetXBLInsertionParent(nsIContent* aContent)
+{
+ if (aContent) {
+ nsDOMSlots *slots = DOMSlots();
+ SetFlags(NODE_MAY_BE_IN_BINDING_MNGR);
+ slots->mXBLInsertionParent = aContent;
+ } else {
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ slots->mXBLInsertionParent = nullptr;
+ }
+ }
+}
+
+CustomElementData*
+FragmentOrElement::GetCustomElementData() const
+{
+ nsDOMSlots *slots = GetExistingDOMSlots();
+ if (slots) {
+ return slots->mCustomElementData;
+ }
+ return nullptr;
+}
+
+void
+FragmentOrElement::SetCustomElementData(CustomElementData* aData)
+{
+ nsDOMSlots *slots = DOMSlots();
+ MOZ_ASSERT(!slots->mCustomElementData, "Custom element data may not be changed once set.");
+ slots->mCustomElementData = aData;
+}
+
+nsresult
+FragmentOrElement::InsertChildAt(nsIContent* aKid,
+ uint32_t aIndex,
+ bool aNotify)
+{
+ NS_PRECONDITION(aKid, "null ptr");
+
+ return doInsertChildAt(aKid, aIndex, aNotify, mAttrsAndChildren);
+}
+
+void
+FragmentOrElement::RemoveChildAt(uint32_t aIndex, bool aNotify)
+{
+ nsCOMPtr<nsIContent> oldKid = mAttrsAndChildren.GetSafeChildAt(aIndex);
+ NS_ASSERTION(oldKid == GetChildAt(aIndex), "Unexpected child in RemoveChildAt");
+
+ if (oldKid) {
+ doRemoveChildAt(aIndex, aNotify, oldKid, mAttrsAndChildren);
+ }
+}
+
+void
+FragmentOrElement::GetTextContentInternal(nsAString& aTextContent,
+ ErrorResult& aError)
+{
+ if (!nsContentUtils::GetNodeTextContent(this, true, aTextContent, fallible)) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ }
+}
+
+void
+FragmentOrElement::SetTextContentInternal(const nsAString& aTextContent,
+ ErrorResult& aError)
+{
+ aError = nsContentUtils::SetNodeTextContent(this, aTextContent, false);
+}
+
+void
+FragmentOrElement::DestroyContent()
+{
+ nsIDocument *document = OwnerDoc();
+ document->BindingManager()->RemovedFromDocument(this, document,
+ nsBindingManager::eRunDtor);
+ document->ClearBoxObjectFor(this);
+
+ uint32_t i, count = mAttrsAndChildren.ChildCount();
+ for (i = 0; i < count; ++i) {
+ // The child can remove itself from the parent in BindToTree.
+ mAttrsAndChildren.ChildAt(i)->DestroyContent();
+ }
+ ShadowRoot* shadowRoot = GetShadowRoot();
+ if (shadowRoot) {
+ shadowRoot->DestroyContent();
+ }
+}
+
+void
+FragmentOrElement::SaveSubtreeState()
+{
+ uint32_t i, count = mAttrsAndChildren.ChildCount();
+ for (i = 0; i < count; ++i) {
+ mAttrsAndChildren.ChildAt(i)->SaveSubtreeState();
+ }
+}
+
+//----------------------------------------------------------------------
+
+// Generic DOMNode implementations
+
+void
+FragmentOrElement::FireNodeInserted(nsIDocument* aDoc,
+ nsINode* aParent,
+ nsTArray<nsCOMPtr<nsIContent> >& aNodes)
+{
+ uint32_t count = aNodes.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsIContent* childContent = aNodes[i];
+
+ if (nsContentUtils::HasMutationListeners(childContent,
+ NS_EVENT_BITS_MUTATION_NODEINSERTED, aParent)) {
+ InternalMutationEvent mutation(true, eLegacyNodeInserted);
+ mutation.mRelatedNode = do_QueryInterface(aParent);
+
+ mozAutoSubtreeModified subtree(aDoc, aParent);
+ (new AsyncEventDispatcher(childContent, mutation))->RunDOMEventWhenSafe();
+ }
+ }
+}
+
+//----------------------------------------------------------------------
+
+// nsISupports implementation
+
+#define SUBTREE_UNBINDINGS_PER_RUNNABLE 500
+
+class ContentUnbinder : public Runnable
+{
+public:
+ ContentUnbinder()
+ {
+ mLast = this;
+ }
+
+ ~ContentUnbinder()
+ {
+ Run();
+ }
+
+ void UnbindSubtree(nsIContent* aNode)
+ {
+ if (aNode->NodeType() != nsIDOMNode::ELEMENT_NODE &&
+ aNode->NodeType() != nsIDOMNode::DOCUMENT_FRAGMENT_NODE) {
+ return;
+ }
+ FragmentOrElement* container = static_cast<FragmentOrElement*>(aNode);
+ uint32_t childCount = container->mAttrsAndChildren.ChildCount();
+ if (childCount) {
+ while (childCount-- > 0) {
+ // Hold a strong ref to the node when we remove it, because we may be
+ // the last reference to it. We need to call TakeChildAt() and
+ // update mFirstChild before calling UnbindFromTree, since this last
+ // can notify various observers and they should really see consistent
+ // tree state.
+ nsCOMPtr<nsIContent> child =
+ container->mAttrsAndChildren.TakeChildAt(childCount);
+ if (childCount == 0) {
+ container->mFirstChild = nullptr;
+ }
+ UnbindSubtree(child);
+ child->UnbindFromTree();
+ }
+ }
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsAutoScriptBlocker scriptBlocker;
+ uint32_t len = mSubtreeRoots.Length();
+ if (len) {
+ for (uint32_t i = 0; i < len; ++i) {
+ UnbindSubtree(mSubtreeRoots[i]);
+ }
+ mSubtreeRoots.Clear();
+ }
+ nsCycleCollector_dispatchDeferredDeletion();
+ if (this == sContentUnbinder) {
+ sContentUnbinder = nullptr;
+ if (mNext) {
+ RefPtr<ContentUnbinder> next;
+ next.swap(mNext);
+ sContentUnbinder = next;
+ next->mLast = mLast;
+ mLast = nullptr;
+ NS_DispatchToMainThread(next);
+ }
+ }
+ return NS_OK;
+ }
+
+ static void UnbindAll()
+ {
+ RefPtr<ContentUnbinder> ub = sContentUnbinder;
+ sContentUnbinder = nullptr;
+ while (ub) {
+ ub->Run();
+ ub = ub->mNext;
+ }
+ }
+
+ static void Append(nsIContent* aSubtreeRoot)
+ {
+ if (!sContentUnbinder) {
+ sContentUnbinder = new ContentUnbinder();
+ nsCOMPtr<nsIRunnable> e = sContentUnbinder;
+ NS_DispatchToMainThread(e);
+ }
+
+ if (sContentUnbinder->mLast->mSubtreeRoots.Length() >=
+ SUBTREE_UNBINDINGS_PER_RUNNABLE) {
+ sContentUnbinder->mLast->mNext = new ContentUnbinder();
+ sContentUnbinder->mLast = sContentUnbinder->mLast->mNext;
+ }
+ sContentUnbinder->mLast->mSubtreeRoots.AppendElement(aSubtreeRoot);
+ }
+
+private:
+ AutoTArray<nsCOMPtr<nsIContent>,
+ SUBTREE_UNBINDINGS_PER_RUNNABLE> mSubtreeRoots;
+ RefPtr<ContentUnbinder> mNext;
+ ContentUnbinder* mLast;
+ static ContentUnbinder* sContentUnbinder;
+};
+
+ContentUnbinder* ContentUnbinder::sContentUnbinder = nullptr;
+
+void
+FragmentOrElement::ClearContentUnbinder()
+{
+ ContentUnbinder::UnbindAll();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FragmentOrElement)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement)
+ nsINode::Unlink(tmp);
+
+ // The XBL binding is removed by RemoveFromBindingManagerRunnable
+ // which is dispatched in UnbindFromTree.
+
+ if (tmp->HasProperties()) {
+ if (tmp->IsHTMLElement() || tmp->IsSVGElement()) {
+ nsIAtom*** props = Element::HTMLSVGPropertiesToTraverseAndUnlink();
+ for (uint32_t i = 0; props[i]; ++i) {
+ tmp->DeleteProperty(*props[i]);
+ }
+ if (tmp->MayHaveAnimations()) {
+ nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
+ for (uint32_t i = 0; effectProps[i]; ++i) {
+ tmp->DeleteProperty(effectProps[i]);
+ }
+ }
+ }
+ }
+
+ // Unlink child content (and unbind our subtree).
+ if (tmp->UnoptimizableCCNode() || !nsCCUncollectableMarker::sGeneration) {
+ uint32_t childCount = tmp->mAttrsAndChildren.ChildCount();
+ if (childCount) {
+ // Don't allow script to run while we're unbinding everything.
+ nsAutoScriptBlocker scriptBlocker;
+ while (childCount-- > 0) {
+ // Hold a strong ref to the node when we remove it, because we may be
+ // the last reference to it. We need to call TakeChildAt() and
+ // update mFirstChild before calling UnbindFromTree, since this last
+ // can notify various observers and they should really see consistent
+ // tree state.
+ nsCOMPtr<nsIContent> child = tmp->mAttrsAndChildren.TakeChildAt(childCount);
+ if (childCount == 0) {
+ tmp->mFirstChild = nullptr;
+ }
+ child->UnbindFromTree();
+ }
+ }
+ } else if (!tmp->GetParent() && tmp->mAttrsAndChildren.ChildCount()) {
+ ContentUnbinder::Append(tmp);
+ } /* else {
+ The subtree root will end up to a ContentUnbinder, and that will
+ unbind the child nodes.
+ } */
+
+ // Clear flag here because unlinking slots will clear the
+ // containing shadow root pointer.
+ tmp->UnsetFlags(NODE_IS_IN_SHADOW_TREE);
+
+ nsIDocument* doc = tmp->OwnerDoc();
+ doc->BindingManager()->RemovedFromDocument(tmp, doc,
+ nsBindingManager::eDoNotRunDtor);
+
+ // Unlink any DOM slots of interest.
+ {
+ nsDOMSlots *slots = tmp->GetExistingDOMSlots();
+ if (slots) {
+ slots->Unlink(tmp->IsXULElement());
+ }
+ }
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(FragmentOrElement)
+
+void
+FragmentOrElement::MarkUserData(void* aObject, nsIAtom* aKey, void* aChild,
+ void* aData)
+{
+ uint32_t* gen = static_cast<uint32_t*>(aData);
+ xpc_MarkInCCGeneration(static_cast<nsISupports*>(aChild), *gen);
+}
+
+void
+FragmentOrElement::MarkNodeChildren(nsINode* aNode)
+{
+ JSObject* o = GetJSObjectChild(aNode);
+ if (o) {
+ JS::ExposeObjectToActiveJS(o);
+ }
+
+ EventListenerManager* elm = aNode->GetExistingListenerManager();
+ if (elm) {
+ elm->MarkForCC();
+ }
+
+ if (aNode->HasProperties()) {
+ nsIDocument* ownerDoc = aNode->OwnerDoc();
+ ownerDoc->PropertyTable(DOM_USER_DATA)->
+ Enumerate(aNode, FragmentOrElement::MarkUserData,
+ &nsCCUncollectableMarker::sGeneration);
+ }
+}
+
+nsINode*
+FindOptimizableSubtreeRoot(nsINode* aNode)
+{
+ nsINode* p;
+ while ((p = aNode->GetParentNode())) {
+ if (aNode->UnoptimizableCCNode()) {
+ return nullptr;
+ }
+ aNode = p;
+ }
+
+ if (aNode->UnoptimizableCCNode()) {
+ return nullptr;
+ }
+ return aNode;
+}
+
+StaticAutoPtr<nsTHashtable<nsPtrHashKey<nsINode>>> gCCBlackMarkedNodes;
+
+static void
+ClearBlackMarkedNodes()
+{
+ if (!gCCBlackMarkedNodes) {
+ return;
+ }
+ for (auto iter = gCCBlackMarkedNodes->ConstIter(); !iter.Done();
+ iter.Next()) {
+ nsINode* n = iter.Get()->GetKey();
+ n->SetCCMarkedRoot(false);
+ n->SetInCCBlackTree(false);
+ }
+ gCCBlackMarkedNodes = nullptr;
+}
+
+// static
+void
+FragmentOrElement::RemoveBlackMarkedNode(nsINode* aNode)
+{
+ if (!gCCBlackMarkedNodes) {
+ return;
+ }
+ gCCBlackMarkedNodes->RemoveEntry(aNode);
+}
+
+static bool
+IsCertainlyAliveNode(nsINode* aNode, nsIDocument* aDoc)
+{
+ MOZ_ASSERT(aNode->GetUncomposedDoc() == aDoc);
+
+ // Marked to be in-CC-generation or if the document is an svg image that's
+ // being kept alive by the image cache. (Note that an svg image's internal
+ // SVG document will receive an OnPageHide() call when it gets purged from
+ // the image cache; hence, we use IsVisible() as a hint that the document is
+ // actively being kept alive by the cache.)
+ return nsCCUncollectableMarker::InGeneration(aDoc->GetMarkedCCGeneration()) ||
+ (nsCCUncollectableMarker::sGeneration &&
+ aDoc->IsBeingUsedAsImage() &&
+ aDoc->IsVisible());
+}
+
+// static
+bool
+FragmentOrElement::CanSkipInCC(nsINode* aNode)
+{
+ // Don't try to optimize anything during shutdown.
+ if (nsCCUncollectableMarker::sGeneration == 0) {
+ return false;
+ }
+
+ //XXXsmaug Need to figure out in which cases Shadow DOM can be optimized out
+ // from the CC graph.
+ nsIDocument* currentDoc = aNode->GetUncomposedDoc();
+ if (currentDoc && IsCertainlyAliveNode(aNode, currentDoc)) {
+ return !NeedsScriptTraverse(aNode);
+ }
+
+ // Bail out early if aNode is somewhere in anonymous content,
+ // or otherwise unusual.
+ if (aNode->UnoptimizableCCNode()) {
+ return false;
+ }
+
+ nsINode* root =
+ currentDoc ? static_cast<nsINode*>(currentDoc) :
+ FindOptimizableSubtreeRoot(aNode);
+ if (!root) {
+ return false;
+ }
+
+ // Subtree has been traversed already.
+ if (root->CCMarkedRoot()) {
+ return root->InCCBlackTree() && !NeedsScriptTraverse(aNode);
+ }
+
+ if (!gCCBlackMarkedNodes) {
+ gCCBlackMarkedNodes = new nsTHashtable<nsPtrHashKey<nsINode> >(1020);
+ }
+
+ // nodesToUnpurple contains nodes which will be removed
+ // from the purple buffer if the DOM tree is black.
+ AutoTArray<nsIContent*, 1020> nodesToUnpurple;
+ // grayNodes need script traverse, so they aren't removed from
+ // the purple buffer, but are marked to be in black subtree so that
+ // traverse is faster.
+ AutoTArray<nsINode*, 1020> grayNodes;
+
+ bool foundBlack = root->IsBlack();
+ if (root != currentDoc) {
+ currentDoc = nullptr;
+ if (NeedsScriptTraverse(root)) {
+ grayNodes.AppendElement(root);
+ } else if (static_cast<nsIContent*>(root)->IsPurple()) {
+ nodesToUnpurple.AppendElement(static_cast<nsIContent*>(root));
+ }
+ }
+
+ // Traverse the subtree and check if we could know without CC
+ // that it is black.
+ // Note, this traverse is non-virtual and inline, so it should be a lot faster
+ // than CC's generic traverse.
+ for (nsIContent* node = root->GetFirstChild(); node;
+ node = node->GetNextNode(root)) {
+ foundBlack = foundBlack || node->IsBlack();
+ if (foundBlack && currentDoc) {
+ // If we can mark the whole document black, no need to optimize
+ // so much, since when the next purple node in the document will be
+ // handled, it is fast to check that currentDoc is in CCGeneration.
+ break;
+ }
+ if (NeedsScriptTraverse(node)) {
+ // Gray nodes need real CC traverse.
+ grayNodes.AppendElement(node);
+ } else if (node->IsPurple()) {
+ nodesToUnpurple.AppendElement(node);
+ }
+ }
+
+ root->SetCCMarkedRoot(true);
+ root->SetInCCBlackTree(foundBlack);
+ gCCBlackMarkedNodes->PutEntry(root);
+
+ if (!foundBlack) {
+ return false;
+ }
+
+ if (currentDoc) {
+ // Special case documents. If we know the document is black,
+ // we can mark the document to be in CCGeneration.
+ currentDoc->
+ MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration);
+ } else {
+ for (uint32_t i = 0; i < grayNodes.Length(); ++i) {
+ nsINode* node = grayNodes[i];
+ node->SetInCCBlackTree(true);
+ gCCBlackMarkedNodes->PutEntry(node);
+ }
+ }
+
+ // Subtree is black, we can remove non-gray purple nodes from
+ // purple buffer.
+ for (uint32_t i = 0; i < nodesToUnpurple.Length(); ++i) {
+ nsIContent* purple = nodesToUnpurple[i];
+ // Can't remove currently handled purple node.
+ if (purple != aNode) {
+ purple->RemovePurple();
+ }
+ }
+ return !NeedsScriptTraverse(aNode);
+}
+
+AutoTArray<nsINode*, 1020>* gPurpleRoots = nullptr;
+AutoTArray<nsIContent*, 1020>* gNodesToUnbind = nullptr;
+
+void ClearCycleCollectorCleanupData()
+{
+ if (gPurpleRoots) {
+ uint32_t len = gPurpleRoots->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ nsINode* n = gPurpleRoots->ElementAt(i);
+ n->SetIsPurpleRoot(false);
+ }
+ delete gPurpleRoots;
+ gPurpleRoots = nullptr;
+ }
+ if (gNodesToUnbind) {
+ uint32_t len = gNodesToUnbind->Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ nsIContent* c = gNodesToUnbind->ElementAt(i);
+ c->SetIsPurpleRoot(false);
+ ContentUnbinder::Append(c);
+ }
+ delete gNodesToUnbind;
+ gNodesToUnbind = nullptr;
+ }
+}
+
+static bool
+ShouldClearPurple(nsIContent* aContent)
+{
+ MOZ_ASSERT(aContent);
+ if (aContent->IsPurple()) {
+ return true;
+ }
+
+ JSObject* o = GetJSObjectChild(aContent);
+ if (o && JS::ObjectIsMarkedGray(o)) {
+ return true;
+ }
+
+ if (aContent->HasListenerManager()) {
+ return true;
+ }
+
+ return aContent->HasProperties();
+}
+
+// If aNode is not optimizable, but is an element
+// with a frame in a document which has currently active presshell,
+// we can act as if it was optimizable. When the primary frame dies, aNode
+// will end up to the purple buffer because of the refcount change.
+bool
+NodeHasActiveFrame(nsIDocument* aCurrentDoc, nsINode* aNode)
+{
+ return aCurrentDoc->GetShell() && aNode->IsElement() &&
+ aNode->AsElement()->GetPrimaryFrame();
+}
+
+bool
+OwnedByBindingManager(nsIDocument* aCurrentDoc, nsINode* aNode)
+{
+ return aNode->IsElement() && aNode->AsElement()->GetXBLBinding();
+}
+
+// CanSkip checks if aNode is black, and if it is, returns
+// true. If aNode is in a black DOM tree, CanSkip may also remove other objects
+// from purple buffer and unmark event listeners and user data.
+// If the root of the DOM tree is a document, less optimizations are done
+// since checking the blackness of the current document is usually fast and we
+// don't want slow down such common cases.
+bool
+FragmentOrElement::CanSkip(nsINode* aNode, bool aRemovingAllowed)
+{
+ // Don't try to optimize anything during shutdown.
+ if (nsCCUncollectableMarker::sGeneration == 0) {
+ return false;
+ }
+
+ bool unoptimizable = aNode->UnoptimizableCCNode();
+ nsIDocument* currentDoc = aNode->GetUncomposedDoc();
+ if (currentDoc && IsCertainlyAliveNode(aNode, currentDoc) &&
+ (!unoptimizable || NodeHasActiveFrame(currentDoc, aNode) ||
+ OwnedByBindingManager(currentDoc, aNode))) {
+ MarkNodeChildren(aNode);
+ return true;
+ }
+
+ if (unoptimizable) {
+ return false;
+ }
+
+ nsINode* root = currentDoc ? static_cast<nsINode*>(currentDoc) :
+ FindOptimizableSubtreeRoot(aNode);
+ if (!root) {
+ return false;
+ }
+
+ // Subtree has been traversed already, and aNode has
+ // been handled in a way that doesn't require revisiting it.
+ if (root->IsPurpleRoot()) {
+ return false;
+ }
+
+ // nodesToClear contains nodes which are either purple or
+ // gray.
+ AutoTArray<nsIContent*, 1020> nodesToClear;
+
+ bool foundBlack = root->IsBlack();
+ bool domOnlyCycle = false;
+ if (root != currentDoc) {
+ currentDoc = nullptr;
+ if (!foundBlack) {
+ domOnlyCycle = static_cast<nsIContent*>(root)->OwnedOnlyByTheDOMTree();
+ }
+ if (ShouldClearPurple(static_cast<nsIContent*>(root))) {
+ nodesToClear.AppendElement(static_cast<nsIContent*>(root));
+ }
+ }
+
+ // Traverse the subtree and check if we could know without CC
+ // that it is black.
+ // Note, this traverse is non-virtual and inline, so it should be a lot faster
+ // than CC's generic traverse.
+ for (nsIContent* node = root->GetFirstChild(); node;
+ node = node->GetNextNode(root)) {
+ foundBlack = foundBlack || node->IsBlack();
+ if (foundBlack) {
+ domOnlyCycle = false;
+ if (currentDoc) {
+ // If we can mark the whole document black, no need to optimize
+ // so much, since when the next purple node in the document will be
+ // handled, it is fast to check that the currentDoc is in CCGeneration.
+ break;
+ }
+ // No need to put stuff to the nodesToClear array, if we can clear it
+ // already here.
+ if (node->IsPurple() && (node != aNode || aRemovingAllowed)) {
+ node->RemovePurple();
+ }
+ MarkNodeChildren(node);
+ } else {
+ domOnlyCycle = domOnlyCycle && node->OwnedOnlyByTheDOMTree();
+ if (ShouldClearPurple(node)) {
+ // Collect interesting nodes which we can clear if we find that
+ // they are kept alive in a black tree or are in a DOM-only cycle.
+ nodesToClear.AppendElement(node);
+ }
+ }
+ }
+
+ if (!currentDoc || !foundBlack) {
+ root->SetIsPurpleRoot(true);
+ if (domOnlyCycle) {
+ if (!gNodesToUnbind) {
+ gNodesToUnbind = new AutoTArray<nsIContent*, 1020>();
+ }
+ gNodesToUnbind->AppendElement(static_cast<nsIContent*>(root));
+ for (uint32_t i = 0; i < nodesToClear.Length(); ++i) {
+ nsIContent* n = nodesToClear[i];
+ if ((n != aNode || aRemovingAllowed) && n->IsPurple()) {
+ n->RemovePurple();
+ }
+ }
+ return true;
+ } else {
+ if (!gPurpleRoots) {
+ gPurpleRoots = new AutoTArray<nsINode*, 1020>();
+ }
+ gPurpleRoots->AppendElement(root);
+ }
+ }
+
+ if (!foundBlack) {
+ return false;
+ }
+
+ if (currentDoc) {
+ // Special case documents. If we know the document is black,
+ // we can mark the document to be in CCGeneration.
+ currentDoc->
+ MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration);
+ MarkNodeChildren(currentDoc);
+ }
+
+ // Subtree is black, so we can remove purple nodes from
+ // purple buffer and mark stuff that to be certainly alive.
+ for (uint32_t i = 0; i < nodesToClear.Length(); ++i) {
+ nsIContent* n = nodesToClear[i];
+ MarkNodeChildren(n);
+ // Can't remove currently handled purple node,
+ // unless aRemovingAllowed is true.
+ if ((n != aNode || aRemovingAllowed) && n->IsPurple()) {
+ n->RemovePurple();
+ }
+ }
+ return true;
+}
+
+bool
+FragmentOrElement::CanSkipThis(nsINode* aNode)
+{
+ if (nsCCUncollectableMarker::sGeneration == 0) {
+ return false;
+ }
+ if (aNode->IsBlack()) {
+ return true;
+ }
+ nsIDocument* c = aNode->GetUncomposedDoc();
+ return
+ ((c && IsCertainlyAliveNode(aNode, c)) || aNode->InCCBlackTree()) &&
+ !NeedsScriptTraverse(aNode);
+}
+
+void
+FragmentOrElement::InitCCCallbacks()
+{
+ nsCycleCollector_setForgetSkippableCallback(ClearCycleCollectorCleanupData);
+ nsCycleCollector_setBeforeUnlinkCallback(ClearBlackMarkedNodes);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(FragmentOrElement)
+ return FragmentOrElement::CanSkip(tmp, aRemovingAllowed);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(FragmentOrElement)
+ return FragmentOrElement::CanSkipInCC(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(FragmentOrElement)
+ return FragmentOrElement::CanSkipThis(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+static const char* kNSURIs[] = {
+ " ([none])",
+ " (xmlns)",
+ " (xml)",
+ " (xhtml)",
+ " (XLink)",
+ " (XSLT)",
+ " (XBL)",
+ " (MathML)",
+ " (RDF)",
+ " (XUL)",
+ " (SVG)",
+ " (XML Events)"
+};
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement)
+ if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
+ char name[512];
+ uint32_t nsid = tmp->GetNameSpaceID();
+ nsAtomCString localName(tmp->NodeInfo()->NameAtom());
+ nsAutoCString uri;
+ if (tmp->OwnerDoc()->GetDocumentURI()) {
+ uri = tmp->OwnerDoc()->GetDocumentURI()->GetSpecOrDefault();
+ }
+
+ nsAutoString id;
+ nsIAtom* idAtom = tmp->GetID();
+ if (idAtom) {
+ id.AppendLiteral(" id='");
+ id.Append(nsDependentAtomString(idAtom));
+ id.Append('\'');
+ }
+
+ nsAutoString classes;
+ const nsAttrValue* classAttrValue = tmp->GetClasses();
+ if (classAttrValue) {
+ classes.AppendLiteral(" class='");
+ nsAutoString classString;
+ classAttrValue->ToString(classString);
+ classString.ReplaceChar(char16_t('\n'), char16_t(' '));
+ classes.Append(classString);
+ classes.Append('\'');
+ }
+
+ nsAutoCString orphan;
+ if (!tmp->IsInUncomposedDoc() &&
+ // Ignore xbl:content, which is never in the document and hence always
+ // appears to be orphaned.
+ !tmp->NodeInfo()->Equals(nsGkAtoms::content, kNameSpaceID_XBL)) {
+ orphan.AppendLiteral(" (orphan)");
+ }
+
+ const char* nsuri = nsid < ArrayLength(kNSURIs) ? kNSURIs[nsid] : "";
+ SprintfLiteral(name, "FragmentOrElement%s %s%s%s%s %s",
+ nsuri,
+ localName.get(),
+ NS_ConvertUTF16toUTF8(id).get(),
+ NS_ConvertUTF16toUTF8(classes).get(),
+ orphan.get(),
+ uri.get());
+ cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
+ }
+ else {
+ NS_IMPL_CYCLE_COLLECTION_DESCRIBE(FragmentOrElement, tmp->mRefCnt.get())
+ }
+
+ // Always need to traverse script objects, so do that before we check
+ // if we're uncollectable.
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+
+ if (!nsINode::Traverse(tmp, cb)) {
+ return NS_SUCCESS_INTERRUPTED_TRAVERSE;
+ }
+
+ tmp->OwnerDoc()->BindingManager()->Traverse(tmp, cb);
+
+ // Check that whenever we have effect properties, MayHaveAnimations is set.
+#ifdef DEBUG
+ nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
+ for (uint32_t i = 0; effectProps[i]; ++i) {
+ MOZ_ASSERT_IF(tmp->GetProperty(effectProps[i]), tmp->MayHaveAnimations());
+ }
+#endif
+
+ if (tmp->HasProperties()) {
+ if (tmp->IsHTMLElement() || tmp->IsSVGElement()) {
+ nsIAtom*** props = Element::HTMLSVGPropertiesToTraverseAndUnlink();
+ for (uint32_t i = 0; props[i]; ++i) {
+ nsISupports* property =
+ static_cast<nsISupports*>(tmp->GetProperty(*props[i]));
+ cb.NoteXPCOMChild(property);
+ }
+ if (tmp->MayHaveAnimations()) {
+ nsIAtom** effectProps = EffectSet::GetEffectSetPropertyAtoms();
+ for (uint32_t i = 0; effectProps[i]; ++i) {
+ EffectSet* effectSet =
+ static_cast<EffectSet*>(tmp->GetProperty(effectProps[i]));
+ if (effectSet) {
+ effectSet->Traverse(cb);
+ }
+ }
+ }
+ }
+ }
+
+ // Traverse attribute names and child content.
+ {
+ uint32_t i;
+ uint32_t attrs = tmp->mAttrsAndChildren.AttrCount();
+ for (i = 0; i < attrs; i++) {
+ const nsAttrName* name = tmp->mAttrsAndChildren.AttrNameAt(i);
+ if (!name->IsAtom()) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
+ "mAttrsAndChildren[i]->NodeInfo()");
+ cb.NoteNativeChild(name->NodeInfo(),
+ NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
+ }
+ }
+
+ uint32_t kids = tmp->mAttrsAndChildren.ChildCount();
+ for (i = 0; i < kids; i++) {
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mAttrsAndChildren[i]");
+ cb.NoteXPCOMChild(tmp->mAttrsAndChildren.GetSafeChildAt(i));
+ }
+ }
+
+ // Traverse any DOM slots of interest.
+ {
+ nsDOMSlots *slots = tmp->GetExistingDOMSlots();
+ if (slots) {
+ slots->Traverse(cb, tmp->IsXULElement());
+ }
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+
+NS_INTERFACE_MAP_BEGIN(FragmentOrElement)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(FragmentOrElement)
+ NS_INTERFACE_MAP_ENTRY(Element)
+ NS_INTERFACE_MAP_ENTRY(nsIContent)
+ NS_INTERFACE_MAP_ENTRY(nsINode)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget)
+ NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
+ NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference,
+ new nsNodeSupportsWeakRefTearoff(this))
+ // DOM bindings depend on the identity pointer being the
+ // same as nsINode (which nsIContent inherits).
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FragmentOrElement)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(FragmentOrElement,
+ nsNodeUtils::LastRelease(this))
+
+//----------------------------------------------------------------------
+
+nsresult
+FragmentOrElement::CopyInnerTo(FragmentOrElement* aDst)
+{
+ uint32_t i, count = mAttrsAndChildren.AttrCount();
+ for (i = 0; i < count; ++i) {
+ const nsAttrName* name = mAttrsAndChildren.AttrNameAt(i);
+ const nsAttrValue* value = mAttrsAndChildren.AttrAt(i);
+ nsAutoString valStr;
+ value->ToString(valStr);
+ nsresult rv = aDst->SetAttr(name->NamespaceID(), name->LocalName(),
+ name->GetPrefix(), valStr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+const nsTextFragment*
+FragmentOrElement::GetText()
+{
+ return nullptr;
+}
+
+uint32_t
+FragmentOrElement::TextLength() const
+{
+ // We can remove this assertion if it turns out to be useful to be able
+ // to depend on this returning 0
+ NS_NOTREACHED("called FragmentOrElement::TextLength");
+
+ return 0;
+}
+
+nsresult
+FragmentOrElement::SetText(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify)
+{
+ NS_ERROR("called FragmentOrElement::SetText");
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+FragmentOrElement::AppendText(const char16_t* aBuffer, uint32_t aLength,
+ bool aNotify)
+{
+ NS_ERROR("called FragmentOrElement::AppendText");
+
+ return NS_ERROR_FAILURE;
+}
+
+bool
+FragmentOrElement::TextIsOnlyWhitespace()
+{
+ return false;
+}
+
+bool
+FragmentOrElement::HasTextForTranslation()
+{
+ return false;
+}
+
+void
+FragmentOrElement::AppendTextTo(nsAString& aResult)
+{
+ // We can remove this assertion if it turns out to be useful to be able
+ // to depend on this appending nothing.
+ NS_NOTREACHED("called FragmentOrElement::TextLength");
+}
+
+bool
+FragmentOrElement::AppendTextTo(nsAString& aResult, const mozilla::fallible_t&)
+{
+ // We can remove this assertion if it turns out to be useful to be able
+ // to depend on this appending nothing.
+ NS_NOTREACHED("called FragmentOrElement::TextLength");
+
+ return false;
+}
+
+uint32_t
+FragmentOrElement::GetChildCount() const
+{
+ return mAttrsAndChildren.ChildCount();
+}
+
+nsIContent *
+FragmentOrElement::GetChildAt(uint32_t aIndex) const
+{
+ return mAttrsAndChildren.GetSafeChildAt(aIndex);
+}
+
+nsIContent * const *
+FragmentOrElement::GetChildArray(uint32_t* aChildCount) const
+{
+ return mAttrsAndChildren.GetChildArray(aChildCount);
+}
+
+int32_t
+FragmentOrElement::IndexOf(const nsINode* aPossibleChild) const
+{
+ return mAttrsAndChildren.IndexOfChild(aPossibleChild);
+}
+
+static inline bool
+IsVoidTag(nsIAtom* aTag)
+{
+ static const nsIAtom* voidElements[] = {
+ nsGkAtoms::area, nsGkAtoms::base, nsGkAtoms::basefont,
+ nsGkAtoms::bgsound, nsGkAtoms::br, nsGkAtoms::col,
+ nsGkAtoms::embed, nsGkAtoms::frame,
+ nsGkAtoms::hr, nsGkAtoms::img, nsGkAtoms::input,
+ nsGkAtoms::keygen, nsGkAtoms::link, nsGkAtoms::meta,
+ nsGkAtoms::param, nsGkAtoms::source, nsGkAtoms::track,
+ nsGkAtoms::wbr
+ };
+
+ static mozilla::BloomFilter<12, nsIAtom> sFilter;
+ static bool sInitialized = false;
+ if (!sInitialized) {
+ sInitialized = true;
+ for (uint32_t i = 0; i < ArrayLength(voidElements); ++i) {
+ sFilter.add(voidElements[i]);
+ }
+ }
+
+ if (sFilter.mightContain(aTag)) {
+ for (uint32_t i = 0; i < ArrayLength(voidElements); ++i) {
+ if (aTag == voidElements[i]) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/* static */
+bool
+FragmentOrElement::IsHTMLVoid(nsIAtom* aLocalName)
+{
+ return aLocalName && IsVoidTag(aLocalName);
+}
+
+void
+FragmentOrElement::GetMarkup(bool aIncludeSelf, nsAString& aMarkup)
+{
+ aMarkup.Truncate();
+
+ nsIDocument* doc = OwnerDoc();
+ if (IsInHTMLDocument()) {
+ nsContentUtils::SerializeNodeToMarkup(this, !aIncludeSelf, aMarkup);
+ return;
+ }
+
+ nsAutoString contentType;
+ doc->GetContentType(contentType);
+ bool tryToCacheEncoder = !aIncludeSelf;
+
+ nsCOMPtr<nsIDocumentEncoder> docEncoder = doc->GetCachedEncoder();
+ if (!docEncoder) {
+ docEncoder =
+ do_CreateInstance(PromiseFlatCString(
+ nsDependentCString(NS_DOC_ENCODER_CONTRACTID_BASE) +
+ NS_ConvertUTF16toUTF8(contentType)
+ ).get());
+ }
+ if (!docEncoder) {
+ // This could be some type for which we create a synthetic document. Try
+ // again as XML
+ contentType.AssignLiteral("application/xml");
+ docEncoder = do_CreateInstance(NS_DOC_ENCODER_CONTRACTID_BASE "application/xml");
+ // Don't try to cache the encoder since it would point to a different
+ // contentType once it has been reinitialized.
+ tryToCacheEncoder = false;
+ }
+
+ NS_ENSURE_TRUE_VOID(docEncoder);
+
+ uint32_t flags = nsIDocumentEncoder::OutputEncodeBasicEntities |
+ // Output DOM-standard newlines
+ nsIDocumentEncoder::OutputLFLineBreak |
+ // Don't do linebreaking that's not present in
+ // the source
+ nsIDocumentEncoder::OutputRaw |
+ // Only check for mozdirty when necessary (bug 599983)
+ nsIDocumentEncoder::OutputIgnoreMozDirty;
+
+ if (IsEditable()) {
+ nsCOMPtr<Element> elem = do_QueryInterface(this);
+ nsIEditor* editor = elem ? elem->GetEditorInternal() : nullptr;
+ if (editor && editor->OutputsMozDirty()) {
+ flags &= ~nsIDocumentEncoder::OutputIgnoreMozDirty;
+ }
+ }
+
+ DebugOnly<nsresult> rv = docEncoder->NativeInit(doc, contentType, flags);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ if (aIncludeSelf) {
+ docEncoder->SetNativeNode(this);
+ } else {
+ docEncoder->SetNativeContainerNode(this);
+ }
+ rv = docEncoder->EncodeToString(aMarkup);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (tryToCacheEncoder) {
+ doc->SetCachedEncoder(docEncoder.forget());
+ }
+}
+
+static bool
+ContainsMarkup(const nsAString& aStr)
+{
+ // Note: we can't use FindCharInSet because null is one of the characters we
+ // want to search for.
+ const char16_t* start = aStr.BeginReading();
+ const char16_t* end = aStr.EndReading();
+
+ while (start != end) {
+ char16_t c = *start;
+ if (c == char16_t('<') ||
+ c == char16_t('&') ||
+ c == char16_t('\r') ||
+ c == char16_t('\0')) {
+ return true;
+ }
+ ++start;
+ }
+
+ return false;
+}
+
+void
+FragmentOrElement::SetInnerHTMLInternal(const nsAString& aInnerHTML, ErrorResult& aError)
+{
+ FragmentOrElement* target = this;
+ // Handle template case.
+ if (nsNodeUtils::IsTemplateElement(target)) {
+ DocumentFragment* frag =
+ static_cast<HTMLTemplateElement*>(target)->Content();
+ MOZ_ASSERT(frag);
+ target = frag;
+ }
+
+ // Fast-path for strings with no markup. Limit this to short strings, to
+ // avoid ContainsMarkup taking too long. The choice for 100 is based on
+ // gut feeling.
+ //
+ // Don't do this for elements with a weird parser insertion mode, for
+ // instance setting innerHTML = "" on a <html> element should add the
+ // optional <head> and <body> elements.
+ if (!target->HasWeirdParserInsertionMode() &&
+ aInnerHTML.Length() < 100 && !ContainsMarkup(aInnerHTML)) {
+ aError = nsContentUtils::SetNodeTextContent(target, aInnerHTML, false);
+ return;
+ }
+
+ nsIDocument* doc = target->OwnerDoc();
+
+ // Batch possible DOMSubtreeModified events.
+ mozAutoSubtreeModified subtree(doc, nullptr);
+
+ target->FireNodeRemovedForChildren();
+
+ // Needed when innerHTML is used in combination with contenteditable
+ mozAutoDocUpdate updateBatch(doc, UPDATE_CONTENT_MODEL, true);
+
+ // Remove childnodes.
+ uint32_t childCount = target->GetChildCount();
+ nsAutoMutationBatch mb(target, true, false);
+ for (uint32_t i = 0; i < childCount; ++i) {
+ target->RemoveChildAt(0, true);
+ }
+ mb.RemovalDone();
+
+ nsAutoScriptLoaderDisabler sld(doc);
+
+ nsIAtom* contextLocalName = NodeInfo()->NameAtom();
+ int32_t contextNameSpaceID = GetNameSpaceID();
+
+ ShadowRoot* shadowRoot = ShadowRoot::FromNode(this);
+ if (shadowRoot) {
+ // Fix up the context to be the host of the ShadowRoot.
+ contextLocalName = shadowRoot->GetHost()->NodeInfo()->NameAtom();
+ contextNameSpaceID = shadowRoot->GetHost()->GetNameSpaceID();
+ }
+
+ if (doc->IsHTMLDocument()) {
+ int32_t oldChildCount = target->GetChildCount();
+ aError = nsContentUtils::ParseFragmentHTML(aInnerHTML,
+ target,
+ contextLocalName,
+ contextNameSpaceID,
+ doc->GetCompatibilityMode() ==
+ eCompatibility_NavQuirks,
+ true);
+ mb.NodesAdded();
+ // HTML5 parser has notified, but not fired mutation events.
+ nsContentUtils::FireMutationEventsForDirectParsing(doc, target,
+ oldChildCount);
+ } else {
+ RefPtr<DocumentFragment> df =
+ nsContentUtils::CreateContextualFragment(target, aInnerHTML, true, aError);
+ if (!aError.Failed()) {
+ // Suppress assertion about node removal mutation events that can't have
+ // listeners anyway, because no one has had the chance to register mutation
+ // listeners on the fragment that comes from the parser.
+ nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
+
+ static_cast<nsINode*>(target)->AppendChild(*df, aError);
+ mb.NodesAdded();
+ }
+ }
+}
+
+nsINode::nsSlots*
+FragmentOrElement::CreateSlots()
+{
+ return new nsDOMSlots();
+}
+
+void
+FragmentOrElement::FireNodeRemovedForChildren()
+{
+ nsIDocument* doc = OwnerDoc();
+ // Optimize the common case
+ if (!nsContentUtils::
+ HasMutationListeners(doc, NS_EVENT_BITS_MUTATION_NODEREMOVED)) {
+ return;
+ }
+
+ nsCOMPtr<nsIDocument> owningDoc = doc;
+
+ nsCOMPtr<nsINode> child;
+ for (child = GetFirstChild();
+ child && child->GetParentNode() == this;
+ child = child->GetNextSibling()) {
+ nsContentUtils::MaybeFireNodeRemoved(child, this, doc);
+ }
+}
+
+size_t
+FragmentOrElement::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t n = 0;
+ n += nsIContent::SizeOfExcludingThis(aMallocSizeOf);
+ n += mAttrsAndChildren.SizeOfExcludingThis(aMallocSizeOf);
+
+ nsDOMSlots* slots = GetExistingDOMSlots();
+ if (slots) {
+ n += slots->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return n;
+}
+
+void
+FragmentOrElement::SetIsElementInStyleScopeFlagOnSubtree(bool aInStyleScope)
+{
+ if (aInStyleScope && IsElementInStyleScope()) {
+ return;
+ }
+
+ if (IsElement()) {
+ SetIsElementInStyleScope(aInStyleScope);
+ SetIsElementInStyleScopeFlagOnShadowTree(aInStyleScope);
+ }
+
+ nsIContent* n = GetNextNode(this);
+ while (n) {
+ if (n->IsElementInStyleScope()) {
+ n = n->GetNextNonChildNode(this);
+ } else {
+ if (n->IsElement()) {
+ n->SetIsElementInStyleScope(aInStyleScope);
+ n->AsElement()->SetIsElementInStyleScopeFlagOnShadowTree(aInStyleScope);
+ }
+ n = n->GetNextNode(this);
+ }
+ }
+}
+
+void
+FragmentOrElement::SetIsElementInStyleScopeFlagOnShadowTree(bool aInStyleScope)
+{
+ NS_ASSERTION(IsElement(), "calling SetIsElementInStyleScopeFlagOnShadowTree "
+ "on a non-Element is useless");
+ ShadowRoot* shadowRoot = GetShadowRoot();
+ while (shadowRoot) {
+ shadowRoot->SetIsElementInStyleScopeFlagOnSubtree(aInStyleScope);
+ shadowRoot = shadowRoot->GetOlderShadowRoot();
+ }
+}
+
+#ifdef DEBUG
+static void
+AssertDirtyDescendantsBitPropagated(nsINode* aNode)
+{
+ MOZ_ASSERT(aNode->HasDirtyDescendantsForServo());
+ nsINode* parent = aNode->GetFlattenedTreeParentNode();
+ if (!parent->IsContent()) {
+ MOZ_ASSERT(parent == aNode->OwnerDoc());
+ MOZ_ASSERT(parent->HasDirtyDescendantsForServo());
+ } else {
+ AssertDirtyDescendantsBitPropagated(parent);
+ }
+}
+#else
+static void AssertDirtyDescendantsBitPropagated(nsINode* aNode) {}
+#endif
+
+void
+nsIContent::MarkAncestorsAsHavingDirtyDescendantsForServo()
+{
+ MOZ_ASSERT(IsInComposedDoc());
+
+ // Get the parent in the flattened tree.
+ nsINode* parent = GetFlattenedTreeParentNode();
+
+ // Loop until we hit a base case.
+ while (true) {
+
+ // Base case: the document.
+ if (!parent->IsContent()) {
+ MOZ_ASSERT(parent == OwnerDoc());
+ parent->SetHasDirtyDescendantsForServo();
+ return;
+ }
+
+ // Base case: the parent is already marked, and therefore
+ // so are all its ancestors.
+ if (parent->HasDirtyDescendantsForServo()) {
+ AssertDirtyDescendantsBitPropagated(parent);
+ return;
+ }
+
+ // Mark the parent and iterate.
+ parent->SetHasDirtyDescendantsForServo();
+ parent = parent->GetFlattenedTreeParentNode();
+ }
+}