michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=79: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Base class for all element classes; this provides an implementation michael@0: * of DOM Core's nsIDOMElement, implements nsIContent, provides michael@0: * utility methods for subclasses, and so forth. michael@0: */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: michael@0: #include "mozilla/dom/FragmentOrElement.h" michael@0: michael@0: #include "mozilla/AsyncEventDispatcher.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/EventListenerManager.h" michael@0: #include "mozilla/EventStates.h" michael@0: #include "mozilla/dom/Attr.h" michael@0: #include "nsDOMAttributeMap.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsINodeInfo.h" michael@0: #include "nsIDocumentInlines.h" michael@0: #include "nsIDocumentEncoder.h" michael@0: #include "nsIDOMNodeList.h" michael@0: #include "nsIContentIterator.h" michael@0: #include "nsFocusManager.h" michael@0: #include "nsILinkHandler.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsIURL.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsIAnonymousContentCreator.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsStyleConsts.h" michael@0: #include "nsString.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsIDOMEvent.h" michael@0: #include "nsDOMCID.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIDOMCSSStyleDeclaration.h" michael@0: #include "nsDOMCSSAttrDeclaration.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsContentList.h" michael@0: #include "nsDOMTokenList.h" michael@0: #include "nsXBLPrototypeBinding.h" michael@0: #include "nsError.h" michael@0: #include "nsDOMString.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIDOMMutationEvent.h" michael@0: #include "mozilla/InternalMutationEvent.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "nsNodeUtils.h" michael@0: #include "nsDocument.h" michael@0: #include "nsAttrValueOrString.h" michael@0: #ifdef MOZ_XUL michael@0: #include "nsXULElement.h" michael@0: #endif /* MOZ_XUL */ michael@0: #include "nsFrameManager.h" michael@0: #include "nsFrameSelection.h" michael@0: #ifdef DEBUG michael@0: #include "nsRange.h" michael@0: #endif michael@0: michael@0: #include "nsBindingManager.h" michael@0: #include "nsXBLBinding.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsPIBoxObject.h" michael@0: #include "nsSVGUtils.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsTextFragment.h" michael@0: #include "nsContentCID.h" michael@0: michael@0: #include "nsIDOMEventListener.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsIBaseWindow.h" michael@0: #include "nsIWidget.h" michael@0: michael@0: #include "js/GCAPI.h" michael@0: michael@0: #include "nsNodeInfoManager.h" michael@0: #include "nsICategoryManager.h" michael@0: #include "nsIDOMUserDataHandler.h" michael@0: #include "nsGenericHTMLElement.h" michael@0: #include "nsIEditor.h" michael@0: #include "nsIEditorIMESupport.h" michael@0: #include "nsContentCreatorFunctions.h" michael@0: #include "nsIControllers.h" michael@0: #include "nsView.h" michael@0: #include "nsViewManager.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "ChildIterator.h" michael@0: #include "mozilla/css/StyleRule.h" /* For nsCSSSelectorList */ michael@0: #include "nsRuleProcessorData.h" michael@0: #include "nsTextNode.h" michael@0: #include "mozilla/dom/NodeListBinding.h" michael@0: #include "mozilla/dom/UndoManager.h" michael@0: michael@0: #ifdef MOZ_XUL michael@0: #include "nsIXULDocument.h" michael@0: #endif /* MOZ_XUL */ michael@0: michael@0: #include "nsCCUncollectableMarker.h" michael@0: michael@0: #include "mozAutoDocUpdate.h" michael@0: michael@0: #include "prprf.h" michael@0: #include "nsDOMMutationObserver.h" michael@0: #include "nsWrapperCacheInlines.h" michael@0: #include "nsCycleCollector.h" michael@0: #include "xpcpublic.h" michael@0: #include "nsIScriptError.h" michael@0: #include "mozilla/Telemetry.h" michael@0: michael@0: #include "mozilla/CORSMode.h" michael@0: michael@0: #include "mozilla/dom/ShadowRoot.h" michael@0: #include "mozilla/dom/HTMLTemplateElement.h" michael@0: michael@0: #include "nsStyledElement.h" michael@0: #include "nsIContentInlines.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: int32_t nsIContent::sTabFocusModel = eTabFocus_any; michael@0: bool nsIContent::sTabFocusModelAppliesToXUL = false; michael@0: uint32_t nsMutationGuard::sMutationCount = 0; michael@0: michael@0: nsIContent* michael@0: nsIContent::FindFirstNonChromeOnlyAccessContent() const michael@0: { michael@0: // This handles also nested native anonymous content. michael@0: for (const nsIContent *content = this; content; michael@0: content = content->GetBindingParent()) { michael@0: if (!content->ChromeOnlyAccess()) { michael@0: // Oops, this function signature allows casting const to michael@0: // non-const. (Then again, so does GetChildAt(0)->GetParent().) michael@0: return const_cast(content); michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsIContent::GetFlattenedTreeParent() const michael@0: { michael@0: if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) { michael@0: nsIContent* parent = GetXBLInsertionParent(); michael@0: if (parent) { michael@0: return parent; michael@0: } michael@0: } michael@0: michael@0: return GetParent(); michael@0: } michael@0: michael@0: nsIContent::IMEState michael@0: nsIContent::GetDesiredIMEState() michael@0: { michael@0: if (!IsEditableInternal()) { michael@0: // Check for the special case where we're dealing with elements which don't michael@0: // have the editable flag set, but are readwrite (such as text controls). michael@0: if (!IsElement() || michael@0: !AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) { michael@0: return IMEState(IMEState::DISABLED); michael@0: } michael@0: } michael@0: // NOTE: The content for independent editors (e.g., input[type=text], michael@0: // textarea) must override this method, so, we don't need to worry about michael@0: // that here. michael@0: nsIContent *editableAncestor = GetEditingHost(); michael@0: michael@0: // This is in another editable content, use the result of it. michael@0: if (editableAncestor && editableAncestor != this) { michael@0: return editableAncestor->GetDesiredIMEState(); michael@0: } michael@0: nsIDocument* doc = GetCurrentDoc(); michael@0: if (!doc) { michael@0: return IMEState(IMEState::DISABLED); michael@0: } michael@0: nsIPresShell* ps = doc->GetShell(); michael@0: if (!ps) { michael@0: return IMEState(IMEState::DISABLED); michael@0: } michael@0: nsPresContext* pc = ps->GetPresContext(); michael@0: if (!pc) { michael@0: return IMEState(IMEState::DISABLED); michael@0: } michael@0: nsIEditor* editor = nsContentUtils::GetHTMLEditor(pc); michael@0: nsCOMPtr imeEditor = do_QueryInterface(editor); michael@0: if (!imeEditor) { michael@0: return IMEState(IMEState::DISABLED); michael@0: } michael@0: IMEState state; michael@0: imeEditor->GetPreferredIMEState(&state); michael@0: return state; michael@0: } michael@0: michael@0: bool michael@0: nsIContent::HasIndependentSelection() michael@0: { michael@0: nsIFrame* frame = GetPrimaryFrame(); michael@0: return (frame && frame->GetStateBits() & NS_FRAME_INDEPENDENT_SELECTION); michael@0: } michael@0: michael@0: dom::Element* michael@0: nsIContent::GetEditingHost() michael@0: { michael@0: // If this isn't editable, return nullptr. michael@0: NS_ENSURE_TRUE(IsEditableInternal(), nullptr); michael@0: michael@0: nsIDocument* doc = GetCurrentDoc(); michael@0: NS_ENSURE_TRUE(doc, nullptr); michael@0: // If this is in designMode, we should return michael@0: if (doc->HasFlag(NODE_IS_EDITABLE)) { michael@0: return doc->GetBodyElement(); michael@0: } michael@0: michael@0: nsIContent* content = this; michael@0: for (dom::Element* parent = GetParentElement(); michael@0: parent && parent->HasFlag(NODE_IS_EDITABLE); michael@0: parent = content->GetParentElement()) { michael@0: content = parent; michael@0: } michael@0: return content->AsElement(); michael@0: } michael@0: michael@0: nsresult michael@0: nsIContent::LookupNamespaceURIInternal(const nsAString& aNamespacePrefix, michael@0: nsAString& aNamespaceURI) const michael@0: { michael@0: if (aNamespacePrefix.EqualsLiteral("xml")) { michael@0: // Special-case for xml prefix michael@0: aNamespaceURI.AssignLiteral("http://www.w3.org/XML/1998/namespace"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aNamespacePrefix.EqualsLiteral("xmlns")) { michael@0: // Special-case for xmlns prefix michael@0: aNamespaceURI.AssignLiteral("http://www.w3.org/2000/xmlns/"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr name; michael@0: if (!aNamespacePrefix.IsEmpty()) { michael@0: name = do_GetAtom(aNamespacePrefix); michael@0: NS_ENSURE_TRUE(name, NS_ERROR_OUT_OF_MEMORY); michael@0: } michael@0: else { michael@0: name = nsGkAtoms::xmlns; michael@0: } michael@0: // Trace up the content parent chain looking for the namespace michael@0: // declaration that declares aNamespacePrefix. michael@0: const nsIContent* content = this; michael@0: do { michael@0: if (content->GetAttr(kNameSpaceID_XMLNS, name, aNamespaceURI)) michael@0: return NS_OK; michael@0: } while ((content = content->GetParent())); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsIContent::GetBaseURI(bool aTryUseXHRDocBaseURI) const michael@0: { michael@0: nsIDocument* doc = OwnerDoc(); michael@0: // Start with document base michael@0: nsCOMPtr base = doc->GetBaseURI(aTryUseXHRDocBaseURI); michael@0: michael@0: // Collect array of xml:base attribute values up the parent chain. This michael@0: // is slightly slower for the case when there are xml:base attributes, but michael@0: // faster for the far more common case of there not being any such michael@0: // attributes. michael@0: // Also check for SVG elements which require special handling michael@0: nsAutoTArray baseAttrs; michael@0: nsString attr; michael@0: const nsIContent *elem = this; michael@0: do { michael@0: // First check for SVG specialness (why is this SVG specific?) michael@0: if (elem->IsSVG()) { michael@0: nsIContent* bindingParent = elem->GetBindingParent(); michael@0: if (bindingParent) { michael@0: nsXBLBinding* binding = bindingParent->GetXBLBinding(); michael@0: if (binding) { michael@0: // XXX sXBL/XBL2 issue michael@0: // If this is an anonymous XBL element use the binding michael@0: // document for the base URI. michael@0: // XXX Will fail with xml:base michael@0: base = binding->PrototypeBinding()->DocURI(); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIURI* explicitBaseURI = elem->GetExplicitBaseURI(); michael@0: if (explicitBaseURI) { michael@0: base = explicitBaseURI; michael@0: break; michael@0: } michael@0: michael@0: // Otherwise check for xml:base attribute michael@0: elem->GetAttr(kNameSpaceID_XML, nsGkAtoms::base, attr); michael@0: if (!attr.IsEmpty()) { michael@0: baseAttrs.AppendElement(attr); michael@0: } michael@0: elem = elem->GetParent(); michael@0: } while(elem); michael@0: michael@0: // Now resolve against all xml:base attrs michael@0: for (uint32_t i = baseAttrs.Length() - 1; i != uint32_t(-1); --i) { michael@0: nsCOMPtr newBase; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(newBase), baseAttrs[i], michael@0: doc->GetDocumentCharacterSet().get(), base); michael@0: // Do a security check, almost the same as nsDocument::SetBaseURL() michael@0: // Only need to do this on the final uri michael@0: if (NS_SUCCEEDED(rv) && i == 0) { michael@0: rv = nsContentUtils::GetSecurityManager()-> michael@0: CheckLoadURIWithPrincipal(NodePrincipal(), newBase, michael@0: nsIScriptSecurityManager::STANDARD); michael@0: } michael@0: if (NS_SUCCEEDED(rv)) { michael@0: base.swap(newBase); michael@0: } michael@0: } michael@0: michael@0: return base.forget(); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: static inline JSObject* michael@0: GetJSObjectChild(nsWrapperCache* aCache) michael@0: { michael@0: return aCache->PreservingWrapper() ? aCache->GetWrapperPreserveColor() : nullptr; michael@0: } michael@0: michael@0: static bool michael@0: NeedsScriptTraverse(nsINode* aNode) michael@0: { michael@0: return aNode->PreservingWrapper() && aNode->GetWrapperPreserveColor() && michael@0: !aNode->IsBlackAndDoesNotNeedTracing(aNode); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsChildContentList) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsChildContentList) michael@0: michael@0: // If nsChildContentList is changed so that any additional fields are michael@0: // traversed by the cycle collector, then CAN_SKIP must be updated to michael@0: // check that the additional fields are null. michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsChildContentList) michael@0: michael@0: // nsChildContentList only ever has a single child, its wrapper, so if michael@0: // the wrapper is black, the list can't be part of a garbage cycle. michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsChildContentList) michael@0: return tmp->IsBlack(); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsChildContentList) michael@0: return tmp->IsBlackAndDoesNotNeedTracing(tmp); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END michael@0: michael@0: // CanSkipThis returns false to avoid problems with incomplete unlinking. michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsChildContentList) michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END michael@0: michael@0: NS_INTERFACE_TABLE_HEAD(nsChildContentList) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_TABLE(nsChildContentList, nsINodeList, nsIDOMNodeList) michael@0: NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsChildContentList) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: JSObject* michael@0: nsChildContentList::WrapObject(JSContext *cx) michael@0: { michael@0: return NodeListBinding::Wrap(cx, this); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsChildContentList::GetLength(uint32_t* aLength) michael@0: { michael@0: *aLength = mNode ? mNode->GetChildCount() : 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsChildContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn) michael@0: { michael@0: nsINode* node = Item(aIndex); michael@0: if (!node) { michael@0: *aReturn = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: return CallQueryInterface(node, aReturn); michael@0: } michael@0: michael@0: nsIContent* michael@0: nsChildContentList::Item(uint32_t aIndex) michael@0: { michael@0: if (mNode) { michael@0: return mNode->GetChildAt(aIndex); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: int32_t michael@0: nsChildContentList::IndexOf(nsIContent* aContent) michael@0: { michael@0: if (mNode) { michael@0: return mNode->IndexOf(aContent); michael@0: } michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(nsNode3Tearoff, mNode) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNode3Tearoff) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMXPathNSResolver) michael@0: NS_INTERFACE_MAP_END_AGGREGATED(mNode) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNode3Tearoff) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNode3Tearoff) michael@0: michael@0: NS_IMETHODIMP michael@0: nsNode3Tearoff::LookupNamespaceURI(const nsAString& aNamespacePrefix, michael@0: nsAString& aNamespaceURI) michael@0: { michael@0: mNode->LookupNamespaceURI(aNamespacePrefix, aNamespaceURI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIHTMLCollection* michael@0: FragmentOrElement::Children() michael@0: { michael@0: FragmentOrElement::nsDOMSlots *slots = DOMSlots(); michael@0: michael@0: if (!slots->mChildrenList) { michael@0: slots->mChildrenList = new nsContentList(this, kNameSpaceID_Wildcard, michael@0: nsGkAtoms::_asterix, nsGkAtoms::_asterix, michael@0: false); michael@0: } michael@0: michael@0: return slots->mChildrenList; michael@0: } michael@0: michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: michael@0: NS_IMPL_ISUPPORTS(nsNodeWeakReference, michael@0: nsIWeakReference) michael@0: michael@0: nsNodeWeakReference::~nsNodeWeakReference() michael@0: { michael@0: if (mNode) { michael@0: NS_ASSERTION(mNode->Slots()->mWeakReference == this, michael@0: "Weak reference has wrong value"); michael@0: mNode->Slots()->mWeakReference = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNodeWeakReference::QueryReferent(const nsIID& aIID, void** aInstancePtr) michael@0: { michael@0: return mNode ? mNode->QueryInterface(aIID, aInstancePtr) : michael@0: NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(nsNodeSupportsWeakRefTearoff, mNode) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNodeSupportsWeakRefTearoff) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_END_AGGREGATED(mNode) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNodeSupportsWeakRefTearoff) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNodeSupportsWeakRefTearoff) michael@0: michael@0: NS_IMETHODIMP michael@0: nsNodeSupportsWeakRefTearoff::GetWeakReference(nsIWeakReference** aInstancePtr) michael@0: { michael@0: nsINode::nsSlots* slots = mNode->Slots(); michael@0: if (!slots->mWeakReference) { michael@0: slots->mWeakReference = new nsNodeWeakReference(mNode); michael@0: NS_ENSURE_TRUE(slots->mWeakReference, NS_ERROR_OUT_OF_MEMORY); michael@0: } michael@0: michael@0: NS_ADDREF(*aInstancePtr = slots->mWeakReference); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: FragmentOrElement::nsDOMSlots::nsDOMSlots() michael@0: : nsINode::nsSlots(), michael@0: mDataset(nullptr), michael@0: mUndoManager(nullptr), michael@0: mBindingParent(nullptr) michael@0: { michael@0: } michael@0: michael@0: FragmentOrElement::nsDOMSlots::~nsDOMSlots() michael@0: { michael@0: if (mAttributeMap) { michael@0: mAttributeMap->DropReference(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::nsDOMSlots::Traverse(nsCycleCollectionTraversalCallback &cb, bool aIsXUL) michael@0: { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mStyle"); michael@0: cb.NoteXPCOMChild(mStyle.get()); michael@0: michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mSMILOverrideStyle"); michael@0: cb.NoteXPCOMChild(mSMILOverrideStyle.get()); michael@0: michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mAttributeMap"); michael@0: cb.NoteXPCOMChild(mAttributeMap.get()); michael@0: michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mUndoManager"); michael@0: cb.NoteXPCOMChild(mUndoManager.get()); michael@0: michael@0: if (aIsXUL) { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mControllers"); michael@0: cb.NoteXPCOMChild(mControllers); michael@0: } michael@0: michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mXBLBinding"); michael@0: cb.NoteNativeChild(mXBLBinding, NS_CYCLE_COLLECTION_PARTICIPANT(nsXBLBinding)); michael@0: michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mXBLInsertionParent"); michael@0: cb.NoteXPCOMChild(mXBLInsertionParent.get()); michael@0: michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mShadowRoot"); michael@0: cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mShadowRoot)); michael@0: michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mContainingShadow"); michael@0: cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIContent*, mContainingShadow)); michael@0: michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mChildrenList"); michael@0: cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMNodeList*, mChildrenList)); michael@0: michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mClassList"); michael@0: cb.NoteXPCOMChild(mClassList.get()); michael@0: michael@0: if (mCustomElementData) { michael@0: for (uint32_t i = 0; i < mCustomElementData->mCallbackQueue.Length(); i++) { michael@0: mCustomElementData->mCallbackQueue[i]->Traverse(cb); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::nsDOMSlots::Unlink(bool aIsXUL) michael@0: { michael@0: mStyle = nullptr; michael@0: mSMILOverrideStyle = nullptr; michael@0: if (mAttributeMap) { michael@0: mAttributeMap->DropReference(); michael@0: mAttributeMap = nullptr; michael@0: } michael@0: if (aIsXUL) michael@0: NS_IF_RELEASE(mControllers); michael@0: mXBLBinding = nullptr; michael@0: mXBLInsertionParent = nullptr; michael@0: mShadowRoot = nullptr; michael@0: mContainingShadow = nullptr; michael@0: mChildrenList = nullptr; michael@0: mUndoManager = nullptr; michael@0: mCustomElementData = nullptr; michael@0: mClassList = nullptr; michael@0: } michael@0: michael@0: size_t michael@0: FragmentOrElement::nsDOMSlots::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t n = aMallocSizeOf(this); michael@0: michael@0: if (mAttributeMap) { michael@0: n += mAttributeMap->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: // Measurement of the following members may be added later if DMD finds it is michael@0: // worthwhile: michael@0: // - Superclass members (nsINode::nsSlots) michael@0: // - mStyle michael@0: // - mDataSet michael@0: // - mSMILOverrideStyle michael@0: // - mSMILOverrideStyleRule michael@0: // - mChildrenList michael@0: // - mClassList michael@0: michael@0: // The following members are not measured: michael@0: // - mBindingParent / mControllers: because they're non-owning michael@0: return n; michael@0: } michael@0: michael@0: FragmentOrElement::FragmentOrElement(already_AddRefed& aNodeInfo) michael@0: : nsIContent(aNodeInfo) michael@0: { michael@0: } michael@0: michael@0: FragmentOrElement::FragmentOrElement(already_AddRefed&& aNodeInfo) michael@0: : nsIContent(aNodeInfo) michael@0: { michael@0: } michael@0: michael@0: FragmentOrElement::~FragmentOrElement() michael@0: { michael@0: NS_PRECONDITION(!IsInDoc(), michael@0: "Please remove this from the document properly"); michael@0: if (GetParent()) { michael@0: NS_RELEASE(mParent); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: FragmentOrElement::GetChildren(uint32_t aFilter) michael@0: { michael@0: nsRefPtr list = new nsSimpleContentList(this); michael@0: if (!list) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIFrame *frame = GetPrimaryFrame(); michael@0: michael@0: // Append :before generated content. michael@0: if (frame) { michael@0: nsIFrame *beforeFrame = nsLayoutUtils::GetBeforeFrame(frame); michael@0: if (beforeFrame) { michael@0: list->AppendElement(beforeFrame->GetContent()); michael@0: } michael@0: } michael@0: michael@0: // If XBL is bound to this node then append XBL anonymous content including michael@0: // explict content altered by insertion point if we were requested for XBL michael@0: // anonymous content, otherwise append explicit content with respect to michael@0: // insertion point if any. michael@0: if (!(aFilter & eAllButXBL)) { michael@0: FlattenedChildIterator iter(this); michael@0: for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { michael@0: list->AppendElement(child); michael@0: } michael@0: } else { michael@0: ExplicitChildIterator iter(this); michael@0: for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { michael@0: list->AppendElement(child); michael@0: } michael@0: } michael@0: michael@0: if (frame) { michael@0: // Append native anonymous content to the end. michael@0: nsIAnonymousContentCreator* creator = do_QueryFrame(frame); michael@0: if (creator) { michael@0: creator->AppendAnonymousContentTo(*list, aFilter); michael@0: } michael@0: michael@0: // Append :after generated content. michael@0: nsIFrame *afterFrame = nsLayoutUtils::GetAfterFrame(frame); michael@0: if (afterFrame) { michael@0: list->AppendElement(afterFrame->GetContent()); michael@0: } michael@0: } michael@0: michael@0: return list.forget(); michael@0: } michael@0: michael@0: static nsIContent* michael@0: FindChromeAccessOnlySubtreeOwner(nsIContent* aContent) michael@0: { michael@0: if (aContent->ChromeOnlyAccess()) { michael@0: bool chromeAccessOnly = false; michael@0: while (aContent && !chromeAccessOnly) { michael@0: chromeAccessOnly = aContent->IsRootOfChromeAccessOnlySubtree(); michael@0: aContent = aContent->GetParent(); michael@0: } michael@0: } michael@0: return aContent; michael@0: } michael@0: michael@0: nsresult michael@0: nsIContent::PreHandleEvent(EventChainPreVisitor& aVisitor) michael@0: { michael@0: //FIXME! Document how this event retargeting works, Bug 329124. michael@0: aVisitor.mCanHandle = true; michael@0: aVisitor.mMayHaveListenerManager = HasListenerManager(); michael@0: michael@0: // Don't propagate mouseover and mouseout events when mouse is moving michael@0: // inside chrome access only content. michael@0: bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree(); michael@0: if ((aVisitor.mEvent->message == NS_MOUSE_ENTER_SYNTH || michael@0: aVisitor.mEvent->message == NS_MOUSE_EXIT_SYNTH || michael@0: aVisitor.mEvent->message == NS_POINTER_OVER || michael@0: aVisitor.mEvent->message == NS_POINTER_OUT) && michael@0: // Check if we should stop event propagation when event has just been michael@0: // dispatched or when we're about to propagate from michael@0: // chrome access only subtree. michael@0: ((this == aVisitor.mEvent->originalTarget && michael@0: !ChromeOnlyAccess()) || isAnonForEvents)) { michael@0: nsCOMPtr relatedTarget = michael@0: do_QueryInterface(aVisitor.mEvent->AsMouseEvent()->relatedTarget); michael@0: if (relatedTarget && michael@0: relatedTarget->OwnerDoc() == OwnerDoc()) { michael@0: michael@0: // If current target is anonymous for events or we know that related michael@0: // target is descendant of an element which is anonymous for events, michael@0: // we may want to stop event propagation. michael@0: // If this is the original target, aVisitor.mRelatedTargetIsInAnon michael@0: // must be updated. michael@0: if (isAnonForEvents || aVisitor.mRelatedTargetIsInAnon || michael@0: (aVisitor.mEvent->originalTarget == this && michael@0: (aVisitor.mRelatedTargetIsInAnon = michael@0: relatedTarget->ChromeOnlyAccess()))) { michael@0: nsIContent* anonOwner = FindChromeAccessOnlySubtreeOwner(this); michael@0: if (anonOwner) { michael@0: nsIContent* anonOwnerRelated = michael@0: FindChromeAccessOnlySubtreeOwner(relatedTarget); michael@0: if (anonOwnerRelated) { michael@0: // Note, anonOwnerRelated may still be inside some other michael@0: // native anonymous subtree. The case where anonOwner is still michael@0: // inside native anonymous subtree will be handled when event michael@0: // propagates up in the DOM tree. michael@0: while (anonOwner != anonOwnerRelated && michael@0: anonOwnerRelated->ChromeOnlyAccess()) { michael@0: anonOwnerRelated = FindChromeAccessOnlySubtreeOwner(anonOwnerRelated); michael@0: } michael@0: if (anonOwner == anonOwnerRelated) { michael@0: #ifdef DEBUG_smaug michael@0: nsCOMPtr originalTarget = michael@0: do_QueryInterface(aVisitor.mEvent->originalTarget); michael@0: nsAutoString ot, ct, rt; michael@0: if (originalTarget) { michael@0: originalTarget->Tag()->ToString(ot); michael@0: } michael@0: Tag()->ToString(ct); michael@0: relatedTarget->Tag()->ToString(rt); michael@0: printf("Stopping %s propagation:" michael@0: "\n\toriginalTarget=%s \n\tcurrentTarget=%s %s" michael@0: "\n\trelatedTarget=%s %s \n%s", michael@0: (aVisitor.mEvent->message == NS_MOUSE_ENTER_SYNTH) michael@0: ? "mouseover" : "mouseout", michael@0: NS_ConvertUTF16toUTF8(ot).get(), michael@0: NS_ConvertUTF16toUTF8(ct).get(), michael@0: isAnonForEvents michael@0: ? "(is native anonymous)" michael@0: : (ChromeOnlyAccess() michael@0: ? "(is in native anonymous subtree)" : ""), michael@0: NS_ConvertUTF16toUTF8(rt).get(), michael@0: relatedTarget->ChromeOnlyAccess() michael@0: ? "(is in native anonymous subtree)" : "", michael@0: (originalTarget && michael@0: relatedTarget->FindFirstNonChromeOnlyAccessContent() == michael@0: originalTarget->FindFirstNonChromeOnlyAccessContent()) michael@0: ? "" : "Wrong event propagation!?!\n"); michael@0: #endif michael@0: aVisitor.mParentTarget = nullptr; michael@0: // Event should not propagate to non-anon content. michael@0: aVisitor.mCanHandle = isAnonForEvents; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIContent* parent = GetParent(); michael@0: // Event may need to be retargeted if this is the root of a native michael@0: // anonymous content subtree or event is dispatched somewhere inside XBL. michael@0: if (isAnonForEvents) { michael@0: #ifdef DEBUG michael@0: // If a DOM event is explicitly dispatched using node.dispatchEvent(), then michael@0: // all the events are allowed even in the native anonymous content.. michael@0: nsCOMPtr t = do_QueryInterface(aVisitor.mEvent->originalTarget); michael@0: NS_ASSERTION(!t || !t->ChromeOnlyAccess() || michael@0: aVisitor.mEvent->eventStructType != NS_MUTATION_EVENT || michael@0: aVisitor.mDOMEvent, michael@0: "Mutation event dispatched in native anonymous content!?!"); michael@0: #endif michael@0: aVisitor.mEventTargetAtParent = parent; michael@0: } else if (parent && aVisitor.mOriginalTargetIsInAnon) { michael@0: nsCOMPtr content(do_QueryInterface(aVisitor.mEvent->target)); michael@0: if (content && content->GetBindingParent() == parent) { michael@0: aVisitor.mEventTargetAtParent = parent; michael@0: } michael@0: } michael@0: michael@0: // check for an anonymous parent michael@0: // XXX XBL2/sXBL issue michael@0: if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) { michael@0: nsIContent* insertionParent = GetXBLInsertionParent(); michael@0: NS_ASSERTION(!(aVisitor.mEventTargetAtParent && insertionParent && michael@0: aVisitor.mEventTargetAtParent != insertionParent), michael@0: "Retargeting and having insertion parent!"); michael@0: if (insertionParent) { michael@0: parent = insertionParent; michael@0: } michael@0: } michael@0: michael@0: if (parent) { michael@0: aVisitor.mParentTarget = parent; michael@0: } else { michael@0: aVisitor.mParentTarget = GetCurrentDoc(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsIContent::GetAttr(int32_t aNameSpaceID, nsIAtom* aName, michael@0: nsAString& aResult) const michael@0: { michael@0: if (IsElement()) { michael@0: return AsElement()->GetAttr(aNameSpaceID, aName, aResult); michael@0: } michael@0: aResult.Truncate(); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsIContent::HasAttr(int32_t aNameSpaceID, nsIAtom* aName) const michael@0: { michael@0: return IsElement() && AsElement()->HasAttr(aNameSpaceID, aName); michael@0: } michael@0: michael@0: bool michael@0: nsIContent::AttrValueIs(int32_t aNameSpaceID, michael@0: nsIAtom* aName, michael@0: const nsAString& aValue, michael@0: nsCaseTreatment aCaseSensitive) const michael@0: { michael@0: return IsElement() && michael@0: AsElement()->AttrValueIs(aNameSpaceID, aName, aValue, aCaseSensitive); michael@0: } michael@0: michael@0: bool michael@0: nsIContent::AttrValueIs(int32_t aNameSpaceID, michael@0: nsIAtom* aName, michael@0: nsIAtom* aValue, michael@0: nsCaseTreatment aCaseSensitive) const michael@0: { michael@0: return IsElement() && michael@0: AsElement()->AttrValueIs(aNameSpaceID, aName, aValue, aCaseSensitive); michael@0: } michael@0: michael@0: bool michael@0: nsIContent::IsFocusable(int32_t* aTabIndex, bool aWithMouse) michael@0: { michael@0: bool focusable = IsFocusableInternal(aTabIndex, aWithMouse); michael@0: // Ensure that the return value and aTabIndex are consistent in the case michael@0: // we're in userfocusignored context. michael@0: if (focusable || (aTabIndex && *aTabIndex != -1)) { michael@0: if (nsContentUtils::IsUserFocusIgnored(this)) { michael@0: if (aTabIndex) { michael@0: *aTabIndex = -1; michael@0: } michael@0: return false; michael@0: } michael@0: return focusable; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsIContent::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) michael@0: { michael@0: if (aTabIndex) { michael@0: *aTabIndex = -1; // Default, not tabbable michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: const nsAttrValue* michael@0: FragmentOrElement::DoGetClasses() const michael@0: { michael@0: NS_NOTREACHED("Shouldn't ever be called"); michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: FragmentOrElement::WalkContentStyleRules(nsRuleWalker* aRuleWalker) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: FragmentOrElement::IsLink(nsIURI** aURI) const michael@0: { michael@0: *aURI = nullptr; michael@0: return false; michael@0: } michael@0: michael@0: nsIContent* michael@0: FragmentOrElement::GetBindingParent() const michael@0: { michael@0: nsDOMSlots *slots = GetExistingDOMSlots(); michael@0: michael@0: if (slots) { michael@0: return slots->mBindingParent; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: nsXBLBinding* michael@0: FragmentOrElement::GetXBLBinding() const michael@0: { michael@0: if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) { michael@0: nsDOMSlots *slots = GetExistingDOMSlots(); michael@0: if (slots) { michael@0: return slots->mXBLBinding; michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::SetXBLBinding(nsXBLBinding* aBinding, michael@0: nsBindingManager* aOldBindingManager) michael@0: { michael@0: nsBindingManager* bindingManager; michael@0: if (aOldBindingManager) { michael@0: MOZ_ASSERT(!aBinding, "aOldBindingManager should only be provided " michael@0: "when removing a binding."); michael@0: bindingManager = aOldBindingManager; michael@0: } else { michael@0: bindingManager = OwnerDoc()->BindingManager(); michael@0: } michael@0: michael@0: // After this point, aBinding will be the most-derived binding for aContent. michael@0: // If we already have a binding for aContent, make sure to michael@0: // remove it from the attached stack. Otherwise we might end up firing its michael@0: // constructor twice (if aBinding inherits from it) or firing its constructor michael@0: // after aContent has been deleted (if aBinding is null and the content node michael@0: // dies before we process mAttachedStack). michael@0: nsRefPtr oldBinding = GetXBLBinding(); michael@0: if (oldBinding) { michael@0: bindingManager->RemoveFromAttachedQueue(oldBinding); michael@0: } michael@0: michael@0: nsDOMSlots *slots = DOMSlots(); michael@0: if (aBinding) { michael@0: SetFlags(NODE_MAY_BE_IN_BINDING_MNGR); michael@0: slots->mXBLBinding = aBinding; michael@0: bindingManager->AddBoundContent(this); michael@0: } else { michael@0: slots->mXBLBinding = nullptr; michael@0: bindingManager->RemoveBoundContent(this); michael@0: if (oldBinding) { michael@0: oldBinding->SetBoundElement(nullptr); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIContent* michael@0: FragmentOrElement::GetXBLInsertionParent() const michael@0: { michael@0: if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) { michael@0: nsDOMSlots *slots = GetExistingDOMSlots(); michael@0: if (slots) { michael@0: return slots->mXBLInsertionParent; michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: ShadowRoot* michael@0: FragmentOrElement::GetShadowRoot() const michael@0: { michael@0: nsDOMSlots *slots = GetExistingDOMSlots(); michael@0: if (slots) { michael@0: return slots->mShadowRoot; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: ShadowRoot* michael@0: FragmentOrElement::GetContainingShadow() const michael@0: { michael@0: nsDOMSlots *slots = GetExistingDOMSlots(); michael@0: if (slots) { michael@0: return slots->mContainingShadow; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::SetShadowRoot(ShadowRoot* aShadowRoot) michael@0: { michael@0: nsDOMSlots *slots = DOMSlots(); michael@0: slots->mShadowRoot = aShadowRoot; michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::SetXBLInsertionParent(nsIContent* aContent) michael@0: { michael@0: nsDOMSlots *slots = DOMSlots(); michael@0: if (aContent) { michael@0: SetFlags(NODE_MAY_BE_IN_BINDING_MNGR); michael@0: } michael@0: slots->mXBLInsertionParent = aContent; michael@0: } michael@0: michael@0: CustomElementData* michael@0: FragmentOrElement::GetCustomElementData() const michael@0: { michael@0: nsDOMSlots *slots = GetExistingDOMSlots(); michael@0: if (slots) { michael@0: return slots->mCustomElementData; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::SetCustomElementData(CustomElementData* aData) michael@0: { michael@0: nsDOMSlots *slots = DOMSlots(); michael@0: MOZ_ASSERT(!slots->mCustomElementData, "Custom element data may not be changed once set."); michael@0: slots->mCustomElementData = aData; michael@0: } michael@0: michael@0: nsresult michael@0: FragmentOrElement::InsertChildAt(nsIContent* aKid, michael@0: uint32_t aIndex, michael@0: bool aNotify) michael@0: { michael@0: NS_PRECONDITION(aKid, "null ptr"); michael@0: michael@0: return doInsertChildAt(aKid, aIndex, aNotify, mAttrsAndChildren); michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::RemoveChildAt(uint32_t aIndex, bool aNotify) michael@0: { michael@0: nsCOMPtr oldKid = mAttrsAndChildren.GetSafeChildAt(aIndex); michael@0: NS_ASSERTION(oldKid == GetChildAt(aIndex), "Unexpected child in RemoveChildAt"); michael@0: michael@0: if (oldKid) { michael@0: doRemoveChildAt(aIndex, aNotify, oldKid, mAttrsAndChildren); michael@0: } michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::GetTextContentInternal(nsAString& aTextContent) michael@0: { michael@0: if(!nsContentUtils::GetNodeTextContent(this, true, aTextContent)) michael@0: NS_RUNTIMEABORT("OOM"); michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::SetTextContentInternal(const nsAString& aTextContent, michael@0: ErrorResult& aError) michael@0: { michael@0: aError = nsContentUtils::SetNodeTextContent(this, aTextContent, false); michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::DestroyContent() michael@0: { michael@0: nsIDocument *document = OwnerDoc(); michael@0: document->BindingManager()->RemovedFromDocument(this, document); michael@0: document->ClearBoxObjectFor(this); michael@0: michael@0: // XXX We really should let cycle collection do this, but that currently still michael@0: // leaks (see https://bugzilla.mozilla.org/show_bug.cgi?id=406684). michael@0: ReleaseWrapper(this); michael@0: michael@0: uint32_t i, count = mAttrsAndChildren.ChildCount(); michael@0: for (i = 0; i < count; ++i) { michael@0: // The child can remove itself from the parent in BindToTree. michael@0: mAttrsAndChildren.ChildAt(i)->DestroyContent(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::SaveSubtreeState() michael@0: { michael@0: uint32_t i, count = mAttrsAndChildren.ChildCount(); michael@0: for (i = 0; i < count; ++i) { michael@0: mAttrsAndChildren.ChildAt(i)->SaveSubtreeState(); michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: // Generic DOMNode implementations michael@0: michael@0: void michael@0: FragmentOrElement::FireNodeInserted(nsIDocument* aDoc, michael@0: nsINode* aParent, michael@0: nsTArray >& aNodes) michael@0: { michael@0: uint32_t count = aNodes.Length(); michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: nsIContent* childContent = aNodes[i]; michael@0: michael@0: if (nsContentUtils::HasMutationListeners(childContent, michael@0: NS_EVENT_BITS_MUTATION_NODEINSERTED, aParent)) { michael@0: InternalMutationEvent mutation(true, NS_MUTATION_NODEINSERTED); michael@0: mutation.mRelatedNode = do_QueryInterface(aParent); michael@0: michael@0: mozAutoSubtreeModified subtree(aDoc, aParent); michael@0: (new AsyncEventDispatcher(childContent, mutation))->RunDOMEventWhenSafe(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: // nsISupports implementation michael@0: michael@0: #define SUBTREE_UNBINDINGS_PER_RUNNABLE 500 michael@0: michael@0: class ContentUnbinder : public nsRunnable michael@0: { michael@0: public: michael@0: ContentUnbinder() michael@0: { michael@0: mLast = this; michael@0: } michael@0: michael@0: ~ContentUnbinder() michael@0: { michael@0: Run(); michael@0: } michael@0: michael@0: void UnbindSubtree(nsIContent* aNode) michael@0: { michael@0: if (aNode->NodeType() != nsIDOMNode::ELEMENT_NODE && michael@0: aNode->NodeType() != nsIDOMNode::DOCUMENT_FRAGMENT_NODE) { michael@0: return; michael@0: } michael@0: FragmentOrElement* container = static_cast(aNode); michael@0: uint32_t childCount = container->mAttrsAndChildren.ChildCount(); michael@0: if (childCount) { michael@0: while (childCount-- > 0) { michael@0: // Hold a strong ref to the node when we remove it, because we may be michael@0: // the last reference to it. We need to call TakeChildAt() and michael@0: // update mFirstChild before calling UnbindFromTree, since this last michael@0: // can notify various observers and they should really see consistent michael@0: // tree state. michael@0: nsCOMPtr child = michael@0: container->mAttrsAndChildren.TakeChildAt(childCount); michael@0: if (childCount == 0) { michael@0: container->mFirstChild = nullptr; michael@0: } michael@0: UnbindSubtree(child); michael@0: child->UnbindFromTree(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: nsAutoScriptBlocker scriptBlocker; michael@0: uint32_t len = mSubtreeRoots.Length(); michael@0: if (len) { michael@0: PRTime start = PR_Now(); michael@0: for (uint32_t i = 0; i < len; ++i) { michael@0: UnbindSubtree(mSubtreeRoots[i]); michael@0: } michael@0: mSubtreeRoots.Clear(); michael@0: Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_CONTENT_UNBIND, michael@0: uint32_t(PR_Now() - start) / PR_USEC_PER_MSEC); michael@0: } michael@0: nsCycleCollector_dispatchDeferredDeletion(); michael@0: if (this == sContentUnbinder) { michael@0: sContentUnbinder = nullptr; michael@0: if (mNext) { michael@0: nsRefPtr next; michael@0: next.swap(mNext); michael@0: sContentUnbinder = next; michael@0: next->mLast = mLast; michael@0: mLast = nullptr; michael@0: NS_DispatchToMainThread(next); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void UnbindAll() michael@0: { michael@0: nsRefPtr ub = sContentUnbinder; michael@0: sContentUnbinder = nullptr; michael@0: while (ub) { michael@0: ub->Run(); michael@0: ub = ub->mNext; michael@0: } michael@0: } michael@0: michael@0: static void Append(nsIContent* aSubtreeRoot) michael@0: { michael@0: if (!sContentUnbinder) { michael@0: sContentUnbinder = new ContentUnbinder(); michael@0: nsCOMPtr e = sContentUnbinder; michael@0: NS_DispatchToMainThread(e); michael@0: } michael@0: michael@0: if (sContentUnbinder->mLast->mSubtreeRoots.Length() >= michael@0: SUBTREE_UNBINDINGS_PER_RUNNABLE) { michael@0: sContentUnbinder->mLast->mNext = new ContentUnbinder(); michael@0: sContentUnbinder->mLast = sContentUnbinder->mLast->mNext; michael@0: } michael@0: sContentUnbinder->mLast->mSubtreeRoots.AppendElement(aSubtreeRoot); michael@0: } michael@0: michael@0: private: michael@0: nsAutoTArray, michael@0: SUBTREE_UNBINDINGS_PER_RUNNABLE> mSubtreeRoots; michael@0: nsRefPtr mNext; michael@0: ContentUnbinder* mLast; michael@0: static ContentUnbinder* sContentUnbinder; michael@0: }; michael@0: michael@0: ContentUnbinder* ContentUnbinder::sContentUnbinder = nullptr; michael@0: michael@0: void michael@0: FragmentOrElement::ClearContentUnbinder() michael@0: { michael@0: ContentUnbinder::UnbindAll(); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(FragmentOrElement) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FragmentOrElement) michael@0: nsINode::Unlink(tmp); michael@0: michael@0: if (tmp->HasProperties()) { michael@0: if (tmp->IsHTML()) { michael@0: nsIAtom*** props = nsGenericHTMLElement::PropertiesToTraverseAndUnlink(); michael@0: for (uint32_t i = 0; props[i]; ++i) { michael@0: tmp->DeleteProperty(*props[i]); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Unlink child content (and unbind our subtree). michael@0: if (tmp->UnoptimizableCCNode() || !nsCCUncollectableMarker::sGeneration) { michael@0: uint32_t childCount = tmp->mAttrsAndChildren.ChildCount(); michael@0: if (childCount) { michael@0: // Don't allow script to run while we're unbinding everything. michael@0: nsAutoScriptBlocker scriptBlocker; michael@0: while (childCount-- > 0) { michael@0: // Hold a strong ref to the node when we remove it, because we may be michael@0: // the last reference to it. We need to call TakeChildAt() and michael@0: // update mFirstChild before calling UnbindFromTree, since this last michael@0: // can notify various observers and they should really see consistent michael@0: // tree state. michael@0: nsCOMPtr child = tmp->mAttrsAndChildren.TakeChildAt(childCount); michael@0: if (childCount == 0) { michael@0: tmp->mFirstChild = nullptr; michael@0: } michael@0: child->UnbindFromTree(); michael@0: } michael@0: } michael@0: } else if (!tmp->GetParent() && tmp->mAttrsAndChildren.ChildCount()) { michael@0: ContentUnbinder::Append(tmp); michael@0: } /* else { michael@0: The subtree root will end up to a ContentUnbinder, and that will michael@0: unbind the child nodes. michael@0: } */ michael@0: michael@0: // Unlink any DOM slots of interest. michael@0: { michael@0: nsDOMSlots *slots = tmp->GetExistingDOMSlots(); michael@0: if (slots) { michael@0: slots->Unlink(tmp->IsXUL()); michael@0: } michael@0: } michael@0: michael@0: { michael@0: nsIDocument *doc; michael@0: if (!tmp->GetParentNode() && (doc = tmp->OwnerDoc())) { michael@0: doc->BindingManager()->RemovedFromDocument(tmp, doc); michael@0: } michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(FragmentOrElement) michael@0: michael@0: void michael@0: FragmentOrElement::MarkUserData(void* aObject, nsIAtom* aKey, void* aChild, michael@0: void* aData) michael@0: { michael@0: uint32_t* gen = static_cast(aData); michael@0: xpc_MarkInCCGeneration(static_cast(aChild), *gen); michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::MarkUserDataHandler(void* aObject, nsIAtom* aKey, michael@0: void* aChild, void* aData) michael@0: { michael@0: xpc_TryUnmarkWrappedGrayObject(static_cast(aChild)); michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::MarkNodeChildren(nsINode* aNode) michael@0: { michael@0: JSObject* o = GetJSObjectChild(aNode); michael@0: if (o) { michael@0: JS::ExposeObjectToActiveJS(o); michael@0: } michael@0: michael@0: EventListenerManager* elm = aNode->GetExistingListenerManager(); michael@0: if (elm) { michael@0: elm->MarkForCC(); michael@0: } michael@0: michael@0: if (aNode->HasProperties()) { michael@0: nsIDocument* ownerDoc = aNode->OwnerDoc(); michael@0: ownerDoc->PropertyTable(DOM_USER_DATA)-> michael@0: Enumerate(aNode, FragmentOrElement::MarkUserData, michael@0: &nsCCUncollectableMarker::sGeneration); michael@0: ownerDoc->PropertyTable(DOM_USER_DATA_HANDLER)-> michael@0: Enumerate(aNode, FragmentOrElement::MarkUserDataHandler, michael@0: &nsCCUncollectableMarker::sGeneration); michael@0: } michael@0: } michael@0: michael@0: nsINode* michael@0: FindOptimizableSubtreeRoot(nsINode* aNode) michael@0: { michael@0: nsINode* p; michael@0: while ((p = aNode->GetParentNode())) { michael@0: if (aNode->UnoptimizableCCNode()) { michael@0: return nullptr; michael@0: } michael@0: aNode = p; michael@0: } michael@0: michael@0: if (aNode->UnoptimizableCCNode()) { michael@0: return nullptr; michael@0: } michael@0: return aNode; michael@0: } michael@0: michael@0: StaticAutoPtr>> gCCBlackMarkedNodes; michael@0: michael@0: static PLDHashOperator michael@0: VisitBlackMarkedNode(nsPtrHashKey* aEntry, void*) michael@0: { michael@0: nsINode* n = aEntry->GetKey(); michael@0: n->SetCCMarkedRoot(false); michael@0: n->SetInCCBlackTree(false); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: static void michael@0: ClearBlackMarkedNodes() michael@0: { michael@0: if (!gCCBlackMarkedNodes) { michael@0: return; michael@0: } michael@0: gCCBlackMarkedNodes->EnumerateEntries(VisitBlackMarkedNode, nullptr); michael@0: gCCBlackMarkedNodes = nullptr; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: FragmentOrElement::RemoveBlackMarkedNode(nsINode* aNode) michael@0: { michael@0: if (!gCCBlackMarkedNodes) { michael@0: return; michael@0: } michael@0: gCCBlackMarkedNodes->RemoveEntry(aNode); michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: FragmentOrElement::CanSkipInCC(nsINode* aNode) michael@0: { michael@0: // Don't try to optimize anything during shutdown. michael@0: if (nsCCUncollectableMarker::sGeneration == 0) { michael@0: return false; michael@0: } michael@0: michael@0: nsIDocument* currentDoc = aNode->GetCurrentDoc(); michael@0: if (currentDoc && michael@0: nsCCUncollectableMarker::InGeneration(currentDoc->GetMarkedCCGeneration())) { michael@0: return !NeedsScriptTraverse(aNode); michael@0: } michael@0: michael@0: // Bail out early if aNode is somewhere in anonymous content, michael@0: // or otherwise unusual. michael@0: if (aNode->UnoptimizableCCNode()) { michael@0: return false; michael@0: } michael@0: michael@0: nsINode* root = michael@0: currentDoc ? static_cast(currentDoc) : michael@0: FindOptimizableSubtreeRoot(aNode); michael@0: if (!root) { michael@0: return false; michael@0: } michael@0: michael@0: // Subtree has been traversed already. michael@0: if (root->CCMarkedRoot()) { michael@0: return root->InCCBlackTree() && !NeedsScriptTraverse(aNode); michael@0: } michael@0: michael@0: if (!gCCBlackMarkedNodes) { michael@0: gCCBlackMarkedNodes = new nsTHashtable >(1020); michael@0: } michael@0: michael@0: // nodesToUnpurple contains nodes which will be removed michael@0: // from the purple buffer if the DOM tree is black. michael@0: nsAutoTArray nodesToUnpurple; michael@0: // grayNodes need script traverse, so they aren't removed from michael@0: // the purple buffer, but are marked to be in black subtree so that michael@0: // traverse is faster. michael@0: nsAutoTArray grayNodes; michael@0: michael@0: bool foundBlack = root->IsBlack(); michael@0: if (root != currentDoc) { michael@0: currentDoc = nullptr; michael@0: if (NeedsScriptTraverse(root)) { michael@0: grayNodes.AppendElement(root); michael@0: } else if (static_cast(root)->IsPurple()) { michael@0: nodesToUnpurple.AppendElement(static_cast(root)); michael@0: } michael@0: } michael@0: michael@0: // Traverse the subtree and check if we could know without CC michael@0: // that it is black. michael@0: // Note, this traverse is non-virtual and inline, so it should be a lot faster michael@0: // than CC's generic traverse. michael@0: for (nsIContent* node = root->GetFirstChild(); node; michael@0: node = node->GetNextNode(root)) { michael@0: foundBlack = foundBlack || node->IsBlack(); michael@0: if (foundBlack && currentDoc) { michael@0: // If we can mark the whole document black, no need to optimize michael@0: // so much, since when the next purple node in the document will be michael@0: // handled, it is fast to check that currentDoc is in CCGeneration. michael@0: break; michael@0: } michael@0: if (NeedsScriptTraverse(node)) { michael@0: // Gray nodes need real CC traverse. michael@0: grayNodes.AppendElement(node); michael@0: } else if (node->IsPurple()) { michael@0: nodesToUnpurple.AppendElement(node); michael@0: } michael@0: } michael@0: michael@0: root->SetCCMarkedRoot(true); michael@0: root->SetInCCBlackTree(foundBlack); michael@0: gCCBlackMarkedNodes->PutEntry(root); michael@0: michael@0: if (!foundBlack) { michael@0: return false; michael@0: } michael@0: michael@0: if (currentDoc) { michael@0: // Special case documents. If we know the document is black, michael@0: // we can mark the document to be in CCGeneration. michael@0: currentDoc-> michael@0: MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration); michael@0: } else { michael@0: for (uint32_t i = 0; i < grayNodes.Length(); ++i) { michael@0: nsINode* node = grayNodes[i]; michael@0: node->SetInCCBlackTree(true); michael@0: gCCBlackMarkedNodes->PutEntry(node); michael@0: } michael@0: } michael@0: michael@0: // Subtree is black, we can remove non-gray purple nodes from michael@0: // purple buffer. michael@0: for (uint32_t i = 0; i < nodesToUnpurple.Length(); ++i) { michael@0: nsIContent* purple = nodesToUnpurple[i]; michael@0: // Can't remove currently handled purple node. michael@0: if (purple != aNode) { michael@0: purple->RemovePurple(); michael@0: } michael@0: } michael@0: return !NeedsScriptTraverse(aNode); michael@0: } michael@0: michael@0: nsAutoTArray* gPurpleRoots = nullptr; michael@0: nsAutoTArray* gNodesToUnbind = nullptr; michael@0: michael@0: void ClearCycleCollectorCleanupData() michael@0: { michael@0: if (gPurpleRoots) { michael@0: uint32_t len = gPurpleRoots->Length(); michael@0: for (uint32_t i = 0; i < len; ++i) { michael@0: nsINode* n = gPurpleRoots->ElementAt(i); michael@0: n->SetIsPurpleRoot(false); michael@0: } michael@0: delete gPurpleRoots; michael@0: gPurpleRoots = nullptr; michael@0: } michael@0: if (gNodesToUnbind) { michael@0: uint32_t len = gNodesToUnbind->Length(); michael@0: for (uint32_t i = 0; i < len; ++i) { michael@0: nsIContent* c = gNodesToUnbind->ElementAt(i); michael@0: c->SetIsPurpleRoot(false); michael@0: ContentUnbinder::Append(c); michael@0: } michael@0: delete gNodesToUnbind; michael@0: gNodesToUnbind = nullptr; michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: ShouldClearPurple(nsIContent* aContent) michael@0: { michael@0: if (aContent && aContent->IsPurple()) { michael@0: return true; michael@0: } michael@0: michael@0: JSObject* o = GetJSObjectChild(aContent); michael@0: if (o && xpc_IsGrayGCThing(o)) { michael@0: return true; michael@0: } michael@0: michael@0: if (aContent->HasListenerManager()) { michael@0: return true; michael@0: } michael@0: michael@0: return aContent->HasProperties(); michael@0: } michael@0: michael@0: // If aNode is not optimizable, but is an element michael@0: // with a frame in a document which has currently active presshell, michael@0: // we can act as if it was optimizable. When the primary frame dies, aNode michael@0: // will end up to the purple buffer because of the refcount change. michael@0: bool michael@0: NodeHasActiveFrame(nsIDocument* aCurrentDoc, nsINode* aNode) michael@0: { michael@0: return aCurrentDoc->GetShell() && aNode->IsElement() && michael@0: aNode->AsElement()->GetPrimaryFrame(); michael@0: } michael@0: michael@0: bool michael@0: OwnedByBindingManager(nsIDocument* aCurrentDoc, nsINode* aNode) michael@0: { michael@0: return aNode->IsElement() && aNode->AsElement()->GetXBLBinding(); michael@0: } michael@0: michael@0: // CanSkip checks if aNode is black, and if it is, returns michael@0: // true. If aNode is in a black DOM tree, CanSkip may also remove other objects michael@0: // from purple buffer and unmark event listeners and user data. michael@0: // If the root of the DOM tree is a document, less optimizations are done michael@0: // since checking the blackness of the current document is usually fast and we michael@0: // don't want slow down such common cases. michael@0: bool michael@0: FragmentOrElement::CanSkip(nsINode* aNode, bool aRemovingAllowed) michael@0: { michael@0: // Don't try to optimize anything during shutdown. michael@0: if (nsCCUncollectableMarker::sGeneration == 0) { michael@0: return false; michael@0: } michael@0: michael@0: bool unoptimizable = aNode->UnoptimizableCCNode(); michael@0: nsIDocument* currentDoc = aNode->GetCurrentDoc(); michael@0: if (currentDoc && michael@0: nsCCUncollectableMarker::InGeneration(currentDoc->GetMarkedCCGeneration()) && michael@0: (!unoptimizable || NodeHasActiveFrame(currentDoc, aNode) || michael@0: OwnedByBindingManager(currentDoc, aNode))) { michael@0: MarkNodeChildren(aNode); michael@0: return true; michael@0: } michael@0: michael@0: if (unoptimizable) { michael@0: return false; michael@0: } michael@0: michael@0: nsINode* root = currentDoc ? static_cast(currentDoc) : michael@0: FindOptimizableSubtreeRoot(aNode); michael@0: if (!root) { michael@0: return false; michael@0: } michael@0: michael@0: // Subtree has been traversed already, and aNode has michael@0: // been handled in a way that doesn't require revisiting it. michael@0: if (root->IsPurpleRoot()) { michael@0: return false; michael@0: } michael@0: michael@0: // nodesToClear contains nodes which are either purple or michael@0: // gray. michael@0: nsAutoTArray nodesToClear; michael@0: michael@0: bool foundBlack = root->IsBlack(); michael@0: bool domOnlyCycle = false; michael@0: if (root != currentDoc) { michael@0: currentDoc = nullptr; michael@0: if (!foundBlack) { michael@0: domOnlyCycle = static_cast(root)->OwnedOnlyByTheDOMTree(); michael@0: } michael@0: if (ShouldClearPurple(static_cast(root))) { michael@0: nodesToClear.AppendElement(static_cast(root)); michael@0: } michael@0: } michael@0: michael@0: // Traverse the subtree and check if we could know without CC michael@0: // that it is black. michael@0: // Note, this traverse is non-virtual and inline, so it should be a lot faster michael@0: // than CC's generic traverse. michael@0: for (nsIContent* node = root->GetFirstChild(); node; michael@0: node = node->GetNextNode(root)) { michael@0: foundBlack = foundBlack || node->IsBlack(); michael@0: if (foundBlack) { michael@0: domOnlyCycle = false; michael@0: if (currentDoc) { michael@0: // If we can mark the whole document black, no need to optimize michael@0: // so much, since when the next purple node in the document will be michael@0: // handled, it is fast to check that the currentDoc is in CCGeneration. michael@0: break; michael@0: } michael@0: // No need to put stuff to the nodesToClear array, if we can clear it michael@0: // already here. michael@0: if (node->IsPurple() && (node != aNode || aRemovingAllowed)) { michael@0: node->RemovePurple(); michael@0: } michael@0: MarkNodeChildren(node); michael@0: } else { michael@0: domOnlyCycle = domOnlyCycle && node->OwnedOnlyByTheDOMTree(); michael@0: if (ShouldClearPurple(node)) { michael@0: // Collect interesting nodes which we can clear if we find that michael@0: // they are kept alive in a black tree or are in a DOM-only cycle. michael@0: nodesToClear.AppendElement(node); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!currentDoc || !foundBlack) { michael@0: root->SetIsPurpleRoot(true); michael@0: if (domOnlyCycle) { michael@0: if (!gNodesToUnbind) { michael@0: gNodesToUnbind = new nsAutoTArray(); michael@0: } michael@0: gNodesToUnbind->AppendElement(static_cast(root)); michael@0: for (uint32_t i = 0; i < nodesToClear.Length(); ++i) { michael@0: nsIContent* n = nodesToClear[i]; michael@0: if ((n != aNode || aRemovingAllowed) && n->IsPurple()) { michael@0: n->RemovePurple(); michael@0: } michael@0: } michael@0: return true; michael@0: } else { michael@0: if (!gPurpleRoots) { michael@0: gPurpleRoots = new nsAutoTArray(); michael@0: } michael@0: gPurpleRoots->AppendElement(root); michael@0: } michael@0: } michael@0: michael@0: if (!foundBlack) { michael@0: return false; michael@0: } michael@0: michael@0: if (currentDoc) { michael@0: // Special case documents. If we know the document is black, michael@0: // we can mark the document to be in CCGeneration. michael@0: currentDoc-> michael@0: MarkUncollectableForCCGeneration(nsCCUncollectableMarker::sGeneration); michael@0: MarkNodeChildren(currentDoc); michael@0: } michael@0: michael@0: // Subtree is black, so we can remove purple nodes from michael@0: // purple buffer and mark stuff that to be certainly alive. michael@0: for (uint32_t i = 0; i < nodesToClear.Length(); ++i) { michael@0: nsIContent* n = nodesToClear[i]; michael@0: MarkNodeChildren(n); michael@0: // Can't remove currently handled purple node, michael@0: // unless aRemovingAllowed is true. michael@0: if ((n != aNode || aRemovingAllowed) && n->IsPurple()) { michael@0: n->RemovePurple(); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: FragmentOrElement::CanSkipThis(nsINode* aNode) michael@0: { michael@0: if (nsCCUncollectableMarker::sGeneration == 0) { michael@0: return false; michael@0: } michael@0: if (aNode->IsBlack()) { michael@0: return true; michael@0: } michael@0: nsIDocument* c = aNode->GetCurrentDoc(); michael@0: return michael@0: ((c && nsCCUncollectableMarker::InGeneration(c->GetMarkedCCGeneration())) || michael@0: aNode->InCCBlackTree()) && !NeedsScriptTraverse(aNode); michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::InitCCCallbacks() michael@0: { michael@0: nsCycleCollector_setForgetSkippableCallback(ClearCycleCollectorCleanupData); michael@0: nsCycleCollector_setBeforeUnlinkCallback(ClearBlackMarkedNodes); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(FragmentOrElement) michael@0: return FragmentOrElement::CanSkip(tmp, aRemovingAllowed); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(FragmentOrElement) michael@0: return FragmentOrElement::CanSkipInCC(tmp); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(FragmentOrElement) michael@0: return FragmentOrElement::CanSkipThis(tmp); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END michael@0: michael@0: static const char* kNSURIs[] = { michael@0: " ([none])", michael@0: " (xmlns)", michael@0: " (xml)", michael@0: " (xhtml)", michael@0: " (XLink)", michael@0: " (XSLT)", michael@0: " (XBL)", michael@0: " (MathML)", michael@0: " (RDF)", michael@0: " (XUL)", michael@0: " (SVG)", michael@0: " (XML Events)" michael@0: }; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(FragmentOrElement) michael@0: if (MOZ_UNLIKELY(cb.WantDebugInfo())) { michael@0: char name[512]; michael@0: uint32_t nsid = tmp->GetNameSpaceID(); michael@0: nsAtomCString localName(tmp->NodeInfo()->NameAtom()); michael@0: nsAutoCString uri; michael@0: if (tmp->OwnerDoc()->GetDocumentURI()) { michael@0: tmp->OwnerDoc()->GetDocumentURI()->GetSpec(uri); michael@0: } michael@0: michael@0: nsAutoString id; michael@0: nsIAtom* idAtom = tmp->GetID(); michael@0: if (idAtom) { michael@0: id.AppendLiteral(" id='"); michael@0: id.Append(nsDependentAtomString(idAtom)); michael@0: id.AppendLiteral("'"); michael@0: } michael@0: michael@0: nsAutoString classes; michael@0: const nsAttrValue* classAttrValue = tmp->GetClasses(); michael@0: if (classAttrValue) { michael@0: classes.AppendLiteral(" class='"); michael@0: nsAutoString classString; michael@0: classAttrValue->ToString(classString); michael@0: classString.ReplaceChar(char16_t('\n'), char16_t(' ')); michael@0: classes.Append(classString); michael@0: classes.AppendLiteral("'"); michael@0: } michael@0: michael@0: nsAutoCString orphan; michael@0: if (!tmp->IsInDoc() && michael@0: // Ignore xbl:content, which is never in the document and hence always michael@0: // appears to be orphaned. michael@0: !tmp->NodeInfo()->Equals(nsGkAtoms::content, kNameSpaceID_XBL)) { michael@0: orphan.AppendLiteral(" (orphan)"); michael@0: } michael@0: michael@0: const char* nsuri = nsid < ArrayLength(kNSURIs) ? kNSURIs[nsid] : ""; michael@0: PR_snprintf(name, sizeof(name), "FragmentOrElement%s %s%s%s%s %s", michael@0: nsuri, michael@0: localName.get(), michael@0: NS_ConvertUTF16toUTF8(id).get(), michael@0: NS_ConvertUTF16toUTF8(classes).get(), michael@0: orphan.get(), michael@0: uri.get()); michael@0: cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); michael@0: } michael@0: else { michael@0: NS_IMPL_CYCLE_COLLECTION_DESCRIBE(FragmentOrElement, tmp->mRefCnt.get()) michael@0: } michael@0: michael@0: // Always need to traverse script objects, so do that before we check michael@0: // if we're uncollectable. michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: michael@0: if (!nsINode::Traverse(tmp, cb)) { michael@0: return NS_SUCCESS_INTERRUPTED_TRAVERSE; michael@0: } michael@0: michael@0: tmp->OwnerDoc()->BindingManager()->Traverse(tmp, cb); michael@0: michael@0: if (tmp->HasProperties()) { michael@0: if (tmp->IsHTML()) { michael@0: nsIAtom*** props = nsGenericHTMLElement::PropertiesToTraverseAndUnlink(); michael@0: for (uint32_t i = 0; props[i]; ++i) { michael@0: nsISupports* property = michael@0: static_cast(tmp->GetProperty(*props[i])); michael@0: cb.NoteXPCOMChild(property); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Traverse attribute names and child content. michael@0: { michael@0: uint32_t i; michael@0: uint32_t attrs = tmp->mAttrsAndChildren.AttrCount(); michael@0: for (i = 0; i < attrs; i++) { michael@0: const nsAttrName* name = tmp->mAttrsAndChildren.AttrNameAt(i); michael@0: if (!name->IsAtom()) { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, michael@0: "mAttrsAndChildren[i]->NodeInfo()"); michael@0: cb.NoteXPCOMChild(name->NodeInfo()); michael@0: } michael@0: } michael@0: michael@0: uint32_t kids = tmp->mAttrsAndChildren.ChildCount(); michael@0: for (i = 0; i < kids; i++) { michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mAttrsAndChildren[i]"); michael@0: cb.NoteXPCOMChild(tmp->mAttrsAndChildren.GetSafeChildAt(i)); michael@0: } michael@0: } michael@0: michael@0: // Traverse any DOM slots of interest. michael@0: { michael@0: nsDOMSlots *slots = tmp->GetExistingDOMSlots(); michael@0: if (slots) { michael@0: slots->Traverse(cb, tmp->IsXUL()); michael@0: } michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(FragmentOrElement) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(FragmentOrElement) michael@0: NS_INTERFACE_MAP_ENTRY(Element) michael@0: NS_INTERFACE_MAP_ENTRY(nsIContent) michael@0: NS_INTERFACE_MAP_ENTRY(nsINode) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget) michael@0: NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget) michael@0: NS_INTERFACE_MAP_ENTRY_TEAROFF(nsISupportsWeakReference, michael@0: new nsNodeSupportsWeakRefTearoff(this)) michael@0: NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIDOMXPathNSResolver, michael@0: new nsNode3Tearoff(this)) michael@0: // DOM bindings depend on the identity pointer being the michael@0: // same as nsINode (which nsIContent inherits). michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(FragmentOrElement) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(FragmentOrElement, michael@0: nsNodeUtils::LastRelease(this)) michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: FragmentOrElement::CopyInnerTo(FragmentOrElement* aDst) michael@0: { michael@0: uint32_t i, count = mAttrsAndChildren.AttrCount(); michael@0: for (i = 0; i < count; ++i) { michael@0: const nsAttrName* name = mAttrsAndChildren.AttrNameAt(i); michael@0: const nsAttrValue* value = mAttrsAndChildren.AttrAt(i); michael@0: nsAutoString valStr; michael@0: value->ToString(valStr); michael@0: nsresult rv = aDst->SetAttr(name->NamespaceID(), name->LocalName(), michael@0: name->GetPrefix(), valStr, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: const nsTextFragment* michael@0: FragmentOrElement::GetText() michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: uint32_t michael@0: FragmentOrElement::TextLength() const michael@0: { michael@0: // We can remove this assertion if it turns out to be useful to be able michael@0: // to depend on this returning 0 michael@0: NS_NOTREACHED("called FragmentOrElement::TextLength"); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: nsresult michael@0: FragmentOrElement::SetText(const char16_t* aBuffer, uint32_t aLength, michael@0: bool aNotify) michael@0: { michael@0: NS_ERROR("called FragmentOrElement::SetText"); michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult michael@0: FragmentOrElement::AppendText(const char16_t* aBuffer, uint32_t aLength, michael@0: bool aNotify) michael@0: { michael@0: NS_ERROR("called FragmentOrElement::AppendText"); michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: bool michael@0: FragmentOrElement::TextIsOnlyWhitespace() michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: FragmentOrElement::HasTextForTranslation() michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::AppendTextTo(nsAString& aResult) michael@0: { michael@0: // We can remove this assertion if it turns out to be useful to be able michael@0: // to depend on this appending nothing. michael@0: NS_NOTREACHED("called FragmentOrElement::TextLength"); michael@0: } michael@0: michael@0: bool michael@0: FragmentOrElement::AppendTextTo(nsAString& aResult, const mozilla::fallible_t&) michael@0: { michael@0: // We can remove this assertion if it turns out to be useful to be able michael@0: // to depend on this appending nothing. michael@0: NS_NOTREACHED("called FragmentOrElement::TextLength"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: uint32_t michael@0: FragmentOrElement::GetChildCount() const michael@0: { michael@0: return mAttrsAndChildren.ChildCount(); michael@0: } michael@0: michael@0: nsIContent * michael@0: FragmentOrElement::GetChildAt(uint32_t aIndex) const michael@0: { michael@0: return mAttrsAndChildren.GetSafeChildAt(aIndex); michael@0: } michael@0: michael@0: nsIContent * const * michael@0: FragmentOrElement::GetChildArray(uint32_t* aChildCount) const michael@0: { michael@0: return mAttrsAndChildren.GetChildArray(aChildCount); michael@0: } michael@0: michael@0: int32_t michael@0: FragmentOrElement::IndexOf(const nsINode* aPossibleChild) const michael@0: { michael@0: return mAttrsAndChildren.IndexOfChild(aPossibleChild); michael@0: } michael@0: michael@0: // Try to keep the size of StringBuilder close to a jemalloc bucket size. michael@0: #define STRING_BUFFER_UNITS 1020 michael@0: michael@0: namespace { michael@0: michael@0: // We put StringBuilder in the anonymous namespace to prevent anything outside michael@0: // this file from accidentally being linked against it. michael@0: michael@0: class StringBuilder michael@0: { michael@0: private: michael@0: class Unit michael@0: { michael@0: public: michael@0: Unit() : mAtom(nullptr), mType(eUnknown), mLength(0) michael@0: { michael@0: MOZ_COUNT_CTOR(StringBuilder::Unit); michael@0: } michael@0: ~Unit() michael@0: { michael@0: if (mType == eString || mType == eStringWithEncode) { michael@0: delete mString; michael@0: } michael@0: MOZ_COUNT_DTOR(StringBuilder::Unit); michael@0: } michael@0: michael@0: enum Type michael@0: { michael@0: eUnknown, michael@0: eAtom, michael@0: eString, michael@0: eStringWithEncode, michael@0: eLiteral, michael@0: eTextFragment, michael@0: eTextFragmentWithEncode, michael@0: }; michael@0: michael@0: union michael@0: { michael@0: nsIAtom* mAtom; michael@0: const char* mLiteral; michael@0: nsAutoString* mString; michael@0: const nsTextFragment* mTextFragment; michael@0: }; michael@0: Type mType; michael@0: uint32_t mLength; michael@0: }; michael@0: public: michael@0: StringBuilder() : mLast(MOZ_THIS_IN_INITIALIZER_LIST()), mLength(0) michael@0: { michael@0: MOZ_COUNT_CTOR(StringBuilder); michael@0: } michael@0: michael@0: ~StringBuilder() michael@0: { michael@0: MOZ_COUNT_DTOR(StringBuilder); michael@0: } michael@0: michael@0: void Append(nsIAtom* aAtom) michael@0: { michael@0: Unit* u = AddUnit(); michael@0: u->mAtom = aAtom; michael@0: u->mType = Unit::eAtom; michael@0: uint32_t len = aAtom->GetLength(); michael@0: u->mLength = len; michael@0: mLength += len; michael@0: } michael@0: michael@0: template michael@0: void Append(const char (&aLiteral)[N]) michael@0: { michael@0: Unit* u = AddUnit(); michael@0: u->mLiteral = aLiteral; michael@0: u->mType = Unit::eLiteral; michael@0: uint32_t len = N - 1; michael@0: u->mLength = len; michael@0: mLength += len; michael@0: } michael@0: michael@0: template michael@0: void Append(char (&aLiteral)[N]) michael@0: { michael@0: Unit* u = AddUnit(); michael@0: u->mLiteral = aLiteral; michael@0: u->mType = Unit::eLiteral; michael@0: uint32_t len = N - 1; michael@0: u->mLength = len; michael@0: mLength += len; michael@0: } michael@0: michael@0: void Append(const nsAString& aString) michael@0: { michael@0: Unit* u = AddUnit(); michael@0: u->mString = new nsAutoString(aString); michael@0: u->mType = Unit::eString; michael@0: uint32_t len = aString.Length(); michael@0: u->mLength = len; michael@0: mLength += len; michael@0: } michael@0: michael@0: void Append(nsAutoString* aString) michael@0: { michael@0: Unit* u = AddUnit(); michael@0: u->mString = aString; michael@0: u->mType = Unit::eString; michael@0: uint32_t len = aString->Length(); michael@0: u->mLength = len; michael@0: mLength += len; michael@0: } michael@0: michael@0: void AppendWithAttrEncode(nsAutoString* aString, uint32_t aLen) michael@0: { michael@0: Unit* u = AddUnit(); michael@0: u->mString = aString; michael@0: u->mType = Unit::eStringWithEncode; michael@0: u->mLength = aLen; michael@0: mLength += aLen; michael@0: } michael@0: michael@0: void Append(const nsTextFragment* aTextFragment) michael@0: { michael@0: Unit* u = AddUnit(); michael@0: u->mTextFragment = aTextFragment; michael@0: u->mType = Unit::eTextFragment; michael@0: uint32_t len = aTextFragment->GetLength(); michael@0: u->mLength = len; michael@0: mLength += len; michael@0: } michael@0: michael@0: void AppendWithEncode(const nsTextFragment* aTextFragment, uint32_t aLen) michael@0: { michael@0: Unit* u = AddUnit(); michael@0: u->mTextFragment = aTextFragment; michael@0: u->mType = Unit::eTextFragmentWithEncode; michael@0: u->mLength = aLen; michael@0: mLength += aLen; michael@0: } michael@0: michael@0: bool ToString(nsAString& aOut) michael@0: { michael@0: if (!aOut.SetCapacity(mLength, fallible_t())) { michael@0: return false; michael@0: } michael@0: michael@0: for (StringBuilder* current = this; current; current = current->mNext) { michael@0: uint32_t len = current->mUnits.Length(); michael@0: for (uint32_t i = 0; i < len; ++i) { michael@0: Unit& u = current->mUnits[i]; michael@0: switch (u.mType) { michael@0: case Unit::eAtom: michael@0: aOut.Append(nsDependentAtomString(u.mAtom)); michael@0: break; michael@0: case Unit::eString: michael@0: aOut.Append(*(u.mString)); michael@0: break; michael@0: case Unit::eStringWithEncode: michael@0: EncodeAttrString(*(u.mString), aOut); michael@0: break; michael@0: case Unit::eLiteral: michael@0: aOut.AppendASCII(u.mLiteral, u.mLength); michael@0: break; michael@0: case Unit::eTextFragment: michael@0: u.mTextFragment->AppendTo(aOut); michael@0: break; michael@0: case Unit::eTextFragmentWithEncode: michael@0: EncodeTextFragment(u.mTextFragment, aOut); michael@0: break; michael@0: default: michael@0: MOZ_CRASH("Unknown unit type?"); michael@0: } michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: private: michael@0: Unit* AddUnit() michael@0: { michael@0: if (mLast->mUnits.Length() == STRING_BUFFER_UNITS) { michael@0: new StringBuilder(this); michael@0: } michael@0: return mLast->mUnits.AppendElement(); michael@0: } michael@0: michael@0: StringBuilder(StringBuilder* aFirst) michael@0: : mLast(nullptr), mLength(0) michael@0: { michael@0: MOZ_COUNT_CTOR(StringBuilder); michael@0: aFirst->mLast->mNext = this; michael@0: aFirst->mLast = this; michael@0: } michael@0: michael@0: void EncodeAttrString(const nsAutoString& aValue, nsAString& aOut) michael@0: { michael@0: const char16_t* c = aValue.BeginReading(); michael@0: const char16_t* end = aValue.EndReading(); michael@0: while (c < end) { michael@0: switch (*c) { michael@0: case '"': michael@0: aOut.AppendLiteral("""); michael@0: break; michael@0: case '&': michael@0: aOut.AppendLiteral("&"); michael@0: break; michael@0: case 0x00A0: michael@0: aOut.AppendLiteral(" "); michael@0: break; michael@0: default: michael@0: aOut.Append(*c); michael@0: break; michael@0: } michael@0: ++c; michael@0: } michael@0: } michael@0: michael@0: void EncodeTextFragment(const nsTextFragment* aValue, nsAString& aOut) michael@0: { michael@0: uint32_t len = aValue->GetLength(); michael@0: if (aValue->Is2b()) { michael@0: const char16_t* data = aValue->Get2b(); michael@0: for (uint32_t i = 0; i < len; ++i) { michael@0: const char16_t c = data[i]; michael@0: switch (c) { michael@0: case '<': michael@0: aOut.AppendLiteral("<"); michael@0: break; michael@0: case '>': michael@0: aOut.AppendLiteral(">"); michael@0: break; michael@0: case '&': michael@0: aOut.AppendLiteral("&"); michael@0: break; michael@0: case 0x00A0: michael@0: aOut.AppendLiteral(" "); michael@0: break; michael@0: default: michael@0: aOut.Append(c); michael@0: break; michael@0: } michael@0: } michael@0: } else { michael@0: const char* data = aValue->Get1b(); michael@0: for (uint32_t i = 0; i < len; ++i) { michael@0: const unsigned char c = data[i]; michael@0: switch (c) { michael@0: case '<': michael@0: aOut.AppendLiteral("<"); michael@0: break; michael@0: case '>': michael@0: aOut.AppendLiteral(">"); michael@0: break; michael@0: case '&': michael@0: aOut.AppendLiteral("&"); michael@0: break; michael@0: case 0x00A0: michael@0: aOut.AppendLiteral(" "); michael@0: break; michael@0: default: michael@0: aOut.Append(c); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsAutoTArray mUnits; michael@0: nsAutoPtr mNext; michael@0: StringBuilder* mLast; michael@0: // mLength is used only in the first StringBuilder object in the linked list. michael@0: uint32_t mLength; michael@0: }; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: static void michael@0: AppendEncodedCharacters(const nsTextFragment* aText, StringBuilder& aBuilder) michael@0: { michael@0: uint32_t extraSpaceNeeded = 0; michael@0: uint32_t len = aText->GetLength(); michael@0: if (aText->Is2b()) { michael@0: const char16_t* data = aText->Get2b(); michael@0: for (uint32_t i = 0; i < len; ++i) { michael@0: const char16_t c = data[i]; michael@0: switch (c) { michael@0: case '<': michael@0: extraSpaceNeeded += ArrayLength("<") - 2; michael@0: break; michael@0: case '>': michael@0: extraSpaceNeeded += ArrayLength(">") - 2; michael@0: break; michael@0: case '&': michael@0: extraSpaceNeeded += ArrayLength("&") - 2; michael@0: break; michael@0: case 0x00A0: michael@0: extraSpaceNeeded += ArrayLength(" ") - 2; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: } else { michael@0: const char* data = aText->Get1b(); michael@0: for (uint32_t i = 0; i < len; ++i) { michael@0: const unsigned char c = data[i]; michael@0: switch (c) { michael@0: case '<': michael@0: extraSpaceNeeded += ArrayLength("<") - 2; michael@0: break; michael@0: case '>': michael@0: extraSpaceNeeded += ArrayLength(">") - 2; michael@0: break; michael@0: case '&': michael@0: extraSpaceNeeded += ArrayLength("&") - 2; michael@0: break; michael@0: case 0x00A0: michael@0: extraSpaceNeeded += ArrayLength(" ") - 2; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (extraSpaceNeeded) { michael@0: aBuilder.AppendWithEncode(aText, len + extraSpaceNeeded); michael@0: } else { michael@0: aBuilder.Append(aText); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: AppendEncodedAttributeValue(nsAutoString* aValue, StringBuilder& aBuilder) michael@0: { michael@0: const char16_t* c = aValue->BeginReading(); michael@0: const char16_t* end = aValue->EndReading(); michael@0: michael@0: uint32_t extraSpaceNeeded = 0; michael@0: while (c < end) { michael@0: switch (*c) { michael@0: case '"': michael@0: extraSpaceNeeded += ArrayLength(""") - 2; michael@0: break; michael@0: case '&': michael@0: extraSpaceNeeded += ArrayLength("&") - 2; michael@0: break; michael@0: case 0x00A0: michael@0: extraSpaceNeeded += ArrayLength(" ") - 2; michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: ++c; michael@0: } michael@0: michael@0: if (extraSpaceNeeded) { michael@0: aBuilder.AppendWithAttrEncode(aValue, aValue->Length() + extraSpaceNeeded); michael@0: } else { michael@0: aBuilder.Append(aValue); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: StartElement(Element* aContent, StringBuilder& aBuilder) michael@0: { michael@0: nsIAtom* localName = aContent->Tag(); michael@0: int32_t tagNS = aContent->GetNameSpaceID(); michael@0: michael@0: aBuilder.Append("<"); michael@0: if (aContent->IsHTML() || aContent->IsSVG() || aContent->IsMathML()) { michael@0: aBuilder.Append(localName); michael@0: } else { michael@0: aBuilder.Append(aContent->NodeName()); michael@0: } michael@0: michael@0: int32_t count = aContent->GetAttrCount(); michael@0: for (int32_t i = count; i > 0;) { michael@0: --i; michael@0: const nsAttrName* name = aContent->GetAttrNameAt(i); michael@0: int32_t attNs = name->NamespaceID(); michael@0: nsIAtom* attName = name->LocalName(); michael@0: michael@0: // Filter out any attribute starting with [-|_]moz michael@0: nsDependentAtomString attrNameStr(attName); michael@0: if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) || michael@0: StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) { michael@0: continue; michael@0: } michael@0: michael@0: nsAutoString* attValue = new nsAutoString(); michael@0: aContent->GetAttr(attNs, attName, *attValue); michael@0: michael@0: // Filter out special case of
used by the editor. michael@0: // Bug 16988. Yuck. michael@0: if (localName == nsGkAtoms::br && tagNS == kNameSpaceID_XHTML && michael@0: attName == nsGkAtoms::type && attNs == kNameSpaceID_None && michael@0: StringBeginsWith(*attValue, NS_LITERAL_STRING("_moz"))) { michael@0: delete attValue; michael@0: continue; michael@0: } michael@0: michael@0: aBuilder.Append(" "); michael@0: michael@0: if (MOZ_LIKELY(attNs == kNameSpaceID_None) || michael@0: (attNs == kNameSpaceID_XMLNS && michael@0: attName == nsGkAtoms::xmlns)) { michael@0: // Nothing else required michael@0: } else if (attNs == kNameSpaceID_XML) { michael@0: aBuilder.Append("xml:"); michael@0: } else if (attNs == kNameSpaceID_XMLNS) { michael@0: aBuilder.Append("xmlns:"); michael@0: } else if (attNs == kNameSpaceID_XLink) { michael@0: aBuilder.Append("xlink:"); michael@0: } else { michael@0: nsIAtom* prefix = name->GetPrefix(); michael@0: if (prefix) { michael@0: aBuilder.Append(prefix); michael@0: aBuilder.Append(":"); michael@0: } michael@0: } michael@0: michael@0: aBuilder.Append(attName); michael@0: aBuilder.Append("=\""); michael@0: AppendEncodedAttributeValue(attValue, aBuilder); michael@0: aBuilder.Append("\""); michael@0: } michael@0: michael@0: aBuilder.Append(">"); michael@0: michael@0: /* michael@0: // Per HTML spec we should append one \n if the first child of michael@0: // pre/textarea/listing is a textnode and starts with a \n. michael@0: // But because browsers haven't traditionally had that behavior, michael@0: // we're not changing our behavior either - yet. michael@0: if (aContent->IsHTML()) { michael@0: if (localName == nsGkAtoms::pre || localName == nsGkAtoms::textarea || michael@0: localName == nsGkAtoms::listing) { michael@0: nsIContent* fc = aContent->GetFirstChild(); michael@0: if (fc && michael@0: (fc->NodeType() == nsIDOMNode::TEXT_NODE || michael@0: fc->NodeType() == nsIDOMNode::CDATA_SECTION_NODE)) { michael@0: const nsTextFragment* text = fc->GetText(); michael@0: if (text && text->GetLength() && text->CharAt(0) == char16_t('\n')) { michael@0: aBuilder.Append("\n"); michael@0: } michael@0: } michael@0: } michael@0: }*/ michael@0: } michael@0: michael@0: static inline bool michael@0: ShouldEscape(nsIContent* aParent) michael@0: { michael@0: if (!aParent || !aParent->IsHTML()) { michael@0: return true; michael@0: } michael@0: michael@0: static const nsIAtom* nonEscapingElements[] = { michael@0: nsGkAtoms::style, nsGkAtoms::script, nsGkAtoms::xmp, michael@0: nsGkAtoms::iframe, nsGkAtoms::noembed, nsGkAtoms::noframes, michael@0: nsGkAtoms::plaintext, michael@0: // Per the current spec noscript should be escaped in case michael@0: // scripts are disabled or if document doesn't have michael@0: // browsing context. However the latter seems to be a spec bug michael@0: // and Gecko hasn't traditionally done the former. michael@0: nsGkAtoms::noscript michael@0: }; michael@0: static mozilla::BloomFilter<12, nsIAtom> sFilter; michael@0: static bool sInitialized = false; michael@0: if (!sInitialized) { michael@0: sInitialized = true; michael@0: for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) { michael@0: sFilter.add(nonEscapingElements[i]); michael@0: } michael@0: } michael@0: michael@0: nsIAtom* tag = aParent->Tag(); michael@0: if (sFilter.mightContain(tag)) { michael@0: for (uint32_t i = 0; i < ArrayLength(nonEscapingElements); ++i) { michael@0: if (tag == nonEscapingElements[i]) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static inline bool michael@0: IsVoidTag(Element* aElement) michael@0: { michael@0: if (!aElement->IsHTML()) { michael@0: return false; michael@0: } michael@0: michael@0: static const nsIAtom* voidElements[] = { michael@0: nsGkAtoms::area, nsGkAtoms::base, nsGkAtoms::basefont, michael@0: nsGkAtoms::bgsound, nsGkAtoms::br, nsGkAtoms::col, michael@0: nsGkAtoms::command, nsGkAtoms::embed, nsGkAtoms::frame, michael@0: nsGkAtoms::hr, nsGkAtoms::img, nsGkAtoms::input, michael@0: nsGkAtoms::keygen, nsGkAtoms::link, nsGkAtoms::meta, michael@0: nsGkAtoms::param, nsGkAtoms::source, nsGkAtoms::track, michael@0: nsGkAtoms::wbr michael@0: }; michael@0: michael@0: static mozilla::BloomFilter<12, nsIAtom> sFilter; michael@0: static bool sInitialized = false; michael@0: if (!sInitialized) { michael@0: sInitialized = true; michael@0: for (uint32_t i = 0; i < ArrayLength(voidElements); ++i) { michael@0: sFilter.add(voidElements[i]); michael@0: } michael@0: } michael@0: michael@0: nsIAtom* tag = aElement->Tag(); michael@0: if (sFilter.mightContain(tag)) { michael@0: for (uint32_t i = 0; i < ArrayLength(voidElements); ++i) { michael@0: if (tag == voidElements[i]) { michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: Serialize(FragmentOrElement* aRoot, bool aDescendentsOnly, nsAString& aOut) michael@0: { michael@0: nsINode* current = aDescendentsOnly ? michael@0: nsNodeUtils::GetFirstChildOfTemplateOrNode(aRoot) : aRoot; michael@0: michael@0: if (!current) { michael@0: return true; michael@0: } michael@0: michael@0: StringBuilder builder; michael@0: nsIContent* next; michael@0: while (true) { michael@0: bool isVoid = false; michael@0: switch (current->NodeType()) { michael@0: case nsIDOMNode::ELEMENT_NODE: { michael@0: Element* elem = current->AsElement(); michael@0: StartElement(elem, builder); michael@0: isVoid = IsVoidTag(elem); michael@0: if (!isVoid && michael@0: (next = nsNodeUtils::GetFirstChildOfTemplateOrNode(current))) { michael@0: current = next; michael@0: continue; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case nsIDOMNode::TEXT_NODE: michael@0: case nsIDOMNode::CDATA_SECTION_NODE: { michael@0: const nsTextFragment* text = static_cast(current)->GetText(); michael@0: nsIContent* parent = current->GetParent(); michael@0: if (ShouldEscape(parent)) { michael@0: AppendEncodedCharacters(text, builder); michael@0: } else { michael@0: builder.Append(text); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case nsIDOMNode::COMMENT_NODE: { michael@0: builder.Append(""); michael@0: break; michael@0: } michael@0: michael@0: case nsIDOMNode::DOCUMENT_TYPE_NODE: { michael@0: builder.Append("NodeName()); michael@0: builder.Append(">"); michael@0: break; michael@0: } michael@0: michael@0: case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: { michael@0: builder.Append("NodeName()); michael@0: builder.Append(" "); michael@0: builder.Append(static_cast(current)->GetText()); michael@0: builder.Append(">"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: while (true) { michael@0: if (!isVoid && current->NodeType() == nsIDOMNode::ELEMENT_NODE) { michael@0: builder.Append("(current); michael@0: if (elem->IsHTML() || elem->IsSVG() || elem->IsMathML()) { michael@0: builder.Append(elem->Tag()); michael@0: } else { michael@0: builder.Append(current->NodeName()); michael@0: } michael@0: builder.Append(">"); michael@0: } michael@0: isVoid = false; michael@0: michael@0: if (current == aRoot) { michael@0: return builder.ToString(aOut); michael@0: } michael@0: michael@0: if ((next = current->GetNextSibling())) { michael@0: current = next; michael@0: break; michael@0: } michael@0: michael@0: current = current->GetParentNode(); michael@0: michael@0: // Template case, if we are in a template's content, then the parent michael@0: // should be the host template element. michael@0: if (current->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) { michael@0: DocumentFragment* frag = static_cast(current); michael@0: nsIContent* fragHost = frag->GetHost(); michael@0: if (fragHost && nsNodeUtils::IsTemplateElement(fragHost)) { michael@0: current = fragHost; michael@0: } michael@0: } michael@0: michael@0: if (aDescendentsOnly && current == aRoot) { michael@0: return builder.ToString(aOut); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::GetMarkup(bool aIncludeSelf, nsAString& aMarkup) michael@0: { michael@0: aMarkup.Truncate(); michael@0: michael@0: nsIDocument* doc = OwnerDoc(); michael@0: if (IsInHTMLDocument()) { michael@0: Serialize(this, !aIncludeSelf, aMarkup); michael@0: return; michael@0: } michael@0: michael@0: nsAutoString contentType; michael@0: doc->GetContentType(contentType); michael@0: bool tryToCacheEncoder = !aIncludeSelf; michael@0: michael@0: nsCOMPtr docEncoder = doc->GetCachedEncoder(); michael@0: if (!docEncoder) { michael@0: docEncoder = michael@0: do_CreateInstance(PromiseFlatCString( michael@0: nsDependentCString(NS_DOC_ENCODER_CONTRACTID_BASE) + michael@0: NS_ConvertUTF16toUTF8(contentType) michael@0: ).get()); michael@0: } michael@0: if (!docEncoder) { michael@0: // This could be some type for which we create a synthetic document. Try michael@0: // again as XML michael@0: contentType.AssignLiteral("application/xml"); michael@0: docEncoder = do_CreateInstance(NS_DOC_ENCODER_CONTRACTID_BASE "application/xml"); michael@0: // Don't try to cache the encoder since it would point to a different michael@0: // contentType once it has been reinitialized. michael@0: tryToCacheEncoder = false; michael@0: } michael@0: michael@0: NS_ENSURE_TRUE_VOID(docEncoder); michael@0: michael@0: uint32_t flags = nsIDocumentEncoder::OutputEncodeBasicEntities | michael@0: // Output DOM-standard newlines michael@0: nsIDocumentEncoder::OutputLFLineBreak | michael@0: // Don't do linebreaking that's not present in michael@0: // the source michael@0: nsIDocumentEncoder::OutputRaw | michael@0: // Only check for mozdirty when necessary (bug 599983) michael@0: nsIDocumentEncoder::OutputIgnoreMozDirty; michael@0: michael@0: if (IsEditable()) { michael@0: nsCOMPtr elem = do_QueryInterface(this); michael@0: nsIEditor* editor = elem ? elem->GetEditorInternal() : nullptr; michael@0: if (editor && editor->OutputsMozDirty()) { michael@0: flags &= ~nsIDocumentEncoder::OutputIgnoreMozDirty; michael@0: } michael@0: } michael@0: michael@0: DebugOnly rv = docEncoder->NativeInit(doc, contentType, flags); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: michael@0: if (aIncludeSelf) { michael@0: docEncoder->SetNativeNode(this); michael@0: } else { michael@0: docEncoder->SetNativeContainerNode(this); michael@0: } michael@0: rv = docEncoder->EncodeToString(aMarkup); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: if (tryToCacheEncoder) { michael@0: doc->SetCachedEncoder(docEncoder.forget()); michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: ContainsMarkup(const nsAString& aStr) michael@0: { michael@0: // Note: we can't use FindCharInSet because null is one of the characters we michael@0: // want to search for. michael@0: const char16_t* start = aStr.BeginReading(); michael@0: const char16_t* end = aStr.EndReading(); michael@0: michael@0: while (start != end) { michael@0: char16_t c = *start; michael@0: if (c == char16_t('<') || michael@0: c == char16_t('&') || michael@0: c == char16_t('\r') || michael@0: c == char16_t('\0')) { michael@0: return true; michael@0: } michael@0: ++start; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::SetInnerHTMLInternal(const nsAString& aInnerHTML, ErrorResult& aError) michael@0: { michael@0: FragmentOrElement* target = this; michael@0: // Handle template case. michael@0: if (nsNodeUtils::IsTemplateElement(target)) { michael@0: DocumentFragment* frag = michael@0: static_cast(target)->Content(); michael@0: MOZ_ASSERT(frag); michael@0: target = frag; michael@0: } michael@0: michael@0: // Fast-path for strings with no markup. Limit this to short strings, to michael@0: // avoid ContainsMarkup taking too long. The choice for 100 is based on michael@0: // gut feeling. michael@0: // michael@0: // Don't do this for elements with a weird parser insertion mode, for michael@0: // instance setting innerHTML = "" on a element should add the michael@0: // optional and elements. michael@0: if (!target->HasWeirdParserInsertionMode() && michael@0: aInnerHTML.Length() < 100 && !ContainsMarkup(aInnerHTML)) { michael@0: aError = nsContentUtils::SetNodeTextContent(target, aInnerHTML, false); michael@0: return; michael@0: } michael@0: michael@0: nsIDocument* doc = target->OwnerDoc(); michael@0: michael@0: // Batch possible DOMSubtreeModified events. michael@0: mozAutoSubtreeModified subtree(doc, nullptr); michael@0: michael@0: target->FireNodeRemovedForChildren(); michael@0: michael@0: // Needed when innerHTML is used in combination with contenteditable michael@0: mozAutoDocUpdate updateBatch(doc, UPDATE_CONTENT_MODEL, true); michael@0: michael@0: // Remove childnodes. michael@0: uint32_t childCount = target->GetChildCount(); michael@0: nsAutoMutationBatch mb(target, true, false); michael@0: for (uint32_t i = 0; i < childCount; ++i) { michael@0: target->RemoveChildAt(0, true); michael@0: } michael@0: mb.RemovalDone(); michael@0: michael@0: nsAutoScriptLoaderDisabler sld(doc); michael@0: michael@0: nsIAtom* contextLocalName = Tag(); michael@0: int32_t contextNameSpaceID = GetNameSpaceID(); michael@0: michael@0: ShadowRoot* shadowRoot = ShadowRoot::FromNode(this); michael@0: if (shadowRoot) { michael@0: // Fix up the context to be the host of the ShadowRoot. michael@0: contextLocalName = shadowRoot->GetHost()->Tag(); michael@0: contextNameSpaceID = shadowRoot->GetHost()->GetNameSpaceID(); michael@0: } michael@0: michael@0: if (doc->IsHTML()) { michael@0: int32_t oldChildCount = target->GetChildCount(); michael@0: aError = nsContentUtils::ParseFragmentHTML(aInnerHTML, michael@0: target, michael@0: contextLocalName, michael@0: contextNameSpaceID, michael@0: doc->GetCompatibilityMode() == michael@0: eCompatibility_NavQuirks, michael@0: true); michael@0: mb.NodesAdded(); michael@0: // HTML5 parser has notified, but not fired mutation events. michael@0: nsContentUtils::FireMutationEventsForDirectParsing(doc, target, michael@0: oldChildCount); michael@0: } else { michael@0: nsRefPtr df = michael@0: nsContentUtils::CreateContextualFragment(target, aInnerHTML, true, aError); michael@0: if (!aError.Failed()) { michael@0: // Suppress assertion about node removal mutation events that can't have michael@0: // listeners anyway, because no one has had the chance to register mutation michael@0: // listeners on the fragment that comes from the parser. michael@0: nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker; michael@0: michael@0: static_cast(target)->AppendChild(*df, aError); michael@0: mb.NodesAdded(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsINode::nsSlots* michael@0: FragmentOrElement::CreateSlots() michael@0: { michael@0: return new nsDOMSlots(); michael@0: } michael@0: michael@0: void michael@0: FragmentOrElement::FireNodeRemovedForChildren() michael@0: { michael@0: nsIDocument* doc = OwnerDoc(); michael@0: // Optimize the common case michael@0: if (!nsContentUtils:: michael@0: HasMutationListeners(doc, NS_EVENT_BITS_MUTATION_NODEREMOVED)) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr owningDoc = doc; michael@0: michael@0: nsCOMPtr child; michael@0: for (child = GetFirstChild(); michael@0: child && child->GetParentNode() == this; michael@0: child = child->GetNextSibling()) { michael@0: nsContentUtils::MaybeFireNodeRemoved(child, this, doc); michael@0: } michael@0: } michael@0: michael@0: size_t michael@0: FragmentOrElement::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t n = 0; michael@0: n += nsIContent::SizeOfExcludingThis(aMallocSizeOf); michael@0: n += mAttrsAndChildren.SizeOfExcludingThis(aMallocSizeOf); michael@0: michael@0: nsDOMSlots* slots = GetExistingDOMSlots(); michael@0: if (slots) { michael@0: n += slots->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: return n; michael@0: }